mysql 5.6前alter table默认锁表因全表拷贝+mdl_write锁;5.6起支持online ddl,但仅部分操作(如加删二级索引、改默认值)可lock=none,需显式指定algorithm和lock并验证。

ALTER TABLE 为什么默认会锁表
MySQL 5.6 之前,ALTER TABLE 几乎总是触发全表拷贝(copy-alter),期间对原表加 MDL_WRITE 锁,所有 DML(INSERT/UPDATE/DELETE)都会被阻塞。哪怕只是加个 VARCHAR(255) 字段,也可能卡住业务几小时。
根本原因是老版本没有分离“元数据变更”和“数据重组织”的能力——结构改了,数据就得重排、重建索引、重写磁盘文件。
5.6 起引入 Online DDL,但不是所有操作都在线;是否真正不锁表,取决于具体操作类型、存储引擎(仅 InnoDB 支持完整 Online DDL)、以及是否显式指定算法和锁级别。
哪些 ALTER 操作能真正 Online(InnoDB)
不是所有 ALTER TABLE 都能免锁。以下常见操作在 MySQL 5.6+ 中可做到 ALGORITHM=INPLACE 且 LOCK=NONE(即读写都不阻塞):
- 添加/删除二级索引(
ADD INDEX/DROP INDEX) - 修改列默认值(
ALTER COLUMN ... SET DEFAULT) - 重命名列(
CHANGE COLUMN或RENAME COLUMN,但不能改类型或约束) - 添加虚拟列(
ADD VIRTUAL COLUMN) - 修改自增值(
ALTER TABLE ... AUTO_INCREMENT = N)
而这些操作即使指定 ALGORITHM=INPLACE,仍至少需要 LOCK=SHARED(阻塞写,允许读):
- 修改列类型(如
VARCHAR(50) → VARCHAR(100)在部分场景下可,但INT → BIGINT不行) - 添加
NOT NULL列(无默认值时必须全表扫描补 NULL,会锁写) - 修改主键(
DROP PRIMARY KEY+ADD PRIMARY KEY组合通常触发拷贝)
如何强制控制 Online DDL 行为
MySQL 允许显式指定 ALGORITHM 和 LOCK 提示,让执行更可控。不写的话,MySQL 自动选最优策略,但有时它会“保守选择”导致锁表。
推荐始终显式声明,例如:
ALTER TABLE users ADD COLUMN status TINYINT DEFAULT 0, ALGORITHM=INPLACE, LOCK=NONE;
关键参数含义:
-
ALGORITHM=INPLACE:禁止拷贝表,只做原地修改(失败则报错,不降级) -
ALGORITHM=COPY:强制全表拷贝(等价于旧版行为) -
LOCK=NONE:不锁读写(仅部分操作支持) -
LOCK=SHARED:允许读,阻塞写 -
LOCK=DEFAULT:让 MySQL 自行决定最小锁级别(默认值,但不可靠)
注意:ALGORITHM=INPLACE 并不保证 LOCK=NONE;必须两者配合检查。执行前可用 EXPLAIN FORMAT=JSON 查看预估行为(MySQL 8.0.12+ 支持):
EXPLAIN FORMAT=JSON ALTER TABLE users ADD INDEX idx_email (email);
返回 JSON 中的 alter_algorithm 和 lock_level 字段就是真实决策依据。
线上执行前必须验证的三件事
Online DDL 不是银弹,尤其在大表上,容易因磁盘 IO、内存不足、长事务或复制延迟引发问题。
- 确认没有活跃的长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60;—— 长事务会阻止 DDL 获取元数据锁,导致 DDL 排队甚至超时 - 检查磁盘空间:
ALGORITHM=INPLACE仍需临时排序空间(尤其建索引),建议预留 ≥ 表大小 20% 的tmpdir空间 - 观察从库延迟:
SHOW SLAVE STATUS\G中的Seconds_Behind_Master—— Online DDL 在主库秒级完成,但从库可能因单线程回放变慢,造成复制积压
最稳妥的做法:先在从库执行,确认无异常、无延迟飙升,再上主库;或者用 pt-online-schema-change(Percona Toolkit)兜底,它用触发器模拟 Online DDL,兼容性更强,但有额外性能开销和风险点(比如触发器失效、binlog 格式限制)。
真正麻烦的从来不是语法对不对,而是你没看到的那个未提交事务,或者那个正在跑的报表查询。











