MySQL 中的字串型別
字元型別包括:
CHAR 與 VARCHAR
CHAR(m) m 取值範圍 0~255。列寬固定,儲存時字串右邊會補空格,取出時自動去掉空格,除非開啟了PAD_CHAR_TO_FULL_LENGTH 。
對於值未超出的情況,VARCHAR 在儲存及查詢時皆會完整保留末尾的空格字元。
VARCHAR(m) m 取值範圍 0~65535,不定長度,根據儲存的值而定,但上限為 m。儲存時,會在值的前面預留一到兩位用來儲存字串長度。0~255時預留一位,超過的情況使用兩位來標識所儲存的字串的長度
在非嚴格模式下,超過的非空格值會自動截斷來儲存,同時生成警告資訊。建議開啟嚴格模式以避免這種不完整資料的產生。
對於超出的空格值,VARCHAR 會在插入錢截斷併產生警告資訊,而對於 CHAR 則沒有警告資訊,直接靜默截斷儲存。
以下表格以 m為4的情況展示了 非嚴格模式下 CHAR,VARCHAR 在儲存不同長度字串時的表現。
Value | CHAR(4) | Storage Required | VARCHAR(4) | Storage Required |
---|---|---|---|---|
'' | ' ' | 4 bytes | '' | 1 byte |
'ab' | 'ab ' | 4 bytes | 'ab' | 3 bytes |
'abcd' | 'abcd' | 4 bytes | 'abcd' | 5 bytes |
'abcdefgh' | 'abcd' | 4 bytes | 'abcd' | 5 byte |
以下程式碼展示了兩者在查詢時對於末尾空格的處理情況。
mysql> CREATE TABLE vc (v VARCHAR(4), c CHAR(4)); Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO vc VALUES ('ab', 'ab'); Query OK, 1 row affected (0.00 sec) mysql> SELECT CONCAT('(', v, ')'), CONCAT('(', c, ')') FROM vc; +---------------------+---------------------+ | CONCAT('(', v, ')') | CONCAT('(', c, ')') | +---------------------+---------------------+ | (ab)| (ab)| +---------------------+---------------------+ 1 row in set (0.06 sec)
在進行排序和字串比較時,具體表現和使用的字符集(character set collation )有關。
Pad 屬性
Pad 即字串長度不夠時的補齊操作,一般是新增空格。大部分字符集都有一個PAD SPACE
的補齊屬性(pad attribute)。基於 UCA 9.0.0 的 Unicode 字符集其補齊屬性為NO PAD
。
可通過查詢INFORMATION_SCHEMA COLLATIONS
表中的PAD_ATTRIBUTE
列獲取到和字符集的補齊屬性。
補齊屬性直接影響非二進位制字串(CHAR, VARCHAR, and TEXT)進行比較時的末尾的空白會如何處理。
對於NO PAD
字符集,末尾的空白會參與字串比較,PAD SPACE
末尾的空白則不影響比較結果,但在使用LIKE
關鍵字時空白是會參與進來的。
mysql> CREATE TABLE names (myname CHAR(10)); Query OK, 0 rows affected (0.03 sec) mysql> INSERT INTO names VALUES ('Monty'); Query OK, 1 row affected (0.00 sec) mysql> SELECT myname = 'Monty', myname = 'Monty' FROM names; +------------------+--------------------+ | myname = 'Monty' | myname = 'Monty' | +------------------+--------------------+ |1 |1 | +------------------+--------------------+ 1 row in set (0.00 sec) mysql> SELECT myname LIKE 'Monty', myname LIKE 'Monty' FROM names; +---------------------+-----------------------+ | myname LIKE 'Monty' | myname LIKE 'Monty' | +---------------------+-----------------------+ |1 |0 | +---------------------+-----------------------+ 1 row in set (0.00 sec)
對於要求列值要求在記錄中唯一(unique values
)的表格,在插入時需要格外注意,如果插入的值只包含空格的區別,在空格不影響比較結果的字符集中會報duplicate-key error
。比如已經有記錄的值為a
嘗試插入a
時會報錯。
BINARY 與 VARBINARY
基本與 CHAR 和 VARCHAR 類似,不同點在於儲存的是二進位制字元值。儲存長度的取值範圍也一樣,只是單位是位元組而不是字元。
寫入時對於超出限制的值,會自動截斷,除非開啟 MySQL 的嚴格模式,此時會報錯而非截斷後成功寫入。
對於BINARY
型別,存入的值長度不夠時會自動補上二進位制中的零0x00
。查詢時也會返回補零後的值。在進行比較操作時,所有位元組都參與計算,注意二進位制的零值0x00
與空格是不同的值,0x00
< 空格。
以下程式碼展示了自動補零在存取時的表現,
mysql> CREATE TABLE t (c BINARY(3)); Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO t SET c = 'a'; Query OK, 1 row affected (0.01 sec) mysql> SELECT HEX(c), c = 'a', c = 'a\0\0' from t; +--------+---------+-------------+ | HEX(c) | c = 'a' | c = 'a\0\0' | +--------+---------+-------------+ | 610000 |0 |1 | +--------+---------+-------------+ 1 row in set (0.09 sec)
所以如果你期望查詢時得到與儲存時一致的值,最好使用VARBINARY
或BLOB
型別。
BLOB 與TEXT 型別
BLOB (binary large object) 即大型二進位制形式的物件,用於資料量大的情況。BLOB 大類中具體包含:TINYBLOB, BLOB, MEDIUMBLOB 以及 LONGBLOB 四種資料型別,他們只存在儲存上限的區別。
文字型別TEXT
下面同樣包含不同上限的具體型別:TINYTEXT, TEXT, MEDIUMTEXT 及 LONGTEXT 。這四種類型功能及上限與 BLOB 中的四種類型一一對應,只是該型別用於字元而非二進位制值。
BLOB 型別內部處理時以二進位制字串的形式處理,在進行比較操作或排序時,按其表示的二進位制數字來進行。而 TEXT 型別表示非二進位制字元,在進行比較或排序時與字符集有關。
非嚴格模式下寫入一個超出長度的值時,會自動截斷後存入,同時生成警告資訊,這一行為可通過開啟嚴格模式來避免。
其中對於 TEXT 型別,擷取末尾的空格字元時始終生成警告資訊,與 MySQL 當前設定的模式無關。
一般來說你可把 BLOB 看作是 VARBLOB,儲存任意大的資料。TEXT 同理,可看成 VARCHAR。但還是有一些區別的:
DEFAULT
因為 BLOB 和 TEXT 所存的值有可能很大,實際使用時會遇到些限制,比如
- 排序時只取max_sort_length 的長度。該值預設為 1024,但可隨時配置:
mysql> SET max_sort_length = 2000; mysql> SELECT id, comment FROM t -> ORDER BY comment;
- 查詢 BLOB 和 TEXT 型別時其結果會處理到一個臨時表中,而不是像其他型別一樣放到記憶體裡,因為記憶體中是不支援這兩種型別的(關於原因詳見Section 8.4.4, “Internal Temporary Table Use in MySQL )。這裡臨時表的操作涉及到額外的檔案 I/O,在查詢時會影響效能,所以應該儘量避免這樣的操作。
- 儘管BLOB 和 TEXT 允許儲存的值很大,實際使用時,其上限受記憶體及傳輸 buffer 大小的影響。
ENUM 型別
列舉值型別通過在建立表時指定該型別可接受的合法值列表,相比一般的字元型別有以下優勢:
- 節省儲存空間,列舉值實際儲存時會轉成數字。
- 語義化。相比 直接使用數字型別,列舉型別在查詢時會返回其對應的字串。
列舉的建立與使用
列舉中的項通過單引號指定,以下是一個建立和使用列舉型別的示例:
CREATE TABLE shirts ( name VARCHAR(40), size ENUM('x-small', 'small', 'medium', 'large', 'x-large') ); INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'), ('polo shirt','small'); SELECT name, size FROM shirts WHERE size = 'medium'; +---------+--------+ | name| size| +---------+--------+ | t-shirt | medium | +---------+--------+ UPDATE shirts SET size = 'small' WHERE size = 'large'; COMMIT;
不支援在建立時使用表示式,比如下面這樣是不行的:
# :rotating_light: CREATE TABLE sizes ( size ENUM('small', CONCAT('med','ium'), 'large') ); # :rotating_light: SET @mysize = 'medium'; CREATE TABLE sizes ( size ENUM('small', @mysize, 'large') );
列舉項的數字值
列舉列表中每項都對應一個數字值,是一個從 1 開始的索引值。列舉型別最多可包含 65,535 個候選項。
對於寫入的非法值會儲存成 0,所以可通過查詢為 0 的列找出所有這些非法的記錄。
mysql> SELECT * FROM tbl_name WHERE enum_col=0;
NULL 的索引值是 NULL。
以ENUM('Mercury', 'Venus', 'Earth')
為例,以下表格展示了不同值其對應的索引值:
Value | Index |
---|---|
NULL | NULL |
'' | 0 |
'Mercury' | 1 |
'Venus' | 2 |
'Earth' | 3 |
在需要數字的上下文中,列舉型別返回時會自動處理成數字,譬如:
mysql> SELECT enum_col+0 FROM tbl_name;
插入時,數字型別的值會自動與列舉中的索引對應,如果插入的數字帶引號,即嘗試以字串形式存入,MySQL 會先從列舉列表中找,如果找不到相應的匹配項,會將這個插入值當索引處理。所以儘量不要設定數字型別的列舉,這導致存取時一些不可預測的情況發生。比如:
numbers ENUM('0','1','2')
當你存入數字2
時,會解析對應到列舉中索引為 2 的值即'1'
。存入字元'2'
時,與列舉中最後一項匹配。嘗試存入'3'
時,列舉值中沒有相應的匹配,此時 MySQL 嘗試將 3 當成索引存入,對應的值是列舉中的'2'
。
mysql> INSERT INTO t (numbers) VALUES(2),('2'),('3'); mysql> SELECT * FROM t; +---------+ | numbers | +---------+ | 1| | 2| | 2| +---------+
檢視列舉值
通過SHOW COLUMNS FROM tbl_name LIKE 'enum_col'
可在結果中的Type
列檢視定義好的列舉其可選值有哪些。
mysql> SHOW COLUMNS FROM enum_test LIKE 'n'; +-------+-------------------+------+-----+---------+-------+ | Field | Type| Null | Key | Default | Extra | +-------+-------------------+------+-----+---------+-------+ | n| enum('0','1','2') | YES|| NULL|| +-------+-------------------+------+-----+---------+-------+ 1 row in set (0.00 sec)
排序規則
排序時根據其在列舉列表中的索引來定。比如ENUM('b', 'a')
中 'b' 會排在a
前面,而不是按常理那樣覺得a
是在b
前面的。為了避免這種不太直觀的情況,在定義列舉時最好按字母順序定義,也可以在使用ORDER BY
語句時通過指定排序型別來修正,即使用ORDER BY CAST(col as CHAR)
或ORDER BY CONCAT(col)
。
空字串會排在非空值前面,NULL 值在所有值前面。
SET 型別
SET 型別是一組字串值,它的值類似於列舉,必需是定義表時指定的候選值中所列出的值,不同點在於它可同時支援設定多個候選值,用逗號分隔(同時意味著候選值自身不能包含逗號)。
比如型別為定義為SET('one', 'two') NOT NULL
的列,可有以下合法的值:
'' 'one' 'two' 'one,two'
SET
最多可定義 64 個候選值。嚴格模式下,定義時如果指定了重複的候選值會拋錯。候選值末尾的空白會自動擷取掉。
內部儲存時其實存的是一個位元組表。每個候選值都對應一個二進位制值中的一位。比如定義如下的表:
CREATE TABLE set_test( rowid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, myset SET('Travel','Sports','Dancing','Fine Dining') );
每個值對應一個二進位制位數值,所以每個候選值也對應一個計算後的數字。
Element | SET Value | Decimal Value |
---|---|---|
Travel | 00000001 | 1 |
Sports | 00000010 | 2 |
Dancing | 00000100 | 4 |
Fine Dining | 00001000 | 8 |
這樣在設定時,如果設定的是數字,會解析成對應的二進位制形式,比如 9=8+1,對應Travel
和Fine Dining
。所以實際設定成了Travel,Fine Dining
。
同時 9 的二進位制表示是00001001
,通過上面表格對比可知相應位(二進位制中第0位和第三位)對應的值也是Travel/00000001
和Fine Dining/00001000
。
在設定時不關心候選值的順序及次數,多次指定同一個候選值最終只會出現一次。
mysql> CREATE TABLE myset (col SET('a', 'b', 'c', 'd')); mysql> INSERT INTO myset (col) VALUES -> ('a,d'), ('d,a'), ('a,d,a'), ('a,d,d'), ('d,a,d'); Query OK, 5 rows affected (0.01 sec) Records: 5Duplicates: 0Warnings: 0 mysql> SELECT col FROM myset; +------+ | col| +------+ | a,d| | a,d| | a,d| | a,d| | a,d| +------+ 5 rows in set (0.04 sec)
通過
FIND_IN_SET()
及
LIKE
來查詢 SET 值。
mysql> SELECT * FROM tbl_name WHERE FIND_IN_SET('value',set_col)>0; mysql> SELECT * FROM tbl_name WHERE set_col LIKE '%value%';
以下用法也是可行的:
mysql> SELECT * FROM tbl_name WHERE set_col & 1; mysql> SELECT * FROM tbl_name WHERE set_col = 'val1,val2';