mysql使用utf8mb4經驗吐血總結


1. utf8 與 utf8mb4 異同

先看 官方手冊 https://dev.mysql.com/doc/refman/5.6/en/charset-unicode-utf8mb4.html 的說明:

1
2
3
4
The character set named utf8 uses a maximum of three bytes per character and contains only BMP characters. The utf8mb4 character set uses a maximum of four bytes per character supports supplementary characters:

- For a BMP character, utf8 and utf8mb4 have identical storage characteristics: same code values, same encoding, same length.
- For a supplementary character, utf8 cannot store the character at all, whereas utf8mb4 requires four bytes to store it. Because utf8 cannot store the character at all, you have no supplementary characters in utf8 columns and need not worry about converting characters or losing data when upgrading utf8 data from older versions of MySQL.

 

MySQL在 5.5.3 之后增加了 utf8mb4 字符編碼,mb4即 most bytes 4。簡單說 utf8mb4 是 utf8 的超集並完全兼容utf8,能夠用四個字節存儲更多的字符。

但拋開數據庫,標准的 UTF-8 字符集編碼是可以用 1~4 個字節去編碼21位字符,這幾乎包含了是世界上所有能看見的語言了。然而在MySQL里實現的utf8最長使用3個字節,也就是只支持到了 Unicode 中的 基本多文本平面(U+0000至U+FFFF),包含了控制符、拉丁文,中、日、韓等絕大多數國際字符,但並不是所有,最常見的就算現在手機端常用的表情字符 emoji和一些不常用的漢字,如 “墅” ,這些需要四個字節才能編碼出來。

注:QQ里面的內置的表情不算,它是通過特殊映射到的一個gif圖片。一般輸入法自帶的就是。

也就是當你的數據庫里要求能夠存入這些表情或寬字符時,可以把字段定義為 utf8mb4,同時要注意連接字符集也要設置為utf8mb4,否則在 嚴格模式 下會出現 Incorrect string value: /xF0/xA1/x8B/xBE/xE5/xA2… for column 'name'這樣的錯誤,非嚴格模式下此后的數據會被截斷。

提示:另外一種能夠存儲emoji的方式是,不關心數據庫表字符集,只要連接字符集使用 latin1,但相信我,你絕對不想這個干,一是這種字符集混用管理極不規范,二是存儲空間被放大(讀者可以想下為什么)。

2. utf8mb4_unicode_ci 與 utf8mb4_general_ci 如何選擇

字符除了需要存儲,還需要排序或比較大小,涉及到與編碼字符集對應的 排序字符集(collation)。ut8mb4對應的排序字符集常用的有 utf8mb4_unicode_ciutf8mb4_general_ci,到底采用哪個在 stackoverflow 上有個討論,What’s the difference between utf8_general_ci and utf8_unicode_ci

主要從排序准確性和性能兩方面看:

  • 准確性
    utf8mb4_unicode_ci 是基於標准的Unicode來排序和比較,能夠在各種語言之間精確排序
    utf8mb4_general_ci 沒有實現Unicode排序規則,在遇到某些特殊語言或字符是,排序結果可能不是所期望的。
    但是在絕大多數情況下,這種特殊字符的順序一定要那么精確嗎。比如Unicode把ߌ當成ssOE來看;而general會把它們當成se,再如ÀÁÅåāă各自都與 A 相等。
  • 性能
    utf8mb4_general_ci 在比較和排序的時候更快
    utf8mb4_unicode_ci 在特殊情況下,Unicode排序規則為了能夠處理特殊字符的情況,實現了略微復雜的排序算法。
    但是在絕大多數情況下,不會發生此類復雜比較。general理論上比Unicode可能快些,但相比現在的CPU來說,它遠遠不足以成為考慮性能的因素,索引涉及、SQL設計才是。 我個人推薦是 utf8mb4_unicode_ci,將來 8.0 里也極有可能使用變為默認的規則。相比選擇哪一種collation,使用者應該更關心字符集與排序規則在db里要統一就好。

這也從另一個角度告訴我們,不要可能產生亂碼的字段作為主鍵或唯一索引。我遇到過一例,以 url 來作為唯一索引,但是它記錄的有可能是亂碼,導致后來想把它們修復就特別麻煩。

3. 怎么從utf8轉換為utf8mb4

3.1 “偽”轉換

如果你的表定義和連接字符集都是utf8,那么直接在你的表上執行

1
ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4;

 

則能夠該表上所有的列的character類型變成 utf8mb4,表定義的默認字符集也會修改。連接的時候需要使用set names utf8mb4便可以插入四字節字符。(如果依然使用 utf8 連接,只要不出現四字節字符則完全沒問題)。

上面的 convert 有兩個問題,一是它不能ONLINE,也就是執行之后全表禁止修改,有關這方面的討論見 mysql 5.6 原生Online DDL解析;二是,它可能會自動該表字段類型定義,如 VARCHAR 被轉成 MEDIUMTEXT,可以通過 MODIFY 指定類型為原類型。

另外 ALTER TABLE tbl_name DEFAULT CHARACTER SET utf8mb4 這樣的語句就不要隨便執行了,特別是當表原本不是utf8時,除非表是空的或者你確認表里只有拉丁字符,否則正常和亂的就混在一起了。

最重要的是,你連接時使用的latin1字符集寫入了歷史數據,表定義是latin1或utf8,不要期望通過 ALTER ... CONVERT ... 能夠讓你達到用utf8讀取歷史中文數據的目的,沒卵用,老老實實做邏輯dump。所以我才叫它“偽”轉換

