跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

SQL 大表加列默认值填充的在线变更性能优化路径

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
。

相关文章