新聞中心
MySQL的 枚舉(ENUM)類型 是程序員群體中的一個(gè)討論熱點(diǎn)。乍一看,我們可以通過(guò)枚舉類型,很好地將記錄值限制在允許范圍內(nèi)。一個(gè)典型的例子是,一個(gè)具有字段名稱為“大陸板塊”的數(shù)據(jù)表:每一個(gè)國(guó)家位于一個(gè)大陸板塊,而這些大陸板塊不太可能經(jīng)常變化。當(dāng)然,或許一天北美板塊會(huì)與亞洲板塊碰撞形成北美亞,但即便你的數(shù)據(jù)庫(kù)能夠延續(xù)使用到那個(gè)時(shí)候,起碼你也不需要研討怎么去重構(gòu)你的數(shù)據(jù)表,那將是當(dāng)時(shí)的開(kāi)發(fā)者要做的工作。

創(chuàng)新互聯(lián)成都網(wǎng)站建設(shè)定制網(wǎng)站制作,是成都網(wǎng)站營(yíng)銷公司,為社區(qū)文化墻提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計(jì)服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計(jì)、前端HTML5制作、后臺(tái)程序開(kāi)發(fā)等。成都網(wǎng)站維護(hù)熱線:18982081108
言歸正傳。如果,使用ENUM是唯一 一個(gè),能夠代表某一個(gè)國(guó)家屬于哪個(gè)大陸板塊的選擇,那我們大可進(jìn)行下一步,去爭(zhēng)辯諸如NoSQL的優(yōu)劣、Git和SVN孰強(qiáng)孰弱、你喜歡的框架有哪些缺點(diǎn)這些其他的問(wèn)題。但這里有一個(gè)普遍適用于實(shí)現(xiàn)枚舉的最佳實(shí)踐:
維基百科 是這樣描述關(guān)系表的:
…這是一種將可知的枚舉數(shù)據(jù)分離出來(lái)的表。例如,一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的倉(cāng)庫(kù)數(shù)據(jù),倉(cāng)庫(kù)里面的“物件”有可能會(huì)有一個(gè)“狀態(tài)”的字段記錄已經(jīng)聲明的值,例如:“已售,預(yù)定,售罄”。在極簡(jiǎn)的數(shù)據(jù)庫(kù)設(shè)計(jì)當(dāng)中,這些值都會(huì)在獨(dú)立的關(guān)系表“狀態(tài)”中存儲(chǔ),以此滿足范式(database normalization)。
所以,關(guān)系表也可以滿足枚舉的實(shí)現(xiàn)。下面就來(lái)看看,ENUM的”八宗罪“到底是什么:
1. 數(shù)據(jù)被錯(cuò)誤對(duì)待
男、女;先生、夫人、小姐;非洲、亞洲,等等。這些人們使用作為ENUM類型字段的短詞稱為數(shù)據(jù)。當(dāng)你使用一個(gè)ENUM類型字段, 技術(shù)上看,是你將數(shù)據(jù)抽離出來(lái) (對(duì)應(yīng)到實(shí)際數(shù)據(jù)表時(shí)), 放到一個(gè)獨(dú)立的地位(一種數(shù)據(jù)庫(kù)的元數(shù)據(jù),具有精確定義字段)。 這不同與約束數(shù)據(jù)類型,如我們通常的做法:數(shù)值型字段只能存儲(chǔ)整型數(shù)據(jù),或者日期型字段不能為空——這些都沒(méi)有問(wèn)題,而且還十分重要。使用ENUM類型字段時(shí),我們實(shí)際上是保存部分?jǐn)?shù)據(jù) 去作為 這個(gè)數(shù)據(jù)模型的一個(gè)特征信息。簡(jiǎn)而言之, ENUM類型字段破壞了范式要求。這也許看起來(lái)十分“學(xué)院派”或“迂腐陳舊”,但這正是以下各種“罪行”的源頭。
2. 更改ENUM類型字段,代價(jià)很昂貴
永恒不變的是, 每次你創(chuàng)建ENUM類型字段的時(shí)候都說(shuō):“這個(gè)字段不可能變的”。人類普遍欠缺顧全大局的能力,預(yù)測(cè)上更是糟糕,其如研發(fā)部的新產(chǎn)品線、貴司新的航運(yùn)方案、北美板塊碰撞亞洲板塊。
使用ALTER TABLE去修改整個(gè)數(shù)據(jù)表的ENUM類型字段,是十分耗費(fèi)資源的。如果將ENUM('red', 'blue', 'black') 改為 ENUM('red', 'blue', 'white'), MySQL 需要重構(gòu)整個(gè)數(shù)據(jù)表,并且檢索 所有數(shù)據(jù)去檢查 'black'這個(gè)無(wú)效值。 MySQL 是真的蠢,它確實(shí)會(huì)在你每次增加一個(gè)新的ENUM值時(shí)都這么做的?。▊餮晕磥?lái)會(huì)處理ENUM類型字段的效率問(wèn)題,但我對(duì)其受重視程度深表懷疑。)
全表重構(gòu)在小型數(shù)據(jù)表中可能沒(méi)有那么痛苦,但在海量數(shù)據(jù)的情況下可能會(huì)導(dǎo)致資源被鎖死很長(zhǎng)很長(zhǎng)一段時(shí)間。如果你使用關(guān)系表去替代ENUM類型字段,改變枚舉集合只不過(guò)是使用INSERT、UPDATE和DELETE,對(duì)比來(lái)看真是滑稽。
很重要的一點(diǎn),當(dāng)更改ENUM類型字段的枚舉集合時(shí),MySQL會(huì)轉(zhuǎn)換任意已有但不存在于新的枚舉集合中的記錄值為''(空的字符串)。使用關(guān)系表,在更改和刪除枚舉集合時(shí)會(huì)靈活很多(下面會(huì)提到)。
3. 幾乎無(wú)法給關(guān)聯(lián)數(shù)據(jù)添加額外的屬性
至今都沒(méi)有一個(gè)可以更加明智地改變ENUM類型字段的方法,這也是我們的常態(tài)。在我們的“國(guó)家、大陸板塊”例子中, 更改“國(guó)土面積”會(huì)出現(xiàn)什么情況?我們沒(méi)有預(yù)料到這個(gè)屬性, 但也要既來(lái)之則安之。使用關(guān)系表設(shè)計(jì),我們可以輕易地拓展“大陸板塊”這個(gè)數(shù)據(jù)表,各種方式為其增加我們想要的數(shù)據(jù)和字段 。ENUM?快別說(shuō)了。
另一種極妙的靈活性體現(xiàn)在關(guān)系表的拓展便捷性上。一個(gè)簡(jiǎn)單的標(biāo)記位字段即可表示這個(gè)“枚舉值”是否可用。所以,當(dāng)你的公司不打算銷售黑色的裝飾品了,你只需在“黑色”所對(duì)應(yīng)的_isdiscontinued字段中做個(gè)標(biāo)記即可。而且你依然可以查詢到已售的顏色(譯者:指的是,ENUM的修改會(huì)導(dǎo)致原有,而現(xiàn)在已經(jīng)沒(méi)有的值變?yōu)榭兆址?,?shù)據(jù)失去了部分特征),同時(shí)你那些黑色裝飾品的訂單依然可統(tǒng)可計(jì)哦!ENUM,你要不要試試?
4. 獲取ENUM全部可能值,很麻煩
一個(gè)很常見(jiàn)的需求是,將數(shù)據(jù)庫(kù)中存在的數(shù)據(jù)顯示在可拖拽列表中,例如:
選擇顏色:
紅 藍(lán) 黑
如果這些數(shù)值存儲(chǔ)在一個(gè)名為‘colors’的數(shù)據(jù)表里,你所要做的僅僅是:SELECT * FROM colors,這樣即可動(dòng)態(tài)地令數(shù)據(jù)地顯示在可拖拽列表中。你可以添加或者改變color關(guān)系表中的顏色,并且,你那酷炫訂單的顏色可選項(xiàng)會(huì)自動(dòng)更新,真了不起。 (譯:此處所舉例子,應(yīng)等同于:“通過(guò)后臺(tái)管理,可以限定前端用戶某類型數(shù)據(jù)的可選項(xiàng)?!边@樣的功能。)
回到ENUM上:你要如何獲取全部的枚舉值?你當(dāng)然可以使用ENUM值搭配DISTINCT去查詢(譯:即是查詢ENUM值互相不相同的數(shù)據(jù),等于利用DISTINCT的唯一性去查詢ENUM),但這樣也只會(huì)返回確實(shí)使用過(guò),并存在于數(shù)據(jù)表ENUM字段可選值中的ENUM值,而不是所有可能的值。你也可以查詢INFORMATION_SCHEMA然后通過(guò)代碼解析返回的數(shù)據(jù),去找到你想要的ENUM的所有值,但這完全是多此一舉。事實(shí)上,我依然沒(méi)有發(fā)現(xiàn),有任何兼顧了優(yōu)雅與原生的SQL方式,可以獲取ENUM類型字段的所有值。
5. ENUM類型字段所提供的優(yōu)化有限
通常使用ENUM的正當(dāng)理由,不外乎“優(yōu)化”二字,譬如,性能提升,簡(jiǎn)化模型與高可讀性。
那我們從性能上看。你可以在未優(yōu)化的數(shù)據(jù)庫(kù)中做很多匪夷所思而夸張的事,但是大多情況是,在數(shù)據(jù)達(dá)到一定規(guī)模前,都不會(huì)出現(xiàn)影響性能的情況,并且通常我們的產(chǎn)品遠(yuǎn)未達(dá)到那個(gè)尺度規(guī)模。有一點(diǎn)需要注意的是,因?yàn)閿?shù)據(jù)庫(kù)開(kāi)發(fā)者們都熱衷于令自己的設(shè)計(jì)可以達(dá)到完備的范式,并且只會(huì)在遇到性能問(wèn)題時(shí)才會(huì)考慮反范式。如果你擔(dān)心使用關(guān)系表會(huì)導(dǎo)致變慢,可以在同一基準(zhǔn)下測(cè)試不同方式下的表現(xiàn),再進(jìn)行考慮。切勿先入為主地認(rèn)為關(guān)聯(lián)查詢會(huì)成為瓶頸,可能有時(shí)并非如此。(可參照 evidence to support that ENUM isn't always appreciably faster than alternatives.)
另一個(gè)關(guān)于ENUM優(yōu)化方式的說(shuō)法是,ENUM可以有效減少數(shù)據(jù)庫(kù)中的數(shù)據(jù)表外鍵。不可置否,使用外鍵相當(dāng)于是將很多不同的盒子以線相連,而且在大型系統(tǒng)中,范式設(shè)計(jì)已可降低對(duì)人類的理解能力界限、復(fù)雜型查詢的要求。但是,我們?yōu)槭裁磿?huì)設(shè)計(jì)模型,為什么要將模型抽象化以便我們能夠理解它。去試試做一個(gè)新數(shù)據(jù)模型圖或者ER圖,并且忽略一些小細(xì)節(jié)和關(guān)系表。有時(shí)候使用ENUM確實(shí)如看上去那般簡(jiǎn)單,但事實(shí)上你在心里需要想著一個(gè)隱式的關(guān)系表,所以并沒(méi)有看上去那般簡(jiǎn)單。
6. ENUM值在其他數(shù)據(jù)表中不可直接復(fù)用
當(dāng)你(在數(shù)據(jù)表中)創(chuàng)建了一個(gè)帶值的ENUM字段,在其他數(shù)據(jù)表中無(wú)法直接復(fù)用這個(gè)ENUM。而當(dāng)有了關(guān)系表,相同應(yīng)用形式下,可以在其他多個(gè)數(shù)據(jù)表中復(fù)用。當(dāng)改變關(guān)系表中的一個(gè)數(shù)據(jù),其他多個(gè)數(shù)據(jù)表也會(huì)得到響應(yīng)。
ENUM類型字段的分離,將使你能在多個(gè)數(shù)據(jù)表中復(fù)用相同的ENUM值(需要保持一致性)。
7. ENUM類型字段有顯然陷阱
假設(shè)你設(shè)置了一個(gè)字段“color”ENUM('blue', 'black', 'red') ,這時(shí)你想INSERT一行數(shù)據(jù),但“color”字段是 'purple', MySQL 會(huì)將不合法的值變?yōu)?'' (空字符串)。 處理上沒(méi)問(wèn)題, 但如果我們使用的是帶外鍵的關(guān)系表, 那么我們的數(shù)據(jù)能因健壯性而更加可靠。
同樣,MySQL 會(huì)為ENUM值關(guān)聯(lián)枚舉索引,并且在使用中會(huì)錯(cuò)誤地調(diào)用到索引而不是ENUM值,反之亦然。
想象一下:
- CREATE TABLE test (foobar ENUM('0', '1', '2'));
- mysql> INSERT INTO test VALUES ('1'), (1);
- Query OK, 2 rows affected (0.00 sec)
- Records: 2 Duplicates: 0 Warnings: 0
- mysql> SELECT * FROM test;
- +--------+
- | foobar |
- +--------+
- | 1 |
- | 0 |
- +--------+
- 2 rows in set (0.00 sec)
我們插入了 '1' (字符串),并且不小心插入了 1 (沒(méi)有引號(hào),數(shù)值型)。 MySQL 會(huì)將我們地?cái)?shù)值型數(shù)據(jù)當(dāng)作是枚舉索引去處理(并沒(méi)有錯(cuò),但會(huì)令人混淆),根據(jù)索引可知,ENUM字段的第一個(gè)值為 0 。(譯:枚舉索引由 1 開(kāi)始)
8. ENUM 的移植性不佳
ENUM類型不是SQL標(biāo)準(zhǔn),屬于MySQL,而其他DBMS不一定有原生的支持。 PostgreSQL, MariaDB,與Drizzle (后面那兩個(gè)就MySQL的分支), 我只知道這三個(gè)是支持的ENUM的。如果某個(gè)人打算將數(shù)據(jù)庫(kù)遷移, 那么他就要花費(fèi)更多的步驟去處理你那些“精妙”的ENUM字段了,相信他會(huì)“更愛(ài)你”。如果(那個(gè)人)是你, 你可會(huì)發(fā)現(xiàn)自己當(dāng)時(shí)真是“聰明夠了”。通常來(lái)說(shuō),數(shù)據(jù)庫(kù)遷移不會(huì)經(jīng)常發(fā)生,并且,由于所有人都會(huì)假設(shè)遷移數(shù)據(jù)庫(kù)的過(guò)程中,必然要出亂子,因此成為“第八宗罪”。
幾時(shí)適合使用ENUM:
1. 當(dāng)你需要存儲(chǔ)的是準(zhǔn)確、不變的值時(shí)
大陸板塊就是最好的例子,定義十分準(zhǔn)確。另一個(gè)常用例子是稱謂:先生、夫人、小姐,或者是撲克的花色:方塊、梅花、紅心、黑桃。但是,即便是這些例子,有時(shí)也需要去拓展值的范圍(例如有人需要你稱呼“陳醫(yī)生”而不是“陳先生”的時(shí)候,或者你的撲克游戲里面需要用到小丑牌)。
2. 你永遠(yuǎn)不需要存儲(chǔ)額外的關(guān)聯(lián)信息
用回?fù)淇伺频睦印淇擞螒蚶仙傧桃?,依賴的?guī)則是梅花和黑桃為黑色,方塊和紅心為紅色(例如,尤克牌)。如果我們需要為花色關(guān)聯(lián)額外的信息,例如顏色,那將如何?如果我們使用關(guān)系表,那我們只需要在關(guān)系表中新增字段即可,小事一樁。如果我們使用ENUM去表示花色,那我們就很難去準(zhǔn)確的表示花色于顏色的關(guān)聯(lián)了,如此我們只能在應(yīng)用層上去達(dá)成這種關(guān)聯(lián)。
3. ENUM值的數(shù)量大于2個(gè)并少于20個(gè)
如果你的ENUM值只有兩個(gè),你完全可以將ENUM換成更加高效的TINYINT(1)或者更更高效的BIT(1)(MySQL5.0.3及以上)。例如: gender ENUM('male', 'female') 可以變換為: is_male BIT(1). 當(dāng)你只有兩個(gè)選項(xiàng)時(shí),完全能以布爾值 true/false,結(jié)合字段名字中的“is”關(guān)鍵詞來(lái)區(qū)分。至于20個(gè)的上限設(shè)定,沒(méi)錯(cuò),ENUM事實(shí)上可以保存多達(dá)65535個(gè)值,但求你千萬(wàn)別試。超過(guò)二十個(gè)值會(huì)變得很累贅,超過(guò)50個(gè)必然難于管理與使用。
如果你無(wú)論如何都要用ENUM:
1. ENUM值千萬(wàn)不要使用數(shù)值型
ENUM定義為字符型數(shù)據(jù)是有原因的。并不是說(shuō)你使用數(shù)值型字段類型去存儲(chǔ)數(shù)字是錯(cuò)誤的,但有充足的證據(jù)顯示,MySQL內(nèi)部機(jī)制使用數(shù)字去引用索引(參考 上面的第七條)。反正不要在ENUM中存儲(chǔ)數(shù)字,OK?
2. 考慮使用嚴(yán)格模式
啟用嚴(yán)格模式,至少在你插入一個(gè)不存在的ENUM值時(shí)會(huì)報(bào)告錯(cuò)誤。否則,只會(huì)簡(jiǎn)單地出現(xiàn)一個(gè)警告,繼而該值被設(shè)置為一個(gè)空字符串""(枚舉索引為0)。抄筆記:如果你設(shè)置了IGNORE,錯(cuò)誤依然會(huì)被忽略。
結(jié)論
從開(kāi)發(fā)、維護(hù)的角度去做有意義的事,性能問(wèn)題出現(xiàn)時(shí)再考慮優(yōu)化——普遍而言,使用關(guān)系表抑或是使用ENUM類型,爭(zhēng)議不斷。
性能瓶頸(這個(gè)概念)被濫用已是不爭(zhēng)事實(shí)。 開(kāi)發(fā)者們浪費(fèi)了大量的時(shí)間去思考它、擔(dān)心它,(例如)非關(guān)鍵代碼上的運(yùn)行速度。這些對(duì)效率的苛求,給調(diào)試與維護(hù)造成了很大的負(fù)面影響。我們理應(yīng)忽略那小部分的效率,就拿(達(dá)到)97%(效率)而言,過(guò)早的優(yōu)化是萬(wàn)惡之源。
本文名稱:MySQL枚舉類型的“八宗罪”
文章轉(zhuǎn)載:http://m.fisionsoft.com.cn/article/dhoicjp.html


咨詢
建站咨詢
