锁机制与死锁
用途:出现锁等待、死锁告警、更新卡住时查阅
锁的层级
「锁的层级」小节链接| 层级 | 说明 |
|---|---|
| 全局锁 | FTWRL,用于逻辑备份(或用 --single-transaction) |
| 表级锁 | 表锁、元数据锁(MDL)、意向锁(IS/IX,用于快速判断表内是否有行锁) |
| 页锁 | 仅 Memory/部分引擎 |
| 行级锁 | InnoDB 在索引记录上加锁 |
MDL 隐性风险:长查询或未提交事务持有 MDL 读锁时,DDL 会排队并阻塞后续所有新查询,造成“表突然全卡”。DDL 必须避开高峰并设置 lock_wait_timeout。
InnoDB 行锁形态
「InnoDB 行锁形态」小节链接| 锁 | 加在 | 作用 |
|---|---|---|
| 记录锁 Record Lock | 单条索引记录 | 阻止对该行的写 |
| 间隙锁 Gap Lock | 索引记录之间的间隙 | 阻止区间内插入(防幻读) |
| next-key Lock | 记录 + 前面的间隙 | RR 默认加锁单位 |
| 插入意向锁 | 插入位置 | 与 gap 锁冲突,是死锁高发点 |
| 隐式锁 | 未加锁的新插入 | 冲突时由其他事务转换为显式锁 |
关键规则:锁是加在索引上的。没有合适索引的 UPDATE/DELETE 会退化为扫描聚簇索引并锁住大量记录(等价于接近表锁的影响范围)。
死锁成因与排查
「死锁成因与排查」小节链接典型成因:两个事务以不同顺序更新同一批行;间隙锁 + 插入意向锁互相等待。
排查步骤:
SHOW ENGINE INNODB STATUS看LATEST DETECTED DEADLOCK段:两个事务各自持有与等待的锁、正在执行的 SQL。performance_schema.data_locks/data_lock_waits(8.0)实时看等待链。information_schema.innodb_trx找长事务与未提交事务。- 复现路径:把两个事务的语句顺序固定下来,用并发测试重现。
处理:InnoDB 会自动回滚代价较小的事务(错误 1213),应用必须捕获死锁错误并重试(有限次 + 退避),不能直接失败给用户。
预防清单
「预防清单」小节链接- 固定加锁顺序:批量更新先按主键排序再更新。
- 事务尽量短小:减少持锁时间,不在事务中做 RPC/IO。
- 走索引:更新条件必须命中索引,避免大范围锁定。
- 降低隔离级别:RC 可显著减少 gap 锁(需 ROW binlog)。
- 热点行拆分:单行高并发更新(库存、计数)改分片累加或异步合并落库。
- 超时兜底:
innodb_lock_wait_timeout设合理值,避免无限等待堆积。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
SELECT 一定不加锁 |
当前读(FOR UPDATE)与 RR 下的快照语义要区分 |
| 死锁是 bug,必须彻底消除 | 死锁无法根除,正确做法是预防 + 自动检测 + 应用重试 |
| 加索引只影响查询 | 也决定更新锁的范围 |
| 用表锁“图省事” | 并发吞吐断崖式下降 |
| 忽略 MDL | 一次 DDL 可以打挂整张热点表 |
延伸阅读
「延伸阅读」小节链接- MySQL 官方文档:InnoDB Locking、Deadlocks、Metadata Locking
- 丁奇《MySQL 实战 45 讲》锁章节
- 《高性能 MySQL》第 5、7 章