#AI#Vibe Coding#DevOps

Vibe Coding 数据库迁移:用 Expand-Contract 管住 AI 的破坏性改表

AI 很容易生成一步到位的 rename、NOT NULL 或类型变更。本文以 Expand-Contract 阶段、回填水位、兼容矩阵和回滚条件,把改表任务变成旧新版本可共存的发布流程。

本文要点

  • 主题聚焦:AI、Vibe Coding、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年7月30日·更新于 2026年7月30日

需求是把 users.name 拆成 given_name 与 family_name,AI 常会给出一段漂亮但危险的迁移:新增两列、从空格切分、删除旧列。它忽略了滚动发布期间仍有旧实例读写 name,也不知道姓名无法被可靠地按空格拆分。真正的问题不是 DDL 能否执行,而是数据语义、应用版本和迁移阶段能否共存。

数据库迁移管理实战提供版本化基础;Vibe Coding 场景还需要限制 AI 每一回合只能推进一个可验证阶段。Expand-Contract 的目标是让变更在“扩展、迁移、切读、收缩”之间有明确停靠点,而不是把所有动作塞进一个部署。

先写兼容矩阵

在生成 SQL 前列出四个参与者:旧应用、新应用、回填任务和数据库 Schema。矩阵逐项回答旧应用能否读写、新应用能否读写、回填是否可重入、回滚到旧应用是否丢数据。只要某个阶段出现“新写入只有新列,旧应用回滚后看不见”,就不能进入该阶段。

把目标拆成以下代际:

  1. Expand:只新增可空列、辅助索引或未验证约束,不删除旧结构。
  2. Dual-write:服务在一个事务内写旧列和新列,并记录不一致指标。
  3. Backfill:按稳定主键分片填充历史行,保存水位与失败清单。
  4. Read switch:先影子读取并比较,再把主读切到新列,保留回退开关。
  5. Enforce:证明覆盖率后验证约束,最后才设为必填。
  6. Contract:所有活跃版本停止访问旧列后,单独发布删除动作。

让 AI 为每一步输出前置条件、SQL、观测查询、停止条件和逆向操作。提示里明确禁止 DROP COLUMN、表重写型转换和无界全表更新,除非人类批准风险说明。PostgreSQL ALTER TABLE 各子命令的锁级别和执行方式不同,不能根据 SQL 行数判断影响。

回填必须与在线写入和解

回填不是一次 UPDATE ... WHERE new_col IS NULL 就结束。它需要固定排序键和 checkpoint,限制每批行数,在提交后推进水位;失败重跑同一批应得到相同结果。若转换不是无损的,例如姓名拆分,就保留 migration_status 与人工修正入口,不能让模型编造拆分规则并宣称完成。

在线写与回填可能竞争。常用策略是 dual-write 先上线,回填只更新尚未填充或版本较旧的行,并以源版本或 updated_at 保护较新的用户编辑。每批记录扫描数、更新数、跳过数和转换失败数;最终对账应按主键范围比较旧新表达,而不只检查 new_col IS NOT NULL。

大规模回填和代际切换可参考数据 Backfill 代际切换。关键是将“任务跑完”与“数据等价”分开:进度到 100% 仍可能包含转换失败、并发覆盖或后来新增的旧格式写入。

约束分两步加固

新增外键或复杂检查时,先让新写入遵守规则,再扫描历史数据。PostgreSQL 支持先添加 NOT VALID 约束、之后 VALIDATE CONSTRAINT;适用命令仍要以当前版本官方文档为准。NOT NULL 的实施也应在零空值证明后进行,且考虑锁与表扫描。

字段重命名通常用“新增字段 + 双写 + 切读 + 删除”模拟,而不是直接 rename,因为直接 rename 会立即破坏旧二进制。类型变化若不能安全隐式转换,可新增目标类型列并显式转换,抽样核对边界值。Schema 演进中的 Reader/Writer 兼容思想也可借鉴Schema Registry 消费者兼容发布,但数据库还多了锁、持久写入和回滚数据完整性。

索引应单独规划。先验证查询确实需要,再选择适合线上创建的方式;失败的并发索引可能留下无效对象,需要清理路径。不要让 AI 把索引创建与长事务回填绑在一起,否则锁和磁盘压力难以独立控制。数据库索引维护可作为后续膨胀与无效索引检查清单。

把每个阶段变成门禁

Expand 门禁验证旧版回归测试仍通过。Dual-write 门禁注入一边写失败,证明事务不会留下半套数据。Backfill 门禁执行中断、重启、重复批次与在线更新竞态。Read switch 在影子读中按语义比较,允许已声明的格式差异,其余差异阻断。Contract 前从代码搜索、查询日志和仍在线版本三处证明旧列无人使用。

回滚也必须按阶段定义:切读失败可回旧读;新应用失败可回旧实例,但前提是 dual-write 没停;一旦删除旧列,回滚就需要数据恢复,不再是普通应用回退。参考代码搜索栈影子迁移的影子验证思路,可以把新旧读路径放在真实流量下比较,但不得让影子结果产生副作用。

AI 最适合生成阶段性脚本、对账查询和失败测试,不适合决定何时删数据。把兼容矩阵和门禁作为提示的固定输入,每次只批准一个阶段,数据库迁移才会从“模型给了一段 SQL”变成可停止、可证明、可逆的发布流程。

Related

相关推荐