我记得我保存表情符号的时候😲😳去年进入MySQL,一直报错无法导入。后来找到了把utf8改成utf8mb4的方法,当时没深究。图片来自Pexels。一年后看到一篇文章说emoji文字占4个字节,一般需要用utf-8接收,其他编码可能不对。突然想到去年操作mysql把utf8改成了utf8mb4。嗯?本身不就是utf8编码吗?那我当时换锤子了?MySQL的utf8不就是真正的UTF-8编码吗??!靠,这个MySQL有bug!带着疑惑查找了很多相关资料,才发现这竟然是MySQL的历史问题。我笑了,没想到这么厉害的MySQL也有这段往事。报错回顾将emoji文字直接写入SQL,执行insert语句报错:INSERTINTO`csjdemo`.`student`(`ID`,`NAME`,`SEX`,`AGE`,`CLASS`,`GRADE`,`HOBBY`)VALUES('20','陈哈哈😓','男','20','181班','9年级','看电影');[Err]1366-Incorrectstringvalue:'\xF0\x9F\x98\x93'forcolumn'NAME'atrow1更改数据库编码、系统编码和表字段编码格式后→utf8mb4,就可以了:INSERTINTO`student`(`ID`,`NAME`,`SEX`,`AGE`,`CLASS`,`GRADE`,`HOBBY`)VALUES(null,'陈哈哈😓😓','男','20','181班','九年级','看电影');MySQL中关于utf8的有趣事实MySQL的“utf8”实际上并不是UTF-8。在MySQL中,“utf8”编码仅支持每个字符最多三个字节,而真正的UTF-8最多支持每个字符四个字节。在utf8编码中,中文占3个字节,其他数字、英文、符号占1个字节。但是emoji符号占用4个字节,一些比较复杂的字符和繁体字也占用4个字节。所以写入失败,应该改成utf8mb4。如上图所示,这是将编码更改为utf8mb4后存储在数据库中的数据。你可以清楚地比较占用的字符数和字节数。正因为如此,在utf8编码中插入4个字节的内容是肯定不行的,是不能插入的吧?MySQL从未修复过这个错误。他们在2010年发布了一个名为“utf8mb4”的字符集,巧妙地绕过了这个问题。当然,他们并没有宣传新的字符集(可能是因为这个bug让他们觉得尴尬),所以网上还是建议开发者使用“utf8”,但是这些建议都是错误的。utf8mb4是真正的UTF-8是的,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服务器。几乎所有的Web应用程序都使用Unicode字符集,因为没有理由使用其他字符集。Unicode字符集包含数百万个字符。最简单的编码是UTF-32,每个字符使用32位。这样做最简单,因为计算机一直将32位视为数字,而计算机最擅长处理数字。但问题是,这是浪费空间。UTF-8可以节省空间。在UTF-8中,字符“C”只需要8位,一些不常见的字符,如“😓”,则需要32位。其他字符可能使用16位或24位。像这篇文章这样的文章,如果用UTF-8编码,只占用UTF-32的四分之一左右的空间。utf8简史MySQL开发者为什么要使“utf8”无效?我们或许可以在MySQL版本提交日志中找到答案。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开发人员或供应商担心用户会做两件事:用CHAR定义列(CHAR现在已经过时了,但在当时,在MySQL中使用CHAR会更快,但自从2005)。将CHAR列的编码设置为“utf8”。我的猜测是MySQL开发人员正试图帮助那些想要同时获得空间和速度双赢的用户,但他们搞砸了“utf8”编码。所以结果是没有赢家。期望空间和速度双赢的用户,当他们使用“utf8”CHAR列时,实际使用的空间比预期的要多,速度也比预期的要慢。而想要正确的用户,当他们使用“utf8”编码时,他们不能保存像“😓”这样的字符,因为“😓”是4个字节。这个非法字符集发布后,MySQL无法修复它,因为它需要所有用户重建他们的数据库。最终,MySQL在2010年重新发布了“utf8mb4”以支持真正的UTF-8。总结一下,网上几乎所有的文章都把“utf8”当作真正的UTF-8,包括我之前写的文章和做的项目(捂脸);所以希望更多的朋友能够看到这篇文章。我相信有很多人和我在同一条船上,这是不可避免的。所以大家以后建MySQL和MariaDB数据库的时候,记得把数据库对应的编码改成utf8mb4。有一天,接你班的程序员或者你的领导发现这个问题后,他会在心里默默地感受到你技术的过硬。作者:_陈哈哈编辑:陶家龙来源:https://sourl.cn/kKbzpH
