MySQL 迁移时我顺手点了"覆盖安装",结果 `information_schema` 里的字符集元数据全乱了,整站变成"乱码博物馆"

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 48 浏览 19 回复

上周帮朋友处理一个老项目的服务器迁移,本来想着"数据导出导入、跑个升级脚本、收工喝茶"三件套,结果在字符集这个阴沟翻了船。记录一下,给同样觉得"UTF-8 不都通用吗"的站长提个醒。

第一坑:建表时的 `utf8` 其实是 "阉割版 UTF-8"

迁移前我习惯性看了眼原库字符集,`SHOW CREATE TABLE` 出来是 `utf8_general_ci`。心想稳了,目标服务器 MySQL 5.7 也支持。导完数据一测,emoji 全变问号。

查了半天才想起来:MySQL 的 `utf8` 最大只支持 3 字节,真正的 4 字节 UTF-8 得写 `utf8mb4`。老项目建表时图省事写的 `utf8`,平时存存文字没问题,一旦遇到用户昵称带表情、或者内容里有生僻字,直接截断或转问号。

迁移时如果目标库默认字符集是 `utf8mb4`,而原表是 `utf8`,mysqldump 不加 `--default-character-set=utf8mb4` 的话,导出的 SQL 文件里 CREATE TABLE 语句会原样保留 `utf8`,等于把坑也一起搬过去了。

我现在迁移前必跑这行:

SELECT table_schema, table_name, column_name, character_set_name FROM information_schema.columns WHERE character_set_name IS NOT NULL AND table_schema = '你的库名';

一眼看清哪些表还在用 "假 UTF-8"。

第二坑:升级脚本里 `ALTER TABLE CONVERT TO CHARACTER SET` 会锁表到怀疑人生

发现字符集不对后,我想着直接 `ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci` 批量改。测试环境 2G 的表跑了 8 分钟,生产环境那张 30G 的日志表我掐指一算,锁表期间整站直接停摆。

后来改用 pt-online-schema-change(Percona Toolkit),边改边复制,原表全程可读写。代价是磁盘要多撑一倍空间,IO 飙高。小表直接 ALTER 无所谓,大表千万别头铁。

还有个更隐蔽的:CONVERT TO 会改表和字符串列的字符集,但 `ENUM`、`SET` 里的值如果涉及字符集转换,可能长度变化导致截断。我遇到过 ENUM('是','否') 没事,但 ENUM('正常','异常','用户已注销并删除所有关联数据') 这种长文本 ENUM 在转换时出了警告。

第三坑:卸载重装时我删了 data 目录,却忘了 `mysql` 系统库里的权限和字符集配置

这次翻车最狠。第一次迁移后发现字符集混乱,我脑抽决定"干净重装"——直接 rm -rf /var/lib/mysql/*,重新初始化。

MySQL 8.0 的 `mysqld --initialize` 确实能重建系统库,但 `lower_case_table_names` 这个参数必须在初始化前写死 my.cnf,初始化后再改启动直接报错。我之前为了兼容老项目代码里写死的大写表名,原库设的是 1(大小写不敏感),重装时忘了这茬,默认成了 0(Linux 下敏感)。

更惨的是 `information_schema` 是内存态的视图库,每次启动根据系统表重建。我重装后导入数据,发现很多查询里 `WHERE table_name = 'User'`(代码里大写)匹配不到 `user`(实际小写),而 `SHOW TABLES` 能看到。这就是 `lower_case_table_names` 不一致导致的——表名存储方式变了,但旧代码里的 SQL 没改。

最后只能再停服务、再删 data、再改 my.cnf、再初始化一遍。凌晨三点,我在机房走廊抽了半包烟。

第四坑:应用层连接字符集和库表字符集"各说各话"

就算库和表都改成 `utf8mb4` 了,PHP 连接时如果没显式 `SET NAMES utf8mb4`,或者 PDO DSN 里没加 `charset=utf8mb4`,数据在传输过程中还是可能乱码。ThinkPHP 的数据库配置里 `charset` 项默认有时候不是 `utf8mb4`,得手动检查。

我现在的迁移 checklist:

1. 源库跑字符集普查脚本,列清每个库、表、列的字符集和排序规则
2. mysqldump 加 `--default-character-set=utf8mb4 --set-charset`,确保导出文件头带 `SET NAMES`
3. 目标服务器 my.cnf 提前确认 `character-set-server`、`collation-server`、`lower_case_table_names`
4. 大表改字符集用 pt-online-schema-change,小表批量 ALTER 放在低峰期
5. 导入后随机抽几行含 emoji、生僻字的数据验证,再抓几个应用连接的实际请求看 `SHOW VARIABLES LIKE 'character_set%'`

字符集这东西,平时不惹事,迁移升级卸载时专挑凌晨搞心态。你们有没有被 `latin1` 或者 `utf8mb4_0900_ai_ci` 坑过的经历?

评论19
回复 · 19
MellowBee
MellowBee 新手 · #19 ·
学到了,顶一下
WadeYang
WadeYang 新手 · #18 ·
已解决,谢谢楼主
Alex
Alex 新手 · #17 ·
学到了,顶一下
未央
未央 新手 · #16 ·
感谢分享!
GraceWei
GraceWei 新手 · #15 ·
同求,期待更新
Knox
Knox 新手 · #14 ·
写得很清楚,收藏了
浪漫的草莓
浪漫的草莓 新手 · #13 ·
同求,期待更新
Drew
Drew 新手 · #12 ·
写得很清楚,收藏了
MayaPark
MayaPark 新手 · #11 ·
同求,期待更新
Kate
Kate 新手 · #10 ·
已解决,谢谢楼主
OpenField
OpenField 新手 · #9 ·
写得很清楚,收藏了
Kate
Kate 新手 · #8 ·
学到了,顶一下
OpenField
OpenField 新手 · #7 ·
学到了,顶一下
郭英怡
郭英怡 新手 · #6 ·
感谢分享!
安然77
安然77 新手 · #5 ·
感谢分享!
CoolKid🌸
CoolKid🌸 新手 · #4 ·
已解决,谢谢楼主
CoolKid🌸
CoolKid🌸 新手 · #3 ·
学到了,顶一下
CoolKid🌸
CoolKid🌸 新手 · #2 ·
学到了,顶一下
CoolKid🌸
CoolKid🌸 新手 · #1 ·
已解决,谢谢楼主
微信客服 微信客服