InnoDB 加索引,这个时候会锁表吗?

#中间件#面试八股

InnoDB 加索引,这个时候会锁表吗?

结论:MySQL 5.6 起支持 Online DDL,给已有表加二级索引通常是 INPLACE + LOCK=NONE,执行期间不阻塞读写。但 DDL 开始与结束各需要一次排他的元数据锁(MDL),如果前面有长事务持着 MDL,ALTER 会等待,并把它后面所有请求一起堵在队列里——这才是「加索引锁表」的真实成因。

一、三种 DDL 算法

算法是否重建表是否允许并发 DML典型操作
COPY是,拷到临时表否,全程锁写改列类型、加主键、5.6 之前的所有 DDL
INPLACE视操作而定多数允许(LOCK=NONE)加/删二级索引、加列、改索引名
INSTANT否,只改元数据是8.0 末尾加列、改默认值、重命名列

加二级索引属于 INPLACE,只扫描现有数据构建索引,同时把并发 DML 的变更记到一块日志里,最后合并。

二、加索引到底发生了什么

ALTER TABLE t_order ADD INDEX idx_user_ctime (user_id, ctime), ALGORITHM=INPLACE;

 ① 准备:取 MDL 排他锁(极短),检查是否支持 INPLACE
        │  拿不到 → 进入 MDL 等待队列,后续所有 SQL 一起排队
        ▼
 ② 执行:降级为 MDL 共享锁,允许并发读写
        ├─ 扫描聚簇索引,构建新索引
        └─ 期间 DML 的变更写入 online log(row log)
        ▼
 ③ 提交:重放 online log 补齐增量 → 再取一次 MDL 排他锁 → 切换元数据

阻塞点只有 ① 和 ③ 两次短暂的排他 MDL,执行阶段本身不阻塞 DML。所以「加索引锁表」通常不是索引构建慢,而是拿不到 MDL。

三、正确的写法

-- 显式声明算法与并发级别;不支持时直接报错,而不是悄悄退化成 COPY
ALTER TABLE t_order
  ADD INDEX idx_user_ctime (user_id, ctime),
  ALGORITHM = INPLACE,
  LOCK = NONE;

-- 报错示例(该操作不支持在线执行):
-- ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: ...

先把长事务清掉,再执行 DDL:

-- 找出运行超过 60 秒的事务
SELECT trx_id, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS secs,
       trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60
ORDER BY trx_started;

-- 限制 DDL 等 MDL 的时间,避免把业务拖死
SET SESSION lock_wait_timeout = 5;

-- 观察 MDL 等待链
SELECT * FROM performance_schema.metadata_locks
WHERE object_name = 't_order';

大表(千万级以上)更稳的做法是用影子表工具:

# gh-ost:无触发器、可限流、可暂停
gh-ost --host=127.0.0.1 --database=demo --table=t_order \
       --alter="ADD INDEX idx_user_ctime (user_id, ctime)" \
       --max-load='Threads_running=30' --critical-load='Threads_running=80' \
       --allow-on-master --execute

四、哪些操作仍然会锁表

操作算法并发 DML说明
加二级索引INPLACE允许需两次短 MDL
加主键INPLACE不允许要重建聚簇索引
改列类型COPY不允许全表重写
加全文索引INPLACE不允许(5.7/8.0 部分版本)构建倒排
加外键COPY不允许需校验子表
末尾加列INSTANT允许8.0.29+ 支持任意位置
改字符集COPY不允许全表重写

五、为什么线上感觉「锁了很久」

  1. 前面的长事务持着 MDL 共享锁:ALTER 在队列头等待,后续所有查询排在它后面,形成雪崩。
  2. DDL 自身耗时长:亿级表建索引可能几十分钟,期间虽然不锁 DML,但 IO 与 CPU 被大量占用。
  3. 主从延迟:DDL 在主库执行后同步到从库,从库重放期间复制延迟上升。
  4. ALGORITHM 未显式指定:某些版本会静默降级为 COPY,此时全程锁写。

常见追问

  • 加索引会锁表吗? 执行阶段不锁,但开始与结束需要极短的排他 MDL;拿不到就会等。
  • LOCK=NONE 一定成功吗? 不一定,操作本身不支持时会报 1846;显式指定正是为了尽早暴露。
  • MDL 为什么这么危险? 它在语句开始时就申请,长事务持有期间 DDL 只能排队,而排队会阻塞后续所有语句。
  • 8.0 的 INSTANT 能加索引吗? 不能,INSTANT 只支持元数据类变更;加索引仍需 INPLACE 构建。
  • 从库上执行 DDL 有什么不同? 从库回放 DDL 时会停止 SQL 线程,大表 DDL 会造成明显延迟。
  • 如何评估 DDL 的影响? 先在影子库用同量级数据试跑,记录耗时与 IO;线上用 gh-ost 限流执行。

小结

「加索引锁表」是 5.5 时代的旧结论。现在加二级索引走 INPLACE,执行期间可读写,真正的风险是 MDL 等待被长事务放大。落地要点:显式写 ALGORITHM=INPLACE, LOCK=NONE、先清长事务、设置 lock_wait_timeout、大表走 gh-ost。

笔记加载中…