MySQL 的 utf8 实际是 utf8mb3,仅支持 3 字节 UTF-8 字符,无法存储补充平面字符(如 ?),必须全链路改用 utf8mb4 并显式配置服务端、数据库、表、列,注意索引限制、排序规则升级及 CHAR/VARCHAR 选型。

MySQL 的 utf8 不是真正的 UTF-8
它只是个历史别名,实际叫 utf8mb3,最多只支持 3 字节编码。所有落在 Unicode 补充平面(U+10000 起)的字符,比如 ?、?、?、?,都会被拒绝插入,报错 Incorrect string value: '\xF0\x9F\x98\x8A'。这不是客户端或应用层的问题,是 MySQL 底层字符集能力缺失导致的硬性拦截。
改用 utf8mb4 必须全链路显式配置
只改数据库默认字符集不够,utf8mb4 需要逐层覆盖:服务端 → 数据库 → 表 → 列。哪怕 character_set_server 是 utf8mb4,已有表的字段仍可能是 utf8,查询 SHOW FULL COLUMNS FROM table_name LIKE 'col_name' 看 Collation 列才能确认真实值。
- 建新表时必须写明:
CREATE TABLE t (c VARCHAR(100)) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci - 改旧表推荐:
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci - 如果字段有索引且原长度是
VARCHAR(255),在utf8mb4下可能突破 InnoDB 默认 767 字节索引限制(4 × 255 = 1020),得同步缩短字段长度,或开启innodb_large_prefix(MySQL 5.7+ 已默认启用)
utf8mb4_0900_ai_ci 是 MySQL 8.0+ 的推荐排序规则
老项目常用 utf8mb4_unicode_ci,但它基于较旧 Unicode 标准,在德语、越南语等带变音符号的语言中排序不准;utf8mb4_0900_ai_ci 基于 Unicode 9.0.0,ai(accent insensitive)、ci(case insensitive),对多语言更鲁棒。注意:它不向后兼容 _unicode_ci 的某些边界行为,迁移前建议在测试环境跑一遍 ORDER BY 和 WHERE 比较逻辑。
CHAR 类型在 utf8mb4 下空间浪费更明显
CHAR(10) 在 utf8mb4 中会预分配 40 字节(10 × 4),而 VARCHAR(10) 只存实际字节数 + 1 或 2 字节长度头。官方明确建议:除非长度严格固定且极短(如国家代码 CHAR(2)),否则一律用 VARCHAR 替代 CHAR。
charset=utf8mb4、应用 ORM 没配对、前端 HTTP header 仍是 charset=utf-8(注意这里没下划线),都可能导致乱码或截断。每个环节都得单独验证,不能靠默认值赌运气。


















