記住沒:永遠不要在 MySQL 中使用 UTF-8

2021-02-20 Java基基

最近我遇到了一個 bug,我試著通過 Rails 在以「utf8」編碼的 MariaDB 中保存一個 UTF-8 字符串,然後出現了一個離奇的錯誤:

Incorrect string value: 『\xF0\x9F\x98\x83 <…』 for column 『summary』 at row 1

我用的是 UTF-8 編碼的客戶端,伺服器也是 UTF-8 編碼的,資料庫也是,就連要保存的這個字符串「 <…」也是合法的 UTF-8。

問題的癥結在於,MySQL 的「utf8」實際上不是真正的 UTF-8。

「utf8」只支持每個字符最多三個字節,而真正的 UTF-8 是每個字符最多四個字節。

MySQL 一直沒有修復這個 bug,他們在 2010 年發布了一個叫作「utf8mb4」的字符集,繞過了這個問題。

當然,他們並沒有對新的字符集廣而告之(可能是因為這個 bug 讓他們覺得很尷尬),以致於現在網絡上仍然在建議開發者使用「utf8」,但這些建議都是錯誤的。

簡單概括如下:

MySQL 的「utf8mb4」是真正的「UTF-8」。MySQL 的「utf8」是一種「專屬的編碼」,它能夠編碼的 Unicode 字符並不多。

我要在這裡澄清一下:所有在使用「utf8」的 MySQL 和 MariaDB 用戶都應該改用「utf8mb4」,永遠都不要再使用「utf8」。

那麼什麼是編碼?什麼是 UTF-8?

我們都知道,計算機使用 0 和 1 來存儲文本。比如字符「C」被存成「01000011」,那麼計算機在顯示這個字符時需要經過兩個步驟:

計算機讀取「01000011」,得到數字 67,因為 67 被編碼成「01000011」。計算機在 Unicode 字符集中查找 67,找到了「C」。

同樣的:

我的電腦將「C」映射成 Unicode 字符集中的 67。我的電腦將 67 編碼成「01000011」,並發送給 Web 伺服器。

幾乎所有的網絡應用都使用了 Unicode 字符集,因為沒有理由使用其他字符集。

Unicode 字符集包含了上百萬個字符。最簡單的編碼是 UTF-32,每個字符使用 32 位。這樣做最簡單,因為一直以來,計算機將 32 位視為數字,而計算機最在行的就是處理數字。但問題是,這樣太浪費空間了。

UTF-8 可以節省空間,在 UTF-8 中,字符「C」只需要 8 位,一些不常用的字符,比如「」需要 32 位。其他的字符可能使用 16 位或 24 位。一篇類似本文這樣的文章,如果使用 UTF-8 編碼,佔用的空間只有 UTF-32 的四分之一左右。

MySQL 的「utf8」字符集與其他程序不兼容,它所謂的「」,可能真的是一坨……

MySQL 簡史

為什麼 MySQL 開發者會讓「utf8」失效?我們或許可以從提交日誌中尋找答案。

MySQL 從 4.1 版本開始支持 UTF-8,也就是 2003 年,而今天使用的 UTF-8 標準(RFC 3629)是隨後才出現的。

舊版的 UTF-8 標準(RFC 2279)最多支持每個字符 6 個字節。2002 年 3 月 28 日,MySQL 開發者在第一個 MySQL 4.1 預覽版中使用了 RFC 2279。

同年 9 月,他們對 MySQL 原始碼進行了一次調整:「UTF8 現在最多只支持 3 個字節的序列」。

是誰提交了這些代碼?他為什麼要這樣做?這個問題不得而知。在遷移到 Git 後(MySQL 最開始使用的是 BitKeeper),MySQL 代碼庫中的很多提交者的名字都丟失了。2003 年 9 月的郵件列表中也找不到可以解釋這一變更的線索。

不過我可以試著猜測一下。

2002 年,MySQL 做出了一個決定:如果用戶可以保證數據表的每一行都使用相同的字節數,那麼 MySQL 就可以在性能方面來一個大提升。為此,用戶需要將文本列定義為「CHAR」,每個「CHAR」列總是擁有相同數量的字符。如果插入的字符少於定義的數量,MySQL 就會在後面填充空格,如果插入的字符超過了定義的數量,後面超出部分會被截斷。

