MySQL字串型別欄位值大小寫問題
太長不看版
- MySQL資料庫預設情況下,字串欄位的所有相關運算是大小寫“不敏感”的。這一點與其它流行的資料庫都不相同。
- 本文介紹了三種方法解決這個問題。
- 其中一種在查詢時指定大小寫敏感,但可能存在效能風險。
- 另外兩種則是在表結構定義時定義。
MySQL資料庫備受爭議的特性
先看一句SQL語句:
SELECT * FROM fruit WHERE name = 'Apple'
這是一句再簡單不過的SQL語句,其目的就是從fruit資料表中查詢name為“Apple”的那條記錄。假設資料庫中已經存在一條name欄位值為“apple”的記錄。沒錯,我沒有拼寫錯誤,就是全部小寫的“apple”。請問上面這個語句能夠查詢出這條記錄麼?
正確的答案是,在MySQL資料庫中你很可能可以查出記錄。(如果你已經注意到“很可能”三個字,那麼請你暫時放下,我會稍後解釋。)也就是說MySQL資料庫在執行查詢時是大小寫“不敏感”的。請問這是一件“好事”麼?如果我們的查詢條件換成id欄位,你還期望大小寫不敏感麼?!
初步震驚後的你,禁不住想問:難道別的資料庫也是這樣麼?我當初帶著這個疑問分別在Oracle和PostgreSQL資料庫中進行了測試。結果令人“心碎”,它們都是大小寫“敏感”的。在這個問題上MySQL是如此“特立獨行”。我又帶著這個疑問到網上搜索了一番,結果卻看到了大量的“吐槽”……
進一步的測試發現,在MySQL中不僅“=”運算大小寫不敏感,所有的比較運算,以及LIKE運算皆是如此。
難道就沒有辦法大小寫“敏感”麼?MySQL作為最流行的資料庫之一,當然不會。共有三種解決方案:
方法一:查詢時指定大小寫敏感。
方法二:定義表結構時指定欄位大小寫敏感。
方法三:修改排序規則(COLLATION)。
方法一:查詢時指定大小寫敏感
MySQL允許在查詢的時候指定以大小寫“敏感”方式,需要使用關鍵字“BINARY”,改寫上述查詢如下:
SELECT * FROM fruit WHERE BINARY name = 'Apple'
或者
SELECT * FROM fruit WHERE name = BINARY 'Apple'
其它運算也是如此,例如:
SELECT * FROM fruit WHERE BINARY name LIKE 'App%'
rumba-commons-jdbc.jar針對這個問題也提供了相應的解決方案。對於使用SQL物件模型構造SQL語句的使用者,可以使用以“CaseSensitive”字尾的方法達到上述同樣的效果。例如:
SelectStatement select = new SelectBuilder() //
.from("fruit") //
.where(Predicates.equalsCaseSensitive("name", "Apple")) //
.build();
很多時候當發現MySQL資料庫存在上述問題時,系統已經運行了一段時間,如果採用方法二或方法三的代價可能會很大。使用此方法最大的好處便是可以快速實現功能。
但是這個方法也存在很大的限制:如此可能因為無法使用索引導致查詢效能下降。原因很好理解,因為此時針對查詢欄位的索引也是按照大小寫不敏感方式建立的。除非資料量不大,或者在你的應用中不在乎這點效能上的損失,那麼只能選擇方法二或方法三了。
方法二:定義表結構時指定欄位大小寫敏感
以下程式碼在建立fruit表時指定其中的name欄位大小寫“敏感”:
CREATE TABLE fruit (
...
name VARCHAR(64) BINARY NOT NULL,
...
)
關鍵字“BINARY”指定name欄位大小寫敏感。如此在查詢時就算不使用“BINARY”關鍵字,以下查詢語句也是大小寫敏感的:
SELECT * FROM fruit WHERE name = 'Apple'
在此基礎上建立的name相關的索引也是大小寫敏感的,也就能夠使用索引來提高效能。MySQL允許在大多數字符串型別上使用BINARY關鍵字,用於指明所有針對該欄位的運算是大小寫敏感的,更多資訊請參見MySQL官方文件。
這種方法使得設計者可以精確地控制每個欄位是否大小寫敏感。不過在很多系統的設計中,期望大部分甚至所有的欄位統一大小寫敏感。MySQL也提供瞭解決方案,這就要用到方法三。
方法三:修改排序規則(COLLATION)
涉及字串的各種運算其核心必然涉及到採用何種字元排序規則(Collation,也有翻譯為“核對”)。本質上MySQL是通過Collation取值決定字串運算是否大小寫敏感。
“utf8_general_ci”是一個具體的Collation取值。每個具體的Collation都對應唯一的字符集,可以看出該Collation對應字符集為“utf8”。而與大小寫敏感問題相關的是其後綴“_ci”,MySQL官方文件對其的解釋是“Case Ignore”的縮寫,即大小寫不敏感。由於MySQL將“utf8_general_ci”指定作為字符集utf8的預設Collation,這也就導致文章開頭所說的現象。與此同時,MySQL也提供了其它的Collation取值選項,“utf8_bin”就是大小寫敏感的。事實上所有大小寫敏感的Collation都以“_bin”或“_cs”為字尾,前者是“Binary”的縮寫,後者是“Case Sensitive”的縮寫。
MySQL資料庫允許在“庫”、“表”和“列”三個級別上指定Collation。當同時指定時,優先關係是:列>表>庫。
以下程式碼指定了資料表的預設排序規則,意味著整個資料表中所有字串型別欄位的預設Collation:
CREATE TABLE fruit (
...
name VARCHAR(64) NOT NULL,
...
) COLLATE = utf8_bin;
需要額外說明的是,由於每個Collation都對應唯一的字符集,因此上述程式碼由於指定Collation,就沒有必要再使用“CHARACTER SET = ”指定採用的字元集了。
以下程式碼在“列”上指定Collation:
CREATE TABLE fruit (
...
name VARCHAR(64) COLLATE utf8_bin NOT NULL,
...
);
以下程式碼在“庫”上指定Collation:
CREATE DATABASE mydb COLLATE utf8_bin;
方法三與方法二一樣都是在表結構定義時指定是否大小寫敏感,因此都能有效避免方法一由於無法利用索引導致查詢效能下降的問題。方法二的優點是簡單易學,但是缺少“表”以及“庫”級別的選項,適合“多數不敏感少數敏感”的情況。方法三則更加靈活,更加適合“多數敏感少數不敏感”的情況,但要求使用者必須瞭解Collation相關的知識。比如Collation有哪些合法取值。可以通過以下語句查詢到所有的Collation:
SELECT * FROM information_schema.COLLATIONS;