数据库变更治理
凌晨发布窗口里,应用已经完成滚动升级,一条给订单表增加非空列的 DDL 却长时间等待元数据锁。值班人员终止语句后发现:一部分实例运行新代码,一部分仍运行旧代码;迁移脚本在测试库执行过,但测试库数据量、索引、字符集和长事务都与生产不同;所谓回滚脚本只会删除新列,无法恢复已经回填的数据。故障不是“SQL 写错了”,而是变更没有版本事实、兼容窗口、运行证据和恢复责任。
数据库变更治理要回答的不是谁有权限敲命令,而是同一个发布能否持续证明:目标 schema 是什么,迁移文件是否被篡改,空库和存量库会经过哪些不同路径,危险操作在哪里被阻断,执行身份能改哪些对象,应用新旧版本能否同时工作,失败后选择回滚、前滚还是数据恢复,以及所有环境最终是否收敛到同一事实。
从现场信号选择工程入口
| 现场信号 | 工程入口 | 应得到的证据 |
|---|---|---|
| 项目偏好按版本顺序执行 SQL,希望约束文件命名、checksum、baseline 和 Spring 启动 | Flyway | schema history 与仓库文件一致,篡改被 validate 拒绝,clean 不会误伤共享库 |
| 变更需要结构化 changelog、precondition、context/label、预览 SQL 和显式 rollback | Liquibase | changeSet 身份稳定,锁和 checksum 可诊断,update/rollback 结果可追踪 |
| 团队希望从期望 schema 或 ORM 模型生成 diff,并在合并前 lint 危险迁移 | Atlas | dev database 可重建,diff 可审查,hash 未漂移,apply 后实际状态收敛 |
| 工具已经能执行 migration,但危险 DDL、数据回填、跨版本兼容和恢复决策仍靠口头约定 | Schema Review 与回滚 | 审查门禁能阻断不可接受风险,发布步骤和恢复路径经过演练 |
工具不是互斥标签。Flyway 或 Liquibase 可以承担迁移执行事实,Atlas 可以在提交前生成或检查迁移,组织门禁再约束风险和证据。但一个 schema 只能有清晰的权威迁移历史;多个执行器同时拥有写入权,却没有所有权和顺序规则,会把“多工具”变成多套互相冲突的事实。
一条可靠变更链怎样流动
迁移文件进入仓库只是起点。空库重建证明新环境不会依赖某位开发者手工修补;存量库升级证明历史版本能走到当前状态;影子库和 lint 尝试在执行前暴露锁、全表重写、数据截断和不兼容语义;生产执行需要独立身份、超时、互斥锁和审计关联;应用与 schema 的兼容窗口决定滚动发布能否成立;恢复演练则证明“有脚本”是否真的等于“能恢复”。
版本历史不能事后补写
versioned migration 一旦在共享环境执行,就成为审计事实。修改旧文件会让新环境重建结果与旧环境不同,checksum 报错正是在阻止这种分叉。正确路径通常是保留旧文件,再追加一个补偿 migration。repair、clear-checksums 或手工修改历史表只能用于已经查明的元数据修复,不能把未经审查的漂移洗成绿色。
baseline 也不是“跳过报错”的按钮。它表达的是:某个既有数据库在某个版本之前由外部历史创建,现在从明确版本开始纳入迁移治理。若没有先核对对象、约束、索引、字符集、扩展和数据形态,baseline 只会把未知状态贴上已知标签。
多人并行时,版本号冲突只是最显眼的问题。两个 migration 即使编号不同,也可能分别重命名同一列、创建同名索引或依赖对方尚未合并的数据。合并前要在共同祖先上重放全部迁移,并对最终 schema 做 diff;发布队列还要保证同一数据库只有一个权威执行者持有迁移锁。
回滚、前滚与恢复不是同一个动作
添加可空列、创建新表或增加兼容索引通常可以结构回滚;删除列、缩窄类型、合并数据、去重和脱敏往往不可逆。即使 DDL 可以撤销,期间写入的新数据也可能无法放回旧结构。回滚设计必须同时回答结构、数据、应用版本和外部事件四层状态,而不是只准备一条反向 SQL。
更稳健的发布常采用 expand-contract:先增加新结构并保持旧代码可运行,再部署能双读或双写的新代码,完成可校验的回填与流量切换,最后在确认所有旧消费者退出后删除旧结构。中途失败时优先停止后续阶段或前滚修复;只有反向路径已演练且不会造成二次数据损失时才执行回滚。误删和错误数据重写则进入备份、日志归档与时间点恢复流程,不能由 migration 工具假装完成。
影子库必须像目标环境,而不是看起来像数据库
SQLite 上成功的 DDL 不能证明 MySQL 或 PostgreSQL 上的锁、事务、默认值、生成列和索引算法安全。空库也无法暴露存量数据违反新约束、长事务阻塞、表尺寸导致重写、复制延迟或磁盘临时空间不足。代表性验证至少要固定引擎大版本、字符集/排序规则、扩展、关键权限和近似数据分布,并对耗时、锁等待、磁盘增长、复制与回滚时间设门槛。
影子数据不应直接复制生产敏感数据。可以使用脱敏快照、结构统计驱动的合成数据和受控采样,但要明确它们缺失什么。测试通过的结论应附带数据量、分布、引擎参数和运行时间;没有这些上下文,绿色结果不能支撑生产判断。
执行身份与流水线边界
应用运行账号通常不应拥有 CREATE、ALTER、DROP 等 DDL 权限。迁移使用独立短期身份,只能访问目标数据库和批准 schema;流水线先执行 validate、diff、lint 或 SQL 预览,再经过环境保护和审批执行。DSN 与密码不能写进 migration 文件、命令参数、构建日志或 artifact,错误输出也要避免回显连接串和业务行值。
执行器必须同时具备互斥和超时。互斥阻止两个发布同时推进历史,超时阻止一个等待锁的 DDL无限悬挂,但超时不是自动安全回滚:某些数据库会留下已提交对象、未完成索引或外部副作用。失败后先确认数据库真实状态和历史表记录,再决定重试、补偿还是恢复,不能盲目再次启动整条流水线。
团队运行底线
每个 schema 只有一个权威迁移历史、一个 owner 和一条批准的生产执行入口。migration 执行后保持不可变;任何修正使用新版本,并把 checksum 异常当作需要调查的漂移证据。空库重建、存量升级、重复执行、失败注入和恢复演练都进入 CI 或发布前门禁。
clean、drop、truncate 和不可逆数据修改默认拒绝;临时放行同时校验环境身份、数据库指纹和人工审批。schema 与应用通过 expand-contract 保持跨版本兼容,不把数据库迁移隐藏在每个应用实例启动阶段竞争执行。迁移身份最小授权、短期有效、全程审计;连接串、SQL 参数、数据样本和备份按敏感资产治理。
变更记录同时保留 migration commit、审批、计划 SQL、执行身份、历史表结果、耗时、锁/复制证据和恢复决策。定期从空库和受控快照重建,核对实际 schema、迁移历史与仓库声明,发现漂移先调查再修复。
当团队能从一条变更记录重建“为什么改、按什么顺序改、谁批准、谁执行、数据库实际发生了什么、应用如何跨版本兼容、失败后怎样恢复”,数据库变更才不再是一段上线备注,而是可以长期维护的工程资产。
