数据库在线迁移实战:用 Expand-Contract 让新旧版本平滑切换
为什么结构变更会影响发布
一次普通的数据库结构变更,可能在发布时变成线上故障:新版本先启动,旧实例仍在处理请求;代码开始读取新列,但回填还没完成;删除旧列后,回滚版本又无法启动。
比较稳妥的做法是把迁移拆成扩展、切换和收缩三段。关键要求是:每次发布期间,新旧应用版本都能使用当前数据库结构。
一、先扩展结构
先增加新列或新表,暂时保留旧结构。新增字段先允许为空,避免数据库立刻扫描并改写所有历史行。对大表创建索引时,选择数据库支持的在线方式,并确认锁等待和执行时间。
例如订单表增加履约状态:
ALTER TABLE orders ADD COLUMN fulfillment_state varchar(24);
不要在同一次变更里添加列、回填数百万行、设置 NOT NULL 并删除旧列。把工作拆开,才能控制锁和回滚范围。
二、发布兼容版本
第一版应用继续能读旧列,同时开始写新列。新旧实例混跑时,两种字段都要保持语义一致。双写会引入短暂的不一致风险,因此要明确写入顺序、失败处理和监控;如果系统已有可靠的变更日志或 CDC,也可以用它同步数据。不要只依赖“部署很快”来假设旧实例不存在。
三、分批回填并验证
按主键范围分批处理,每批提交后记录进度,设置限速,并让任务可以重复执行。更新条件应排除已完成行,这样任务中断后可以安全续跑。观察数据库负载、复制延迟和业务接口的 p95 延迟。
回填完成后,比较新旧字段的空值数量和业务抽样结果。必要时做分批校验,不要在高峰期跑一次全表锁定式更新。
四、切换读取路径
通过配置或功能开关逐步让应用读取新字段。先小流量观察错误率、数据差异和查询耗时,再扩大范围。发现问题时可以把读取切回旧字段;扩展阶段保留的旧列正是回滚缓冲区。
五、最后再收缩
等所有实例都不再依赖旧字段、异步任务和报表也完成切换后,再经过一个观察窗口删除旧列。删除前检查代码搜索结果、慢查询、后台任务和数据导出脚本。收缩是最后一步,不能和新版本发布绑在一起。
上线检查清单
- 新旧代码都能运行在扩展后的结构上。
- 回填有批次、进度、限速和重复执行保护。
- 监控包含锁等待、复制延迟、错误率及接口延迟。
- 切换读取有开关,回滚时不需要立刻逆向改表。
- 删除旧结构前,已确认所有消费者都完成迁移。
不同数据库对在线 DDL、索引构建和锁的行为各不相同,执行前要查对应版本的官方文档,并在接近生产的数据量上演练。Expand-Contract 不能消除迁移风险,但它把一次不可逆的大操作拆成多个可观察、可暂停、可回退的小步骤。
- 点赞
- 收藏
- 关注作者
评论(0)