2017-02-21 24 views
2

我一直在研究這個問題幾個小時,在SO,MySQL文檔和其他地方,但仍然找不到滿意的解決方案。問題是:如何使MySQL像SQLite那樣處理字符串,關於Unicode和整理?

什麼是使SQL像SQLite一樣簡單的方式處理字符串,沒有任何額外的「智能」轉換?

例如,下面的作品完美SQLite中:

CREATE TABLE `dummy` (`key` VARCHAR(255) NOT NULL UNIQUE); 

INSERT INTO `dummy` (`key`) VALUES ('one'); 
INSERT INTO `dummy` (`key`) VALUES ('one '); 
INSERT INTO `dummy` (`key`) VALUES ('One'); 
INSERT INTO `dummy` (`key`) VALUES ('öne'); 

SELECT * FROM `dummy`; 

然而,在MySQL中使用以下設置:

[client] 
default-character-set = utf8mb4 

[mysql] 
default-character-set = utf8mb4 

[mysqld] 
character-set-client-handshake = FALSE 
character-set-server = utf8mb4 
collation-server = utf8mb4_bin 

及以下CREATE DATABASE聲明:

CREATE DATABASE `dummydb` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_bin; 

它仍然在第二個INSERT

我寧願保持字符串列聲明儘可能簡單,SQLite的的TEXT是理想。 貌似VARBINARY是去的方式,但我還是想聽聽其他任何你的意見,可能更好選項


附錄:該SHOW CREATE TABLE dummy輸出

