背景

线上经常偶发死锁问题,当时处理一张表,也没有联表处理,但是有两个mq入口,并且消息体存在一样的情况,频率还不是很低,这么一个背景,我非常容易怀疑到,两个消息同时近到这一个事务里面导致的,但是是偶发的,又模拟不出来什么场景会导致死锁,只能进行代码分析,问题还原的方式去排查问题。

业务代码简化成下面

begin

update test set yn = 0 where dm_code = "3";
SELECT * from test where dm_code = '3'
INSERT INTO demand_flow_followers (dm_code, erp )
values
('3', 'a')
,
('3', 'b')
,
('3', 'c')

也就是说先update ,select , insert 这么一个顺序

表中存在dm_code ,erp 唯一索引

如果不存在索引 第一行update 会导致行锁升级为表锁,反而不会导致问题出现,但是并发太差

结论

先说结论:

session1 session2
开启事务
update
开启事务
update
insert
insert出现死锁

重点: 无论哪个事务insert,两个事务必须都update 完成,只要满足这个条件,两个insert执行的时候就会报死锁

原因:我先按照自己的理解解释下:

innodb的行锁,存在间隙锁,为啥要去有索引,如果没有索引,第一个update 就直接进行了表锁,这样导致另外一个事务无法进入,就只能进行等待了。

有索引的情况下:

两个事务都执行update,都拿到了[当前值,+∞) 的锁(记录锁+间隙锁),(update的时候,无数据命中)

第一个insert时,希望等待另外一个事务释放锁。第二个事务希望第一个事务释放锁,因此出现了死锁问题

相关知识梳理

InnoDB有三种行锁的算法:

1.Record Lock:是加在索引记录上的。

2.Gap Lock(间隙锁):对索引记录间的范围加锁,或者加在最后一个索引记录的前面或者后面

3.Next-Key Lock:前两种锁的结合,锁定一个范围,并且锁定记录本身,主要目的是解决幻读的问题。

间隙锁主要是防止幻象读,用在Repeated-Read(简称RR)隔离级别下。在Read-Commited(简称RC)下,一般没有间隙锁(有外键情况下例外,此处不考虑)。间隙锁还用于statement based replication

间隙锁有些副作用,如果要关闭,一是将会话隔离级别改到RC下,或者开启 innodb_locks_unsafe_for_binlog(默认是OFF)。

间隙锁(无论是S还是X)只会阻塞insert操作。

