使用 ChatGPT、Codex 修改数据库 Schema、实体类或者 Migration 时,经常会遇到一种很典型的问题:
明明已经给字段加了默认值,新数据也正常,为什么数据库里的旧记录还是一堆 NULL?
常见表现包括:
- 新增字段以后设置了默认值;
- 新插入的数据都有值;
- 历史数据却完全没变化;
- ORM实体类里已经写了默认值;
- Migration也执行成功;
- 页面上新记录正常,老记录却不断触发空值判断;
- Codex检查代码时看起来没有任何问题。
这类问题的核心不是:
默认值没有生效。
而是:
“默认值影响新写入”和“给历史数据补值”本来就是两件事。
一、数据库Default通常只负责“以后怎么写”
假设给用户表新增一个字段:
status
默认值是:
active
很多人会自然理解成:
加完默认值以后,原来所有NULL都会自动变成active。
但数据库里的 Default 更常见的作用其实是:
以后插入新记录时,如果没有主动提供这个字段,就自动使用默认值。
它解决的是:
未来的数据写入。
而不是自动重写:
过去已经存在的数据。
二、为什么新数据正常,旧数据却完全不变?
例如表里原来已经有100万条历史记录。
现在新增:
status DEFAULT 'active'
之后新建用户时:
如果没有传status,数据库可以自动填:
active
但历史100万条数据是否会被补值,要看:
- 数据库类型;
- Migration具体写法;
- 字段是否允许NULL;
- 是否执行了Backfill。
不能只看到:
Default已经存在
就认为:
历史数据也已经迁移完成。
三、应用层默认值和数据库默认值也不是一回事
有时候 ChatGPT、Codex 会在实体类里写:
status = "active"
这属于:
应用层默认值。
意思是:
通过当前应用创建对象时,status默认是active。
但如果数据来自:
- SQL脚本;
- 其他服务;
- 数据导入;
- 后台任务;
- 旧版本程序;
就不一定经过这层默认逻辑。
所以系统里可能同时存在:
ORM默认值
和
数据库Default
两套机制。
最好明确谁才是最终数据边界。
四、NULL和默认值的业务含义也要先想清楚
有些项目一看到NULL,就想全部改成默认值。
但这并不一定总是正确。
例如:
approved_at = NULL
可能表示:
还没有审批。
如果简单补成某个默认时间,反而破坏业务语义。
所以真正需要先确认的是:
NULL到底代表“缺失”,还是代表一种真实状态?
只有确定:
历史NULL本来就应该等价于默认值
才适合批量补齐。
五、历史数据需要单独做Backfill
如果确认老数据应该补成默认值,就需要单独考虑:
历史数据回填。
也就是Backfill。
例如逻辑上可能是:
将所有status为NULL的历史记录更新为active。
这一步和:
给字段设置Default
应该分开理解。
一个负责:
修历史。
一个负责:
管未来。
很多Migration的问题,就是只做了第二件事,没有做第一件事。
六、大表Backfill不能随便一次全改
如果表只有几千条记录,问题可能不大。
但如果有几百万、上千万条数据,一次性更新全部NULL记录,可能带来:
- 长事务;
- 锁等待;
- IO压力;
- 主从延迟;
- 线上查询变慢。
所以历史数据回填也要考虑:
数据量和执行方式。
很多时候更稳的是:
分批更新 + 观察影响 + 最终校验。
不能因为只是“补一个默认值”,就低估Migration风险。
七、字段允许NULL时,Default也挡不住主动写NULL
这是非常容易误解的一点。
即使数据库定义:
DEFAULT 'active'
如果应用明确写入:
NULL
数据库仍然可能保存NULL。
因为Default通常在:
字段没有提供值
时生效。
而不是:
字段明确传了NULL
时强制替换。
所以如果业务上真的不允许NULL,还需要继续考虑:
NOT NULL约束。
八、Migration成功不代表数据迁移完成
很多人看到:
Migration completed successfully.
就认为任务已经完成。
但Migration成功只说明:
SQL执行成功。
还应该继续验证:
- 字段是否真的有Default;
- 历史NULL数量还有多少;
- 新写入是否符合预期;
- 应用层有没有继续主动写NULL;
- 老数据回填是否完整。
如果不做这些检查,就很容易出现:
结构改完了,数据状态却没改完。
九、最好把字段改造拆成几个步骤
比较稳的思路是:
第一步:明确NULL和默认值的业务含义。
确认历史数据到底应该是什么。
第二步:新增字段或Default。
保证新数据有明确规则。
第三步:回填历史数据。
必要时分批处理。
第四步:增加NOT NULL等约束。
如果业务确实不允许NULL。
第五步:验证新旧数据一致性。
确认系统不会继续产生新的NULL。
这样比“一条Migration全做完”更容易控制风险。
十、可以直接这样让ChatGPT、Codex检查
以后遇到这类问题,可以直接要求:
请不要只检查字段Default是否已经设置。先确认这个默认值只影响新写入,还是历史数据也需要单独Backfill;检查ORM默认值和数据库Default是否一致,并确认应用是否会主动写NULL。如果业务不允许NULL,再评估NOT NULL约束。对于大表历史数据,请给出分批回填和最终校验方案,不要只以Migration执行成功作为完成标准。
这样比简单说:
给这个字段加个默认值。
更完整。
最后
Codex给数据库字段加了默认值以后,旧数据还是NULL,真正的问题通常不是:
Default失效。
而是:
默认值解决的是未来数据,而历史数据需要单独迁移。
稳定的数据改造应该区分:
Default → 管未来写入
Backfill → 修历史数据
NOT NULL → 守最终约束
所以以后看到:
新数据都有值,旧数据还是NULL。
第一反应不要继续改Default。
更应该先问:
这些历史NULL到底有没有做过真正的数据回填?
持续更新 ChatGPT、Codex、AI编程与后端工程实战内容,更多深度内容和稳定订阅渠道欢迎搜索关注「孤狼GPT」。