mysql> SHOW CREATE TABLE dummy; 
+-------+----------------------------------------------------- 
| Table | Create Table           
+-------+----------------------------------------------------- 
| dummy | CREATE TABLE `dummy` (
    `key` varchar(255) COLLATE utf8mb4_bin NOT NULL, 
    UNIQUE KEY `key` (`key`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin | 
+-------+----------------------------------------------------- 
1 row in set (0.00 sec) 
+0

這些都是默認值。你是否在你的'CREATE TABLE'語句中覆蓋它們? –

+0

阿爾瓦羅,你的意思是哪個部分?所有的SQL都如前所述。 –

+0

版本是5.7.17。 –

回答

1

的MySQL希望做INSERTSELECT時,將字符串轉換。轉換是你聲明客戶有和被聲明爲存儲什麼之間。

要避免這種情況的唯一方法是使用VARBINARYBLOB而不是VARCHARTEXT

採用COLLATION utf8mb4_bin不避轉換到/從CHARACTER SET utf8mb4;它只是說WHEREORDER BY應該比較比特而不是處理重音和大小寫摺疊。

記住CHARACTER SET utf8mb4是對文字編碼的方式; COLLATION utf8mb4_*是用於比較該編碼中的文本的規則。 _bin是simpleminded。

UNIQUE涉及比較平等,因此COLLATION。在大多數utf8mb4排序規則中,3(不含空格)將比較相等。 utf8mb4_bin將視爲不同的3。 utf8mb4_hungarian_ci對待one = One>öne。

的後空間由柱(VARCHAR或其他)的數據類型來控制。最新版甚至有一個關於是否考慮拖尾空間的設置。

+0

不確定我關注你,但排序也會影響唯一索引。由於他實際上有文本(而不是二進制數據,例如原始散列),所以他的方法很好(它*應該可以工作)。 –

+0

感謝您的回答。我已經切換到'VARBINARY',並且已經將排序規則恢復爲'utf8mb4_unicode_ci',因爲怪異更明顯。是的,我打算在編寫查詢時牢記可能的隱式轉換。 –

+0

關於尾部空格,你的意思是8.0.x的「最新版本」?我已經看到5.7.x文檔中提到了'PADSPACE',但沒有看到如何改變它的列。 –

1

的問題應該(大部分)所示的方法在MySQL工作得很好,原因如下:

  1. 整理(不要與編碼相混淆)是定義如何排序的集合或規則並比較字符,通常用於在數據庫級別複製用戶從文化角度的期望(如果我搜索cafe I 期望也可以找到café)。

  2. 排序法對獨特的約束條件起着重要的作用,因爲它建立了獨特的定義

  3. 二進制排序規則專門用於忽略文字規則和字節級別的工作,因此utf8mb4_bin是正確的選擇。

  4. MySQL允許使用列級粒度設置編碼和歸類的組合。

  5. 如果列定義缺少排序規則,它將使用表級別1。

  6. 如果表定義缺少排序規則,它將使用數據庫級別1。

  7. 如果數據庫定義缺少排序規則,它將使用服務器級別1。

另外值得一提的是,MySQL將編碼之間只要透明地轉換:

  • 連接編碼是正確設置
  • 轉換爲實際可能的(例如,所有的源字符也屬於目標編碼)

由於這最後一個原因,VARBINARY可能不是列文本的最佳選擇,因爲它是ope ns是從配置爲使用ISO-8859-1的連接獲取café並且無法從配置爲使用UTF-8的連接正確檢索它的大門。


邊注:所示可以觸發以下錯誤表定義:

ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

索引可以具有相對小的最大尺寸。從docs

If innodb_large_prefix is enabled (the default), the index key prefix limit is 3072 bytes for InnoDB tables that use DYNAMIC or COMPRESSED row format. If innodb_large_prefix is disabled, the index key prefix limit is 767 bytes for tables of any row format.

innodb_large_prefix is deprecated and will be removed in a future release. innodb_large_prefix was introduced in MySQL 5.5 to disable large index key prefixes for compatibility with earlier versions of InnoDB that do not support large index key prefixes.

The index key prefix length limit is 767 bytes for InnoDB tables that use the REDUNDANT or COMPACT row format. For example, you might hit this limit with a column prefix index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character.

Attempting to use an index key prefix length that exceeds the limit returns an error. To avoid such errors in replication configurations, avoid enabling innodb_large_prefix on the master if it cannot also be enabled on slaves.

由於utf8_mb8分配每個字符的4個字節,一個767限制將與僅192個字符被溢出。


我們有更多的一個問題:

mysql> CREATE TABLE `dummy` (
    -> `key` varchar(191) COLLATE utf8mb4_bin NOT NULL, 
    -> UNIQUE KEY `key` (`key`) 
    ->) 
    -> ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; 
Query OK, 0 rows affected (0.01 sec) 

mysql> INSERT INTO `dummy` (`key`) VALUES ('one'); 
Query OK, 1 row affected (0.00 sec) 

mysql> INSERT INTO `dummy` (`key`) VALUES ('one '); 
ERROR 1062 (23000): Duplicate entry 'one ' for key 'key' 

赦免?

mysql> INSERT INTO `dummy` (`key`) VALUES ('One'); 
Query OK, 1 row affected (0.00 sec) 

mysql> INSERT INTO `dummy` (`key`) VALUES ('öne'); 
Query OK, 1 row affected (0.00 sec) 

mysql> SELECT * FROM `dummy`; 
+-----+ 
| key | 
+-----+ 
| One | 
| one | 
| öne | 
+-----+ 
3 rows in set (0.00 sec) 

這最後一個問題是MySQL歸類的一個有趣的微妙之處。從docs

All MySQL collations are of type PADSPACE. This means that all CHAR, VARCHAR, and TEXT values in MySQL are compared without regard to any trailing spaces. 「Comparison」 in this context does not include the LIKE pattern-matching operator, for which trailing spaces are significant

[...] For those cases where trailing pad characters are stripped or comparisons ignore them, if a column has an index that requires unique values, inserting into the column values that differ only in number of trailing pad characters will result in a duplicate-key error.

我敢說那麼VARBINARY類型是克服這一點的唯一途徑...

+0

感謝您的詳細解答。澄清:我沒有收到任何錯誤(除了PADSPACE導致的INSERT錯誤'one '),所以密鑰長度問題可能與您的特定MySQL設置有關。 –

+0

@SeaCoastofTibet密鑰大小問題在我共享的文檔摘錄中進行了解釋。不過,我必須承認,我一直誤解你的問題,直到現在。我固執地認爲二進制整理沒有起作用,事實並非如此。我讀得太快了。我正在刪除相關評論以避免傳播錯誤信息。 –