總結:被MySQL UTF8編碼坑的慘痛教訓...

timg.jpg
來源:https://blog.liexing.me/2018/...html

最近遇到幾個項目被MySQL的utf8編碼坑,想起以前編碼問題被坑的慘痛教訓,記錄一下,警示本身。 mysql

曾幾什麼時候,每次建庫都選utf8,以爲本身比那些用亂七八糟編碼的人不知道酷到哪裏去了。直到好多年前的某次課程設計作項目的時候,愉快的建了個用戶表:sql

CREATE TABLE `test_user` ( 
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
`name` varchar(32) DEFAULT NULL,  
 PRIMARY KEY (`id`)
 ) 
ENGINE=InnoDB DEFAULT CHARSET=utf8;

而後愉快的新增用戶:INSERT INTO test_user(name) VALUES("我是😁"),接着愉快的反思人生:數據庫

Incorrect string value: '\xF0\x9F\x98\x81' for column 'name' at row 1

我是誰?我來自哪裏?我在幹嗎?難道是我代碼裏面的字符集用錯了?不對啊我全部地方都用的utf8啊…… segmentfault

#MySQL的UTF8編碼是什麼?服務器

首先來看官方文檔:ide

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.oop

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.性能

咱們再看看維基百科對UTF8編碼的解釋:

UTF-8 is a variable width character encoding capable of encoding all 1,112,064 valid code points in Unicode using one to four 8-bit bytes.

能夠看出,MySQL中的utf8實質上不是標準的UTF8。MySQL中,utf8對每一個字符最多使用三個字節來表示,因此一些emoji甚至是一些生僻漢字就存不下來了,好比「𡋾」。

MySQL一直不認可這是一個bug,他們在2010年發佈了「utf8mb4」字符集來繞過這個問題,在MySQL中,utf8mb4才應該是標準的utf8編碼,而且官方很雞賊的偷偷在最新的文檔中加上了,算是認識到錯誤了吧:

utf8 is an alias for the utf8mb3 character set.

The utf8mb3 character set will be replaced by utf8mb4 in some future MySQL version. Although utf8 is currently an alias for utf8mb3, at that point utf8 will become a reference to utf8mb4. To avoid ambiguity about the meaning of utf8, consider specifying utf8mb4 explicitly for character set references instead of utf8.

#MySQLUTF8問題簡史

MySQL從4.1版本開始支持utf8,即2003年,可是如今的utf8標準(RFC 3629)是在其後發佈的。MySQL在2002年3月28日的4.1預覽版中使用了舊版的utf8標準(RFC 2279),該標準最多支持每一個字符6個字節,同年9月MySQL調整其utf8字符集最多支持3字節,而這個調整可能只是爲了優化空間(05年前推薦使用CHAR類字段,而一個utf8的CHAR將會佔用6字節長度)和時間性能(05年前在MySQL中使用CHAR字段會有更優的速度)。嗯能夠在GitHub中看到你們對這個坑的吐槽:

可是這個字符編碼發佈出來,就不能輕易的修改,由於若是已經有用戶開始使用了,就須要這些用戶從新構建其數據庫。

怎麼補救呢?在上面最新文檔中能夠看出,他們將當前的utf8做爲utf8mb3的別名,而且在未來的某一天會把utf8從新做爲utf8mb4別名,這樣來解決這個多年的巨坑。

# 啥是UTF8

# utf8mb4_unicode_ci 和 utf8mb4_general_ci

字符除了存儲,還須要排序或者比較,這個操做與編碼字符集有關,稱爲collation,與utf8mb4對應的是utf8mb4_unicode_ci 和 utf8mb4_general_ci這兩個collation。

準確性

utf8mb4_unicode_ci 是基於標準Unicode來進行排序比較的,能保持在各個語言之間的精確排序;

utf8mb4_general_ci 並不基於Unicode排序規則,所以在某些特殊語言或者字符上的排序結果可能不是所指望的。

性能

utf8mb4_general_ci 在比較和排序時更快,由於其實現了一些性能更好的操做,可是在現代服務器上,這種性能提高几乎能夠忽略不計。

utf8mb4_unicode_ci 使用Unicode的規則進行排序和比較,其排序規則爲了處理一些特殊字符,實現更加複雜。

如今基本沒有理由繼續使用utf8mb4_general_ci了,由於其帶來的性能差別很小,遠不如更好的數據設計,好比使用索引等等。

# MySQL用錯編碼怎麼救

  1. 備份,否則崩了就只有刪庫跑路了;
  2. 升級MySQL服務端到5.3.3及以上版本,以支持utf8mb4;
  3. 將數據庫、表、列的字符編碼、collation改成utf8mb4:
# For each database:
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
# For each table:
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# For each column:
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(length) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

4.檢查列和索引鍵的最大長度;

5.修改鏈接、客戶端、服務端的字符集;

6.修復和優化全部的表,以避免出現一些莫名其妙的錯誤,可使用以下的方式:

# For each table
REPAIR TABLE table_name;
OPTIMIZE TABLE table_name;

或者是使用`mysqlcheck`工具:

$ mysqlcheck -u root -p --auto-repair --optimize --all-databases

# 其餘坑

MySQL表字段字符集不一樣致使的索引失效問題

# 參考

若有錯誤或其它問題,歡迎小夥伴留言評論、指正。若有幫助,歡迎點贊+轉發分享。

歡迎你們關注民工哥的公衆號:民工哥技術之路
image.png

相關文章
相關標籤/搜索