前言
mysql中的utf8并不是真正的UTF-8编码,它只有3个字节来表示字符,如果有超过3个字节的字符,那么这种编码方式就表示不了,并且会抛错。这是为什么呢,当然是有历史原因的,后面详细介绍一下。
一、mysql的utf8编码
mysql的utf8编码能够表示Unicode的BMP标准面的所有字符,但是后来随着Unicode的扩展,总共有17个面,对于其他面的字符,包括 Emoji 表情(Emoji 是一种特殊的 Unicode 编码,常见于 ios 和 android 手机上),和很多不常用的汉字,以及任何新增的 Unicode 字符等等。utf8就表示不了。如果进行相应的存储那么就会报错。接下来看看历史渊源,为什么mysql的utf8是3个字节。
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 年以后就不是这样子了)。
-
将 CHAR 列的编码设置为“utf8”。
我的猜测是 MySQL 开发者本来想帮助那些希望在空间和速度上双赢的用户,但他们搞砸了“utf8”编码。
所以结果就是没有赢家。那些希望在空间和速度上双赢的用户,当他们在使用“utf8”的 CHAR 列时,实际上使用的空间比预期的更大,速度也比预期的慢。而想要正确性的用户,当他们使用“utf8”编码时,却无法保存像“”,emoji表情等超过3个字节这样的字符。
二、utf8mb4
在这个不合法的字符集发布了之后,MySQL 就无法修复它,因为这样需要要求所有用户重新构建他们的数据库。最终,在 2010 年MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。
三、使用建议
实际使用中,很多人其实想当然的认为此utf8为彼UTF-8,很有可能会出现意想不到的问题。所以,在5.5.3之后的版本中,新建库表的时候,基本编码方式选择utf8mb4,避免不必要的问题产生。当然如果开发者能明确的知道所开发的应用在存活期内不会存储超过3个字节的字符,可以使用utf8。目前,随着存储和CPU的快速升级换代,基本的存储和性能问题实际并不会太大的影响产品的使用。综合考虑,建议直接使用utf8mb4的编码格式。
参考:
https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4