原文地址:https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434html
今天打开电脑,导出都是这篇文章,我以为大多数开发者可能在没有遇到这个问题时,不会太注意。可是,这么操蛋的坑,仍是提早知晓为妙。mysql
用MySQL的朋友们请不要使用"utf8",请使用"utf8mb4"linux
今天我试图把UTF-8编码的字符串插入使用“utf8”编码的MariaDB数据库中,Rails抛出一个古怪的异常:git
Incorrect string value: ‘\xF0\x9F\x98\x83 <…’ for column ‘summary’ at row 1github
一切都很UTF-8:UTF-8 client,UTF-8的服务器,UTF-8编码的数据库,使用UTF-8的字符集。“😃 <…”是个有效的UTF-8字符串。web
可是问题的关键是:MySQL数据库的 “utf8”并非真正概念里的 UTF-8。sql
MySQL中的“utf8”编码只支持最大3字节每字符。真正的你们正在使用的UTF-8编码是应该能支持4字节每一个字符。数据库
MySQL的开发者没有修复这个bug。他们在2010年增长了一个变通的方法:一个新的字符集“utf8mb4”服务器
固然,他们并无对外公布(可能由于这个bug有点尴尬)。如今不少指南推荐用户使用“utf8”其实都错了。并发
简单的说:
MySQL中的 “utf8mb4” 才是 真正意义上的“UTF-8”。
MySQL的“utf8”是个“特殊的字符编码”。这种编码不少Unicode字符保存不了。
我强烈建议MySQL和MariaDB用户使用“utf8mb4”而不是“utf8”。
编码是什么?什么是UTF-8?
Joel on Software上有一遍我最喜欢的介绍,我精简描述以下:
计算机使用0和1存储文字。好比第一段第一个字符存储为“01000011”表示“C”,计算机经过如下两个步骤选择用“C”表示:
计算机读取到“01000011”后计算出这是数字67。
计算机经过查找Unicode字符集来确认67表明的“C”。
一样的事情发生在我打字输入C的时候。
计算机经过Unicode字符集将“C” 映射为67。
计算机把67编码为“01000011”发送给web服务器。
几乎全部的程序和互联网应用使用Unicode字符集。
Unicode字符集里有超过100万个字符(“C” 和 “💩” 是两种不一样的字符。)。UTF-32是最简单的编码方式,它在表示每一个字符的时候使用32个bits。这样编码简单,可是并不实用,明显浪费了太多的空间。
UTF-8相比UTF-32更加节约空间。在UTF-8中,像“C”这样的字符占用8bits,“💩”这样的占用32 bits。其余字符占用16或者24 bits。如本篇这样的文章用UTF-8存储比用UTF-32节省4倍左右的空间。更小的空间占用也意味着加载速度会快上4倍。
而MySQL中的 “utf8”字符集则和其余应用行为不同。好比根本无法表示“💩”。
一点关于MySQL的历史
为何MySQL的开发者开发了一个奇怪的“utf8”。咱们能够经过提交的日志来揣测一下。
MySQL从4.1版开始支持UTF-8。那是在比今天UTF-8 RFC 3629标准更早的2003年。
在此以前的UTF-8标准,RFC 2279中规定6个bytes表示一个字符。MySQL的开发者在2002.3.28编码实现了RFC 2279 。并发布了pre-pre-release 的 MySQL 4.1
而后在9月出现了一个神秘的字节调整。“UTF8 now works with up to3 byte sequences only.”
是谁提交了此次更新?为何?我没法解答。MySQL的源码移到Git后丢失了旧的做者信息(MySQL 曾经和linux内核同样使用BitKeeper)
可是我大概能猜出来缘由。
回到2002年,若是用户能够保证表中的每一行具备相同的字节数,MySQL就能够提升用户的速度。为了获得这个提高,用户就须要定义保存文字的列为“CHAR”。一个“CHAR”列老是拥有相同的字符数。若是存入的字符较少则会在最后补齐空白。若是存入的数据过多则会被抛弃多余的字符。
当MySQL的开发者第一次尝试以6字节每字符实现UTF-8时,他们意识到CHAR(1)的列会占用6字节,CHAR(2)会占用12字节,以此类推。
显而易见的是,这个没有被使用的实现方式是正确的,任何一个理解UTF-8的开发者将会认同这一点。
个人猜想是:MySQL的开发者违背了“utf8”编码去帮助那些1)试图去优化空间和速度的人,2)尝试优化空间和速度失败的人。
这是个无人获益的改动。那些想要更快性能,更小空间的获得的依然是比他们曾经使用版本更大更慢的实现,而那些想要正确的“utf8”的人获得的是个“💩”都存储不了的实现。
MySQL发布了这个错误的版本后,在也没有修复它:由于那样不少使用者将被迫重建他们的数据库。MySQL最终在2010年更新了一个以“utf8mb4”命名的UTF-8实现。
Why it’s so frustrating
为何这么操蛋
这周我过得很操蛋。我遇到一个很难发现的bug,就由于我被“utf8”这个名字给愚弄了。并且我也不是个案,我发现几乎每篇推荐使用“utf8”的文章都错了。
“utf8”的命名在mysql依然是错的。这是个专有的实现。这形成了新的问题,并且没有解决他应该解决的问题。
若是你使用MySQL或者 MariaDB,不要使用“utf8”,应该老是使用“utf8mb4”,不然总有一天会遇到头疼的事情。