MySQL锁机制实战:间隙锁与死锁定位处理方案

线上MySQL出现ERROR 1213(Deadlock found)的报错,事务回滚重试;或者明明只更新一行,高并发下其他连接却一直在等锁超时。这些问题的根源都在InnoDB的锁机制。本文从锁类型入手,讲清间隙锁的触发条件与死锁的定位、消除方法。

InnoDB锁类型与隔离级别的对应

InnoDB默认隔离级别是RR(可重复读),这个级别下使用行锁+间隙锁的组合(Next-Key Lock)防止幻读。锁的类型包括:

1. 记录锁:锁定单行记录,SELECT … FOR UPDATE或UPDATE/DELETE命中唯一索引时使用;
2. 间隙锁:锁住索引记录之间的空隙,防止其他事务往这个区间插入数据;
3. Next-Key Lock:记录锁+间隙锁的组合,锁住记录及其前面一段区间;
4. 意向锁:表级别的IX/IS,用于快速判断事务能否获取表锁。

隔离级别降到RC(读已提交)时,间隙锁基本只保留外键检查场景,所以很多高并发场景会把隔离级别调低减少锁冲突,代价是业务层要处理不可重复读问题。

间隙锁的触发场景与排查

下面这个查询就会产生间隙锁:

-- 表中已有 id=10, id=20 的记录
-- 该语句在RR级别下会锁住(10,20]区间的Next-Key Lock
SELECT * FROM orders WHERE id BETWEEN 10 AND 20 FOR UPDATE;

-- 另一个事务插入id=15的记录会被阻塞:
INSERT INTO orders (id, ...) VALUES (15, ...);

间隙锁的意义是防幻读,但代价大:范围锁会锁住不存在的区间,并发插入被阻断。排查锁等待,用以下命令:

-- 查看当前锁等待信息
SHOW ENGINE INNODB STATUS;

-- 查看锁相关连接与等待
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM performance_schema.data_locks;

死锁定位:读懂死锁日志

死锁发生时MySQL会回滚其中一个事务,并在错误日志里留下死锁信息。人工分析执行:

SHOW ENGINE INNODB STATUS\G

重点看LATEST DETECTED DEADLOCK段:

*** (1) TRANSACTION:
TRANSACTION 10020, ACTIVE 3 sec updating rows
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 8 page no 4 n bits 80 index PRIMARY of table `db`.`orders`
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD lock of index PRIMARY ...
*** (2) TRANSACTION:
... (2) HOLDS THE LOCK(S):
RECORD LOCKS ...
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

日志会明确显示两个事务各自持有、等待的锁及索引。典型死锁场景是两个事务以相反顺序更新同一组记录(A先更order再更user,B先更user再更order),并发下互相等待。定位后固定访问顺序即可解决。

死锁与锁等待的处理方案

处理锁定问题的常用手段:

1. 缩短事务时间:把耗时外部调用移出事务,事务只包住必须原子化的操作;
2. 统一加锁顺序:多表更新固定按表名字母顺序,避免交叉等待;
3. 让锁更精确:WHERE条件走索引,避免全表扫描产生大量行锁;
4. 降低隔离级别:RC级别下间隙锁大幅减少,用唯一约束兜底重复读;
5. 死锁重试机制:死锁是系统主动回滚的结果,应用层捕获1213后重试即可,不算故障。

锁等待超过innodb_lock_wait_timeout(默认50秒)会报1205锁等待超时,把超时调到5-10秒,让问题SQL尽早失败暴露,而不是让请求堆积。MySQL 8的锁信息全部记录在performance_schema里,用data_locks表和sys.innodb_lock_waits视图能按线程、表、锁类型过滤分析,定位效率比看INNODB STATUS高。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-suo-ji-zhi-shi-zhan-jian-xi-suo-yu-si-suo-ding-wei/

(0)
小编小编
上一篇 3小时前
下一篇 3小时前

相关推荐