高并发mysql

并发insert on duplicate key update

2022-04-19  本文已影响0人  小波同学

前言

数据库死锁问题,是一个老生常谈且很常见的问题,网上也有非常多对于各类死锁场景的解析和复现,但凡和死锁有关,无外乎不涉及数据库隔离等级、索引、以及innodb锁等相关原因。

一、问题的背景:

我们的数据库中的发生死锁的表是具有”多列组合构建的唯一索引“(不包含自增的主键),且数据库的隔离等级为Read Committed,另外对于这个表来说是写入远大于读取的,由于业务的原因,经常会出现同一数据反复插入(同一数据指唯一索引值相同的数据,但其他非索引字段可能不同),所以为了简化代码,我们使用insert on duplicate key update来解决这种问题,当mysql检测到唯一键冲突时,仅更新特定(非索引)字段。但是问题就出现在大规模多worker并发插入的时候,会经常出现"Deadlock found when trying to get lock"。

即任务提交到线程池,多线程并发执行insert on duplicate key update,并且为长事务时,抛出下面异常:

java.lang.RuntimeException: org.springframework.dao.CannotAcquireLockException: 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may involve com.kfang.service.price.dict.dao.automapper.DictGardenAppraisalMapper.insertDictGardenAppraisal-Inline
### The error occurred while setting parameters
### SQL: insert into kfang_price.t_dict_garden_appraisal (             ) values(             ) 
        ON DUPLICATE KEY UPDATE     
        FTRUST_NUM = values(FTRUST_NUM),  
        FTRUST_AVG_PRICE = values(FTRUST_AVG_PRICE),             
        FTRUST_TOTAL_PRICE = values(FTRUST_TOTAL_PRICE),             
        FTRUST_TOTAL_AREA = values(FTRUST_TOTAL_AREA),             
        FTRADE_NUM = values(FTRADE_NUM),             
        FTRADE_AVG_PRICE = values(FTRADE_AVG_PRICE),             
        FTRADE_TOTAL_PRICE = values(FTRADE_TOTAL_PRICE),             
        FTRADE_TOTAL_AREA = values(FTRADE_TOTAL_AREA),             
        FOUTER_REFER_PRICE = values(FOUTER_REFER_PRICE)
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

二、图文说明:

一般定位死锁原因第一步就是执行”show engine innodb status“, 查看innodb Standard monitor输出结果,这里面会有数据库最后一次的死锁记录。会记录出现死锁的两个事务,它们分别在等待什么锁,并且手里持有什么锁。mysql在检测到发生死锁的时候,会随机回滚其中的一个事务,从而解开死锁。下面的截图是发生死锁的时候innodb status截图(和业务相关的数据已脱敏,这里均用column_n和value_n表示)

Transaction1:

Transaction2:

现象阐述:

从上方两个截图可以发现,死锁均发生在insert on duplicate key update语句执行的时候,并且每个insert语句均为批量插入多个数据。对于事务一,可以看到事务一在等待某个锁的获取,且这个锁是"lock_mode X locks gap before rec insert intention waiting",直接翻译过来就是插入意向锁在等待排他gap锁的释放,也就是只有排他gap锁释放后插入意向锁才能获取到(关于这些锁的含义见下一节)。对于事务二,同样可以看到相同的一句话。并且两个事务的锁冲突均发生在”唯一索引“上。再进一步观察可以看到,事务二所持有("Holds the Locks"下方展示的索引值)的排它锁所在的索引(锁均是加在索引上或者索引区间上的),与事务一等待获取锁的索引是一样的。进一步展示了的确,在同一个索引上出现了一个等待获取,一个已经获取的冲突现象。

三、相关概念:

在分析问题前,有必要概述一下(详细了解可以见附录),这里面涉及到的锁相关知识。具体可以详见Mysql手册

innodb级锁按照隔离能力,主要分为共享锁(S锁)和排他锁(X锁)。事务T1的某行上持有S锁,则另一事务T2可以在此行获取S锁,但是不能获取此行的X锁,而如果T1在某行上持有X锁,则另一事务T2,对此行既无法获取S锁,也无法获取X锁。(除了S和X锁外,还有表级锁,分别是意向共享IS锁和意向排他IX锁,这里不做深入)。

按照锁的种类:主要有四种。

mysq官方手册中,对Next-key lock在innodb monitor中的打印如下图所示:

可以发现和我们在”问题的现象“一节贴的日志中事务二”Hold the Locks“处非常相似。所以可以怀疑当时死锁发生的时候,出现了排他的next-key lock。

根据上面,对锁的种类说明,其实我们已经能猜到,大概是什么锁导致了死锁的出现。(这里我要再明确一点,我们的数据库隔离等级为Read Committed级别。)本质上就是两个事务同时获取到了不同间隙的X Next-key锁,而这个两个事务又同时想要向对方已经获取了next-key锁的间隙内插入新的数据,于是乎死锁出现了。下面我们来完全复现一下。

四、问题的复现:

数据库准备:数据库中能够包含一个unique key: code

CREATE TABLE `test2` (
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `code` int(11) NOT NULL,
  `other` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB 

初始数据:insert into test2 (code, other) values(1,1),(3,3),(5,5)

复现场景:原始的code字段为1,3, 5,现在要在中间插入code为2,3,4,5的row, 如果碰到唯一键约束则更新other字段。

死锁出现后,我们查看innodb status中的死锁记录,如下:


可以发现,复现出来的结果,和上文中的案例几乎完全一致。下面我们对此结果进行分析。

五、问题的分析:

至此:死锁的现象可以顺利的解释通。(当然,这里还有一个疑惑不是很明白,当出现唯一冲突的时候为什么要加Next-Key Lock。)

六、问题的拓展:

如果将insert on duplicate key update换成insert ignore语句,是否可以避免死锁的发生呢?答案是:否定的。其实原理都是一样的。如果我们将上述复现中的insert on duplicate key update换成insert ignore,同样会在T4时刻出现死锁。

同样,update和insert on duplicate key update组合也可以构造出死锁的出现。数据库中表结构不变,数据初始化为(1,1,1),(3,3,3),(5,5,5) 分别对应id, code,other, id是pk.

七、总结:

说了这么多,死锁的原因找到了,解决的办法其实比较简单。

参考:
https://developer.aliyun.com/article/727076

https://blog.csdn.net/m0_43452671/article/details/111590466

上一篇下一篇

猜你喜欢

热点阅读