使用 ChatGPT、Codex 修改数据库表关系时,经常会遇到一种看起来“删除成功”,实际上影响范围远超预期的问题:
明明只删了一条主记录,结果关联表里的任务、附件、评论甚至历史记录也一起没了。
常见表现包括:
- 删除一个项目,下面的任务也全部消失;
- 删除一个订单,关联明细一起被清掉;
- 接口返回200,没有任何异常;
- 数据库也没有报错;
- 测试只验证“主记录删除成功”;
- 过一段时间才发现,其他页面的数据也跟着少了。
这类问题往往不是:
Delete语句写错了。
而是:
外键上的级联删除规则,已经替你继续往下删了。
一、ON DELETE CASCADE到底做了什么?
假设有两张表:
project
和:
task
每个 task 都通过project_id关联 project。
如果外键配置了:
ON DELETE CASCADE
那么删除一条 project 时,数据库会自动删除:
所有引用这条project的task。
从数据库一致性的角度看,这很方便。
因为你不用手动一张张清理关联数据。
但问题也正出在这里:
删除动作的真实影响范围变大了。
二、为什么接口看起来完全正常?
因为数据库认为:
整个操作是合法的。
例如接口执行:
删除项目ID=1001
主表删除成功。
关联任务也根据外键规则自动删除。
事务正常提交。
最后返回:
200 OK
整个过程中可能没有任何异常。
所以单纯看接口状态码,你根本发现不了:
到底顺带删掉了多少数据。
三、最危险的是“多层级联”
比如关系是:
项目 → 任务 → 评论 → 附件
如果每一层都配置了级联删除,那么删除项目时,影响链可能变成:
1条项目
→20条任务
→300条评论
→500个附件记录
最终一个简单Delete,可能影响几百条数据。
这就是为什么级联删除最需要关注的不是:
能不能删成功。
而是:
会沿着关系链继续删到哪里。
四、Codex为什么容易顺手加上CASCADE?
因为从代码实现角度看,CASCADE确实很“干净”。
没有它时,删除主记录可能报:
foreign key constraint failed
于是 Codex 很容易得出一个直接解决方案:
给外键加
ON DELETE CASCADE。
这样错误马上消失。
但这里真正应该先问的是:
这些子数据是不是业务上也应该被一起删除?
数据库约束正确,
不等于业务规则正确。
五、有些关联数据其实应该保留
例如删除一个项目以后:
任务可以删除。
但审计记录可能必须保留。
又比如删除用户以后:
用户资料可以失效。
但历史订单、财务记录通常不能因为用户删除就一起消失。
所以关联关系不能简单理解成:
有父子关系,就应该CASCADE。
更合理的判断应该是:
生命周期是不是完全一致。
只有真正“随父记录一起出生、一起消失”的数据,才更适合考虑级联删除。
六、CASCADE、RESTRICT和SET NULL不是一回事
数据库常见的删除策略,不只有CASCADE。
CASCADE
父记录删除,子记录一起删除。
RESTRICT / NO ACTION
存在关联数据时,不允许直接删除父记录。
SET NULL
父记录删除后,子记录保留,但关联字段变成NULL。
到底选哪一种,取决于业务关系。
如果关联数据价值很高,很多时候:
阻止删除
反而比自动删除更安全。
七、软删除和物理删除也要分清
有些项目已经采用:
deleted_at
或者:
is_deleted
做软删除。
这时候如果某一层突然用了数据库CASCADE物理删除,就可能出现很奇怪的结果:
主记录只是被标记删除。
但关联数据却真的被物理清掉。
或者反过来:
主记录物理删除后,历史数据全部跟着消失。
所以在已有软删除体系里,更要明确:
哪些表允许物理删除,哪些必须保留历史。
八、测试不能只检查“主记录没了”
例如测试代码只验证:
删除后查询不到project。
这个测试即使通过,也不代表删除行为正确。
更完整的测试应该继续检查:
- task还在不在;
- comment是否应该保留;
- attachment是否被误删;
- audit log是否还存在;
- 关联数量是否符合预期。
真正该验证的是:
删除后的整个数据状态。
九、删除前最好先做影响范围检查
对于高风险删除操作,可以先统计:
这条主记录当前关联多少子数据?
例如删除前先确认:
- 12个任务;
- 46条评论;
- 8个附件;
- 3条历史记录。
这样才能知道:
这次删除到底会影响多少数据。
如果实际影响范围明显超过预期,就应该先停下来。
不要让Agent看到外键报错以后,就自动把CASCADE一路加下去。
十、可以直接这样让ChatGPT、Codex检查
以后让 ChatGPT、Codex 修改数据库删除逻辑,可以直接要求:
如果删除主记录遇到外键约束,不要直接默认增加ON DELETE CASCADE。先列出所有关联表和依赖关系,确认每类数据的生命周期是否应该随主记录一起结束。分别评估CASCADE、RESTRICT、SET NULL和软删除,并检查是否存在多层级联。修改完成后,不只验证主记录被删除,还要验证每张关联表最终应该保留还是删除。
这样能避免把:
“解决外键报错”
变成:
“把一串历史数据一起删掉”。
最后
Codex加了级联删除以后,删一条主记录,关联数据也跟着没了,真正的问题通常不是:
数据库执行错了。
恰恰相反。
数据库只是严格执行了:
你定义好的删除规则。
真正应该关注的是:
这条规则是否符合业务数据的生命周期。
所以删除逻辑设计时,最好始终检查:
关联关系 → 生命周期 → 删除策略 → 多层影响 → 最终数据状态。
以后看到:
删除接口成功了。
不要只确认主记录不存在。
还要再问一句:
它到底顺带删掉了什么?
持续更新 ChatGPT、Codex、AI编程与后端工程实战内容,更多深度内容和稳定订阅渠道欢迎搜索关注「孤狼GPT」。