MySQL 開發者在最開始嘗試 UTF-8 時使用了每個字符 6 個字節,CHAR(1) 使用 6 個字節,CHAR(2) 使用 12 個字節,並以此類推。

應該說,他們最初的行為才是正確的,可惜這一版本一直沒有發布。但是文檔上卻這麼寫了,而且廣為流傳,所有了解 UTF-8 的人都認同文檔裡寫的東西。

不過很顯然,MySQL 開發者或廠商擔心會有用戶做這兩件事:

使用 CHAR 定義列(在現在看來,CHAR 已經是老古董了,但在那時,在 MySQL 中使用 CHAR 會更快,不過從 2005 年以後就不是這樣子了)。

我的猜測是 MySQL 開發者本來想幫助那些希望在空間和速度上雙贏的用戶,但他們搞砸了「utf8」編碼。

所以結果就是沒有贏家。那些希望在空間和速度上雙贏的用戶,當他們在使用「utf8」的 CHAR 列時,實際上使用的空間比預期的更大,速度也比預期的慢。而想要正確性的用戶,當他們使用「utf8」編碼時,卻無法保存像「」這樣的字符。

在這個不合法的字符集發布了之後,MySQL 就無法修復它,因為這樣需要要求所有用戶重新構建他們的資料庫。最終,MySQL 在 2010 年重新發布了「utf8mb4」來支持真正的 UTF-8。

為什麼這件事情會讓人如此抓狂

因為這個問題,我整整抓狂了一個禮拜。我被「utf8」愚弄了,花了很多時間才找到這個 bug。但我一定不是唯一的一個,網絡上幾乎所有的文章都把「utf8」當成是真正的 UTF-8。

「utf8」只能算是個專有的字符集,它給我們帶來了新問題,卻一直沒有得到解決。

總結

如果你在使用 MySQL 或 MariaDB,不要用「utf8」編碼,改用「utf8mb4」。這裡(https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4)提供了一個指南用於將現有資料庫的字符編碼從「utf8」轉成「utf8mb4」。

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

