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











