跳转到内容

锁机制与死锁

用途:出现锁等待、死锁告警、更新卡住时查阅

层级 说明
全局锁 FTWRL,用于逻辑备份(或用 --single-transaction)
表级锁 表锁、元数据锁(MDL)、意向锁(IS/IX,用于快速判断表内是否有行锁)
页锁 仅 Memory/部分引擎
行级锁 InnoDB 在索引记录上加锁

MDL 隐性风险:长查询或未提交事务持有 MDL 读锁时,DDL 会排队并阻塞后续所有新查询,造成“表突然全卡”。DDL 必须避开高峰并设置 lock_wait_timeout。

锁 加在 作用
记录锁 Record Lock 单条索引记录 阻止对该行的写
间隙锁 Gap Lock 索引记录之间的间隙 阻止区间内插入(防幻读)
next-key Lock 记录 + 前面的间隙 RR 默认加锁单位
插入意向锁 插入位置 与 gap 锁冲突,是死锁高发点
隐式锁 未加锁的新插入 冲突时由其他事务转换为显式锁

关键规则:锁是加在索引上的。没有合适索引的 UPDATE/DELETE 会退化为扫描聚簇索引并锁住大量记录(等价于接近表锁的影响范围)。

典型成因:两个事务以不同顺序更新同一批行;间隙锁 + 插入意向锁互相等待。

排查步骤:

  1. SHOW ENGINE INNODB STATUS 看 LATEST DETECTED DEADLOCK 段:两个事务各自持有与等待的锁、正在执行的 SQL。
  2. performance_schema.data_locks / data_lock_waits(8.0)实时看等待链。
  3. information_schema.innodb_trx 找长事务与未提交事务。
  4. 复现路径:把两个事务的语句顺序固定下来,用并发测试重现。

处理:InnoDB 会自动回滚代价较小的事务(错误 1213),应用必须捕获死锁错误并重试(有限次 + 退避),不能直接失败给用户。

  1. 固定加锁顺序:批量更新先按主键排序再更新。
  2. 事务尽量短小:减少持锁时间,不在事务中做 RPC/IO。
  3. 走索引:更新条件必须命中索引,避免大范围锁定。
  4. 降低隔离级别:RC 可显著减少 gap 锁(需 ROW binlog)。
  5. 热点行拆分:单行高并发更新(库存、计数)改分片累加或异步合并落库。
  6. 超时兜底:innodb_lock_wait_timeout 设合理值,避免无限等待堆积。
误区 修正
SELECT 一定不加锁 当前读(FOR UPDATE)与 RR 下的快照语义要区分
死锁是 bug,必须彻底消除 死锁无法根除,正确做法是预防 + 自动检测 + 应用重试
加索引只影响查询 也决定更新锁的范围
用表锁“图省事” 并发吞吐断崖式下降
忽略 MDL 一次 DDL 可以打挂整张热点表
  • MySQL 官方文档:InnoDB Locking、Deadlocks、Metadata Locking
  • 丁奇《MySQL 实战 45 讲》锁章节
  • 《高性能 MySQL》第 5、7 章