MySQL 5.7 平滑迁 8.0 那晚,我因为 `utf8mb3` 这个"幽灵字符集"差点回滚
上周帮一个跑了六年的老站做数据库升级,从 5.7 切 8.0。本以为最头疼的是 `sql_mode` 或者认证插件,结果栽在了一个我以为早就不存在的玩意儿上——utf8mb3。
先交代背景。这个站用的某老牌 CMS,当年建表时图省事,全库默认字符集设的 utf8。5.7 环境下跑了这么多年,中文、emoji 混着存,表面风平浪静。迁移方案是按官方套路:逻辑导出、8.0 新实例导入、改连接串、切流量。
导入 8.0 时没报错,我还挺得意。结果业务跑起来第一天,用户反馈昵称里的 🐱 全变问号了。查表结构,发现 8.0 里这些字段居然还是 utf8mb3,而不是我以为会自动升上来的 utf8mb4。
原来 MySQL 8.0 虽然默认字符集改成了 utf8mb4,但不会自动转换已有表的字符集。你导出的 SQL 里如果显式写了 CHARSET=utf8,8.0 解析时会把这玩意儿映射成 utf8mb3——对,就是那个只支持三字节的"假 UTF-8"。emoji 四字节往上一怼,直接截断或转问号。
更阴间的是,有些表在 5.7 里实际是 utf8mb4(手动改过),但导出时因为库级默认是 utf8,mysqldump 会在文件头写 SET NAMES utf8,导致导入时新实例按 utf8mb3 理解,字段被"降级"。
我现在的迁移 checklist 里专门加了一条:导出前先看一遍 information_schema 里的字符集分布,导出时带上 --default-character-set=utf8mb4,导入前用 sed 扫一遍有没有漏网的 utf8mb3。8.0 新实例建库时显式指定 utf8mb4_0900_ai_ci,别指望默认行为。
还有个顺带踩的坑:8.0 的 utf8mb4_0900_ai_ci 排序规则和 5.7 的 utf8mb4_general_ci 在部分字符上顺序不一致。如果表里有唯一索引且依赖排序规则判重(比如用户名),迁移后可能出现"原来不冲突、现在冲突"的尴尬局面。我那次就爆了个 Duplicate entry,查了半天才发现是 Å 和 A 在新规则下被视为等价。
现在每次做迁移,我都会先拿 mysqldump --no-data 导结构,在测试实例跑一遍全量业务模拟,专门挑带特殊字符的数据做回归。字符集这东西平时隐身,一炸就是生产事故。
你们迁移数据库时,有没有被这种"沉默差异"背刺过?

