ChatGPT、Codex工程实战:数据库字段准备改名,为什么不能让Agent一次性把旧字段全部删掉? 最近用 ChatGPT、Codex 做大型重构时我越来越不建议一种看起来特别“干净”的做法字段准备改名直接全仓库搜索旧字段统一替换然后删掉旧字段。比如原来数据库里有一个字段user_name现在准备统一改成display_name从代码层面看这件事似乎很简单。Codex可以很快完成修改Entity。修改DTO。修改Repository。修改SQL。修改测试。修改接口返回。最后把旧字段删掉。本地Build通过。测试全绿。Git Diff看起来也很完整。于是很容易得出结论“迁移完成了。”但真正上线以后问题可能才刚开始。因为生产环境不是旧版本瞬间消失 → 新版本瞬间全部替换。真实的滚动发布通常更像旧Pod还在跑。新Pod已经启动。两个版本同时访问同一个数据库。这时候一个非常重要的问题就出现了新代码改完了旧代码还能不能继续工作这也是为什么数据库字段准备改名时我现在更倾向于让Agent遵循Expand → Migrate → Contract而不是Rename → Delete → Done一、最危险的不是字段改错而是新旧版本共存时互相看不懂假设旧版本代码一直读取user_name新版本已经全部改成display_name如果数据库直接执行DROP COLUMN user_name ADD COLUMN display_name新版本当然可以正常工作。问题是滚动发布期间可能还有一半旧Pod没有退出。这些旧Pod继续执行SELECT user_name FROM users结果立刻报错。所以你会看到一种特别典型的现场新版本单独测试正常。旧版本单独运行也正常。但新旧版本一起存在时系统反而坏了。这个问题本质上不是字段设计问题。而是Version Compatibility——版本兼容。二、Agent特别容易把“最终状态”当成“迁移步骤”这是我觉得Agent工程里非常值得注意的一点。你告诉Codex把user_name改成display_name。它天然会倾向于生成一个“最终正确状态”代码里只剩display_name。数据库里也只剩display_name。测试全部围绕新字段。从最终架构看非常漂亮。但真实工程需要的不是Final State而是Safe Transition Path也就是系统怎样从旧状态安全走到新状态。这两件事完全不同。最终结果可能只需要一个字段。但迁移过程中可能必须允许两个字段暂时同时存在。三、第一步不是Rename而是Expand假设现在数据库只有user_name更安全的第一步通常不是删掉它。而是先新增display_name此时数据库里变成user_namedisplay_name两个字段同时存在。旧版本继续读取user_name新版本可以开始识别display_name这样做看起来有点“重复”。但它真正换来的东西是兼容窗口。你给新旧代码提供了一段可以安全共存的时间。四、为什么这个兼容窗口这么重要因为生产发布不会只包含数据库。还有应用实例。缓存。消费者。定时任务。异步任务。旧客户端。这些东西不一定同时升级。比如10:00 数据库Schema已经更新。10:02 第一批新Pod上线。10:05 还有一半旧Pod在跑。10:10 消息消费者才完成升级。10:20 某个定时任务还是旧版本。如果你把Schema改成只能支持新代码那么整个升级过程必须做到所有组件同时切换。这在真实系统里通常非常困难。而Expand阶段的作用就是让新旧组件在过渡期都能生存。五、第二步才是Migrate字段新增以后真正复杂的部分才开始。因为这时候可能出现新数据写到哪里旧数据怎么办旧代码写user_name。新代码写display_name。两边数据会不会不一致这时通常需要设计迁移策略。比较常见的一种方式是Dual Write——双写在过渡阶段新代码同时写user_name和display_name比如用户改昵称两个字段都更新。这样即使请求下一次落到旧Pod旧版本读取user_name仍然能拿到最新值。这解决的是写兼容。六、读也不能直接只读新字段如果历史数据还没有迁完很多老记录里display_name可能还是空的。所以新版本直接只读新字段也可能出问题。更安全的读取方式通常是优先读display_name如果为空回退到user_name也就是Read New, Fallback Old这样即使历史数据Backfill还没全部完成新代码也能继续工作。所以迁移阶段常见的组合就是写新旧双写读新字段优先旧字段兜底这套逻辑虽然临时看起来比较“脏”但它的价值就是把升级风险拆散。七、历史数据必须单独Backfill新增字段以后数据库里可能有几百万甚至几千万历史记录。不能简单假设以后用户重新保存一次就会自然补齐。所以通常还需要Backfill把旧数据里的user_name逐步写入display_name这里也不建议让Agent直接生成一句UPDATE users SET display_name user_name然后在线上一把跑完。如果表很大可能带来长事务。锁等待。IO压力。主从延迟。日志暴涨。所以更稳的方式是分批。限速。可恢复。有进度记录。例如每次迁移1000条。记录Last ID。失败后继续。同时观察数据库CPU。IO。Replication Lag。事务耗时。八、为什么Schema Migration通常要比代码修改更保守因为代码回滚相对容易。新版本有问题可以重新部署旧版本。但数据库变更很多时候没有这么简单。尤其是删除字段。改变字段类型。重写历史数据。合并字段。一旦数据真的被破坏你不能只靠git revert恢复。所以数据库变更里一个很重要的原则是Prefer Additive Changes First优先做新增字段。新增表。新增索引。而不是直接删除。直接覆盖。直接不可逆修改。Agent改代码越快这条原则反而越重要。九、真正危险的是“代码可以回滚数据库却回不去”假设新版本上线以后出现问题。你决定Rollback。应用很快切回旧版本。结果旧版本开始报Unknown column user_name因为数据库已经把旧字段删掉了。这时候你会发现应用回滚成功。系统还是起不来。这就是Code Rollback ≠ Schema Rollback所以在设计迁移时应该提前问如果新版本上线10分钟后必须回滚旧版本还能不能正常访问当前Schema如果答案是不能说明这个Migration还不够安全。十、Expand阶段真正想保证的是N/N-1兼容复杂系统里经常要考虑当前版本N。上一版本N-1。在一段时间内同时运行。比如Version 2.3和Version 2.2一起访问数据库。如果Schema只支持2.3滚动发布就会很危险。所以一个更实用的设计目标是当前Schema至少短期同时支持N和N-1。这对滚动发布。快速回滚。多Region发布。灰度升级都非常重要。十一、什么时候可以停止Dual Write这一步不能凭感觉。通常至少要确认所有新版本已经稳定上线。旧Pod全部退出。旧消费者退出。旧定时任务停止。历史数据Backfill完成。新字段覆盖率达到预期。监控一段时间没有异常。然后才能进入下一步停止写旧字段。这时可以把双写改成只写新字段。但旧字段仍然先保留。因为停止写旧字段和删除旧字段不应该是同一个动作。十二、Contract才是最后一步等到所有运行代码都已经不再依赖旧字段。历史数据完成迁移。旧版本已经确认不会再回滚。观察期也足够长。这时候才进入Contract也就是删除旧读取逻辑。删除双写。删除兼容代码。最终删除user_name这时候系统才真正完成迁移。所以整个流程其实是Expand新增新字段保持旧字段。↓Migrate双写。兼容读。Backfill。↓Switch新字段成为主路径。↓Observe确认旧字段不再被使用。↓Contract最后删除旧字段。十三、为什么不能让Codex一次把所有步骤做完因为这些阶段之间需要Production Evidence比如Backfill到底完成没有旧Pod到底退出没有还有没有旧客户端消息消费者是否全部升级新字段还有没有空值这些事情不是代码生成完成就能证明的。它们需要真实运行。监控。时间窗口。线上数据。所以Agent可以一次性帮你生成Migration Plan。新字段代码。双写逻辑。Backfill脚本。监控。Contract脚本。但真正执行时不应该一次全部放出去。这里要把Code Generation和Change Execution分开。十四、让Agent做Migration时我更建议先输出“阶段计划”不要直接“把user_name改成display_name。”更好的任务是先让它输出Phase 1新增新字段不改变现有行为。Phase 2增加双写与兼容读。Phase 3历史数据Backfill。Phase 4切新字段为主。Phase 5确认旧版本退出。Phase 6删除旧字段。每个Phase再明确需要修改哪些文件。验证什么。如何回滚。进入下一阶段的条件是什么。这样Agent才是在执行Migration Workflow而不是简单Search Replace。十五、Backfill完成也不能只看“脚本跑完了”真正应该验证的是新字段覆盖率。新旧字段一致率。异常数据数量。空值数量。失败重试。例如1000万条记录。脚本显示“Completed”。但里面有3万条失败。那这次Migration当然还没有结束。所以迁移完成应该基于Data Evidence而不是Job Status十六、给自己看一个指标Backward Compatibility Coverage这篇我建议只保留一个指标Backward Compatibility Coverage——向后兼容覆盖率可以简单理解成迁移期间关键读写路径中同时能够支持新旧版本的路径数量 ÷ 关键迁移路径总数例如这次字段改名影响API读取。订单写入。消息消费者。定时任务。管理后台。一共5条关键路径。其中4条已经兼容新旧Schema。但一个老定时任务仍然只能读旧字段。那么向后兼容覆盖率 80%。这时候最危险的不是新代码写得不好。而是还有一个隐藏旧路径没有被纳入Migration。十七、兼容覆盖率低不应该继续删旧字段如果Agent报告代码全绿。新字段也能用。但兼容覆盖率仍然只有60%这时候最不应该做的就是Contract。更应该继续找还有哪些旧消费者。还有哪些批处理。还有哪些脚本。还有哪些外部服务。仍在使用旧Schema。只有这些依赖逐渐清零旧字段才真正有资格删除。十八、Plus和Pro怎么判断如果你平时主要让ChatGPT、Codex做小型数据库修改。新增几个字段。改单个接口。影响范围有限。这种任务下Plus通常已经够用。真正更重要的是不要让Agent把数据库Migration当成一次代码重构。先把兼容。Backfill。回滚。阶段边界设计清楚。如果你的实际开发已经变成大型系统频繁做Schema演进。一次改动涉及多个服务。多个消费者。历史数据迁移。灰度。滚动发布。数据回填。多轮验证。还需要Codex持续分析依赖、生成Migration、写Backfill、改代码、补测试、做回归这种高频、长时间、多阶段工程Workflow下Pro会更适合。因为更多任务容量可以真正用于大型代码库理解。长任务。多阶段迁移。反复验证。但无论使用Plus还是Pro最关键的原则都一样Agent可以一次把最终代码写出来但生产迁移不能一次跳到最终状态。最后数据库字段准备改名为什么不能让Agent一次把旧字段全部删掉因为真实生产系统需要解决的不是“最终字段叫什么”而是“旧系统怎么安全走到新系统”一个成熟的Schema Migration真正重要的不是最终Diff有多干净。而是整个过程中旧版本还能运行。新版本能够灰度。历史数据能够迁移。出现问题还能回滚。当ChatGPT、Codex开始越来越快地完成大范围代码修改以后数据库迁移反而应该更加保守。因为代码可以几分钟重写。历史数据和线上兼容性却不能靠一次Prompt重来。所以真正安全的路线通常不是Rename → Delete而是Expand → Migrate → Contract先兼容。再迁移。最后清理。这才是Agent真正进入生产工程以后数据库Schema演进应该保持的节奏。持续分享 Codex、大模型开发与 AI 编程实战内容。长期深度使用各类代码大模型也整理了稳定的Plus/Pro会员订阅渠道有需要可自取。