MySQL为什么在这个地方犯2。以下内容仅仅为一种猜测。
先说一下utf8的标准,早期是用1~6个byte来表示一个字符。所以最早的MySQL实现,一个Char是用6个Bytes去实现的。这是正确的做法。但是MySQL为了性能,希望用户使用等长度的字符列。也就是说,一个字符如果用不到6个byte,存储里就会被填充空白符号。学过计算机的人都会明白等长字符,用数组的索引值去找到数据会非常快。
早期的RFC2279规定一个UTF8字符被编码为1~6个byte。后来才改成了1~4个。
为此,MySQL甚至搞过一个“static-format“的存储结构,就是用定长数据来加速数据访问。(但这个东西只能用在MyISAM上,那时候MySQL还没收InnoDB)
15.2.3.1 Static (Fixed-Length) Table Characteristicsdev.mysql.com
但也很容易看出来,这么干实在是太浪费空间了。绝大部分的英文用utf8编码1个byte就搞定了,中文等字符是3个byte。在1Charater = 6Byte的设计里,再加上2000年初时存储并不便宜,谁看了都会心疼。
好,基础讲完,再说历史。让我们回到2002年。MySQL计划在4.1版本支持utf8。4.1的早期开发版本用最多6个byte表示一个utf8字符,这是对的。但是MySQL不知道脑子里抽了哪根筋,在2002年9月27日,for no particular reason,搞出这么一个commit,强制让utf8编码只能处理最多3个byte的序列。
See:UTF8 now works with up to 3 byte sequences only · mysql/mysql-server@43a506c
这个事情我查不到任何前因后果,查不到任何的文章,讨论和相关资料。
但大致猜测就是,MySQL当时又想用定长的存储,又觉得太浪费,干脆一鼓作气,把6改成了3。
在Unicode中,3个Byte可以支持所有的BMP(basic multi-lingual plane)的字符;但是无法支持SMP(supplementary multi-lingual plane),包括emoji(这是重灾区),一些生僻的CJK字符,一部分生僻的符号等。对于主要的文字(英文、欧洲各种语种、中文、日文……),3个byte的utf8也算是够用。
但是,多年之后,也许是苹果强力推emoji,大家才发现MySQL的utf8其实并不那么utf8。直到2010年,MySQL的5.5.3版本的时候,才引入了utf8mb4(从此刻开始,utf8是“utf8mb3“的alias)。这个change非常的不起眼,在更新总体介绍时压根就没提,只在明细里写了一行。也许是因为6改3这个变更实在太过于傻缺,不想张扬?
见:https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-3.html
本题问为啥不把utf8原地升级,这个非常容易理解。大量的数据库文件已经按照了utf8的格式存储。如果“原地升级”也就意味着,所有已经存在的数据库文件都要重建。数据这个东西~都懂得。如果MySQL真的这么干了,估计就不是会被骂,而是会直接被判死刑。用utf8mb4的形式慢慢过渡也算是可以理解的做法。
顺便说一句。UTF8早期的标准RFC2279规定一个UTF8字符是1~6个Byte。这也是为什么早期Mysql把一个UTF8字符设计为6个Byte的原因。但是2003年11月,出了新的标准RFC3629,规定一个UTF8字符是1~4个Byte,就比MySQL做出那个很傻的commit晚了一年。历史真的很有趣。
从2002年到2019年,已经17年过去了,MySQL 8.0刚刚GA不久,但是utf8依然是utf8mb3的别名。不知道什么时候这个在一个莫名其妙的决策下诞生的奇行种才会被完全干掉。
哎~
摘自MySQL8.0的文档