1、概述
MySQL 锁表通常不是"表真的坏了",而是有会话长时间持有锁,导致其他操作被阻塞。排查思路是:先定位阻塞源,再决定终止或优化,最后从 SQL、事务和索引层面预防。
2、常见原因
| 原因 | 典型表现 | 说明 |
|---|---|---|
| 事务未提交 | INSERT/UPDATE/DELETE 卡住 | BEGIN 后没有 COMMIT/ROLLBACK,锁一直不释放 |
| 长事务 | 连接堆积、超时 | 事务中执行远程调用、文件 IO、大批量处理 |
| 索引缺失 | 行锁退化为表锁 | 更新/删除未命中索引,InnoDB 可能扫描更多行 |
| DDL 冲突 | ALTER TABLE 卡住 |
DDL 需要 MDL 写锁,会被未提交事务阻塞 |
| 显式锁表 | 表无法写入 | 使用了 LOCK TABLES ... WRITE 但未解锁 |
| 存储引擎问题 | 并发差 | MyISAM 默认表锁,高并发下容易阻塞 |
| 唯一键/热点行冲突 | INSERT 等待 | 并发写入同一唯一键或同一行,锁竞争加剧 |
3、排查与处理
3.1 先看谁在阻塞
-- 查看当前连接和卡住的 SQL
SHOW FULL PROCESSLIST;
-- 查看活跃事务,重点找运行时间长的事务
SELECT * FROM information_schema.INNODB_TRX;
-- 查看被占用的表
SHOW OPEN TABLES WHERE In_use > 0;
3.2 MySQL 8.0 推荐这样查锁等待
-- 直接看阻塞关系
SELECT * FROM sys.innodb_lock_waits;
-- 或查 performance_schema
SELECT * FROM performance_schema.data_lock_waits;
重点关注 blocking_pid,它就是持有锁的会话。
3.3 如果是 MDL 锁
常见现象是大量会话处于:
Waiting for table metadata lock
可查:
SELECT * FROM performance_schema.metadata_locks;
通常要找到持有该表 MDL 锁的旧事务并终止。
3.4 终止阻塞会话
确认不是核心业务长事务后:
KILL <blocking_pid>;
注意:KILL 会触发事务回滚,如果该事务修改数据很多,回滚本身也需要时间。
4、根本解决方案
- 缩短事务:事务里不要做 HTTP 调用、文件处理、复杂计算;核心事务尽量控制在几秒内。
- 拆分大事务:批量更新/删除/插入按 500~1000 条分批提交。
- 补索引 :确保
UPDATE/DELETE的WHERE条件命中索引,避免全表扫描。 - 统一加锁顺序:多表更新时按固定顺序访问,降低死锁概率。
- 使用 InnoDB:避免 MyISAM 表锁问题。
- 谨慎执行 DDL:避开业务高峰,必要时使用在线 DDL 工具。
- 调整超时参数 :可适当调小
innodb_lock_wait_timeout,让异常请求快速失败,避免连接堆积。
5、术语释义
- MDL:Metadata Lock,元数据锁,MySQL Server 层用于保护表结构元信息的锁。
- DML :Data Manipulation Language,数据操纵语言,用于增删改查(
SELECT/INSERT/UPDATE/DELETE)。 - DDL :Data Definition Language,数据定义语言,用于修改表结构(
ALTER/DROP/CREATE/TRUNCATE/RENAME)。 - InnoDB:MySQL 主流存储引擎,支持事务、行锁、MVCC、外键。
- MyISAM:MySQL 早期存储引擎,不支持事务,使用表级锁,并发写入性能差。
- RC:Read Committed,读已提交,事务隔离级别之一。
- RR:Repeatable Read,可重复读,InnoDB 默认的事务隔离级别。