首页/开发/CI/CD 与发布速查/扩展-收缩迁移(Expand/Contract)
环境与配置管理60 条

扩展-收缩迁移(Expand/Contract)

把破坏性结构变更拆成安全序列:加新结构 → 双写/回填 → 切换读 → 移除旧结构,每一步都可独立回滚。

CI/CD 与发布速查开发更新于 2026-10-13

扩展-收缩迁移是什么

把破坏性结构变更拆成安全序列:加新结构(扩展)→ 双写与回填 → 切换读取 → 移除旧结构(收缩),每一步都可独立回滚,任意时刻新旧代码都能工作。价值在于把一次危险变更变成多次安全变更,代价是多几个版本的观察期与回填窗口。适用场景:重命名列、改类型、拆分表、迁移存储格式。

四个阶段

阶段 1 扩展新增列、表或索引(兼容旧代码)
阶段 2 迁移双写 + 回填历史数据 + 校验一致性
阶段 3 切换读切到新结构,观察
阶段 4 收缩确认无依赖后删除旧结构

各阶段回滚点

阶段回滚方式前提
扩展后停用新结构无外部依赖
双写期停双写回旧读旧结构完整
切换后读切回旧结构双写仍在
收缩前保持原样观察期未结束

典型使用场景

  • 列重命名:新列、双写、切读、删旧列四步替代一次重命名
  • 类型变更:建新类型列按步迁移,避免一次性转换锁表
  • 表拆分:先建新表双写,读切换后再停止写旧表
  • 大表索引重建:新索引建好后再切换查询路径
  • 存储迁移:双写新存储,校验一致后关闭旧路径
  • 回填开关:回填任务支持暂停与续跑,中断后从断点继续而不全量重来

序列示意(重命名字段)

序列示意(重命名字段)
-- 1. 加新列
ALTER TABLE orders ADD COLUMN ref_no text;
-- 2. 双写并分批回填
UPDATE orders SET ref_no = legacy_ref WHERE ref_no IS NULL;  -- 分批执行
-- 3. 读切换到 ref_no,观察稳定
-- 4. 停止双写,删除旧列
每步之间按天或周留观察窗:数据量越大,回填与校验越要拆批执行。
双写要考虑写失败策略:两个写入点一个失败时以哪边为准、如何补偿,先定清楚再上线。
校验先行:回填后跑行数、抽样与聚合比对,校验通过才允许切读;观察期设好时长与错误率阈值,达标再进入收缩。

易错点

  • 一次上线完成加列改名删列:回滚窗口内旧版本直接报错,拆步执行
  • 回填不设限速:大表回填打满 IO 拖垮线上,分批并监控延迟
  • 双写期的写放大无评估:写入量翻倍压垮数据库,提前压测评估
  • 收缩过早:观察期未满就删旧结构,回退路径被自己关掉
  • 大表 DDL 直上:加列、改索引直接在业务高峰执行会锁表,用在线 DDL 或专用变更工具执行

数据来源与参考

  • Martin Fowler《ParallelChange》
  • GitHub Engineering《安全迁移实践》
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