永远不要在 MySQL 中使用 utf8,使用 utf8mb4 作为代替

MySQL 技术社区

今天的错误:我试图在 MariaDB 「utf8」 编码的数据库中存储一个 UTF-8 字符串,Rails 出现了一个奇怪的错误:

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 开发人员从未修复过这个错误。 他们在 2010 中发布了一个解决方法:一个名为「utf8mb4」的新字符集。

当然,他们从来没有宣传过这个(可能是因为这个错误太尴尬了)。 现在,网络上的指南建议用户使用「utf8」。 所有这些指南都是错误的。

简而言之:

  • MySQL「utf8mb4」表示「UTF8」。
  • MySQL「utf8」表示「专有字符编码」。 这种编码不能编码许多 Unicode 字符。

我将在这里做一个全面的声明:所有 当前使用「utf8」的 MySQL 和 MariaDB 用户应该 实际上 使用「utf8mb4」。 没有人应该使用「utf8」。

什么是编码? 什么是 UTF-8?

Joel on Software 写了我最喜欢的介绍, 我来简述它。

计算机将文本存储为 1 和 0。 本段中的第一个字母存储为「01000011」,计算机绘制了「C」。 计算机分两步选择了「C」:

  1. 计算机读取「01000011」并确定它是数字 67。这是因为 67 被编码为「01000011」。
  2. 你的计算机在 Unicode字符集 中查找字符编号 67,发现 67 表示「C」。

当我输入「C」时,同样的事情发生在我身上:

  1. 我的电脑将「C」映射到 Unicode 字符集中的 67。
  2. 我的电脑 编码 67,将「01000011」发送到此 Web 服务器。

字符集 是一个已解决的问题。 互联网上几乎每个程序都使用 Unicode 字符集,因为没有动机使用另一个字符集。

编码 更像是一种判断。 Unicode 有超过一百万个字符的插槽。 (「C」和「🍋」就是两个这样的字符。)最简单的编码 UTF-32 使每个字符占用 32 位。 这很简单,因为计算机多年来一直将 32 位组视为数字,而且它们真的很擅长。 但它没有用:这是浪费空间。

UTF-8 节省空间。 在 UTF-8 中,像「C」这样的常见字符占用 8 位,而像「🍋」这样的稀有字符占用 32 位。 其他字符占用 16 或 24 位。 像这样的博客文章在 UTF-8 中占用的空间大约是 UTF-32 中的四倍。 所以它的加载速度快了四倍。

你可能没有意识到,但我们的计算机在幕后同意使用 UTF-8。 如果他们没有,那么当我输入「🍋」时,你会看到一堆乱七八糟的随机数据。

MySQL 「utf8」 字符集与其他程序不一致。 当他们说「🍋」时,它就开始了。

一点 MySQL 历史

为什么 MySQL 开发人员让「utf8」无效? 我们可以通过查看提交日志来猜测。

MySQL 支持 UTF-8,因为 version 4.1

那是 2003 年——在今天的 UTF-8 标准之前,RFC 3629.

之前的 UTF-8 标准 RFC 2279 支持每个字符最多六个字节。 MySQL 开发人员于 2002 年 3 月 28 日在 [MySQL 4.1 的第一个预发布版本] (github.com/mysql/mysql-server/comm...) 中编写了 RFC 2279。

然后在 9 月对 MySQL 的源代码进行了一个神秘的单字节调整:「UTF8 现在最多可处理 3 字节序列。」

谁要求这个改变? 为什么? 我不知道。 2003 年 9 月左右的邮件列表中没有任何内容可以解释这一变化。 (RFC 2279 在 2003 年 11 月被宣布过时,为当前的 UTF-8 标准让路,RFC 3629。)

但我可以猜到为什么 MySQL 违反了标准。

早在 2002 年,如果用户可以保证表中的每一行都具有相同的内容,MySQL 就为用户提供了 速度提升 字节数。 为此,用户将文本列声明为「CHAR」。 「CHAR」列中的每条记录的值都具有相同数量的字符。 如果输入的字符太少,MySQL 会在末尾添加空格; 如果输入太多字符,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」编码来帮助这些用户:1)试图优化空间和速度的用户;
2) 忽略了对速度和空间的优化。

没有人是赢家。 想要速度和空间的用户仍然 错误地使用 「utf8」CHAR 字段,因为这些字段仍然比它们应有的更大和更慢。 而原本想要正确性的开发者使用「utf8」是错误的,因为它不能存储「🍋」。

一旦 Mysql 发布了这个无效的字符集,它就永远无法修复它:这将迫使每个用户重建数据库。MySQL 最终在 2010 ,发布了 UTF-8 的支持,有一个不同的名字:「utf8mb4」。

为何如此令人沮丧

显然这周我很沮丧。我的错误很难被找到,因为我被「uft8」这个名字所迷惑了。而且我并不是唯一一个,几乎我在网上找到的文章都将「uft8」吹捧为「UTF-8」。

「utf8」总是错误的。 它是一个专有的字符串集。它创造了新的问题,而且并没有解决它本来想要解决的问题。

我的总结

  1. 数据库系统有微妙的错误和怪异,你可以通过避免使用数据库系统来避免很多错误。
  2. 如果你需要一个数据库,请不要使用「MySQL」或者「MariaDB」。请使用 「 Postgresql 」。
  3. 如果你需要使用 「MySQL」或者「MariaDB」,千万不要使用「UTF-8」,当你想要用「UTF-8」的时候,总是使用「utf8mb4」,现在就 转换你的数据库 从而避免之后的麻烦。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/@adamhooper/in-mysql-...

译文地址:https://learnku.com/mysql/t/71564

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 2
保安

msyql8存储字符集默认utf8mb4

2周前 评论
MArtian 2周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!