数据库演进全景图
不要把演进写成迁移路线大全;先解释“数据库已经上线以后,为什么不能像改代码一样随便回滚”
这一页只做入口判断: 数据库演进不是新增知识分支,而是回答生产状态怎样继续变化。只要改动会影响表结构、历史数据、读写路径、事实源或旧字段退场,它就不是普通发布,而是数据库演进。
团队把 entries.category 迁移成 category_id。上线后发现报表错了,于是回滚应用镜像。结果旧代码读不到新写入的数据,新旧字段已经分叉。代码回滚成功,数据库状态没有回滚。
| 误判 | 真实问题 | 演进设计 |
| 应用能回滚,所以发布安全 | 数据状态一旦写入,回滚模型不同 | 每阶段写清可逆点和不可逆点 |
| 迁移脚本跑完就结束 | 旧字段、旧报表、旧任务还在用 | 退场也是迁移的一部分 |
| 新表上线就是新事实源 | 新旧读写可能持续分叉 | 影子校验和切读证据 |
| 问题 | 如果答案是“是” |
| 会改变持久化结构吗 | 加字段、删字段、拆表、合表、改索引都算演进 |
| 会改变历史数据解释吗 | 分类、金额、币种、状态、月报口径变化都要版本和校验 |
| 会改变事实源吗 | 两个系统都自称主数据,会长期分叉 |
| 会影响下游读模型吗 | 搜索、缓存、月报宽表、数仓都要对账或重建 |
| 应用回滚后数据能不能跟着退 | 不能退就要写阶段回滚边界 |
| 旧结构什么时候退场 | 没有 Contract,复杂度会永久翻倍 |
evolution_change
- change_id
- old_structure
- new_structure
- phase: expand | migrate | switch | contract
- reversible_until
- affected_tables
- affected_read_models
- validation_query
- owner
吸收点: 数据库演进要记录阶段,而不是只记录 SQL。因为不同阶段能退的东西不一样。
| 阶段 | 做什么 | 事故防线 |
| Expand | 先加新字段 / 新表,让旧应用仍能跑 | 防止一上线就打断旧版本 |
| Migrate | 双写、回填、追增量、修差异 | 防止新旧数据长期分叉 |
| Switch | 影子读通过后灰度切到新读写路径 | 防止没证明就一刀切 |
| Contract | 旧依赖归零后删除旧字段、旧表、旧任务 | 防止复杂度永久翻倍 |
关键边界: 最危险的不是切不过去,而是切过去以后旧世界不退场,系统永远背着两套语义。
| 样本 | 通过标准 |
| 旧应用仍在线 | 新结构上线后旧版本读写不报错 |
| 新旧双写失败一次 | 能发现差异并幂等补齐 |
| 回填中断 | 能续跑,重复批次不污染数据 |
| 影子读对比 | 旧读和新读在关键查询上对得上 |
| 应用回滚 | 知道哪些数据可退、哪些只能补偿前进 |
| 旧字段删除前 | 真实读取、写入、报表、脚本依赖归零 |
总结: 数据库演进不是迁移脚本大全。它只抓一个判断:生产状态怎样在旧版本、新版本、历史数据、下游读模型和旧结构退场之间继续可信变化。