MySQL字符編碼的討論:如何處理emoji等4字節的Unicode字符-utf8mb4vs.utf8Collations
1. Unicode是什麼
Unicode(中文:萬國碼、國際碼、統一碼、單一碼)是計算機科學領域裡的一項業界標准。它對世界上大部分的文字系統進行了整理、編碼,使得電腦可以用更為簡單的方式來呈現和處理文字。
簡單說來,就是把世界上所有語言的字,加上所有能找到的符號(如高音譜號、麻將、emoji)用同一套編碼表示出來。
2. UTF-8是什麼
UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼。可變長度的意思在於,如果能使用1字節編碼,UTF-8絕對不會使用2字節去表示。舉個例子,UTF-8的1字節部分和ASCII碼是相同的。所以表示'A'這個字符的時候,UTF-8與ASCII碼不僅編碼相同,而且都是只使用1字節。
3. Character Set和Collation是什麼
Character Set是一套符號以及編碼。Collation是character set的排序方法。在中文版的MySQL中,character set被翻譯為“字符集”,collation被翻譯為“整理”。
舉個例子,UTF-8是character set,utf8_unicode_ci和utf8mb4_unicode_ci就是collation。
Collation的作用主要有二:字符排序與查找字符。
字符排序的作用是顯而易見的,不過還是要用幾個例子加以說明。比如要比較a和b的大小,因為在26個英文字母裡面,a在b前,所以在編碼的時候,也把a放在b前面。這樣就產生了第一種排序方式,通過字符編碼的大小來排序。而在中文裡面,“年”和“日”的排序,除了按照字符編碼大小,還可以有另外一些標准。比如可以按照筆畫序,“年”的第一筆是丿,“日”的第一筆是丨,而丨是排在丿前的,所以就將“日”排在前面;也可以按拼音序,“年”是n開頭,“日”是r開頭,於是把“年”排在前面。除此以外,還可以定義部首序、筆畫數序等等,而不同的排序方法會有不同的結果。英文也有大小寫敏感與不敏感的排序方式。種種不同的排序方式,就形成了不同的collations。
Collation的第二個作用則是查找字符是否在一個字符集裡面。既然是一個有序的集合,則可以快速地通過一個編碼值確定一個字符是否在集合內。這個特性是我們在不知不覺中使用的。比如使用中文輸入法,就是通過輸入法找到一個編碼,通過collation把它查找出來的。
4. Unicode再深入:Plane和中日韓越統一表意文字
utf8_unicode_ci和utf8mb4_unicode_ci這兩個collations都是基於UTF-8編碼的,但排序方面或多或少會有差別。可是更大的差別是它查找字符的集合。這需要提到一個Unicode的概念:Plane。
4.1. Plane
Plane中文譯作“Unicode平面字符映射”,不過我們還是叫它plane好啦。目前的Unicode字符分為17個planes,而每個plane擁有65536(即2^16)個代碼點。可以認為一個plane就是一個范圍的編碼。
Plane 0也叫做BMP(Basic Multilingual Plane,基本多文種平面),存放著世界上各種語言與標記中最常用的字符。
Plane 1也叫做SMP(Supplementary Multilingual Plane,多文種補充平面),放著表情符號(emoji)、字母與數學符號、音樂符號、太玄經(太極符號)、裝飾符號、撲克牌、麻將符號、箭頭擴展和一些世界上各種語言不太常用的文字等等。
Plane 2也叫做SIP(Supplementary Ideographic Plane,表意文字補充平面),用於存放統一漢字(見4.2)的一些罕用字與漢藏語系其他語言的用字(如粵語用字)。
4.2. 統一漢字的分布
對於統一漢字(中日韓越統一表意文字,CJKV Unified Ideographs)來說,BMP存放著最初的版本(也是最常用字)與擴展A區的漢字。擴展B區到即將到來的擴展E區都放在SIP中。
在這些區中,除了獨立字源的字,還有同一個字源或部首不同的變體或寫法。比如“戶”的第一筆,中國大陸與香港寫作“戶”,台灣寫作“戶”,日本則寫作“戸”。這些差異也會在Unicode中用三個不同的編碼去表示。所以B區到E區有不少此種字體。
舉些B區的例子。網絡上之前流行的“不會功夫不要艹我”被寫成““xx巭嫑莪”,其中“xx”這個字就是在B區。而粵語“x雞”(閹雞)、“x完松”(和一個人發生關系後棄之而去)兩個詞的首字也是在B區。
5. utf8_unicode_ci和utf8mb4_unicode_ci的異同
這兩種collations所對應的字符都是UTF-8編碼的一個子集。utf8_unicode_ci最多能找到3個字節的Unicode編碼,而utf8mb4_unicode_ci則能找到4個字節的編碼。由於調整後的UTF-8編碼格式規定最多使用4字節(原來是6字節)編碼,所以utf8mb4系列可以說是覆蓋了整個Unicode編碼。
由於utf8_unicode_ci最多能找到3個字節的編碼,意味著它只支持BMP中的字符,對於SMP與SIP以及其他頭一字節不為0x00、需要4字節編碼的planes來說,utf8_unicode_ci這種collation是無法支持。當使用4字節的字符(如emoji與B區以後的統一漢字)對使用此種collation的字段進行增刪查改時,數據庫會報一個非法字符的異常。而utf8mb4則沒有此問題。由此也看出,utf8mb4_unicode_ci是utf8_unicode_ci的超集。
6. utf8mb4_unicode_ci的優缺點
utf8mb4系列的Collation在MySQL 5.5以上開始支持。相比起utf8_unicode_ci,它有如下的特性:
1) 在數據表中,對於BMP中的字符(最多使用3字節的字符,最常用的字符),兩種collations具有完全相同的存儲特性:相同的碼值,相同的編碼方式,相同的存儲長度。不會增加任何的存儲開銷。
2) 在數據表中,對於其他plains的字符,utf8系列的collation根本不能存儲,而utf8mb4系列的collations則可以存儲。
3) 在數據表中,對於變長的字段(如VARCHAR2,TEXT),utf8mb4最大可存儲的字符可能少於utf8系列的collation。
4) 在索引中,對於文本類型的字段,utf8mb4可索引的字符少於utf8系列的collations。如InnoDB的索引最多使用767字節。如果使用utf8mb4,每一個字符都會預留4字節做索引,而utf8則預留3字節。故此前者是191個字符,後者是255個字符。
5) 由於4)的原因,加上字符集大,utf8mb4的性能可能比utf8系列的collations低。
6) 若升級前的字段做了索引,需要把索引字符限制在191字符或以內。
7. 當前系統用哪個好
在當前的系統,全部都使用utf8_unicode_ci這種collation。但是在存儲網頁標題時,標題帶有SMP或者SIP的字符,如emoji、粵語字,會引發數據庫寫入異常。於是,就有兩種解決方向:
1) 扔掉。
1.1) 扔掉或截斷引發異常的字。采取此種方法,需要對每一個標題進行掃描。
1.2) 扔掉整條記錄。可以采取掃描法,或者扔掉引發異常的記錄。
2) 升級到utf8mb4。會略為降低數據庫性能。
7.1. 性能考慮
首先對於寫入性能,查找字體的性能損耗由於在寫入前字符都已經變成編碼,基本可以忽略。對於網絡傳輸的性能,則需要繼續查找相關資料繼續查證。但初步估計由於目前數據庫在本地,故此這部分開銷的增長不太明顯。
而對於索引的性能,由於網頁標題這一字段沒有做索引,在可預見的將來也未有此計劃,故此沒有性能的損耗,也沒有升級兼容性的擔心。
況且,倘若走扔掉數據的方向,若采取掃描法,則需要付出掃描的開銷。若采取扔掉記錄法,則會先觸發事務回滾,其他記錄需要下次重新寫入。而且當一批記錄寫入時有k個記錄引發異常,則需要回滾與重試k次,除非使用掃描法預先掃描出這些異常的記錄。但這也會引入額外的程序與數據庫開銷。若不使用事務,則數據庫總體寫入性能會大為降低。
雖然沒有實測過,但從感覺上來定性判斷,似乎扔掉記錄比升級collation帶來的性能退化要大。
7.2. 存儲空間考慮
當前的網頁標題是使用VARCHAR2存儲。對於現在可用的、常見的BMP字符,不會引入額外的存儲開銷。BMP字符在VARCHAR的類型下不會為每一字符引入額外33%的空間開銷。反之,定長的CHAR就會引入這種額外開銷。
7.3. 目標數據考慮
網頁標題作為以後特征分析的數據源。在分析需求完全沒有確定的情況下,我認為扔掉任何數據都是不宜采取的辦法,特別是整條記錄扔掉更是不推薦。因為現階段我們沒有一套標准去判定何為有效數據、何為無效數據。有可能引發異常的那部分數據確實是沒用的數據,也有可能那部分人群更傾向於在我們平台上活躍使用。既然各種可能性都存在,我們主動放棄一部分可能性,似乎不太恰當。
7.4. API設計與兼容性考慮
由於utf8_unicode_ci與utf8mb4_unicode_ci都是使用UTF-8編碼,所以對於JAVA,使用MyBatis生成的代碼是一樣的,都是使用String類型。這點已經實測過。加上這兩種collations在BMP中的編碼完全一致,所以使用3字節與4字節的系統,對於BMP中的字符都是完全兼容、正常顯示的。而對於3字節的系統,4字節的字符一般會顯示成一個方框,或者在一個方框中有幾個小數字,不會引發系統異常。
8. 總結
誠然,emoji對分詞分析目前來說還沒有什麼效果,粵語詞而且在SIP中也只是其中一部分,也不知道有多少日本動漫或者愛情動作片的網頁會遇到這些生僻字,音樂符號也少人用,太極符號也不是每次都出現,一些數學增補的字符與箭頭增補圖案也不是每個人都會用。這些加起來可能不知夠不夠全部的千分之一。
但是倘若每一兩個小時就會由於字符不能寫入,引發數據庫的異常。通過上面的分析,我認為增加這種兼容性帶來的成本是可以接受的。
故此,我建議使用升級的方法,兼容所有Unicode字符。