ALTER TABLE ... SET DEFAULT 不填充已有数据,仅影响后续INSERT;需用分批UPDATE、pt-online-schema-change或应用层兜底实现填充。

为什么 ALTER TABLE ... SET DEFAULT 不会填充已有数据
ALTER TABLE t ADD COLUMN status INT DEFAULT 1 只影响后续 INSERT,对已存在的 NULL 或旧值毫无作用。很多人误以为设了 DEFAULT 就自动“补全”,结果查出来全是 NULL —— 这是概念混淆,不是锁表问题。
UPDATE 分批执行时如何避免长事务和锁表
直接 UPDATE t SET status = 1 会锁全表(尤其 MyISAM)或产生巨大 undo log(InnoDB),还可能触发主从延迟甚至 OOM。必须分批更新,关键点有三个:
- 用主键或唯一索引字段做游标,比如
id,避免OFFSET(越往后越慢) - 每次更新控制在 5000–10000 行,太小效率低,太大易超时或锁升级
- 加
WHERE status IS NULL(或对应判断条件),防止重复执行污染数据
示例语句:
UPDATE t SET status = 1 WHERE id BETWEEN 1000000 AND 1009999 AND status IS NULL;
执行完立刻 COMMIT,再取下一段。
使用 pt-online-schema-change 填充默认值的实操要点
pt-online-schema-change 是 Percona 工具,本质是建影子表 + 触发器同步 + 重命名,全程不锁原表。但它**不能直接设 DEFAULT 并填充**,必须配合 --alter 显式写 UPDATE 逻辑:
- 先确保表有主键(否则工具拒绝执行)
- 命令中用
--alter "ADD COLUMN status INT DEFAULT 1, MODIFY status SET status = 1"是错的 ——MODIFY不支持表达式 - 正确做法:先
ADD COLUMN,再用--execute或额外脚本分批UPDATE新列 - 更稳妥的是:用
--chunk-index指定主键索引,搭配--chunk-size 10000,让工具自动切片更新
真正“无感知”的方案:应用层写入兜底 + 渐进式迁移
如果业务允许短暂不一致,最轻量的方式其实是不动历史数据,靠代码兼容:
- SQL 查询时统一用
COALESCE(status, 1)替代裸列 - INSERT/UPDATE 接口强制写入默认值,不再依赖 DB 层 DEFAULT
- 后台起一个低优先级任务,按天扫描并更新老数据(比如每天跑 500 万行),用
SLEEP(0.1)控制节奏,避免冲击线上
这个方案没锁、没工具依赖、失败可重试,但要求团队接受“状态最终一致”。很多人卡在“必须立刻全量修正”的思维里,反而忽略了业务真实容忍度。
真正难的不是语法,而是评估哪部分数据必须强一致、哪部分可以妥协——填默认值只是表象,背后是数据一致性模型的选择。










