知识全景图/ 软件工程与系统/ 数据库生产治理/ 如何设计数据库迁移与在线演进

如何设计数据库迁移与在线演进

不要把迁移当跑脚本;先解释“分类字段改成 category_id,为什么不能直接上线”

这一页只解决一个问题: 业务数据库已经上线以后,表结构和数据模型怎样继续变化而不打断旧应用、不写坏历史数据、不让新旧读写分叉。Schema 评审、在线 DDL、索引回填和锁风险都收敛到这条迁移主线里;迁移不是一次 SQL,而是一段可验证的过渡期。
一、事故:直接把 category 字符串改成 category_id

Web 记账系统最初在账目表里存 category 文本。后来要支持分类改名、预算科目和多级分类,于是准备迁移成 categories 表 + category_id。如果直接改字段、跑脚本、上线新代码,旧应用、历史数据和报表会一起出问题。

失败为什么严重逼出的设计
旧应用还读 category应用版本不可能瞬间全部升级新旧结构要共存
回填脚本跑到一半失败一半数据有 category_id,一半没有回填要分批、幂等、可续跑
新旧字段同时存在但不一致事实源不清楚,报表口径分叉双写和差异校验
代码回滚后数据库回不去数据状态已经改变按阶段定义回滚边界
二、迁移前先过 Schema 预审
预审问题最低通过标准迁移里防什么
每张表保存什么事实核心表能说清记录什么、不记录什么改表时把事实、文案、快照混在一起
数据归属边界是谁owner、actor、tenant 分开建模回填或切读后归属口径漂移
哪些不变量必须兜底唯一性、金额、状态、归属写清由谁保证新旧字段共存时制造坏数据
真实查询样本是什么首页、月报、流水、导出都有对应索引和样本结构改完以后核心查询变慢或查错
坏数据怎么修能圈定对象、找到旧值、dry-run、小批次修复和复核迁移失败后只能靠人工猜
判断: Schema 评审不是字段清单,而是提前问“哪里会改不动、查不动、修不动”。这些问题答不清,就不要进入迁移执行。
三、DDL、索引和回填是执行风险
风险上线前要证明停止条件
元数据锁和长事务执行前能发现长事务、锁等待和危险会话发现等待链或写入排队立即暂停
大表扫描和日志放大估算行数、索引大小、WAL / binlog、磁盘和复制延迟复制延迟、磁盘水位或 IO 超阈值就限速
索引收益不清楚每个索引都有 SQL fingerprint、读收益和写成本没有明确服务的 SQL 就不加
约束过早变硬nullable 字段、新写入、历史回填、缺失率巡检、硬约束分阶段完成缺失率没有归零前不加 NOT NULL / UNIQUE / FK
回填不可重入批次、进度、失败样本、幂等键和续跑策略都可见不能暂停和续跑的脚本不上生产
吸收点: DDL 不是普通发布命令。它要和迁移计划绑定,留下目标、风险、前置检查、停止条件、owner 和验收样本。
四、迁移前只问 6 个问题
问题例子
旧结构还能保留多久entries.category 暂时不删,给旧代码留兼容窗口
新结构怎样先长出来先建 categories,再给 entries 加 nullable category_id
新旧写入怎样保持一致新账目同时写 category 和 category_id,失败可补偿
历史数据怎样补齐按账本或主键分批回填,记录进度和失败样本
切读前怎样证明等价影子读比较旧字段和新 join 结果、月报分类金额
旧结构什么时候退场确认没有应用、报表、脚本再读旧字段后再 Contract
五、最小迁移模型
migration_batches - migration_id - batch_key - range_start - range_end - status: pending | running | done | failed - scanned_count - migrated_count - failed_count - started_at - finished_at migration_diffs - migration_id - entry_id - old_category - new_category_id - diff_reason - status
吸收点: 回填不是一次性脚本,而是临时数据系统。它要有批次、进度、失败样本、差异和停止能力。
六、迁移只有 4 个动作:Expand、Migrate、Switch、Contract
阶段做什么不要做什么
Expand新增新表 / 新字段,让新旧结构共存不要立刻删旧字段或强制 NOT NULL
Migrate双写新旧结构,分批回填历史数据不要用不可重入的一次性 UPDATE
Switch影子校验通过后灰度切到新读路径不要没对账就一刀切
Contract确认旧依赖归零后删除旧字段和旧逻辑不要让旧结构永久残留
关键边界: 应用回滚不等于数据回滚。每个阶段都要写清还能退回哪里,哪里已经不可逆。
七、用 6 个样本证明迁移可信
样本通过标准
旧应用仍在运行新字段上线后旧读写不报错
新增账目category 和 category_id 同时正确,失败能补偿
回填中断重启后从批次进度继续,重复执行不污染数据
历史脏分类无法映射的 entry 进入差异表,不硬猜
影子读对比旧读和新读在账目列表、月报分类金额上对得上
旧字段退场真实读取和写入依赖归零后再删除
八、下一步只按迁移风险下钻
如果风险是迁移后数据错
如果风险是下游派生数据分叉
如果要做发布门禁
总结: 数据库迁移的核心不是“怎么跑脚本”,而是先制造兼容窗口,再让新旧写入对齐,历史数据分批补齐,切读前影子校验,最后旧结构退场。只要 Expand、Migrate、Switch、Contract 任何一步说不清,就不要把迁移当完成。