热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

mysql死锁查询和插入_Mysql死锁排查:insertonduplicate死锁一次排查分析过程

前言遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insertonduplicate死锁问题,本文将基于这个死锁问题&#

前言

遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insert on duplicate死锁问题,本文将基于这个死锁问题,分享排查分析过程,希望对大家有帮助。

死锁案发还原

表结构:

CREATE TABLE`song_rank` (

`id`int(11) NOT NULLAUTO_INCREMENT,

`songId`int(11) NOT NULL,

`weight`int(11) NOT NULL DEFAULT '0',PRIMARY KEY(`id`),UNIQUE KEY`songId_idx` (`songId`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

隔离级别:

mysql> select @@tx_isolation;+-----------------+

| @@tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set, 1 warning (0.00 sec)

数据库版本:

+------------+

| @@version |

+------------+

| 5.7.21-log |

+------------+

1 row in set (0.00 sec)

关闭自动提交:

mysql> select @@autocommit;+--------------+

| @@autocommit |

+--------------+

| 1 |

+--------------+

1 row in set (0.00sec)

mysql> set autocommit=0;

Query OK,0 rows affected (0.00sec)

mysql> select @@autocommit;+--------------+

| @@autocommit |

+--------------+

| 0 |

+--------------+

1 row in set (0.00 sec)

表中的数据:

mysql> select * fromsong_rank;+----+--------+--------+

| id | songId | weight |

+----+--------+--------+

| 1 | 10 | 30 |

| 2 | 20 | 30 |

+----+--------+--------+

2 rows in set (0.01 sec)

死锁案发原因:

并发环境下,执行insert into … on duplicate key update…导致死锁

死锁模拟复现:

事务一执行:

mysql> begin; //第一步

Query OK,0 rows affected (0.00sec)

mysql> insert into song_rank(songId,weight) values(15,100) on duplicate key update weight=weight+1; //第二步

Query OK,1 row affected (0.00sec)

mysql> rollback; //第七步

Query OK,0 rows affected (0.00 sec)

事务二执行:

mysql> begin; //第三步

Query OK,0 rows affected (0.00sec)

mysql> insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1; //第四步

Query OK,1 row affected (40.83 sec)

事务三执行:

mysql> begin; //第五步

Query OK,0 rows affected (0.00sec)

mysql> insert into song_rank(songId,weight) values(18,100) on duplicate key update weight=weight+1; //第六步

事务一,事务二,事务三执行:

步骤事务一事务二事务三

第一步

begin;

第二步

insert into song_rank(songId,weight) values(15,100) on duplicate key update weight=weight+1; (Query OK, 1 row affected (0.00 sec) )

第三步

begin;

第四步

insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1; //被阻塞

第五步

begin;

第六步

insert into song_rank(songId,weight) values(18,100) on duplicate key update weight=weight+1; //被阻塞

第七步

rollback;

结果

Query OK, 1 row affected (40.83 sec)

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

死锁浮出水面:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

死锁破案排查分析

遇到死锁问题时,我们应该怎么处理呢?分一下几个步骤

1.查看死锁日志

当数据库发生死锁时,可以通过以下命令获取死锁日志:

show engine innodb status;

上面例子insert on duplicate死锁问题的日志如下:

*** (1) TRANSACTION:TRANSACTION 27540, ACTIVE 19sec inserting

mysql tablesin use 1, locked 1LOCK WAIT3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1MySQL thread id23, OS thread handle 14896, query id 582 localhost ::1 root update

insert into song_rank(songId,weight) values(18,100) on duplicate key update weight=weight+1

*** (1) WAITING FOR THIS LOCK TOBE GRANTED:

RECORD LOCKSspace id 116 page no 4 n bits 72 index songId_idx of table `test2`.`song_rank` trx id 27540lock_mode X

locks gap before recinsertintention waiting

Record lock, heap no3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000014; asc;;1: len 4; hex 80000002; asc;;*** (2) TRANSACTION:TRANSACTION 27539, ACTIVE 41 sec inserting, thread declared inside InnoDB 1mysql tablesin use 1, locked 1

4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1MySQL thread id22, OS thread handle 6976, query id 580 localhost ::1 root update

insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1

*** (2) HOLDS THE LOCK(S):

RECORD LOCKSspace id 116 page no 4 n bits 72 index songId_idx of table `test2`.`song_rank` trx id 27539lock_mode X

locks gap before rec

Record lock, heap no3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000014; asc;;1: len 4; hex 80000002; asc;;*** (2) WAITING FOR THIS LOCK TOBE GRANTED:

RECORD LOCKSspace id 116 page no 4 n bits 72 index songId_idx of table `test2`.`song_rank` trx id 27539lock_mode X

locks gap before recinsertintention waiting

Record lock, heap no3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000014; asc;;1: len 4; hex 80000002; asc ;;

2.分析死锁日志

如何分析死锁日志呢? 分享一下我的思路

死锁日志分事务1,事务2拆分

找出发生死锁的SQL

找出事务持有什么锁,都在等待什么锁

SQL加锁分析

事务1日志分析

f04a51dc6791eb7f3653054ade973c8c.png

从日志我们可以看到事务1正在执行的SQL为:

insert into song_rank(songId,weight) values(18,100) on duplicate key update weight=weight+1

该条语句正在等待索引songId_idx的插入意向排他锁:

lock_mode X locks gap before rec insert intention waiting

事务2日志分析

a6586a22013b1985e001682555ca755a.png

从日志我们可以看到事务2正在执行的SQL为:

insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1

该语句持有一个索引songId_idx的间隙锁:

lock_mode X locks gap before rec

该条语句正在等待索引songId_idx的插入意向排他锁:

lock_mode X locks gap before rec insert intention waiting

锁相关概念补充(附):

考虑到有些读者可能对上面insert intention锁等不太熟悉,所以这里这里补一小节锁相关概念。

官方文档

InnoDB 锁类型思维导图:

ac9785e439eee58b0d1b751ec8beb46e.png

我们主要介绍一下兼容性以及锁模式类型的锁

1.共享锁与排他锁:

InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)。

共享锁(S锁):允许持锁事务读取一行。

排他锁(X锁):允许持锁事务更新或者删除一行。

如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

T2 请求 s 锁立即被允许,结果 T1 T2 都持有 r 行的 s 锁

T2 请求 x 锁不能被立即允许

如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容。

2.意向锁

意向共享锁( IS 锁):事务想要获得一张表中某几行的共享锁

意向排他锁( IX 锁): 事务想要获得一张表中某几行的排他锁

比如:事务1在表1上加了S锁后,事务2想要更改某行记录,需要添加IX锁,由于不兼容,所以需要等待S锁释放;如果事务1在表1上加了IS锁,事务2添加的IX锁与IS锁兼容,就可以操作,这就实现了更细粒度的加锁。

InnoDB存储引擎中锁的兼容性如下表:

4d34b917df980adaeb630b373310a62d.png

3.记录锁(Record Locks)

记录锁是最简单的行锁,仅仅锁住一行。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE

记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。

会阻塞其他事务对其插入、更新、删除

记录锁的事务数据(关键词:lock_mode X locks rec but not gap),记录如下:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table`test`.`t`

trx id10078 lock_mode X locks rec but notgap

Record lock, heap no2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 8000000a; asc;;1: len 6; hex 00000000274f; asc 'O;;

2: len 7; hex b60000019d0110; asc ;;

4.间隙锁(Gap Locks)

间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。

使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。

5.Next-Key Locks

Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。

6.插入意向锁(Insert Intention)

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。

假设有索引值4、7,几个不同的事务准备插入5、6,每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了4、7之间的间隙,但是不阻塞对方因为插入行不冲突。

事务数据类似于下面:

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table`test`.`child`

trx id8731 lock_mode X locks gap before rec insertintention waiting

Record lock, heap no3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000066; ascf;;1: len 6; hex 000000002215; asc" ;;2: len 7; hex 9000000172011c; asc r ;;...

SQL加锁分析:

通过分析死锁日志,我们可以找到发生死锁的SQL,以及相关等待的锁,我们再对对应的SQL进行加锁分析,其实问题就迎刃而解了。

OK,我们回到对应的SQL,insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1 执行过程到底加了什么锁呢?加锁机制官方文档

insert加锁策略:

insert语句会对插入的这条记录加排他记录锁,在加记录锁之前还会加一种 GAP 锁,叫做插入意向(insert intention)锁,如果出现唯一键冲突,还会加一个共享记录(S)锁。

(SQL加锁分析非常重要,在这里给大家推荐一篇文章,讲的非常好,解决死锁之路 - 常见 SQL 语句的加锁分析)

insert on duplicate key加锁验证

为了验证一下insert on duplicate key加锁情况,我们拿上面demo的事务1和2在走一下流程。

事务1:

mysql> begin; //第一步

Query OK,0 rows affected (0.00sec)

mysql> insert into song_rank(songId,weight) values(15,100) on duplicate key

update weight=weight+1; //第二步

Query OK,1 row affected (0.00 sec)

事务2(另开窗口):

mysql> begin; //第三步

Query OK,0 rows affected (0.00sec)

mysql> insert into song_rank(songId,weight) values(16,100) on duplicate key

update weight=weight+1; // 第四步

使用show engine innodb status查看当前锁请求信息,如图:

a698f042d44d58b708c51240bcdbcacf.png

有图可得:

事务2持有:IX锁(表锁),gap x锁,insert intention lock(在等待事务1的gap锁)

所以,insert on duplicate 执行过程会上这三把锁。

死锁原因分析

回归到本文开头介绍的死锁案发模拟现场(事务1,2,3)以及死锁日志现场,

案发后事务1的锁:

257b22a9fb049920f576f4cc854a4bd3.png

案发后事务2的锁:

4e0c3fab80d46a8d6d4e66fbd3fa204c.png

案发复原路线:

1.首先,执行事务1执行:

begin;insert into song_rank(songId,weight) values(15,100) on duplicate key update weight=weight+1;

会获得 gap锁(10,20),insert intention lock(插入意向锁)

2.接着,事务2执行:

begin;insert into song_rank(songId,weight) values(16,100) on duplicate key update weight=weight+1;

会获得 gap锁(10,20),同时等待事务1的insert intention lock(插入意向锁)。

3.再然后,事务3执行:

begin;insert into song_rank(songId,weight) values(18,100) on duplicate key update weight=weight+1;

会获得 gap锁(10,20),同时等待事务1的insert intention lock(插入意向锁)。

4.最后,事务1回滚(rollback),释放插入意向锁,导致事务2,3同时持有gap锁,等待insert intention锁,死锁形成!

锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):

