讲解

字符集(CHARACTER SET)决定能存哪些字符,排序规则(COLLATION)决定比较和排序的规则(比如是否区分大小写)。MySQL 的字符集有四个层级:服务器、库、表、列,下层不指定就继承上层;此外还有「连接层」——客户端告诉服务器「我发来的字节是什么编码」(character_set_client / connection / results),SET NAMES utf8mb4 一条命令同时设置这三个。

MySQL 历史上最大的坑是 utf8 这个名字:MySQL 的 utf8 最多只存 3 字节,而真正的 UTF-8 是 1 到 4 字节——emoji(😀)和少数生僻汉字是 4 字节,往 utf8 列里插 emoji 会直接报错 1366 Incorrect string value,这就是著名的「emoji 事故」。完整的 UTF-8 在 MySQL 里叫 utf8mb4,8.0 起它就是默认字符集。排序规则推荐 utf8mb4_0900_ai_ci:0900 表示基于 Unicode 9.0,ai 不区分重音,ci 不区分大小写。

LENGTH() 返回字节数、CHAR_LENGTH() 返回字符数,这对组合是排查字符集问题的常用工具:utf8mb4 下一个汉字占 3 字节、一个 emoji 占 4 字节。如果 CHAR_LENGTH 和 LENGTH 的比例不对,或者内容里出现问号、乱码,多半是某一层的字符集设置错了——数据写入时连接层声明的编码和实际字节不符,是乱码的最常见来源。

示例

查看 utf8 家族字符集和 utf8mb4 的 0900 系列排序规则:

SHOW CHARACTER SET WHERE Charset LIKE 'utf8%';

SHOW COLLATION WHERE Charset = 'utf8mb4' AND Collation LIKE 'utf8mb4_0900%';

建一张 utf8mb4 的表,SET NAMES 声明连接编码后插入 emoji,用字节数与字符数的对比验证存取无损(🎉 占 4 字节):

CREATE TABLE messages (
  id INT AUTO_INCREMENT PRIMARY KEY,
  content VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci
);

SET NAMES utf8mb4;

INSERT INTO messages (content) VALUES ('发布会定档 🎉 敬请期待'), ('纯中文内容');

SELECT content, CHAR_LENGTH(content) AS chars, LENGTH(content) AS bytes FROM messages;

排序规则影响比较结果:默认的 utf8mb4_0900_ai_ci 不区分大小写,所以 'a' = 'A' 为真:

SELECT 'a' = 'A' AS case_insensitive_compare, @@collation_connection AS connection_collation;

常见坑

  • 用 utf8 存 emoji:报 1366 或者存成问号。新表一律 utf8mb4;老表迁移用 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4。
  • 只改了表,连接层没改:表是 utf8mb4,但客户端按 latin1 发送字节,存进去就是双重编码的乱码。应用连接串、mysql 客户端都要显式指定 utf8mb4。
  • 两张表排序规则不同就 JOIN:报 1267 Illegal mix of collations。统一全库的字符集与排序规则,不要表表不同。
  • 以为 utf8mb4 占空间是 utf8 的四倍:mb4 是「最多 4 字节」,普通汉字仍是 3 字节、ASCII 仍是 1 字节,迁移不会暴涨存储。

小结

utf8mb4 才是完整 UTF-8,配 utf8mb4_0900_ai_ci;四层字符集加连接层都要对齐;LENGTH 数字节、CHAR_LENGTH 数字符。下一章进入性能的核心主题:索引。