相關焦點

  • 面試官:為什麼不建議在 MySQL 中使用 UTF-8?
    難道,MySQL的utf8不是真正的UTF-8編碼嗎??! 臥槽這。。MySQL有bug!帶著疑問查詢了很多相關材料,才發現這竟然是MySQL的一個歷史遺留問題~~ 我笑了,沒想到這麼牛B的MySQL也會有這段往事。
  • 知乎面試官:為什麼不建議在 MySQL 中使用 UTF-8?
    我笑了,沒想到這麼牛 B 的 MySQL 也會有這段往事。將 emoji 文字直接寫入 SQL 中,執行 insert 語句報錯 :INSERT INTO `csjdemo`.在 MySQL 中,「utf8」編碼只支持每個字符最多三個字節,而真正的 UTF-8 是每個字符最多四個字節。在 utf8 編碼中,中文是佔 3 個字節,其他數字、英文、符號佔一個字節。
  • 經驗總結:被MySQL UTF8編碼坑的慘痛教訓
    可以看出,MySQL中的utf8實質上不是標準的UTF8。MySQL中,utf8對每個字符最多使用三個字節來表示,所以一些emoji甚至是一些生僻漢字就存不下來了,比如「𡋾」。MySQL一直不承認這是一個bug,他們在2010年發布了「utf8mb4」字符集來繞過這個問題,在MySQL中,utf8mb4才應該是標準的utf8編碼,並且官方很雞賊的偷偷在最新的文檔中加上了,算是認識到錯誤了吧:utf8 is an alias for the utf8mb3 character set.
  • MySQL 關鍵字查詢
    SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;DROP TABLE IF EXISTS `test`;CREATE TABLE `test` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID', `key` varchar
  • 一日一技:utf-8-sig是什麼?CSV存儲為中文亂碼怎麼解決
    我們使用Pandas的時候,存儲為csv文件的時候,經常會出現亂碼的情況,原因在於Windows使用BOM來標記文本文件的編碼方式的
  • 德州撲克好好打牌的四個「永遠不要」
    永遠不要侮辱謾罵對手只要是人都有鬧脾氣的時候,這在線下賽事牌桌上尤為明顯。
  • 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.
  • 微波爐使用注意事項:這13大禁忌要牢記!
    2、酸奶盒 一次性塑料容器——比如酸奶,黃油或者奶油的盒子,都不能丟到微波爐裡。它們真的是一次性的,而且不能承受高溫。容器可能會在微波爐裡變形或融化,會將化學物質滲入食物中去。 3、雞蛋 不要被網絡迷惑了——用微波爐做白煮蛋會有麻煩的。如果你想嘗試,那做好打掃衛生的準備。微波爐快速加熱會在雞蛋裡產生很多水蒸氣。
  • 記住:cold fish千萬不要翻譯冰凍魚
    文/英語老師劉江華今天和戰友一起學習一個短語cold fish,按字面意思可能翻譯為冰凍魚,千萬不要這樣說哦
  • 小美人魚的黑暗史:12件星巴克店員永遠不會告訴你的事!
    如果我們問你貴姓,不要太認真,因為根本沒人在乎你的名字有沒有寫對,它只是一種辨認的方式。哪一天真的拼對了,反而新鮮。3、點單的時候,不要打電話!點單時你的態度,我們永遠會記得住,一清二楚。7、要求我們能一次記住,不用婆媽!
  • 記住這三招,讓你遠離各種病毒木馬
    記住這三招,讓你遠離各種病毒木馬        從谷歌商店安裝應用是最安全的方法        知道了木馬病毒的入侵方式,自然就該知道如何防範手機病毒。記住以下幾點,就能讓你遠離各種木馬病毒的騷擾:        1、不要在谷歌應用商店以外的地方下載安裝軟體和遊戲。如果這個方法行不通,那麼,國內比較靠譜的應用商店是很少的,但是確實有那麼一兩個,不知道大家知道不?
  • aelf Enterprise 0.8.0 alpha版正式發布
    ▋ aelf Enterprise 0.8.scanner v0.8.0 alpha  □ aelf Scanner Mysql plugin v0.8.0 alpha  □ aelf explorer v0.8.0 alpha
  • 記住8個字就可以了
    其實,只要記住 8 個字就夠了:  具體來說,在每天的飲食中應該包括這五大類食物:穀物、水果、蔬菜、乳製品、肉類或其他蛋白質豐富的食物。二、攝入要適量  孕中晚期多補充蛋白質和鈣進入孕中晚期,胎兒開始快速成長,要注意多補充蛋白質和鈣。補鈣也同樣重要。胎兒的牙齒和骨骼發育需要大量的鈣,如果母體自身鈣含量不足,就會動用自身骨骼中的鈣,來補給給胎兒,這樣就可能會造成孕媽媽骨質疏鬆。 那鈣該怎麼補?
  • 《頭腦特工隊》,獻給永遠不要長大孩子們
    8.4)不僅口碑炸裂,《頭腦特工隊》票房也不差。他們有各自不同的性格和想法,情緒們在腦內的動作影響著萊莉現實中的行為。為了保護萊莉,他們在她腦內來了一場充滿想像力的大冒險。我們能看到不同的情緒發揮主導作用
  • 周鵬烈士,我們永遠記住你
    3月30日17時 ,四川省涼山州木裡縣境內發生森林火災,3月31日下午,撲火行動中,突發林火爆燃,30名撲火人員失聯,4月1日晚,經全力搜救,30名失聯撲火人員全部找到,27名森林消防隊員和3名地方撲火人員犧牲,犧牲的27名森林消防隊員中,周鵬是其中一員,他犧牲時年僅22歲。周鵬1997年7月15日出生,宜春市袁州區水江鎮滄溪村人。
  • 兇手竟是⋯⋯這8條一定要讓孩子記住!
    中午,周先生做好飯,叫回了王某玲的父親吃飯,沒想到,王某玲卻不見了。一家人趕緊四處尋找。周先生說,王某玲身高140cm以上,體重60多斤,身穿藍色背帶褲,白色長袖套頭衫,腳上穿白色板鞋,頭髮長,扎馬尾辮。
  • 記住:千萬不要把bad company翻譯為「壞公司」
    文/英語老師劉江華今天和戰友分享一個單詞company,相信您第一時間想到的是公司,但是這個單詞除了表示公司外還有公司;陪伴,同伴;連隊,所以記住一個單詞一定要分語境去分析bad company 千萬不要翻譯為「壞公司」這樣就特別的中式,老外理解的bad company是相處得不好的夥伴;志趣不相投的夥伴;乏味的夥伴。company美 ['kʌmpəni]在這裡是陪伴It is better to be alone than in bad company.交友不慎不如獨處。