MySQL 5.7及之前版本ADD COLUMN带DEFAULT会触发全表重写,导致卡顿;8.0.12+仅当不带DEFAULT、加在末尾、InnoDB且无全文/空间索引等限制下才支持INSTANT;PG 11+元数据记录默认值,查询时动态返回,但首次SELECT或VACUUM才填充。
ALTER TABLE ADD COLUMN 带 DEFAULT 为什么卡住?
因为 MySQL 5.7 及之前版本(含大多数线上 8.0 实例)在
同时指定
,且表行数超百万时,会触发全表重写——不是只改元数据,而是逐行填充默认值并写入新段。IO 和锁时间直线上升,业务常感知为“卡死”。
常见错误现象:
执行十几分钟没反应;
显示
;从库延迟飙升。
关键判断点:是否真需要“已有行立刻有值”。如果只是新插入行用默认值,老数据允许为
或后续异步补,就别在 DDL 里写
。
MySQL 8.0.12+ 的 INSTANT 算法能跳过重写吗?
能,但限制极多,不是写了
就生效。
必须同时满足以下条件:
且不带
值(哪怕写
也不行)
新增列不能是
或
(必须加在最后)
表引擎是
,且未启用
没有全文索引、空间索引、虚拟生成列
示例可行操作:
—— 这条毫秒级完成;但加
就退化为 COPY。
要填默认值又不能停服,分三步走更稳
核心思路:把“加列 + 填值 + 改默认”拆开,用业务可接受的低峰期窗口逐步推进,避免长事务锁表。
第一步:加列不带默认(INSTANT 或 FAST)
第二步:分批 UPDATE 老数据(控制单次影响行数,加 WHERE 防全表扫)
注意:WHERE 条件必须走索引,否则变全表锁;每次执行后 sleep 几百毫秒,避免 IO 打满。
第三步:补默认值并设 NOT NULL(此时已无历史 NULL 数据)
PostgreSQL 怎么处理大表加默认值?
PG 11+ 对
做了优化:元数据层面直接记录默认值,查询时动态返回,不物理写盘——所以加列本身秒级。但首次执行
或
时,会触发后台填充,可能引发 IO 毛刺。
风险点在于:如果应用代码假设“加完列就能查到默认值”,而实际某些行还没被填充,
返回的仍是
(尤其高并发读场景)。必须确认 PG 版本 ≥ 11,且监控
观察填充进度。
安全做法:
- 先
(带 DEFAULT)
- 等待后台填充完成(或手动跑一次
)
- 再改
(这时才真正校验所有行)
别跳过填充阶段直接上
,否则报错:
。
ADD COLUMNDEFAULTALTER TABLE t ADD COLUMN status TINYINT DEFAULT 1SHOW PROCESSLISTcopy to tmp tableNULLDEFAULTALGORITHM=INSTANTADD COLUMNDEFAULTDEFAULT NULLFIRSTAFTER xxxInnoDBinnodb_file_format = AntelopeALTER TABLE t ADD COLUMN flag TINYINTDEFAULT 0ALTER TABLE t ADD COLUMN status TINYINTUPDATE t SET status = 1 WHERE id BETWEEN 100000 AND 199999 AND status IS NULL LIMIT 1000ALTER TABLE t ALTER COLUMN status SET DEFAULT 1, MODIFY COLUMN status TINYINT NOT NULLADD COLUMN ... DEFAULTSELECTVACUUMSELECTNULLpg_stat_progress_vacuumADD COLUMNVACUUM tNOT NULLNOT NULLcolumn "x" contains null values