1、簡介 php
MySQL在5.5.3以後增長了這個utf8mb4的編碼,mb4就是most bytes 4的意思,專門用來兼容四字節的unicode。好在utf8mb4是utf8的超集,除了將編碼改成utf8mb4外不須要作其餘轉換。固然,爲了節省空間,通常狀況下使用utf8也就夠了。html
2、內容描述 java
那上面說了既然utf8可以存下大部分中文漢字,那爲何還要使用utf8mb4呢? 原來mysql支持的 utf8 編碼最大字符長度爲 3 字節,若是遇到 4 字節的寬字符就會插入異常了。三個字節的 UTF-8 最大能編碼的 Unicode 字符是 0xffff,也就是 Unicode 中的基本多文種平面(BMP)。也就是說,任何不在基本多文本平面的 Unicode字符,都沒法使用 Mysql 的 utf8 字符集存儲。包括 Emoji 表情(Emoji 是一種特殊的 Unicode 編碼,常見於 ios 和 android 手機上),和不少不經常使用的漢字,以及任何新增的 Unicode 字符等等。python
3、問題根源 mysql
最初的 UTF-8 格式使用一至六個字節,最大能編碼 31 位字符。最新的 UTF-8 規範只使用一到四個字節,最大能編碼21位,正好可以表示全部的 17個 Unicode 平面。linux
utf8 是 Mysql 中的一種字符集,只支持最長三個字節的 UTF-8字符,也就是 Unicode 中的基本多文本平面。android
Mysql 中的 utf8 爲何只支持持最長三個字節的 UTF-8字符呢?我想了一下,多是由於 Mysql 剛開始開發那會,Unicode 尚未輔助平面這一說呢。那時候,Unicode 委員會還作着 「65535 個字符足夠全世界用了」的好夢。Mysql 中的字符串長度算的是字符數而非字節數,對於 CHAR 數據類型來講,須要爲字符串保留足夠的長。當使用 utf8 字符集時,須要保留的長度就是 utf8 最長字符長度乘以字符串長度,因此這裏理所固然的限制了 utf8 最大長度爲 3,好比 CHAR(100) Mysql 會保留 300字節長度。至於後續的版本爲何不對 4 字節長度的 UTF-8 字符提供支持,我想一個是爲了向後兼容性的考慮,還有就是基本多文種平面以外的字符確實不多用到。ios
要在 Mysql 中保存 4 字節長度的 UTF-8 字符,須要使用 utf8mb4 字符集,但只有 5.5.3 版本之後的才支持(查看版本: select version();)。我以爲,爲了獲取更好的兼容性,應該老是使用 utf8mb4 而非 utf8. 對於 CHAR 類型數據,utf8mb4 會多消耗一些空間,根據 Mysql 官方建議,使用 VARCHAR 替代 CHAR。c++
引用批註 web
[1] 談談字符集與字符編碼
http://my.oschina.net/leejun2005/blog/232732#OSC_h3_4
[2] 關於 MySQL UTF8 編碼下生僻字符插入失敗/假死問題的分析
http://my.oschina.net/leejun2005/blog/343353
原創 2016-12-21 周曉 ACMUG
ACMUG徵集原創技術文章。詳情請添加 A_CMUG或者掃描文末二維碼關注咱們的微信公衆號。有獎徵稿,請發送稿件至:acmug@acmug.com。
3306現金有獎徵稿說明:知識無價,勞動有償,ACMUG特約撰稿人有獎回報計劃(修訂版)
做者簡介:周曉
網絡經常使用id seanlook 。之前在TP-LINK作了2年Oracle DBA,後來專職作MySQL了。平時在工做中遇到的些問題和處理經驗,有空會寫寫放在本身的網站上 http://seanlook.com
01
utf8 與 utf8mb4 異同
先看 官方手冊 https://dev.mysql.com/doc/refman/5.6/en/charset-unicode-utf8mb4.html 的說明:
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,但相信我,你絕對不想這個幹,一是這種字符集混用管理極不規範,二是存儲空間被放大(讀者能夠想下爲何)。
02
utf8mb4_unicode_ci 與 utf8mb4_general_ci 如何選擇
字符除了須要存儲,還須要排序或比較大小,涉及到與編碼字符集對應的 排序字符集(collation)。ut8mb4對應的排序字符集經常使用的有 utf8mb4_unicode_ci、utf8mb4_general_ci,到底採用哪一個在 stackoverflow 上有個討論,What’s the difference between utf8_general_ci and utf8_unicode_ci
主要從排序準確性和性能兩方面看:
準確性
utf8mb4_unicode_ci 是基於標準的Unicode來排序和比較,可以在各類語言之間精確排序
utf8mb4_general_ci 沒有實現Unicode排序規則,在遇到某些特殊語言或字符是,排序結果可能不是所指望的。
可是在絕大多數狀況下,這種特殊字符的順序必定要那麼精確嗎。好比Unicode把?、?當成ss和OE來看;而general會把它們當成s、e,再如????ā?各自都與 A 相等。
性能
utf8mb4_general_ci 在比較和排序的時候更快
utf8mb4_unicode_ci 在特殊狀況下,Unicode排序規則爲了可以處理特殊字符的狀況,實現了略微複雜的排序算法。
可是在絕大多數狀況下,不會發生此類複雜比較。general理論上比Unicode可能快些,但相比如今的CPU來講,它遠遠不足以成爲考慮性能的因素,索引涉及、SQL設計纔是。 我我的推薦是utf8mb4_unicode_ci,未來 8.0 裏也極有可能使用變爲默認的規則。
這也從另外一個角度告訴咱們,不要可能產生亂碼的字段做爲主鍵或惟一索引。我遇到過一例,以 url 來做爲惟一索引,可是它記錄的有多是亂碼,致使後來想把它們修復就特別麻煩。
03
怎麼從utf8轉換爲utf8mb4
3.1 「僞」轉換
若是你的表定義和鏈接字符集都是utf8,那麼直接在你的表上執行
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。因此我才叫它「僞」轉換
一旦你決定使用utf8mb4,強烈建議你要修改服務端 character-set-server=utf8mb4,不一樣的語言對它的處理方法不同,c++, php, python能夠設置character-set,但java驅動依賴於 character-set-server 選項,後面有介紹。
同時還要謹慎一些特殊選項,如 遇到騰訊雲CDB鏈接字符集設置一個坑。我的不建議設置全局 init_connect。
04
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。
COMPRESSED和DYNAMIC格式不受限制,但也依然不建議索引太長,太浪費空間和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 膨脹厲害的一個緣由。
05
C/C++ 內存空間分配問題
這是咱們這邊的開發遇到的一個棘手的問題。C或C++鏈接MySQL使用的是linux系統上的 libmysqlclient 動態庫,程序獲取到數據以後根據自定義的一個網絡協議,按照mysql字段定義的固定字節數來傳輸數據。從utf8轉utf8mb4以後,c++裏面針對character單字符內存空間分配,從3個增長到4個,引發異常。
這個問題實際上是想說明,使用utf8mb4以後,官方建議儘可能用 varchar 代替 char,這樣能夠減小固定存儲空間浪費(關於char與varchar的選擇,可參考 這裏)。但開發設計表時 varchar 的大小不能隨意加大,它雖然是變長的,但客戶端在定義變量來獲取數據時,是以定義的爲準,而非實際長度。按需分配,避免程序使用過多的內存。
06
java驅動使用
Java語言裏面所實現的UTF-8編碼就是支持4字節的,因此不須要配置 mb4 這樣的字眼,但若是從MySQL讀寫emoji,MySQL驅動版本要在 5.1.13 及以上版本,數據庫鏈接依然是 characterEncoding=UTF-8 。
但還沒完,遇到一個大坑。官方手冊 裏還有這麼一段話:
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。然而其餘語言沒有依賴於這樣的特性。
07
主從複製錯誤
這個問題沒有遇到,只是看官方文檔有提到,曾經也看到過相似的技術文章。
大概就是從庫的版本比主庫的版本低,致使有些字符集不支持;或者人工修改了從庫上的表或字段的字符集定義,都有可能引發異常。
08
join 查詢問題
這個問題是以前在姜承堯老師公衆號看到的一篇文章 「MySQL表字段字符集不一樣致使的索引失效問題」,本身也驗證了一下,的確會有問題:
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’)
官網能找到這一點解釋的仍是開頭那個地址:
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的隱式類型轉換,見這裏)。
09
參考
https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-conversion.html
http://forums.mysql.com/read.php?103,187048,188748#msg-188748
Why are we using utf8mb4_general_ci and not utf8mb4_unicode_ci?
About Me
.............................................................................................................................................
● 本文整理自網絡,如有侵權請聯繫小麥苗刪除
● 本文在itpub(http://blog.itpub.net/26736162/abstract/1/)、博客園(http://www.cnblogs.com/lhrbest)和我的微信公衆號(xiaomaimiaolhr)上有同步更新
● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/
● 本文博客園地址:http://www.cnblogs.com/lhrbest
● 本文pdf版、我的簡介及小麥苗雲盤地址:http://blog.itpub.net/26736162/viewspace-1624453/
● 數據庫筆試面試題庫及解答:http://blog.itpub.net/26736162/viewspace-2134706/
● DBA寶典今日頭條號地址:http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826
.............................................................................................................................................
● QQ羣號:230161599(滿)、618766405
● 微信羣:可加我微信,我拉你們進羣,非誠勿擾
● 聯繫我請加QQ好友(646634621),註明添加原因
● 於 2017-12-01 09:00 ~ 2017-12-31 22:00 在魔都完成
● 文章內容來源於小麥苗的學習筆記,部分整理自網絡,如有侵權或不當之處還請諒解
● 版權全部,歡迎分享本文,轉載請保留出處
.............................................................................................................................................
● 小麥苗的微店:https://weidian.com/s/793741433?wfr=c&ifr=shopdetail
● 小麥苗出版的數據庫類叢書:http://blog.itpub.net/26736162/viewspace-2142121/
.............................................................................................................................................
使用微信客戶端掃描下面的二維碼來關注小麥苗的微信公衆號(xiaomaimiaolhr)及QQ羣(DBA寶典),學習最實用的數據庫技術。
小麥苗的微信公衆號 小麥苗的DBA寶典QQ羣2 《DBA筆試面寶典》讀者羣 小麥苗的微店
.............................................................................................................................................
來自 「 ITPUB博客 」 ,連接:http://blog.itpub.net/26736162/viewspace-2148812/,如需轉載,請註明出處,不然將追究法律責任。