MySQL 元数据锁实战:从原理到问题排查(Metadata Locks MDL)
1. 元数据锁的前世今生第一次遇到MySQL卡死的时候我盯着屏幕上那个Waiting for table metadata lock的提示发了半小时呆。那是个周五的晚上原本想给用户表加个字段就下班结果整个系统突然卡成PPT。后来才知道有个同事在测试环境跑了条没加limit的select语句这个小操作直接让生产环境瘫痪了两小时。元数据锁就像数据库里的交通警察它的本职工作很简单保证你在查数据的时候表结构不会突然消失或变样。想象你在看菜单点菜这时候厨师突然把菜单撕了重写——这就是元数据锁要防止的灾难场景。普通select语句虽然不锁数据行但会在表结构上加个共享锁S锁就像在菜单上贴个正在使用的便利贴。2. 元数据锁的工作原理2.1 锁的两种面孔MySQL里的元数据锁主要分两种共享锁S锁和排他锁X锁。我习惯把它们比作图书馆的借阅规则S锁就像多人同时借阅同一本书DML操作select/insert等都会自动获取这种锁X锁则像图书管理员要收回书籍修订DDL操作alter table等需要这种独占锁这里有个容易踩坑的点X锁具有优先权。当DDL请求出现时它会插队到所有等待S锁的请求前面。这就解释了为什么有时只是加个字段整个系统就雪崩了——后续所有查询都会被这个插队的DDL堵住。2.2 锁的生命周期通过下面这个实验可以清晰看到锁的运作-- 会话1 BEGIN; SELECT * FROM users WHERE id1; -- 获取S锁 -- 会话2 ALTER TABLE users ADD COLUMN age INT; -- 等待X锁 -- 会话3 SELECT * FROM users; -- 会被会话2阻塞关键点在于事务不提交S锁不释放。很多开发习惯用GUI工具执行查询如果没关闭自动创建的事务窗口这个隐藏的事务可能持有锁几小时。3. 问题排查实战指南3.1 锁监控三板斧当数据库突然变慢时我常用的排查组合拳快速查看阻塞会话SHOW PROCESSLIST;深度分析锁等待链SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id w.requesting_trx_id;查看详细元数据锁信息SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA你的数据库名;3.2 典型案例分析去年我们遇到过一个经典案例凌晨的备份脚本导致早高峰瘫痪。原因是备份程序用mysqldump时加了--single-transaction参数这个长达2小时的事务阻塞了所有DDL操作。最后是通过这个查询找到罪魁祸首SELECT ps.* FROM performance_schema.threads t JOIN information_schema.processlist ps ON ps.id t.PROCESSLIST_ID WHERE t.THREAD_ID IN ( SELECT OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE LOCK_STATUSGRANTED AND OBJECT_NAMEusers );4. 避坑与优化方案4.1 预防性措施根据血泪教训总结的checklistDDL操作永远放在低峰期执行执行前检查长事务SELECT * FROM information_schema.innodb_trx使用pt-online-schema-change等工具代替直接ALTER设置锁等待超时SET SESSION lock_wait_timeout 30;4.2 紧急救援方案当线上已经出现锁等待时立即停止DDL操作如果还能执行命令用SHOW PROCESSLIST找到阻塞源头谨慎使用KILL命令优先考虑终止源头会话对于无法终止的重要事务考虑将DDL拆分成多个小步骤有个特别实用的技巧在MySQL 8.0中可以使用SELECT * FROM sys.schema_table_lock_waits快速定位问题这个视图已经把复杂的关联查询封装好了。5. 进阶Online DDL的真相很多同学以为用了Online DDL就万事大吉其实不然。Online DDL只是在大部分阶段不阻塞DML但在准备和提交阶段仍然需要短暂的X锁。我实测过一个900万行的表ALTER TABLE big_table ADD INDEX idx_new (new_column), ALGORITHMINPLACE, LOCKNONE;这个无锁操作仍然导致了0.3秒的完全阻塞对于高并发系统已经足够引发连接池爆炸。真正的解决方案是使用gh-ost等第三方工具先在从库执行DDL然后主从切换对于新增索引考虑先创建隐藏索引再切换记得有次给账户表加字段时我提前用下面这个语句检查了可能存在的长事务SELECT NOW(6) - trx_started AS duration, trx.* FROM information_schema.innodb_trx trx ORDER BY duration DESC LIMIT 10;结果发现有个统计任务已经运行了6小时成功避免了一次重大事故。这种预防性检查应该成为DBA的肌肉记忆。