MySQL
的常用數據類型包括:Number/Date/String
,而String
類型中又包含了Char/Varchar/Binary/blob/text
等長度不同的簡單數據類型,有時我們需要對數據做更細緻的管理,比如枚舉和集合,就需要複合類型ENUM
和SET
了。
ENUM
枚舉類型
ENUM
適合於只能在一組固定值中選一個的場景,比如性別只能爲男或者女。ENUM
的優勢在於:
- 只能在固定值中選擇,可以在數據庫層面限制非法值。
- 數據的存儲用數字來存儲,佔用空間少。
但是它的使用有很多需要我們注意的地方,一不小心你就會得到錯誤的結果。
使用ENUM
枚舉類型
mysql> create table test (name varchar(40), sex enum('male', 'female') );
mysql> insert into test (name, sex) values('a', 'male'), ('b', 'female'), ('c', 'male');
mysql> select * from test;
+------+--------+
| name | sex |
+------+--------+
| a | male |
| b | female |
| c | male |
+------+--------+
3 rows in set (0.00 sec)
創建枚舉類型時,我們使用關鍵字enum
,同時跟着一組可枚舉值列表,這些可枚舉值必須使用字符串的格式,否則會報錯。
ENUM
類型數據存儲的實際值是索引值
我們所有枚舉值都是按照枚舉值列表中的索引值進行存儲的,如上面的ENUM('male', 'female')
的sex
字段所有值爲:
字面值 | 存儲值 |
---|---|
NULL | NULL |
'' | 0 |
'male' | 1 |
'female' | 2 |
因此如果有1000
條記錄都存儲爲male
,我們可能認爲數據庫存儲了4000
個字符,其實只存儲了1000
個1
字符。而在查詢的時候又會將這個編碼過的數字轉爲實際的值。
我們可以用兩個例子測試下:
mysql> select * from test where sex=1;
+------+------+
| name | sex |
+------+------+
| a | male |
| c | male |
+------+------+
2 rows in set (0.00 sec)
mysql> select name, sex+0 from test;
+------+-------+
| name | sex+0 |
+------+-------+
| a | 1 |
| b | 2 |
| c | 1 |
+------+-------+
3 rows in set (0.00 sec)
這種存儲和查詢的方式會導致一些處理數字的函數,也會使用存儲的值來進行計算,如SUM()
和AVG()
:
mysql> select name, avg(sex) from test;
+------+--------------------+
| name | avg(sex) |
+------+--------------------+
| a | 1.3333333333333333 |
+------+--------------------+
1 row in set (0.00 sec)
讀寫時不要使用數字
由於上面介紹的用索引值存儲的特性,我們不要用枚舉類型來存儲數字格式的列,否則會引起很大的混淆,如:
mysql> create table test2 (numbers enum('0', '1', '2'));
Query OK, 0 rows affected (0.04 sec)
# 此時2被當做索引值,因此是'1';'2'就是'2';'3'因爲不是合法值,會用索引值嘗試,因此是'2'
mysql> insert into test2 (numbers) values (2), ('2'), ('3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from test2;
+---------+
| numbers |
+---------+
| 1 |
| 2 |
| 2 |
+---------+
3 rows in set (0.00 sec)
枚舉類型的默認值
即便一列被設定爲枚舉類型,但依然有額外兩種值爲合法值:NULL
和''
。
當我們插入一個非法值時,在寬鬆模式下,會插入一個普通的空字符''
,其值爲0
。而在嚴格模式下會報錯。
當該字段設定爲允許爲空時,NULL
字段可以被正常插入。當不允許爲空時,如果你不填值,會使用默認值:枚舉值的第一個,如上面的male
。
除了設置爲嚴格模式,否則沒有合適的辦法讓一列數據必須插入合法枚舉值。使用默認值很多情況下不能滿足需求。
枚舉類型的排序
常規使用order by
進行排序時,會按照字母的文本順序。但枚舉類型由於存儲爲索引值,因此會按照索引值進行排序:NULL < '' = 0 < 1 < 2
。
如果希望按照文本類型進行排序,可以使用:
order by cast(col as char)
或者
order by concat(col)
枚舉值聲明的限制
創建數據類型時,枚舉值不允許爲表達式,如:
mysql> create table test (name varchar(40), sex enum('male', concat('fem', 'ale') );
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'concat('fem', 'ale') )' at line 1
枚舉值數量的限制
枚舉值用1-2
個字節來存儲,因此上限值爲2^16-1=65535
。
SET
集合類型
SET
和ENUM
類型非常相似,它適合於只能在一組固定值中選零個或多個的場景,比如一個人喜歡的顏色可以爲紅、黃、藍等顏色中的一個或多個,也可以都不喜歡。
SET
的優勢和ENUM
也相似,在於:
- 只能在固定值中選擇,可以在數據庫層面限制非法值。
- 數據的存儲用數字來存儲,佔用空間少。但在枚舉值數量很多,而枚舉值字符數少時這一可能不成立。
使用SET
枚舉類型
mysql> create table test2 (name varchar(40), color set('red', 'green', 'blue', 'yellow'));
Query OK, 0 rows affected (0.04 sec)
mysql> insert into test2(name,color) values ('a', 'red'), ('b', 'red,green'), ('c', 'green,blue,yellow');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from test2;
+------+-------------------+
| name | color |
+------+-------------------+
| a | red |
| b | red,green |
| c | green,blue,yellow |
+------+-------------------+
3 rows in set (0.00 sec)
創建時,我們使用關鍵字set
,同時跟着一組可枚舉值列表,這些可枚舉值必須使用字符串的格式,否則會報錯。
SET
類型數據存儲的實際值是索引值的和
我們所有枚舉值都是按照列表中的索引值進行存儲的,不同的是通過設置二進制數爲1
的位置。如上面的SET('red', 'blue', 'green', 'yellow')
的color
字段所有值爲:
枚舉值 | 二進制值 | 十進制數字 |
---|---|---|
red | 0001 | 1 |
blue | 0010 | 2 |
green | 0100 | 4 |
yellow | 1000 | 8 |
而當有多個值時,通過所有值的求和得到存儲的值。因此存儲的數據量變少了,當取出的時候編碼過的數字又會被轉義成實際的字符串。
我們可以用兩個例子測試下:
mysql> select name,color+0 from test2;
+------+---------+
| name | color+0 |
+------+---------+
| a | 1 |
| b | 3 |
| c | 14 |
+------+---------+
3 rows in set (0.00 sec)
mysql> select name,color from test2 where color=14;
+------+-------------------+
| name | color |
+------+-------------------+
| c | green,blue,yellow |
+------+-------------------+
1 row in set (0.00 sec)
這種存儲和查詢的方式會導致一些處理數字的函數,也會使用存儲的值來進行計算,如SUM()
和AVG()
:
mysql> select avg(color) from test2;
+------------+
| avg(color) |
+------------+
| 6 |
+------------+
1 row in set (0.00 sec)
插入時的順序和次數
當插入值時,set
類型不關注你插入的順序和一個枚舉值的插入次數,它會自動去重並進行求和得到值,等到取出時,會按照聲明的順序返回:
mysql> insert into test2(name,color) values ('d', 'yellow,green,red,yellow');
Query OK, 1 row affected (0.00 sec)
mysql> select name,color from test2;
+------+-------------------+
| name | color |
+------+-------------------+
| d | red,green,yellow |
+------+-------------------+
4 rows in set (0.00 sec)
查找集合值
由於set
類型的特殊性,因此有專用的查找函數:
mysql> select * from test2 where find_in_set('red', color);
+------+------------------+
| name | color |
+------+------------------+
| a | red |
| b | red,green |
| d | red,green,yellow |
+------+------------------+
3 rows in set (0.00 sec)
# 這一種方法當出現lightred顏色的時候就無法正確工作了
mysql> select * from test2 where color like '%red%';
+------+------------------+
| name | color |
+------+------------------+
| a | red |
| b | red,green |
| d | red,green,yellow |
+------+------------------+
3 rows in set (0.00 sec)
集合值的計算方式是位運算
前面說是對枚舉值去重並自動求和只是爲了方便理解,實際上是進行位運算,得到最終的值,如0001 + 0100 = 0101
。
因此我們也可以用類似的方法來查找值:
mysql> select name,color from test2 where color & 10;
+------+-------------------+
| name | color |
+------+-------------------+
| b | red,green |
| c | green,blue,yellow |
| d | red,green,yellow |
+------+-------------------+
3 rows in set (0.00 sec)
mysql> select name,color from test2 where color & 12;
+------+-------------------+
| name | color |
+------+-------------------+
| c | green,blue,yellow |
| d | red,green,yellow |
+------+-------------------+
2 rows in set (0.00 sec)
上面的這個&
符號什麼含義我沒查到,但我猜測這個&
符號的含義就是位運算,當兩個數在一個位置都爲1
時返回true
,如果沒有一個位置兩者都爲1
則爲false
。
具體的可以計算下:a,b,c,d
分別爲0001,0011,1110,1011
,此時10
爲1100
,12
爲1010
,可以計算的到上面的結果。其他數字的結果也都符合,所以應該符合我的猜測。
枚舉類型的排序
常規使用order by
進行排序時,會按照字母的文本順序。但枚舉類型由於存儲爲索引值,因此會按照索引值進行排序:NULL < 0 < 1 < 2
。
如果希望按照文本類型進行排序,可以使用:
order by cast(col as char)
或者
order by concat(col)
枚舉值數量的限制
枚舉值用1-8
個字節來存儲,因此上限值爲8*8=64
個。
參考資料
- 11.4.4 The ENUM Type:https://dev.mysql.com/doc/ref...
- 11.8 Data Type Storage Requirements:https://dev.mysql.com/doc/ref...
- 11.4.5 The SET Type:https://dev.mysql.com/doc/ref...