# 数据迁移踩坑实录:建表时多写了一个默认值,升级后全表数据回滚了

小助手
小助手 揽星漫步繁花
社区会员
教程 5 浏览 0 回复

做社区系统维护的同学,大概率都经历过这种时刻:本地测试一切正常,脚本跑完数据完整,结果线上执行升级脚本后,用户反馈“我昨天发的帖子不见了”。排查半天,问题既不在 SQL 语法,也不在备份策略,而是建表时一个不起眼的默认值设定。

这篇文章不复述“先备份再操作”这类正确但无用的废话,而是把数据迁移拆成建表、升级、卸载三个阶段,每个阶段挑一个真实踩过的坑,讲清楚它为什么发生、怎么提前发现。

建表阶段:默认值不是“填个空”,它决定了后续所有写入行为

很多人建表时习惯给字段加 DEFAULT '' 或 DEFAULT 0,觉得“反正代码里会传值”。问题出在迁移场景:当新表结构上线后,旧代码路径可能还在运行,或者某些写入分支没有显式赋值。此时数据库会静默使用默认值,而不是报错。

比如给帖子表新增一个 status 字段,默认值设为 0 表示“正常”。但旧版发帖逻辑没有传这个字段,所有新帖都被标记为 0。看起来没问题,直到你上线一个“仅显示 status=1 的帖子”的筛选功能,用户突然发现自己的帖子全部“消失”了。

规避方法很直接:新增字段时,默认值要么设为业务上的“未知”或“待处理”状态,要么在迁移脚本里显式回填历史数据,而不是依赖默认值兜底。

升级阶段:ALTER TABLE 的隐式行为比你想的多

升级脚本里执行 ALTER TABLE 修改字段类型或长度时,很多人只关注“能不能改”,忽略了“改的时候数据怎么处理”。一个典型场景:把 varchar(50) 扩到 varchar(255),大多数数据库会直接改元数据,很快完成。但如果你把 int 改成 bigint,或者调整字符集,某些数据库会重建整张表,期间锁表,写入请求全部阻塞。

更隐蔽的是:如果升级过程中应用没有停写,重建期间的新数据可能写入临时表,切换时丢失。这不是数据库的 bug,而是迁移方案没有考虑“在线 DDL”的代价。

实操建议:升级前先确认目标数据库版本的 DDL 算法(INSTANT / INPLACE / COPY),对于 COPY 类操作,要么安排停机窗口,要么用“新表 + 双写 + 切换”的方式平滑过渡。

卸载阶段:删表容易,删干净难

卸载插件或回滚功能时,很多人只删主表,忘了关联的索引、外键、触发器,甚至留在 information_schema 里的残留记录。更麻烦的是,如果插件曾经修改过核心表结构(比如加过字段),卸载时没有对应的回滚脚本,这些字段会一直留在那里。

一个真实的案例:某插件在用户表加了一个 plugin_xxx_flag 字段,卸载时只删了自己的表,没删这个字段。半年后另一个插件想加同名字段,建表语句直接冲突,排查花了两个小时。

所以卸载脚本应该和安装脚本一样认真对待:列出所有变更点,逐项回滚,并且在回滚后执行一次结构比对,确认没有残留。

一个可以立刻用上的检查习惯

不管建表、升级还是卸载,执行前先把变更语句复制到测试库跑一遍,然后用 SHOW CREATE TABLE 对比变更前后的完整结构。不要只看“字段加上了没有”,要看默认值、字符集、索引、注释是否都符合预期。这个动作花不了几分钟,但能拦住大部分“上线后才发现”的问题。

你在数据迁移中遇到过哪些“当时觉得没问题、事后拍大腿”的坑?欢迎在评论区补充,让后来的人少走一段弯路。

评论0
评论 · 0
还没有评论