高并发状态下Replace Into造成的死锁问题解决
作者:PigeonEssence 发布时间:2024-01-17 10:17:37
1.问题出现:
在测试阶段,大数据并发的情况下,发现sql语句造成表的死锁,过一段时间,死锁消失。于是进行排查
报错如下:
对应的sql语句如下:
@Insert("replace into ${tableName}( windcode,date, \n" +
" code, high, open, low, \n" +
" `close`, volume, turnover,gtm_modify) "
+ "values (#{obj.windcode},#{obj.date},#{obj.code},#{obj.high},#{obj.open},#{obj.low},#{obj.close},#{obj.volume},#{obj.turnover},#{obj.updateTime})" )
int insertOne(@Param("obj") KDTO obj, @Param("tableName") String tableName);
在排除了数据问题和线程重复调用以后,我们关注了一下sql语句本身。 看了网上很多经验分享,觉得问题可能出现在 Replace Into 语句上。
2.分析解决
首先我们分析一下为什么并发replace into导致MySQL死锁
Replace into 一般作用是,当存在冲突时,会把旧记录替换成新的记录。也就是说这条语句执行,分为了两个大步:判断和执行
1.判断:
首先判断我们需要操作的记录是否存在(根据主键或者唯一索引判断)
2.操作:
针对不存在的记录,语句会执行insert,插入操作。
针对已经存在的记录,语句可以拆分为delete+insert操作
测试:
建立表
插入数据:
我们使用replace into语句去执行一个已经存在的数据:
可以清楚的发现,影响的行数是两行
第一行的数据被修改了
我们使用replace into语句去执行一个不存在的数据:
可以清楚的发现,影响的行数是一行
执行了插入操作:
逻辑非常的清晰,但是这种单条sql语句在什么情况下会出现死锁呢?我们就要去考虑这个加锁的时机。
正常的插入逻辑是:
首先插入聚集索引记录,在上例中id列为自增列。
随后插入二级索引num,由于其是唯一索引,在检查duplicate key时,为其加上类型为LOCK_X的记录锁。
发现错误:
由于检测到duplicate key,因此第一步插入的聚集索引记录需要被回滚掉(row_undo_ins)。
从InnoDB层失败返回到Server层后,收到duplicate key错误,首先检索唯一键冲突的索引,并对冲突的索引记录(及聚集索引记录)加锁。
转换模式:
如果发生uk冲突的索引是最后一个唯一索引、没有外键引用、且不存在delete trigger时,使用UPDATE ROW的方式来解决冲突;
否则,使用DELETE ROW + INSERT ROW的方式解决冲突。
更新记录:
对于聚集索引,由于PK列发生变化,采用delete + insert 聚集索引记录的方式更新。
对于二级uk索引,同样采用标记删除 + 插入的方式。
所以死锁的问题多半就会出现在X记录锁上面。
死锁分析:
所以再多线程高并发的环境状态下,存在两个事务同时去获取一个记录的修改的情况:
事务1拿到X记录锁,
事务2检测到冲突,获取X|NK锁,被事务1阻塞
事务1检测到冲突,申请获取S|NK,被事务2阻塞
事务1 | 事务2 |
---|---|
LOCK_X LOCK_NOT_GAP | - |
- | LOCK_X-LOCK_NEXT_KEY 阻塞 |
LOCK_S-LOCK_NEXT_KEY | 死锁回滚 |
所以在等待执行期间sql会有死锁报错,高并发环境下的死锁也就出现了,再事务执行完成回滚操作以后,死锁回滚,也就解释了死锁消失的问题。
3.解决方案:
经过多方讨论,最终决定使用 insetr + ON DUPLICATE KEY UPDATE语句替换高并发环境下的Replace Into语句解决死锁问题。
ON DUPLICATE KEY UPDATE语句的作用是:
若该数据的主键值/ UNIQUE KEY 已经在表中存在,则执行更新操作, 即UPDATE 后面的操作。
否则插入一条新的记录。
实现了Replace Into有相同的查重替换功能,而避免了高并发的死锁问题。
但是UPDATE操作性能相比DELETE操作会有一定的性能上的影响,需要后续测试跟进。
来源:https://blog.csdn.net/m0_56289903/article/details/121007012
猜你喜欢
- 析构函数当某个对象成为垃圾或者当对象被显式销毁时执行。PHP5中提供的析构函数是__destruct,其与构造方法__construct相对
- 变量输入就是用代码获取用户通过键盘输入的信息。python中可以使用input()函数实现输入变量, input() 函数接受一个标准输入数
- 目的两年前曾为了租房做过一个找房机器人 「爬取豆瓣租房并定时推送到微信」,维护一段时间后就荒废了。当时因为代码比较简单一直没开源,现在想想说
- 1、如何认识可视化?需要指出的是,虽然不同绘图工具包的功能、效果会有差异,但在常用功能上相差并不是很大。与选择哪种绘图工具包相比,更重要的是
- 以下插件是我在项目中经常使用的jQuery插件,不见得是最好的,但是我目前接触到的jQuery插件中最适合我的。01. jQuery.Fle
- 递归和尾递归简单的说,递归就是函数自己调用自己,它做为一种算法在程序设计语言中广泛应用。其核心思想是把一个大型复杂的问题层层转化为一个与原问
- 见下,把数字转成条形图、条形码的一个程序:<%Sub ShowChart(ByRef aValues,
- 问题联邦学习原始论文中给出的FedAvg的算法框架为:参数介绍: K 表示客户端的个数, B表示每一次本地更新时的数据量, E 表示本地更新
- 如果只是想实现将jenkins的构建结果发送到企业微信进行通知,最简便的方式是安装Qy Wechat Notification Plugin
- 前言大家应该都知道在很多时候我们不得不和时间打交道,但在Python标准库中处理时间的模块其实设计的不是很友好,为什么我会这么说?因为我相信
- 使用golang并发求和,作为对golang并发的一个练习.为了验证结果的正确性,要给出最传统的版本:func sum1(data []in
- 本文通过一个csv实例文件来展示如何删除Pandas.DataFrame的行和列数据文件名为:example.csv内容为:datespri
- 这篇文章主要介绍了windows环境中利用celery实现简单任务队列过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定
- 一 使用SELECT子句进行多表查询SELECT 字段名 FROM 表1,表2 …&nbs
- josn基本操作1.导入import json2.字典转json:json.dumps(dict,ensure_ascii=False),加
- 本文实例讲述了python通过定义一个类实例作为ftp回调方法。分享给大家供大家参考。具体实现方法如下:class Writer: &nbs
- 最近在用Pycharm学习Python的时候,总有两个地方感觉不是很舒服,比如调用方法的时候区分大小写(thread就不会出现Thread,
- 【尝尝管理员的滋味】- 淡然看,其实一切都很简单IE的滤镜泄露是最大的危害,没有找到方法解决,所以禁用了滤镜。<!DOCTYPE ht
- 当使用vue做登录的时候,我们会把拿到的部分用户信息存在vuex+cookie中,我们知道,vuex的数据是会随着浏览器刷新而丢失的,此时我
- 最近在倒腾一个txt文件,因为文件太大,所以给切割成了好几个小的文件,只有第一个文件有标题,从第二个开始就没有标题了。我的需求是取出指定的列