临键锁引发的死锁

简介: 【8月更文挑战第4天】

先说一个最简单的锁优化方案,MySQL的锁是依赖索引机制来实现的,如果查询没有使用索引,就会使用表锁,那么显然最简单的方案就是给这种查询创建一个索引,避免使用表锁。关键词是缺索引

早期发现我们的业务有一个神奇的性能问题,就是响应时间偶尔会突然延长,后来经过排查,确认响应时间是因为数据库查询变慢引起的。但是那些变长的查询,SQL完全没有问题,而且用EXPLAIN去分析,都很正常,也走了索引。
直到后面我们去排查业务代码的提交记录,才发现新加的功能会执行一个SQL,但是这个SQL本身不会命中任何索引,于是数据库就会使用表锁,偏偏这个SQL本身没有命中索引,又很慢,导致表锁一直得不到释放。结果其他正常的SQL反而被他拖累了。最终我们重新优化了这个使用表锁的SQL,让它走了一个索引,就解决了这个问题。

这个方案还是比较简单,还有两个稍微复杂的方案:

在一个业务中,有一个场景是先从数据库中查询数据并锁住。如果这个数据不存在,那么就需要执行一段逻辑,计算出一个数据,然后插入。如果已经有数据了,就把原始数据取出来,再利用这个数据执行一段逻辑,计算出一个结果,执行更新。
在这里插入图片描述
因为两端运算逻辑不同,所以不能简单地使用INSERT ON DUPLICATE的语句来取代。

以没有数据的逻辑来看,在计算之后插入新数据,伪代码如下:

BEGIN;
SELECT * FROM biz WHERE id = ? FOR UPDATE
// 中间有很多业务操作
INSERT INTO biz(id, data) VALUE(?, ?);
COMMIT;

事实上,这个地方会引起死锁。
假如现在数据库中ID最大的值是78,那么如果两个业务进来,同时执行这个逻辑,一个准备插入id=79的数据,一个准备插入id=80的数据,执行时序如下图

[40001][1213] Deadlock found when trying to get lock; try restarting transaction

造成死锁的原因是:在线程1执行SELECT FOR UPDATE的时候,因为id是79的数据不存在,所以数据库会产生一个(78,supremum]的临键锁;类似的,线程2也会产生一个(78,supremum]的临键锁。当线程1想要执行插入的时候,他想要获得79的行锁;当线程2想要执行插入的时候,它想要获得id=80的行锁,这个时候就会出现死锁,因为线程1和线程2同时还在等着对方释放掉持有的间隙锁。

目录
相关文章
|
3月前
|
SQL 关系型数据库 MySQL
MySQL使用行级锁时,并非直接锁定记录,而是锁定涉及的索引。对于表`user_item`,更新语句先锁定非主键索引`idx_1`,再锁定主键索引。若两条更新语句分别按不同顺序锁定这两个索引,可能导致互相等待对方释放锁,引发死锁。解决方案包括先查询待更新记录的主键,再按主键更新,确保一致的锁定顺序。
43 2
|
3月前
|
数据库 SQL
间隙锁+临键锁
【8月更文挑战第1天】
46 0
|
6月前
|
存储 关系型数据库 MySQL
分布式事物【悲观锁、乐观锁、读锁、写锁、间隙锁、临键锁 、 表锁、行锁、页面锁、 如何避免死锁】(二)-全面详解(学习总结---从入门到深化)
分布式事物【悲观锁、乐观锁、读锁、写锁、间隙锁、临键锁 、 表锁、行锁、页面锁、 如何避免死锁】(二)-全面详解(学习总结---从入门到深化)
285 0
|
5月前
|
SQL 关系型数据库 MySQL
MySQL数据库——锁-行级锁(行锁、间隙锁和临键锁)
MySQL数据库——锁-行级锁(行锁、间隙锁和临键锁)
91 0
|
6月前
|
数据库 数据安全/隐私保护
什么是行级锁和表级锁
什么是行级锁和表级锁
118 0
|
Linux API C++
锁、避免死锁等相关
锁、避免死锁等相关
67 0
《锁》有那些?
锁是计算机科学中用于控制对共享资源的访问的一种同步机制。不同种类的锁适用于不同的场景和需求。下面是一些常见的锁的种类及其详细介绍:
77 1
|
存储 算法 安全
辛辛苦苦的劳动成果,如何上把锁?
辛辛苦苦的劳动成果,如何上把锁?
|
数据可视化 Java
lock锁和死锁
lock锁和死锁
共享锁(读锁)和排他锁(写锁)
共享锁(读锁)和排他锁(写锁)
144 0