MySQL 8.0 迁移到 RDS 那晚,我差点被 `lower_case_table_names` 这个沉默参数送走

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

上周把跑了三年的本地 MySQL 8.0 迁到阿里云 RDS,数据量不大,就 60 多个 G,但表结构够复杂——两百多张表,各种外键、触发器、存储过程,还有早年用 Navicat 导出来的那堆带 `DEFINER` 的视图。想着 mysqldump 一把梭,导完改个连接配置就能收工,结果从晚上九点折腾到凌晨四点,踩了三个这辈子都忘不掉的坑。

坑一:建表时的大小写,在 RDS 里变成了"薛定谔的表名"

本地环境 `lower_case_table_names=0`,表名该大写大写、该小写小写,代码里 `SELECT * FROM UserLog` 和 `SELECT * FROM userlog` 井水不犯河水。RDS 默认参数组这个值是 1,表名全给你转小写存进去。导完数据一跑业务,ThinkPHP 模型里 `protected $name = 'UserLog'` 直接报找不到表,但 Navicat 左侧明明看得见那张表——只是它现在叫 `userlog` 了。

最骚的是 RDS 这个参数改不了,只能重建实例。我愣是又导了一遍,这次提前把代码里所有表名统一成小写。后来查文档才知道,MySQL 8.0 初始化后这个参数就焊死了,迁移前不核对等于给自己埋雷。

坑二:卸载旧环境时,我把 `mysql` 系统库一起打包了

迁移完手贱,在本地执行了 `mysqldump --all-databases` 想留份"完整备份",然后 `DROP` 旧实例。第二天测试环境要搭个从库,拿这份备份恢复,结果用户权限全乱套——`mysql.user` 表里的账号密码跟着过来了,但本地和 RDS 的 root 密码根本不是一回事,恢复完连自己都登不进去。

现在我的备份脚本里必加 `--databases db1 db2 db3`,`--all-databases` 这个选项已经被我拉进黑名单。系统库迁移?除非你是做机房级容灾,否则别碰。

坑三:升级时忽略的 `utf8mb3` 幽灵字段

RDS 版本比本地高一个小版本,导数据没报错,但业务跑起来后用户反馈昵称里的emoji变成问号。排查发现早年建的几个字段还是 `utf8mb3`,本地 MySQL 8.0 居然能正常存emoji(估计是某个小版本的兼容行为),RDS 新版本严格按标准来,直接截断。

最后写了个脚本遍历 `information_schema.COLUMNS`,把 `CHARACTER_SET_NAME='utf8mb3'` 的字段全揪出来改成 `utf8mb4_0900_ai_ci`。这活儿本该在迁移前的结构比对阶段做完,但我当时只关注了数据行数对不对,没看字符集这种"细节"。

现在我的迁移 checklist 长这样:

1. 源和目标 `lower_case_table_names`、`sql_mode`、`character_set_server` 三条对比,截图存档
2. mysqldump 必带 `--single-transaction --set-gtid-purged=OFF --no-tablespaces`,视图单独导出再手动清 `DEFINER`
3. 恢复后跑一遍 `mysqlcheck --check-upgrade`,MySQL 自带的升级检查比人眼靠谱
4. 旧环境保留至少两周,卸载前先停服务观察,别急着 `rm -rf`

那次迁移后我在 Grafana 里加了个面板,专门监控 `Aborted_connects` 和 `Handler_read_rnd_next`,RDS 和本地环境的基线差异一目了然。数据迁移这活儿,技术难度真不高,全是细节密度太大,一步走神后面全是连锁反应。你们迁移时有没有被哪个"沉默参数"坑过?

评论0
回复 · 0
还没有回复
微信客服 微信客服