MySQL 锁机制是什么?

2025年 阅读约 15 分钟 面试指南 · MySQL

深入解析MySQL InnoDB锁机制:行锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)、表锁、意向锁、死锁排查与解决,附实战案例和面试模拟问答。

一句话总结

InnoDB 锁机制分为行锁(Record Lock、Gap Lock、Next-Key Lock)和表锁(意向锁、AUTO-INC 锁)。默认使用 Next-Key Lock(行锁+间隙锁),在 RR 级别下解决幻读。加锁规则:等值查询唯一索引命中 → 退化为 Record Lock;等值查询未命中 → Gap Lock;范围查询 → Next-Key Lock。死锁排查用 SHOW ENGINE INNODB STATUS。

初级理解

InnoDB 锁分类

锁类型粒度加锁方式冲突
Record Lock(记录锁)单行记录锁住索引记录S 与 X 互斥,S 与 S 兼容
Gap Lock(间隙锁)索引间隙锁住索引记录之间的间隙间隙锁之间不冲突
Next-Key Lock(临键锁)记录+间隙Record Lock + Gap LockInnoDB 默认行锁
意向锁(IS/IX)表级加行锁前先加意向锁表锁与意向锁冲突
AUTO-INC 锁表级自增列插入时插入完成后释放

共享锁(S)与排他锁(X)

# 共享锁(S Lock):允许其他事务读,不允许写 SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; # MySQL 8.0 前 SELECT * FROM users WHERE id = 1 FOR SHARE; # MySQL 8.0+ # 排他锁(X Lock):不允许其他事务读和写 SELECT * FROM users WHERE id = 1 FOR UPDATE; # 兼容矩阵 # | S | X | # S | ✅ | ❌ | # X | ❌ | ❌ |
一句话总结:InnoDB 默认 Next-Key Lock = Record Lock + Gap Lock,既锁行又锁间隙,解决幻读。

中级深入

加锁规则(丁奇总结)

两个原则、两个优化、一个 bug(MySQL 5.x):

原则1:加锁的基本单位是 Next-Key Lock(前开后闭区间)

原则2:查找过程中访问到的对象才会加锁

优化1:等值查询唯一索引命中 → Next-Key Lock 退化为 Record Lock

优化2:等值查询向右遍历到第一个不满足条件的值 → Next-Key Lock 退化为 Gap Lock

# 假设表 t,id 主键:1, 5, 10, 15 # 间隙:(负无穷,1], (1,5], (5,10], (10,15], (15,正无穷) # 场景1:等值查询唯一索引命中 → Record Lock SELECT * FROM t WHERE id = 5 FOR UPDATE; # 只锁 id=5 这一行(Next-Key Lock 退化为 Record Lock) # 场景2:等值查询未命中 → Gap Lock SELECT * FROM t WHERE id = 7 FOR UPDATE; # 锁住 (5, 10) 间隙(防止插入 id=6,7,8,9) # 场景3:范围查询 → Next-Key Lock SELECT * FROM t WHERE id >= 10 AND id < 15 FOR UPDATE; # 锁住 (5,10] + (10,15](Next-Key Lock) # 场景4:非唯一索引等值查询 # 假设 age 有索引,值:10, 20, 20, 30 SELECT * FROM t WHERE age = 20 FOR UPDATE; # 锁住 age=20 的两行(Record Lock) # + age=20 前后的间隙 (10,20) 和 (20,30)(Gap Lock) # 防止插入 age=20 的新行

意向锁 — 表锁与行锁的桥梁

意向锁是表级锁,表示事务想要在表中的行上加锁。作用是让表锁(如 LOCK TABLES)不需要遍历每一行检查是否有行锁。

# 意向共享锁(IS):事务想要获取表中某些行的 S 锁 # 意向排他锁(IX):事务想要获取表中某些行的 X 锁 # 加行锁前自动加意向锁: SELECT * FROM t WHERE id = 1 FOR UPDATE; # 1. 先加表级 IX 锁 # 2. 再加行级 X 锁 # 表锁与意向锁兼容矩阵: # | IS | IX | S | X | # IS | ✅ | ✅ | ✅ | ❌ | # IX | ✅ | ✅ | ❌ | ❌ | # S | ✅ | ❌ | ✅ | ❌ | # X | ❌ | ❌ | ❌ | ❌ |
中级要点:等值唯一命中 → Record Lock;等值未命中 → Gap Lock;范围查询 → Next-Key Lock;意向锁是表锁与行锁的桥梁。

高级拓展

死锁的四个条件

1. 互斥:资源只能被一个事务持有

2. 持有并等待:事务持有资源的同时等待其他资源

3. 不可剥夺:资源不能被强制释放

4. 循环等待:事务之间形成等待环

# 死锁示例 # 事务A:先锁 id=1,再锁 id=2 # 事务B:先锁 id=2,再锁 id=1 # 两个事务互相等待对方释放锁 → 死锁 # 事务A BEGIN; SELECT * FROM t WHERE id = 1 FOR UPDATE; # 锁 id=1 # 事务B 此时执行: # BEGIN; # SELECT * FROM t WHERE id = 2 FOR UPDATE; # 锁 id=2 SELECT * FROM t WHERE id = 2 FOR UPDATE; # 等待 id=2(事务B持有) # 事务B 此时执行: # SELECT * FROM t WHERE id = 1 FOR UPDATE; # 等待 id=1(事务A持有) # → 死锁!InnoDB 检测到后回滚其中一个事务

死锁排查

# 1. 查看最近一次死锁信息 SHOW ENGINE INNODB STATUS\G # 找到 LATEST DETECTED DEADLOCK 部分 # 2. 开启死锁日志(MySQL 5.7+) SET GLOBAL innodb_print_all_deadlocks = ON; # 死锁信息会输出到 error log # 3. 查看当前锁等待 SELECT * FROM information_schema.innodb_lock_waits; # MySQL 5.7 SELECT * FROM performance_schema.data_lock_waits; # MySQL 8.0 # 4. 查看当前持有的锁 SELECT * FROM performance_schema.data_locks; # MySQL 8.0

避免死锁的最佳实践

1. 按相同顺序访问资源:所有事务都先锁 id=1 再锁 id=2

2. 尽量缩短事务:减少锁持有时间

3. 使用低隔离级别:RC 级别没有 Gap Lock,死锁概率更低

4. 合理使用索引:避免锁升级为表锁

5. 一次锁定所有资源:用 SELECT ... FOR UPDATE 提前锁定

# 死锁预防:按主键顺序更新 # ❌ 容易死锁 UPDATE t SET val = val + 1 WHERE id IN (2, 1); # 加锁顺序不确定 # ✅ 按主键排序 UPDATE t SET val = val + 1 WHERE id IN (1, 2) ORDER BY id;

实战场景

场景:线上死锁排查

# 1. 查看死锁日志 SHOW ENGINE INNODB STATUS\G # 关键信息解读: # ------------------------ # LATEST DETECTED DEADLOCK # ------------------------ # *** (1) TRANSACTION: # 事务1 # UPDATE t SET val=val+1 WHERE id=2 # 持有 id=2 的锁,等待 id=1 # *** (1) HOLDS THE LOCK(S): # RECORD LOCKS ... # 持有的锁 # *** (1) WAITING FOR THIS LOCK TO BE GRANTED: # RECORD LOCKS ... # 等待的锁 # *** (2) TRANSACTION: # 事务2 # UPDATE t SET val=val+1 WHERE id=1 # 持有 id=1 的锁,等待 id=2 # *** WE ROLL BACK TRANSACTION (2) # InnoDB 回滚了事务2 # 2. 解决方案:统一加锁顺序 # 所有事务都按 id 升序加锁 UPDATE t SET val = val + 1 WHERE id IN (1, 2) ORDER BY id;

面试模拟

面试官:什么是 Next-Key Lock?

你:Next-Key Lock = Record Lock + Gap Lock,是 InnoDB 默认的行锁算法。它锁住索引记录和记录之间的间隙(前开后闭区间),在 RR 级别下解决幻读问题。等值查询唯一索引命中时会退化为 Record Lock。

面试官:Gap Lock 之间会冲突吗?

你:不会。Gap Lock 的唯一目的是防止其他事务在间隙中插入数据,多个事务可以在同一个间隙上加 Gap Lock,互不冲突。这与 Record Lock 不同,Record Lock 的 S 和 X 是互斥的。

面试官:如何排查和解决死锁?

你:排查用 SHOW ENGINE INNODB STATUS 查看死锁日志,找到互相等待的事务和 SQL。解决:1)统一加锁顺序(按主键排序);2)缩短事务时间;3)降低隔离级别到 RC(无 Gap Lock);4)死锁重试机制(捕获 DeadlockLoserDataAccessException 后重试)。