云计算百科
云计算领域专业知识百科平台

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_name

display_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会员订阅渠道,有需要可自取。

赞(0)
未经允许不得转载:网硕互联帮助中心 » ChatGPT、Codex工程实战:数据库字段准备改名,为什么不能让Agent一次性把旧字段全部删掉?
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!