CREATE TABLE `test` (

  `id` bigint(20) NOT NULL,

  `k` bigint(20) DEFAULT '0',

  PRIMARY KEY (`id`),

  KEY `idx_k` (`k`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT into test values(2,2),(5,5),(10,10)
select @@global.tx_isolation, @@tx_isolation;

RR隔离级别

delete from test where k=5;

session2

insert into test (id,k) values (3,3)
insert into test (id,k) values (4,4)
insert into test (id,k) values (6,6)
insert into test (id,k) values (7,7)
insert into test (id,k) values (8,8)
insert into test (id,k) values (9,9)

上面都报错:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

这个证明id (3,5)都被间隙锁锁住了

insert into test (id,k) values (1,1)
insert into test (id,k) values (11,11)
delete from test where id in (1,11)

(3,5) 区间之外都可以执行insert,delete操作

可以看到,delete k=5的记录阻塞了k=3、4、5、6、7、8、9记录的插入操作,事实上,除了对于k=5这条记录上record lock之外,innoDB对于delete和update在辅助索引(非主键索引)上的条件时会对扫过的记录上间隙锁,为了防止幻读,会锁住k=5这条记录的前面一条记录(id=2,k=2)到后面一条记录(id=10,k=10)之间的区间,即锁住k在区间(2,10)的范围(如果没有后一条记录,一直锁到正无穷),至于在边界k=2及k=10上,由于索引内是按照主键排序的,不会锁住(id<2,k=2)但是会锁住(id>2,k=2),同理不会锁住(id>10,k=10)但是会锁住(id<10,k=10).

insert into test (id,k) values (1,2) ok
insert into test (id,k) values (11,2) no
insert into test (id,k) values (11,9) no
insert into test (id,k) values (11,10) ok
insert into test (id,k) values (1,10) no
insert into test (id,k) values (11,10) ok

由于索引内是按照主键排序的,不会锁住(id<2,k=2)但是会锁住(id>2,k=2),同理不会锁住(id>10,k=10)但是会锁住(id<10,k=10).

值得注意的是,delete和update在唯一索引(primary key/unique key)上更新存在的记录时只会上行级记录锁(record key),而在唯一索引上更新不存在的记录时同辅助索引一样会上间隙锁;在上例中,delete id=5只会在(id=5,k=5)这条记录上上X锁,而delete id=7却会锁住(id>5&&id<10)这个区间。

线上问题还原

session1 session2
begin
begin
update test set k = 20 where id = 20
update test set k = 20 where id = 20
INSERT into test values(25,25)
INSERT into test values(25,25)

重点: insert 之前两个回话都执行完update

SQL 错误 [1213] [40001]: Deadlock found when trying to get lock; try restarting transaction

解决办法:

避免更新或者删除不存在的记录,虽然更新存在的记录也会产生间隙锁,但是间隙锁锁住的范围会更小;

更新不存在的记录会锁住意想不到的区间范围,极其容易导致死锁问题

这些仅仅是解决问题的一个小的技巧,不能从根本上解决问题,如果想从根本上解决就从代码级别上加锁,这样避免了这种问题,但是同时并发就小了,根据自己的实际情况进行定夺方案

作者:京东零售 吴法刚

来源:京东云开发者社区 转载请注明来源

MySQL innoDB 间隙锁产生的死锁问题的更多相关文章

  1. 巧用MySQL InnoDB引擎锁机制解决死锁问题(转)

    该文会通过一个实际例子中的死锁问题的解决过程,进一步解释innodb的行锁机制 最近,在项目开发过程中,碰到了数据库死锁问题,在解决问题的过程中,笔者对MySQL InnoDB引擎锁机制的理解逐步加深 ...

  2. Mysql innodb 间隙锁

    前段时间系统老是出现insert死锁,很是纠结.经过排查发现是间隙锁!间隙锁是innodb中行锁的一种, 但是这种锁锁住的却不止一行数据,他锁住的是多行,是一个数据范围.间隙锁的主要作用是为了防止出现 ...

  3. Mysql innodb 间隙锁 (转)

    MySQL InnoDB支持三种行锁定方式: 行锁(Record Lock):锁直接加在索引记录上面. 间隙锁(Gap Lock):锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记 ...

  4. Mysql Innodb 间隙锁浅析

    间隙锁说明 innodb引擎自动使用间隙锁来避免幻读(原因是因为innodb采用单行锁+间隙锁组合而成的行锁,会锁定一个范围和记录本身的行),参数默认innodb_locaks_unsafe_for_ ...

  5. Innodb间隙锁,细节讲解(转)

    关于innodb间隙锁,网上有很多资料,在此不做赘述,我们讲解一下关于innodb的间隙锁什么情况下会产生的问题. 网上有些资料说innodb的间隙锁是为了防止幻读,这个论点真的是误人子弟.了解inn ...

  6. Mysql InnoDB行锁实现方式(转)

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  7. Mysql InnoDB行锁实现方式

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  8. innodb 间隙锁

    innodb 间隙锁, 参考 MySQLInnoDB锁机制(二) 针对于辅助索引,也称范围索引 间隙锁只会出现在辅助索引上,唯一索引和主键索引是没有间隙锁.间隙锁(无论是S还是X)只会阻塞insert ...

  9. 从一个死锁看mysql innodb的锁机制

    背景及现象 线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操 作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到 ...

  10. MySQL的间隙锁

    什么是间隙锁当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁:对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也 ...

随机推荐

  1. 面试官:“同学,你做的这几个项目都不错。但怎么问QPS你就胡说呢?”

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 这位同学,你比上一位面试者好多了,你的简历中做的几个项目都不错.既有业务项目,也有技术项目,看 ...

  2. 7. 特殊SQL的执行

    1. 模糊查询 ‍ 演示代码: /** * 测试模糊查询 * @param mohu * @return */ List<User> testMohu(@Param("mohu& ...

  3. SAP ABAP 动态结构实现发送企业微信应用消息

    企业微信官方接口: 应用支持推送文本.图片.视频.文件.图文等类型. 请求方式:POST(HTTPS)请求地址: https://qyapi.weixin.qq.com/cgi-bin/message ...

  4. 推荐一款.NET开源跨平台的开箱即用的DNS服务器软件

    前言 今天要给大家推荐一款.NET开源跨平台的开箱即用的DNS服务器软件(用于提供 DNS 解析服务):Technitium DNS Server. 项目介绍 Technitium DNS Serve ...

  5. 相较于Scrum, 我更推崇精益Kanban,帮助团队建立价值交付流,识别瓶颈问题

    最近在学习实践精益Kanban方法,结合自己团队实践Srum的经历,整理些资料二者的差异.相较于Scrum, 我更推崇精益Kaban. Agile是一套理论和原则,就像天边的北极星.Devops是一种 ...

  6. Redis核心技术与实践 01 | 基本架构:一个键值数据库包含什么?

    原文地址:https://time.geekbang.org/column/article/268262 个人博客地址:http://njpkhuan.cn/archives/redis-he-xin ...

  7. Win32API中的宽字符

    4.1了解什么是Win32API Win32API就是windows操作系统提供给我们的函数(应用程序接口),其主要存放在C:\Windows\System32 (存储的DLL是64位).C:\Win ...

  8. zabbix 主动模式下报文分析

    获取监控项列表 客户端发起请求 3次握手之后,请求监控项列表: {"request":"active checks","host":&quo ...

  9. SPI总线学习笔记

    SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空 ...

  10. Mysql高级4-索引的使用规则

    一.最左前缀法则 如果索引了多列(联合索引),要遵守最左前缀法则.最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列,如果跳跃某一列,索引将部分失效(后面的字段索引失效) 示例1:acco ...