MySQL innoDB 间隙锁产生的死锁问题

mysql,innodb,间隙,产生,死锁,问题 · 浏览次数 : 71

小编点评

**问题原因:** 1. **间隙锁:**由于没有索引, `innodb` 使用间隙锁来实现并发更新。 2. **更新存在非唯一索引的记录:** 针对唯一索引更新,`innodb` 会上行级锁(record key),但对于非唯一索引更新,会上间隙锁。 3. **事务隔离级别选择:** 当使用 `RR`隔离级别时,即使一个事务等待另一个事务释放锁,另一个事务也会被阻塞。 **问题排查方法:** 1. **优化更新策略:** 将 `UPDATE` 语句改为 `REPLACE` 或 `INSERT` 语句,以便更安全地进行更新。 2. **调整事务隔离级别:** 使用 `RC`(Read Committed)隔离级别,避免间隙锁。 3. **添加索引:** 创建覆盖唯一索引的索引,以提高更新效率。 4. **避免更新存在非唯一索引的记录:** 如果可能,在更新过程中,对唯一索引进行排序或删除操作。 5. **优化代码性能:** 使用高效的查询条件、索引和事务管理机制等方法优化代码性能。 **代码改动建议:** ```sql -- 使用 REPLACE 语句进行更新 UPDATE demand_flow_followers SET erp = 'a' WHERE dm_code = '3'; -- 使用 INSERT 语句进行更新 INSERT INTO demand_flow_followers (dm_code, erp) VALUES ('3', 'a'); ```

正文

背景

线上经常偶发死锁问题,当时处理一张表,也没有联表处理,但是有两个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 间隙锁产生的死锁问题相似的内容:

MySQL innoDB 间隙锁产生的死锁问题

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

MySQL—MySQL的存储引擎之InnoDB

MySQL—MySQL的存储引擎之InnoDB 存储引擎及种类 存储引擎 说明 MyISAM 高速引擎,拥有较高的插入,查询速度,但不支持事务 InnoDB 5.5版本后MySQL的默认数据库存储引擎,支持事务和行级锁,比MyISAM处理速度稍慢 ISAM MyISAM的前身,MySQL5.0以后不

[转帖]mysql中innodb创建表的一些限制

https://www.cnblogs.com/jackssybin/p/16258953.html 1、 背景 在新创建mysql数据表的时候。不太确定表能创建多少个字段,多少个索引。索引多少有限制么?mysql的数据是怎么存储的存在在哪里。 2、基本个数限制 在MySQL5.6.9以后的版本,一

【转帖】MySQL InnoDB存储原理深入剖析与技术分析

一、MySQL记录存储: MySQL InnoDB的数据由B+树来组织,数据记录存储在B+树数据页(page)中,每个数据页16kb,数据页 包括页头、虚记录、记录堆、自由空间链表、未分配空间、slot区、页尾七部分组成。 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)

[转帖]MySQL InnoDB存储引擎大观

https://baijiahao.baidu.com/s?id=1709263187856706948&wfr=spider&for=pc MySQL InnoDB 引擎现在广为使用,它提供了事务,行锁,日志等一系列特性,本文分析下 InnoDB的内部实现机制,MySQL 版本为 5.7.24,操

MYSQL-INNODB索引构成详解

作者:郑啟龙 摘要: 对于MYSQL的INNODB存储引擎的索引,大家是不陌生的,都能想到是 B+树结构,可以加速SQL查询。但对于B+树索引,它到底“长”得什么样子,它具体如何由一个个字节构成的,这些的基础知识鲜有人深究。本篇文章从MYSQL行记录开始说起,层层递进,包括数据页,B+树聚簇索引,B

MySQL之InnoDB存储结构

InnoDB存储引擎最早由Innobase Oy公司开发(属第三方存储引擎)。从MySQL 5.5版本开始作为表的默认存储引擎。该存储引擎是第一个完整支持ACID事务的MySQL存储引擎,特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,非常适合OLTP场景的应用使用。目前也是应用最广泛的存储引擎。

MySQL 的 InnoDB 存储引擎简介

MySQL 是世界上最流行的开源关系型数据库管理系统之一,而其中的存储引擎则是其关键组成部分之一。InnoDB 存储引擎在 MySQL 中扮演了重要角色,提供了许多高级功能和性能优化,适用于各种应用程序和工作负载。本文将深入介绍 InnoDB 存储引擎的各个方面,以帮助您更好地理解它的特性和优势。

[转帖]聚簇索引和非聚簇索引的区别

转载:Mysql的InnoDB索引深层次原理解析 转载请注明来源:https://www.cnblogs.com/bugutian/

[转帖]深入理解mysql-第五章 InnoDB记录存储结构-页结构

前言: 页是InnoDB管理存储空间的基本单位,上一章我们主要分析了页中的主要的构成行的存储结构-行格式,其中简单提了一下页的概念。这章我们详细讲解一下页的存储结构。 一、数据页结构 前边我们简单提了一下页的概念,它是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。和存储一条条数据的