mysql 8.0 的 add column 并非完全不锁表,仅在 ddl 元数据阶段加短时 mdl 锁;需满足 algorithm=instant、列非 null 且有默认值、不指定 first/after 三条件,否则退化为 inplace 或 copy 模式。

MySQL 8.0 的 ADD COLUMN 真的不锁表吗?
不是完全不锁,而是「只在 DDL 元数据阶段加短时 MDL 锁」,真正写数据(填充默认值)的过程是 Online 的。前提是满足三个条件:ALGORITHM=INSTANT、列非 NULL 且有默认值、不指定 FIRST/AFTER 位置。否则会退化为 INPLACE 或 COPY 模式。
常见误判点:即使用了 8.0,如果执行 ALTER TABLE t ADD COLUMN c INT NOT NULL(没给 DEFAULT),MySQL 会报错或强制走 INPLACE;而加 DEFAULT 0 后,c 列在磁盘上并不实际写入旧行,只存于数据字典,新插入行才真正落盘——这是瞬时的关键。
-
INSTANT模式下,ALTER命令本身耗时通常 - 但后续第一次读取老行时,若该列无默认值定义,会触发隐式填充(lazy fill),可能引发单行延迟
- 用
SHOW CREATE TABLE能看到列定义带DEFAULT,但SELECT * FROM t LIMIT 1不会触发填充,只有SELECT c FROM t WHERE id = X才可能触发(取决于存储引擎和访问路径)
MySQL 5.7 怎么加列才不至于锁死业务?
5.7 不支持 INSTANT,只能靠 ALGORITHM=INPLACE 配合 LOCK=NONE 尽量减少阻塞。但能否真正 LOCK=NONE,取决于是否修改行格式、是否加索引、是否涉及全文字段等。对纯加列操作,只要不改 NOT NULL 约束、不设自增、不调序,通常可做到只锁写不锁读。
典型失败场景:ALTER TABLE t ADD COLUMN c VARCHAR(255) NOT NULL —— 因为 NOT NULL 且无 DEFAULT,MySQL 必须给所有现存行填值,只能走 COPY,全程锁表。
- 安全写法:必须显式加
DEFAULT,例如ADD COLUMN c INT DEFAULT 0 - 避免
FIRST或AFTER,它们会触发表重建(COPY) - 加列后立刻
UPDATE大量旧数据?别在 DDL 里做,拆成两步:先ADD COLUMN ... DEFAULT,再用小批量UPDATE分批填充 - 用
pt-online-schema-change是兜底方案,但它本身有双写开销和 binlog 放大风险,不是“更优”,只是“可控”
怎么确认你的加列操作到底走了哪种算法?
执行 ALTER 时加 VERBOSE 并观察输出,或者查 performance_schema.table_lock_waits_summary_by_table(需提前开启相关 instruments)。更直接的是看 SHOW PROCESSLIST 中状态是否长时间卡在 altering table,以及错误日志是否有 Using original table(COPY)或 Using online DDL(INPLACE)提示。
也可以在执行前用 SELECT @@version 和 SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_NAME='t' 确认版本与引擎——INSTANT 仅支持 InnoDB,且要求 MySQL ≥ 8.0.12。
- 8.0 下执行后查
information_schema.INNODB_TABLES,INSTANT列为YES表示成功启用瞬时模式 - 5.7 下如果看到
Waiting for table metadata lock卡住,大概率是其他事务正持有 MDL 锁,而不是 DDL 本身慢 -
innodb_online_alter_log_max_size设置过小(默认 128M)会导致大表INPLACE中途失败,报错含online DDL log exhausted
为什么加完列,应用读出来还是 NULL?
这不是 bug,是 8.0 INSTANT 的行为设计:老行物理上没存这个字段值,查询时由 server 层按默认值“合成”。但如果客户端用的是预编译协议(如某些 JDBC 驱动未刷新列元信息),或用了 SELECT * 但应用层缓存了旧的 ResultSetMetaData,就可能读不到新加的列,或读成 NULL(而非默认值)。
- 验证方式:用 mysql client 直连执行
SELECT c FROM t LIMIT 1,看是否返回默认值 - JDBC 用户要确保驱动 ≥ 8.0.19,且连接参数加
cachePrepStmts=false&useServerPrepStmts=false避免元数据缓存 - ORM 如 MyBatis 需手动更新
resultMap,否则映射层仍按旧结构解析 - 注意:
SELECT COUNT(*)或SELECT COUNT(c)结果不同——后者会把老行当作NULL计数,因字段逻辑存在但物理未存
瞬时加列省的是 DDL 时间,不是数据一致性时间;真正的“可见性”依赖客户端如何获取和解释元数据,这点容易被忽略。











