最近我遇到了一个错误,我试图通过Rails在MariaDB中保存编码为“utf8”的UTF-8字符串并得到一个奇怪的错误:Incorrectstringvalue:'\xF0\x9F\x98\x83<...'forcolumn'summary'atrow1我用的是UTF-8编码的客户端,服务端也是UTF-8编码的,数据库也是,连要保存的字符串"<..."也是合法的UTF-8。问题的症结在于MySQL的“utf8”实际上并不是真正的UTF-8。“utf8”仅支持每个字符最多三个字节,而真正的UTF-8每个字符最多四个字节。MySQL一直没有修复这个bug,他们在2010年发布了一个名为“utf8mb4”的字符集,绕过了这个问题。当然,他们并没有宣传新的字符集(可能是因为这个bug让他们觉得尴尬),以至于网上仍然建议开发者使用“utf8”,但这些建议都是错误的。简单总结如下:1、MySQL的“utf8mb4”才是真正的“UTF-8”。2.MySQL的“utf8”是一种“专有编码”,它能编码的Unicode字符并不多。我想在这里澄清一下:所有正在使用“utf8”的MySQL和MariaDB用户都应该切换到“utf8mb4”并且永远不要再使用“utf8”。那么什么是编码?什么是UTF-8?我们都知道计算机使用0和1来存储文本。例如,字符“C”存储为“01000011”,那么计算机在显示这个字符时需要经过两步:1、计算机读取“01000011”得到数字67,因为67被编码为“01000011””。2、计算机在Unicode字符集中查找67,找到“C”。同样的事情:1.我的计算机将“C”映射到Unicode字符集中的67。2、我的电脑将67编码成“01000011”发送给web服务器。几乎所有的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标准(RFC3629)是后来才出现的。较旧的UTF-8标准(RFC2279)支持每个字符最多6个字节。2002年3月28日,MySQL开发人员在MySQL4.1的第一个预览版中使用了RFC2279。同年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开发者或厂商担心用户会做两件事:1.使用CHAR来定义列(在今天看来,CHAR已经是古董了,但在那个时候,在MySQL中使用CHAR会更快,但不是因为2005)。2.设置CHAR列的编码为“utf8”。我的猜测是MySQL开发人员试图帮助那些想要在空间和速度上双赢的用户,但他们搞砸了“utf8”编码。所以结果是没有赢家。希望空间和速度双赢的用户,当他们使用“utf8”CHAR列时,实际使用的空间比预期的要多,速度也比预期的要慢。而追求正确性的用户,当他们使用“utf8”编码时,不能保存“”这样的字符。这个非法字符集发布后,MySQL无法修复它,因为它需要所有用户重建他们的数据库。最终,MySQL在2010年重新发布了“utf8mb4”以支持真正的UTF-8。为什么这件事会让人如此疯狂?因为这个问题,我整整疯了一个星期。我被“utf8”骗了,我花了很多时间才找到这个错误。但我绝不能成为***中的一员。网上几乎所有的文章都把“utf8”当作真正的UTF-8。“utf8”只能算是专有的字符集,给我们带来了新的问题,但一直没有解决。总结如果您使用的是MySQL或MariaDB,请不要使用“utf8”编码,而应使用“utf8mb4”。这里(https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4)提供了将现有数据库的字符编码从“utf8”转换为“utf8mb4”的指南。
