今天的错误:我试图将一个UTF-8字符串存储在MariaDB“utf8”编码的数据库中,并且引发了一个奇怪的错误:
Incorrect string value: ‘\xF0\x9F\x98\x83 <…’ for column ‘summary’ at row 1
这是UTF-8客户端和UTF-8服务器,位于UTF-8数据库中,具有UTF-8编码规则。字符串“?”是有效的UTF-8。
但问题是:MySQL的“ utf8 ” 不是UTF-8。
“utf8”编码仅支持每个字符三个字节。真正的UTF-8编码 - 每个人都使用,包括你 - 每个字符最多需要四个字节。
MySQL开发人员从未修复过这个bug。他们在2010年发布了一个解决方法:一个名为“ utf8mb4 ” 的新字符集。
当然,他们从未公布过这个(可能是因为这个bug太尴尬了)。现在,Web上的指南建议用户使用“utf8”。所有这些指南都是错误的。
简而言之:
· MySQL的“utf8mb4”表示“UTF-8”。
· MySQL的“utf8”意味着“专有字符编码”。此编码不能编码许多Unicode字符。
我将在这里做一个彻底的陈述:目前使用“utf8”的所有 MySQL和MariaDB用户实际上应该使用“utf8mb4”。没有人应该使用“utf8”。
Joel on Software写了我最喜欢的介绍(https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
)。我会缩减它。
Computer(计算机)将文本存储为1和0。本段中的第一个字母存储为“01000011”,你的计算机显示为“C”。你的计算机分两步选择“C”:
1. 你的计算机读取“01000011”并确定它是数字67.这是因为67被编码为“01000011”。
2. 你的计算机在Unicode 字符集中查找字符编号67 ,并且发现67表示“C”。
当我键入“C”时,我的结果发生了同样的事情:
1. 我的计算机将Unicode字符集中的“C”映射到67。
2. 我的计算机编码为 67,向此Web服务器发送“01000011”。
字符集是一个解决的问题。几乎互联网上的每个程序都使用Unicode字符集,因为没有动机使用另一个。
但编码更像是一种判断。Unicode具有超过一百万个字符的插槽。(C和“?”是两个字符)
最简单的编码(utf-32)使每个字符占用32位。这很简单,因为计算机已经把32位的组当作数字处理了很多年,而且他们真的很擅长。但它没用:这是浪费空间。
UTF-8节省空间。在UTF-8中,像“C”这样的常见字符占8位,而像“其他字符需要16或24位。像这样的博客文章在UTF-8中占用的空间比在UTF-32中少四倍。所以加载速度快四倍。
你可能没有意识到,但我们的计算机在幕后同意了UTF-8。如果他们没有,然后当我输入
“?”时,你会看到一堆随机数据。
MySQL的“utf8”字符集与其他程序不一致。当他们说“?”时,它会犹豫。
为什么MySQL开发人员使“utf8”无效?我们可以通过查看提交日志来猜测。
MySQL从版本4.1开始支持UTF-8 。那是2003年 - 在今天的UTF-8标准之前,RFC 3629。
以前的UTF-8标准RFC 2279每个字符最多支持6个字节。MySQL开发人员在2002年3月28日的MySQL 4.1的第一个预发行版本中编写了RFC 2279 。
然后在9月对MySQL的源代码进行了一次神秘的,一字节的调整:“UTF8现在只能处理3个字节的序列。”
是谁提交了这个?为什么?我说不出来。MySQL的代码库在采用Git时似乎丢失了旧的作者名称。(MySQL过去常常使用BitKeeper,就像Linux内核一样。)2003年9月左右的邮件列表中没有任何内容可以解释这一变化。
但我可以猜到。
早在2002年,如果用户可以保证表中的每一行具有相同的字节数,MySQL就会为用户提供速度提升。为此,用户会将文本列声明为“CHAR”。“CHAR”列始终具有相同的字符数。如果你输入的字符太少,它会在末尾添加空格; 如果你输入太多的字符,它会截断最后的字符。
当MySQL开发人员第一次尝试使用UTF-8时,每个字符的后六个字节,他们可能会犹豫不决:一个CHAR(1)列需要六个字节; CHAR(2)列需要12个字节; 等等。
让我们明确一点:从未发布的初始行为是正确的。它得到了很好的记录和广泛采用,任何理解UTF-8的人都会同意这是正确的。
但显然,MySQL开发人员(或商人)担心一两个用户会做两件事:
1.选择CHAR列。(CHAR格式现在是遗物。当时,使用CHAR列,MySQL速度更快。直到2005年,它不是。)
2.选择将这些CHAR列编码为“utf8”。
我的猜测是MySQL开发人员打破了他们的“utf8”编码来帮助这些用户:1)试图优化空间和速度的用户; 2)未能优化速度和空间。
没人赢。想要速度和空间的用户使用“utf8”CHAR列仍然是错误的,因为那些列仍然比它们原来更大更慢。想要正确性的开发人员使用“utf8”是错误的,因为它无法存储
“?”
一旦MySQL发布了这个无效的字符集,它就永远无法解决它:这将迫使每个用户重建每个数据库。MySQL最终在2010年发布了UTF-8支持,名称不同:“utf8mb4”。
很明显,本周我很沮丧。我的bug很难找到,因为我被“utf8”这个名字所迷惑。而且我不是唯一一个 - 我在网上发现的几乎所有文章都将“utf8”称为UTF-8。
名称“utf8”始终是错误的。这是一个专有的字符集。它创造了新问题,并没有解决它要解决的问题。
这是虚假的广告。
My take-away lessons
1.Database systems have subtle bugs and oddities, and you can avoid a lot of bugs by avoiding database systems.
2.If you need a database, don’t use MySQL or MariaDB. Use PostgreSQL.
3.If you need to use MySQL or MariaDB, never use “utf8”. Always use “utf8mb4” when you want UTF-8. Convert your database now to avoid headaches later.