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

2021-02-19 21ic電子網

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

曾幾何時,每次建庫都選utf8,覺得自己比那些用亂七八糟編碼的人不知道酷到哪裡去了。直到好多年前的某次課程設計做項目的時候,愉快的建了個用戶表:

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啊……

# MySQL的UTF8編碼是什麼?

首先來看官方文檔:

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.

我們再看看維基百科對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.

# MySQL UTF8問題簡史

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用錯編碼怎麼救

備份,不然崩了就只有刪庫跑路了;

升級MySQL服務端到5.3.3及以上版本,以支持utf8mb4;

將資料庫、表、列的字符編碼、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表欄位字符集不同導致的索引失效問題

# 參考

https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434

https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8.html

https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci

https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4

作者:liexing

來源:https://blog.liexing.me/2018/07/27/mysql-utf8/

相關焦點

  • 記住沒:永遠不要在 MySQL 中使用 UTF-8
    』 at row 1我用的是 UTF-8 編碼的客戶端,伺服器也是 UTF-8 編碼的,資料庫也是,就連要保存的這個字符串「 <…」也是合法的 UTF-8。MySQL 的「utf8」是一種「專屬的編碼」,它能夠編碼的 Unicode 字符並不多。我要在這裡澄清一下:所有在使用「utf8」的 MySQL 和 MariaDB 用戶都應該改用「utf8mb4」,永遠都不要再使用「utf8」。那麼什麼是編碼?什麼是 UTF-8?
  • 面試官:為什麼不建議在 MySQL 中使用 UTF-8?
    一年後,我看到一篇文章講到emoji文字佔4個字節,通常要用utf-8去接收才行,其他編碼可能會出錯。我突然想到去年操作MySQL把utf8改成utf8mb4的事兒。嗯?他本身不就是utf8編碼麼!那我當時還改個錘子?難道,MySQL的utf8不是真正的UTF-8編碼嗎??! 臥槽這。。MySQL有bug!
  • 經典總結:ICD-10合併編碼的規則探討
    首頁主要診斷:慢性腎炎 N03.5、N03.7、N03.8(糾正:合併為N02.5、N02.7)其他診斷:繫膜增生性腎炎伴新月體形成及硬化                   IgA腎病LeeV級                   慢性腎衰竭CKD3期N18.8編碼查找與分析
  • MySQL 關鍵字查詢
    15', 'Lixiao', '33'), ('16', 'LIXIAOLI', '49'), ('17', 'sunYU', '50'), ('18', 'Liaihua', '54'), ('19', 'huXIaoYang', '44'), ('20', 'chenxiaoXIAO', '34'), ('21', 'wangjun', '90%'), ('22', 'zhaoqian', '_mysql
  • 慘痛教訓太多……
    ☀報料投稿|廣告合作|請加客服微信:sxfx114☀本平臺特約法律顧問: 李雲強 TEL: 182 9176 9966不管幹啥手機都不離手最近在安徽合肥教訓慘痛的一幕發生了近年來各地出現無數慘痛案例⚠️️2019年12月 貴州一名裝修工人邊玩手機邊上電梯電梯門開了但電梯卻沒到他一腳踩空掉進電梯井裡幸好他及時抓住鋼索救援人員發現他時他已經從30樓順著鋼索滑墜至8樓
  • 知乎面試官:為什麼不建議在 MySQL 中使用 UTF-8?
    他本身不就是 utf8 編碼麼!那我當時還改個錘子?難道,MySQL 的 utf8 不是真正的 UTF-8 編碼嗎??! 臥槽,這 MySQL 有 Bug!帶著疑問查詢了很多相關材料,才發現這竟然是 MySQL 的一個歷史遺留問題。我笑了,沒想到這麼牛 B 的 MySQL 也會有這段往事。
  • 相親必備知識:相親經驗總結
    男生相親失敗的原因有很多關鍵還是要好技巧,下面,整理奉上相親經驗總結:秘籍一、形象加分,上「眼緣」
  • 完全解析:視頻編碼與封裝
    -----       理解了碼率之後我們就應當理解視頻的編碼,視頻編碼的存在意義就是正確的拋棄視頻信息來達到壓縮視頻容量的目的。所謂正確就是指編碼高效率,智能的在儘可能不改變畫面觀感的情況下捨棄信息。常規的有損編碼可以分為二種:幀內壓縮與幀間壓縮。幀內壓縮顧名思義,代表對於每一幀畫面進行獨立的壓縮,這種壓縮方式的優點就是對於電腦的負擔較小,缺點則是文件的大小會非常巨大。幀間壓縮則是利用多幀比較進行壓縮,通過這個方法文件大小可以被大大縮減,但是卻對電腦有著較大的負擔。
  • 教訓太慘痛!只因在寧波這個地方停了車,這輛私家車被罰1100元!
    陳某表示:「這次教訓太慘痛了,以後一定不會亂停車了。」
  • MySQL 5.7.14 發布
    script in the mysql-community-server package depended on mysqladmin for the ping command.Packages including this script now are made dependent on the mysql-community-client package.The systemd service still used mysqld_safe.
  • 越南政府總理阮春福:處理臺塑越南鋼鐵廠海洋環境汙染事故為地方政府提供了重要的經驗和教訓
    越南政府總理阮春福:處理臺塑越南鋼鐵廠海洋環境汙染事故為地方政府提供了重要的經驗和教訓
  • 馬馭|如何選購馬匹(上)-如何跳過買馬的坑
    我將我近20年10餘次購馬的經驗和教訓總結給大家。首先,在購馬之前要明確幾點,我購馬的目的到底是什麼?投資?自己騎乘然後升值賣掉?笑話,你能把花的錢的利息收回就不錯。繁育?買個公馬賣精子,買個母馬下個小馬,建議你省省,馬匹繁育是個系統工程,你這充其量叫繁殖,後代根本沒價值,也收不回成本。
  • 【吳慧專欄】《數字編碼》教學設計
    (報警電話)3.像110這樣,將數字或者字母按照一定的規則排列,用來表達一定含義的,我們把它稱為編碼。(完成板書:數字既可以表示數量和順序,也能用來表示編碼。)編碼能表達一定的信息。今天,我們一起來學習《數字編碼》。
  • HS海關編碼是什麼?
    1、定義:海關編碼即HS編碼,為編碼協調製度的簡稱。
  • 你把車借朋友,受到過哪些慘痛教訓?網友:不加油不洗車違章扣分
    你把車借朋友,受到過哪些慘痛教訓?網友:不加油不洗車違章扣分我小舅子借車一開一個多月,車裡車外弄得髒的一塌糊塗不給洗車、雨刮器洗玻璃的水還不加,經常借,每次借都給弄點傷回來,新車剛買開車不看限高衝進橋洞還好只是車頂掉漆車頂輕微變形,給他姐說走保險給報帳2300元自己修錢用不完,還掙幾百呢!媳婦居然還說他弟為我們著想。還說自己不花錢還掙錢呢!
  • 工程量清單項目編碼
    編碼以五級編碼設置,用十二位阿拉伯數字表示。一、二、三、四級編碼統一;第五級編碼由工程量清單編制人區分具體工程的清單項目特徵而分別編碼。各級編碼代表的含義是:①第一級表示分類碼(分二位,按建設工程工程量清單計價規範 GB 50500-2008附錄順序確定);附錄A 建築工程為01,附錄B裝飾裝修工程為02,附錄C安裝工程為03,附錄D市政工程為04,附錄E園林綠化工程為05;②第二級表示章(專業工程)順序碼(分二位);③第三級表示節(分部工程)順序碼(分二位);
  • 海關HS編碼查詢網址大全
    海關HS編碼查詢網址大全,這是小編收集和整理的一些比較權威的海關HS編碼查詢網站,希望能為外貿朋友們需要查詢海關HS編碼查詢是提供幫助。
  • 純乾貨 | 高效學荷蘭語: 0-A2 (YUYU的親身經驗總結)
    ✨下期預告✨純乾貨 | 高效學習方法(適用荷蘭) YUYU碩士畢業的經驗總結純乾貨 | 疫情就業難,如何在荷蘭找到大廠
  • 詳細解析:紅外遙控編碼與解碼
    一般而言,一個通用的紅外遙控系統由發射和接收兩大部分組成,如圖1 所示:其中發射部分主要包括鍵盤矩陣、編碼調製、紅外發射管;接收部分包括光、電信號的轉換以及放大、解調、解碼電路。對於NTC編碼,由引導碼、用戶編碼低位,用戶編碼高位、鍵數據編碼、鍵數據編碼五部分組成,引導碼由一個9ms的載波波形和4.5ms的關斷時間構成,它作為隨後發射的碼的引導,這樣當接收系統是由微處理器構成的時候,能更有效地處理碼的接收與檢測及其它各項控制之間的時序關係
  • 經驗總結:如何養水
    大家也都知道,換水時使用的最好的水是經過充分爆氧(論壇主流經驗:至少48小時以上)、晾曬(論壇主流經驗:至少3天以上)或靜置(論壇主流經驗:至少7天以上)後澄清的水,目的主要是為了除去對魚具有劇毒傷害的「氯」。但實際上,大家會發現很多人日常養魚,他們用新鮮的自來水直接就往魚缸裡面加,魚也活得好好的(論壇裡不乏這類人)。