1. 程式人生 > 其它 >MySQL 列舉型別如何定義比較好 tinyint?enum?varchar?

MySQL 列舉型別如何定義比較好 tinyint?enum?varchar?

技術標籤:筆記雜談程式猿修煉資料庫mysql列舉類enum

目錄

前言

enum介紹

實踐

最後的建議:


前言

當專案中遇到比較多的列舉欄位時怎麼選擇MySQL的型別呢?tinyint,varchar還是enum?據我觀察大家還是用tinyint的比較多,少數也會直接用varchar。

說到列舉,這個型別真的是有點坑,寫的時候又不想校驗(尤其是列舉值比較多的時候),又想直接在表裡可以顯示出原字元(說到底還是懶)。tinyint可以校驗插入的值為自己定義的數值且索引友好但是顯示不友好,varvhar可以直接在表中顯示欄位值但對索引又不友好。

兩種都不是最優選擇,後來MySQL出現了enum型別,可以直接顯示值且內部為數字索引。本來以為這個enum可以解放我們的雙手了,但我調研了一下發現還是有一些大坑的,難怪很少有人用。

enum介紹

先來介紹一下enum型別吧。

ENUM 是一個字串物件,其值通常選自一個允許值列表中,該列表在表建立時的列規格說明中被明確地列舉。(建表的時候寫到建表語句裡)

雖然表面是字串值,但其內部是數字索引,其索引值從1開始。

注意:下標並不是從0開始,而0則具有其它的意義(空)

ENUM資料型別提供以下優點:

  • 節省儲存空間,MySQL ENUM使用數字索引(1,2,3,…)來表示字串值。
  • 可讀查詢和輸出,數字將轉換回查詢結果中的相應字元

實踐

下面我們來建個表試一下這個enum型別。

CREATE TABLE `test_enum` (

`id` INT ( 11 ) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

`color` ENUM('red','yellow','blue')COMMENT '顏色',

PRIMARY KEY ( `id` )

) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = '顏色表';


這是一個有red,blue,yellow三種顏色的顏色表。如果我們插入列表中的資料,是完全沒問題的。

INSERT INTO test_enum(color) VALUES ('red'), ('yellow')

這個時候看一下確實使用的是數字索引:

SELECT color+0 FROM test_enum

但如果我們插入了一個不存在enum列表中值white,會怎麼樣呢?

INSERT INTO test_enum(color) VALUES ('red'), ('white')

這個時候MySQL會有個報錯:

1265 - Data truncated for column 'color' at row 2, Time: 0.017000s

這是因為我們的MySQL開啟了嚴格模式。如果關掉嚴格模式,或者使用比較古老的版本,僅僅是發出了一個警告而已。

但你以為關閉了嚴格模式就真的可以插入成功了嗎?

Naive!

你只是插入了一個空字串,對此MySQL 官方還是作出了一點的解釋:

「如果在 ENUM 列中插入無效值(即,允許值列表中不存在的字串),則會插入空字串 ('') 作為特殊錯誤值,這個空字串可以通過此字串具有數字值0來區分正常的空字串 」

翻譯一下:

就是說如果往 enum 列中插入了無效的值,可以被插入,但插入的是一個特殊的空字串,而該空字串的數值是 0。所以正常的enum索引從1開始,0就是留給這個空字串的。

但是0和‘0’還是不一樣的

INSERT INTO test_enum(color) VALUES(0)

這個時候會報錯:

1265 - Data truncated for column 'color' at row 1, Time: 0.001000s

如果這樣,就可以:
INSERT INTO test_enum(color) VALUES('0')

所以enum型別對於php等弱語言型別的支援很差,弱語言型別打引號和不打引號的值可能是同一型別,但是對於mysql中enum型別的欄位來說,那就不一定是一回事了。

這裡可以看出來enum型別確實對列舉值進行了一個校驗,但是如果我們這個欄位需要新增一種顏色而表結構忘記改了,那麼我們以為插入資料成功了,其實只是插入一個空,這點真是一個值得注意的坑。

如果使用數字作為ENUM列舉常量,這種雙重性很容易導致混亂,例如ENUM(‘1’,’2’,’3’)。建議儘量避免這麼做。

所以超級不推薦在mysql中設定某一欄位型別為enum,但是存的值為數字,比如‘0’,‘1’,‘2’;

另外,對於enum的排序也是個問題,其不是按列舉值字母順序排序的,而是按數字索引排序的,也就是按你定義的順序排序的。這點也是需要特別注意的。

SELECT color FROM test_enum order by color

總結一下enum主要優點:

1、資料更緊湊,節省儲存空間。因為 ENUM 列一般都是有限的值,一般不多餘 5 個這樣,這就比儲存truefalse節省空間多了。因為 MySQL 會在建立或者修改表結構時將 enum 允許的值自動編碼為數字,而這個數字一般的分配空間為 1~2 位元組 ( byte ) ,具體取決於實現。

例如,將值為 yellow的100萬行插入表將需要 100 萬字節的儲存空間,而如果將實際字串yellow儲存在 VARCHAR 列中則需要 600 萬字節。


2、更好的可讀性,雖然在儲存的是數字,但在輸入和輸出時使用的都是對應的字元值。

3、如果啟用了嚴格的SQL模式,錯誤值會導致警告或錯誤,可在一定程度上過濾掉髒資料。

但是,他的缺點卻更多:

1,在MySQL語句中可使用ENUM的索引值,也可以使用字串,容易誤用,尤其對於數字型列舉值。

2,更改enum列舉成員需要使用ALTER TABLE語句重建整個表,大部分情況下會進行全表掃描;對於一些列未來可能會改變的字串,使用列舉不是一個好主意,除非能接受只在列表末尾新增元素。

3,如果啟用了嚴格的 SQL 模式 (sql_mode) ,嘗試插入無效的 ENUM 值會導致錯誤。如果關閉嚴格模式插入的資料仍然是錯誤的。

4,字元列舉值排序是按定義順序排的而不是按字母順序排的,如果想按字母順序需要 ORDER BY CONCAT(col)。

5,ENUM型別不是SQL標準,屬於MySQL,而其他DBMS不一定有原生的支援。

6,列舉值不能是表示式,即使是計算字串值的表示式也是如此。

看了這麼多缺點,我還是決定放棄使用enum了。

那麼情況下可以使用enum呢?

1,你的enum值是固定不變的,比如撲克牌的花色等。

2,enum的值數量大於2個並少於10個。(我覺得超過10個就很難管理與使用了)

3,這個表不需要儲存額外的關聯資訊。比如撲克牌花色想關聯黑色和紅色那麼就很難了。

最後的建議:

  1. 非常不建議使用ENUM存數字,如果搭配弱型別語言,那簡直就是給自己找麻煩。
  2. 儘量不要用這個型別,除非你非常確定你的列舉成員不會改變,或者新增成員也只是在隊尾新增,還有你沒有轉換資料庫的需求;
  3. 如果欄位是字串,並且長度固定,建議用char型別;如果不固定且沒有搜尋要求用varchar也沒啥問題。
  4. 如果是數值型,建議使用tinyint,只佔1個位元組,比較穩妥。就是這個欄位備註要寫清楚了,不然別人看錶全是數字一臉懵逼。