3.2 character-set-server

一旦你決定使用utf8mb4,強烈建議你要修改服務端 character-set-server=utf8mb4,不同的語言對它的處理方法不一樣,c++, php, python可以設置character-set,但java驅動依賴於 character-set-server 選項,后面有介紹。

同時還要謹慎一些特殊選項,如 遇到騰訊雲CDB連接字符集設置一個坑。個人不建議設置全局 init_connect

4. key 768 long 錯誤

字符集從utf8轉到utf8mb4之后,最容易引起的就是索引鍵超長的問題。

對於表行格式是 COMPACT或 REDUNDANT,InnoDB有單個索引最大字節數 768 的限制,而字段定義的是能存儲的字符數,比如 VARCHAR(200) 代表能夠存200個漢字,索引定義是字符集類型最大長度算的,即 utf8 maxbytes=3, utf8mb4 maxbytes=4,算下來utf8和utf8mb4兩種情況的索引長度分別為600 bytes和800bytes,后者超過了768,導致出錯:Error 1071: Specified key was too long; max key length is 767 bytes

COMPRESSEDDYNAMIC格式不受限制,但也依然不建議索引太長,太浪費空間和cpu搜索資源。

如果已有定義超過這個長度的,可加上前綴索引,如果暫不能加上前綴索引(像唯一索引),可把該字段的字符集改回utf8或latin1。
但是,( 敲黑板啦,很重要 ),要防止出現 Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '=' 錯誤:連接字符集使用utf8mb4,但 SELECT/UPDATE where條件有utf8類型的列,且條件右邊存在不屬於utf8字符,就會觸發該異常。表示踩過這個坑。

再多加一個友好提示:EXPLAIN 結果里面的 key_len 指的搜索索引長度,單位是bytes,而且是以字符集支持的單字符最大字節數算的,這也是為什么 INDEX_LENGTH 膨脹厲害的一個原因。

5. C/C++ 內存空間分配問題

這是我們這邊的開發遇到的一個棘手的問題。C或C++連接MySQL使用的是linux系統上的 libmysqlclient 動態庫,程序獲取到數據之后根據自定義的一個網絡協議,按照mysql字段定義的固定字節數來傳輸數據。從utf8轉utf8mb4之后,c++里面針對character單字符內存空間分配,從3個增加到4個,引起異常。

這個問題其實是想說明,使用utf8mb4之后,官方建議盡量用 varchar 代替 char,這樣可以減少固定存儲空間浪費(關於char與varchar的選擇,可參考 這里)。但開發設計表時 varchar 的大小不能隨意加大,它雖然是變長的,但客戶端在定義變量來獲取數據時,是以定義的為准,而非實際長度。按需分配,避免程序使用過多的內存。

6. java驅動使用

Java語言里面所實現的UTF-8編碼就是支持4字節的,所以不需要配置 mb4 這樣的字眼,但如果從MySQL讀寫emoji,MySQL驅動版本要在 5.1.13 及以上版本,數據庫連接依然是 characterEncoding=UTF-8 。

但還沒完,遇到一個大坑。官方手冊 里還有這么一段話:

1
2
3
4
Connector/J did not support utf8mb4 for servers 5.5.2 and newer.

Connector/J now auto-detects servers configured with character_set_server=utf8mb4 or treats the Java encoding utf-8 passed
using characterEncoding=... as utf8mb4 in the SET NAMES= calls it makes when establishing the connection. (Bug #54175)

 

意思是,java驅動會自動檢測服務端 character_set_server 的配置,如果為utf8mb4,驅動在建立連接的時候設置 SET NAMES utf8mb4。然而其他語言沒有依賴於這樣的特性。

7. 主從復制報錯

這個問題沒有遇到,只是看官方文檔有提到,曾經也看到過類似的技術文章。
大概就是從庫的版本比主庫的版本低,導致有些字符集不支持;或者人工修改了從庫上的表或字段的字符集定義,都有可能引起異常。

8. join 查詢問題

這個問題是之前在姜承堯老師公眾號看到的一篇文章 MySQL表字段字符集不同導致的索引失效問題,自己也驗證了一下,的確會有問題:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE t1 (
f_id varchar(20) NOT NULL,
f_action char(25) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY (`f_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

CREATE TABLE t1_copy_mb4 (
f_id varchar(20) CHARACTER SET utf8mb4 NOT NULL,
f_action char(25) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY (`f_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

1.
EXPLAIN extended select * from t1 INNER JOIN t1_copy_mb4 t2 on t1.f_id=t2.f_id where t1.f_id='421036';

2.
EXPLAIN extended select * from t1 INNER JOIN t1_copy_mb4 t2 on t1.f_id=t2.f_id where t2.f_id='421036';

 

對應上面1,2 的截圖:

其中 2 的warnings 有convert:

  • (convert(t1.f_id using utf8mb4) = ‘421036’)

官網能找到這一點解釋的還是開頭那個地址:

1
2
3
4
Similarly, the following comparison in the WHERE clause works according to the collation of utf8mb4_col:

SELECT * FROM utf8_tbl, utf8mb4_tbl
WHERE utf8_tbl.utf8_col = utf8mb4_tbl.utf8mb4_col;

 

只是索引失效發生在utf8mb4列 在條件左邊。(關於MySQL的隱式類型轉換,見這里)。

9. 參考


原文鏈接地址:http://seanlook.com/2016/10/23/mysql-utf8mb4/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM