MySQL :InnoDB 锁机制与死锁
适用版本:MySQL 5.7 / InnoDB
一、为什么有 MVCC 还需要锁?
MVCC:主要负责普通快照读的数据版本可见性。
锁机制:协调并发事务之间的读写冲突,保护记录与索引范围。
在 RC、RR 下,普通 SELECT 通常是快照读;SELECT ... FOR UPDATE、UPDATE、DELETE 等需要结合锁机制。
二、行级锁与 S/X 锁的区别
这是两个不同的分类维度。
例如:
SELECT * FROM orders
WHERE id = 25
FOR UPDATE;假设主键等值命中已有记录,在 RR 下通常是:
对 id=25 的索引记录加 X 模式的 Record Lock。
所以记录锁与排他锁并不矛盾,可以同时描述同一个锁。
InnoDB 的行级锁实际作用于索引记录及其相关间隙,不是直接锁住 Java 对象。
三、记录锁、间隙锁、临键锁
假设主键索引中存在:
20、25、301. 为什么记录锁叫行级锁?
因为它的锁定粒度可以小到某条已有索引记录,而不是整张表。
例如 id=25 的 X 记录锁,通常会阻止其他事务更新这一条记录,但不会因为该锁本身阻止更新 id=30。
2. 为什么叫间隙锁?
间隙是索引中相邻两条记录之间的空位。
例如只有:
20、30那么存在间隙:
(20,30)如果 25 已经存在:
20、25、30间隙就变成:
(20,25)
(25,30)因为 25 把原来的间隙分成了两个部分。
注意:讨论的是同一个索引中的相邻索引项;其他索引可能具有不同的键值排列。
3. 纯间隙锁是否阻止 UPDATE?
纯 Gap Lock 主要阻止插入,不直接锁住已有记录。
假设事务 A 只持有 (20,25) 与 (25,30) 的纯间隙锁,不持有记录锁,也没有其他冲突。
如果 UPDATE 修改索引键,导致需要向被锁定的间隙插入新的索引项,则仍可能被阻塞。
记忆:纯间隙锁锁的是插入位置,不是已有记录。
4. 临键锁为什么包含右边记录?
在常见索引区间表示中:
Gap Lock:(20,25)
Record Lock:25
Next-Key Lock:(20,25]因此:
Next-Key Lock = Gap Lock + Record Lock
左括号 ( 表示不包含 20,右方括号 ] 表示包含 25。
例如事务 A 持有 X 模式的 (20,25] 临键锁:
插入 id=22:通常等待。
更新 id=25:通常等待。
更新 id=20:不因为这个临键锁本身等待。
注意:具体 SQL 获得的锁定范围由索引扫描决定,可能超出 WHERE 条件指定的边界。
四、共享锁 S 与排他锁 X
1. 共享锁
SELECT * FROM orders
WHERE id = 25
LOCK IN SHARE MODE;允许不同事务对同一记录同时持有 S 锁。
2. 排他锁
SELECT * FROM orders
WHERE id = 25
FOR UPDATE;对命中的记录申请 X 锁。
3. S/X 兼容性
S + S:读读共享。
S + X:读写互斥。
X + X:写写互斥。
注意:这里讨论的是同一索引记录上的记录锁。纯 Gap Lock 的 S/X 锁可以相互兼容,不能照搬记录锁的兼容表。
五、MVCC、记录锁和间隙锁之间的关系
在 RR 下,InnoDB 利用 MVCC 处理普通快照读的可见性,利用记录锁、间隙锁和临键锁协调当前读、写操作及范围并发。
在 RC 下,普通搜索和索引扫描通常不使用间隙锁,但唯一键冲突检查、外键检查等仍可能使用。
六、MySQL 死锁
死锁:多个事务相互等待对方持有的资源,形成循环等待。
例如:
事务 A:锁住 id=1
事务 B:锁住 id=2
事务 A:等待 id=2
事务 B:等待 id=1
形成循环等待 → 死锁死锁发生后
InnoDB 默认开启死锁检测,检测到死锁后:
分析锁等待关系。
选择一个事务作为牺牲者。
回滚该事务的全部操作,释放锁。
让其他事务继续执行。
常见错误码:1213。
死锁与锁等待超时
锁等待超时也可能因为配置或 Spring 事务异常处理导致整个事务回滚。
排查命令
SHOW ENGINE INNODB STATUS\G重点查看:
LATEST DETECTED DEADLOCK记录所有检测到的死锁:
SET GLOBAL innodb_print_all_deadlocks = ON;排查结束:
SET GLOBAL innodb_print_all_deadlocks = OFF;七、生产环境优化建议
统一加锁顺序:多个事务按相同顺序访问资源,减少循环等待。
缩短事务时间:避免在事务中执行耗时 HTTP 请求。
合理设计索引:减少不必要的扫描和锁定范围。
正确处理重试:死锁发生后,应在新的事务中有限重试完整业务操作,并做好幂等保护。
八、面试重点
1. 行级锁和 X 锁有什么区别?
行级锁描述锁的粒度和范围,X 锁描述排他模式。可以同时存在,例如 X 模式的记录锁。
2. 为什么 25 已经存在时,间隙是 (20,25) 和 (25,30)?
因为间隙由索引中相邻记录之间的空位决定,25 的存在把原来的区间分成了两个间隙。
3. 纯 Gap Lock 能阻止更新 id=25 吗?
通常不能阻止更新已有记录的非索引字段。它主要阻止向受保护间隙插入索引记录。
4. Gap Lock 与 Next-Key Lock 的区别?
Gap Lock 只保护间隙,例如 (20,25);Next-Key Lock 同时保护间隙和右边索引记录,例如 (20,25]。
5. S 锁与 X 锁如何兼容?
同一记录上 S/S 兼容,S/X 和 X/X 冲突。
6. 死锁与普通锁等待的区别?
普通锁等待不一定形成循环;死锁存在循环等待,InnoDB 默认会自动检测并选择事务回滚。
九、最终速记
记录锁:锁已有索引记录。
间隙锁:阻止向相邻索引记录之间插入。
临键锁:间隙锁 + 右侧记录锁。
S 锁:共享模式。
X 锁:排他模式。
行级锁与 S/X 锁:分别描述范围和模式。
死锁:事务循环等待,InnoDB 回滚牺牲事务。
最重要的一句话:
锁的范围决定锁住哪里,锁的模式决定哪些操作互斥;Record Lock 锁记录,Gap Lock 锁插入间隙,Next-Key Lock 同时锁住前面的间隙和右侧记录。
