MySQL 5.6创建InnoDB索引触发锁表,如何实现无锁创建?
问题拆解与解决方案
为什么你执行CREATE INDEX会导致锁表?
首先得明确:MySQL 5.6里InnoDB的CREATE INDEX确实支持在线DDL,理论上不该阻塞读写,但你碰到的Waiting for table metadata lock问题,核心锅不在DDL本身,而是元数据锁(MDL)的等待连锁反应:
- 当你发起建索引命令时,MySQL会先尝试获取该表的MDL排他锁,用来修改表结构元数据。
- 如果此时表上有未完成的长事务(比如跑了很久的SELECT、没提交的UPDATE/INSERT),这些事务正持有该表的MDL共享锁,那你的DDL就会进入等待状态。
- 更糟的是,后续所有访问这张表的请求(不管是查还是写),都需要获取MDL共享锁,它们会排队等着前面的DDL释放锁,最终整个表就被“堵死”了,看起来像是被锁了一样。
说白了:不是建索引本身锁了表,而是你执行DDL的时机不对,刚好撞上了未结束的长事务,引发了MDL锁的等待队列。
要不要显式设置LOCK参数?该选哪个?
必须要!显式指定LOCK参数能精准控制DDL期间的锁行为,避免意外阻塞:
LOCK=NONE:这绝对是你的最优选择,它强制DDL全程不锁表,表在索引创建的整个过程中都能正常读写。InnoDB 5.6的CREATE INDEX完全支持这个参数,因为它是通过异步构建索引、不影响主表的方式实现的。LOCK=SHARED:这个只允许读操作,会阻塞写入,显然不符合你“完全不锁表”的需求,直接pass。- 默认的
LOCK=DEFAULT:MySQL会自动选最宽松的锁模式(优先用LOCK=NONE,不支持就降级到SHARED),但某些边缘场景下(比如存在旧版本的事务快照),可能会意外降级锁模式。显式写LOCK=NONE能彻底消除这种不确定性。
怎么验证和避免再踩坑?
- 执行DDL前先查下表上有没有未完成的事务:用
SHOW ENGINE INNODB STATUS看事务列表,或者SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX找出长时间运行的事务,等它们跑完再执行DDL。 - 直接用这条命令确保无锁:
CREATE INDEX my_index ON my_table(col1, col2) LOCK=NONE; - 如果必须在业务高峰操作,建议用
pt-online-schema-change这类工具,它能更优雅地处理MDL锁等待,不会一下子把表堵死。
内容的提问来源于stack exchange,提问作者user984003