50d2c7ae06fe7d4d736408a83d1db997.png

如何避免该insert on duplicate死锁问题

1.把insert on duplicate改为insert

try{insert();

}catch(DuplicateKeyException e){update();

}

因为insert不会加gap锁,所以可以避免该问题。

2.更改MySql版本

既然这是MySql5.7的一个bug,那么可以考虑更改Mysql版本。

3.尽量减少使用unique index。

gap锁跟索引有关,并且unique key 和foreign key会引起额外的index检查,需要更大的开销,所以我们尽量减少使用不必要的索引。

本文总结(重要)

本文介绍了MySql5.7死锁的一个bug。我们应该怎样去排查死锁问题呢?

1.show engine innodb status;查看死锁日志

2.找出死锁SQL

3.SQL加锁分析

4.分析死锁日志(持有什么锁,等待什么锁)

5.熟悉锁模式兼容矩阵,InnoDB存储引擎中锁的兼容性矩阵。



推荐阅读
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了在Oracle数据库中创建序列时如何选择cache或nocache参数。cache参数可以提高序列的存取速度,但可能会导致序列丢失;nocache参数可以避免序列丢失,但在高并发访问时可能导致性能问题。文章详细解释了两者的区别和使用场景。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
author-avatar
致力于流浪动物救助量
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有