MYSQL死锁真实案例

​最近例行巡检时候发现一个死锁,阿里云RDS FOR MYSQL 8.0.X!
虽然阿里云的死锁页面看起来比较友好,不过跟社区版一样只是显示事务最后一条死锁SQL和相关的信息.一不小心对初级MYSQL DBA来说,深深地误导,浪费大量时间研究这两个SQL怎么发生了死锁!
阿里云RDS默认情况下审计没有开线程ID或者是事务ID.后期开启后才能根据SQL抓到线程ID,然后根据线程ID抓出相关的SQL.

业务结算:我们商户进行结算的时候,用户下单购买多个商品,每次给我们平台给商户结算是按用户订单来的,每个商品的价格要累加到订单上的.
为了快速响应,应用架构采用异步方式,下订单,付款,给商家结算都是异步模式,
每一步工作完成后会向LTS记录一笔,然后LTS定时启动该笔对应的JAVA接口去完成剩余工作.

有时候虽然前台用户下单少,LTS也会启动剩余工作时候造成短时间的并发量.
从某种程度来说LTS就是个高并发源!

事务 1(巳回滚)

事务 2

Session ID

37095516

37095518

Thread id

364126

366610

请求类型

updating

updating
事务ID64136665

164136664

涉及表

DB`.`trans_daily_collect DB.`trans_order`/* Partition`request date time 20240403`

db`,`trans_daily collect db`.`trans_order’/* Partitionreguest date time 20240403`

等待锁

index PRIMARY of table db`.`trans_daily_collect xid 164136665 lock mode X locks rec but not gap waiting

index iux trans no of table `db`,'trans_order” /* Partition `reguest_date_time 20248403`*/ trx id 164136664 1ock mode X 1ocks rec but not gap waiting
等待锁索引名

PRIMARY

iux trans_no
等待锁类型

X locks rec but not gap waiting

X locks rec but not gap waiting
持有锁

index iux trans no of table 'db'.'trans_order’ /* Partition`request date time 20248483`*/ trx id 164136665 lock mode X 1ocks rec but not gap

index PRIMARY of table "db'.'trans_daily_collect' trx id 164136664 lock mode X locks rec but not gap

持有锁索引名

iux_ trans_no

RIMARY
持有锁类型X locks rec but not gap

X locks rec but not gap

事务SQL

UPDATE SK TRANS DAILY COLLECT SET TOTAL AMONT = TOTAL AMONT + 5888800 WHERE id = 212 and SETTLE_NO is null AND STATUS = '180'

update trans_oder SET collect_code = '202404020100001' where status ='02'and trans_no ='2024840200300003215'AND request datetime < DATE ADD( 2024-84-02 16:13:42',interval 1 hour) AND reguestdate time > DATE ADD('2024-04-02 16:13:42',INTERVAL -1 hour)

上面涉及订单表和汇总表, 事务1更新汇总表,事务2更新订单表

事务1 持有唯一索引订单号,等待主键索引
事务2 持有主键索引,等待唯一索引IUX_TRANS_NO

粗绿一看 确实是互相持有对方的锁. 仔细一看不对啊,最后两个UPDATE语句更新的表只是不同的,而且都是单张表更新.

看唯一索引忽然明白,事务其它语句更新了对方的表,才会导致持有对方的锁.

MYSQL死锁信息锁类型:

lock_modesRRRC
X locks rec but not gap记录锁记录锁
gap间隙锁间隙锁
recNEXT-KEY

上面是MSYQL死锁信息时候显示锁类型,里面隐藏的指向.尤其是REC需要根据隔离级别来区别,表达意思词不达意! 也就是说死锁信息输出不够全面,又容易误导.要是增加当前事务的隔离级别,然后REC,GAP,NEXT-KEY 三种简单明了. 比让人猜字谜好多了!

通过SQL日志审计页面根据线程ID找到该事务的全部SQL分析.非云环境应该是开启了通用日志.

事务2

用到的索引

1

update trans_order

SET

  status = '02',

  pay_time =  '2024-04-02 16:14:44',

last_update_time = '2024-04-02 16:14:45.08',

where

  status in  ('01', '06', '10')

  and trans_no =  '2024040200300003216'

  AND request_date_time <  DATE_ADD('2024-04-02 16:13:43', interval 1 hour)

  AND  request_date_time > DATE_ADD('2024-04-02 16:13:43', INTERVAL -1 hour);

IUX_TRANS_NO

2

UPDATE TRANS_COLLECT 

SET TOTAL_AMONT = TOTAL_AMONT + 50000.00 

TRANS_COUNT = TRANS_COUNT + 1

WHERE id = 212

 and  SETTLE_NO is null        

AND STATUS = '100';

PRIMARY

事务1索引

1

update  trans_order

SET

  status = '02',

  pay_time = '2024-04-02  16:14:44',

  last_update_time = '2024-04-02 16:14:45.076',

where

  statusin ('01''06''10')

  and trans_no = '2024040200300003215'

  AND request_date_time < DATE_ADD('2024-04-02 16:13:42', interval 1 hour)

  AND request_date_time > DATE_ADD('2024-04-02 16:13:42', INTERVAL -1 hour);

 

iux_trans_no

2

UPDATE  TRANS_COLLECT

SET

   TOTAL_AMONT = TOTAL_AMONT + 25000.00,

   TRANS_COUNT = TRANS_COUNT + 1

WHERE

  id = 212

  and SETTLE_NO isnull

  ANDSTATUS = '100';

PRIMARY

3

UPDATE   USER_ACCOUNT

SET

   UNSETTLE_AMOUNT = UNSETTLE_AMOUNT + 24750.00

WHERE

   user_NO = '010003';

idx_user_no

4

update  trans_ordre

SET

   collect_code = '202404020100001'

where

  status = '02'

  and`trans_no` = '2024040200300003215'

  AND  request_date_time < DATE_ADD('2024-04-02 16:13:42', interval 1 hour)

  AND  request_date_time > DATE_ADD('2024-04-02 16:13:42', INTERVAL -1 hour);

iux_trans_no

把两个事务SQL根据时间并在一起对照查看

事务1

时间

持有锁

时间

事务2

持有锁

update  trans_order

SET

  status = '02',

   pay_time = '2024-04-02 16:14:44',

   last_update_time = '2024-04-02 16:14:45.076'

where

  statusin ('01', '06', '10')

  and trans_no = '2024040200300003215'

  AND  request_date_time < DATE_ADD('2024-04-02 16:13:42', interval 1 hour)

  AND  request_date_time > DATE_ADD('2024-04-02 16:13:42', INTERVAL -1 hour);

2024-04-02 17:14:45.078

UPDATE  TRANS_COLLECT

SET

   TOTAL_AMONT = TOTAL_AMONT + 25000.00,

   TRANS_COUNT = TRANS_COUNT + 1

WHERE

  id = 212

  and SETTLE_NO isnull

  ANDSTATUS = '100';

2024-04-02 17:14:45.082

index PRIMARY  trx id 164136664  lock_mode X locks rec but not gap

2024-04-02 17:14:45.082

update trans_order

SET

  status = '02',

  pay_time =  '2024-04-02 16:14:44',

   last_update_time = '2024-04-02 16:14:45.08'

where

  status in  ('01', '06', '10')

  and trans_no =  '2024040200300003216'

  AND  request_date_time < DATE_ADD('2024-04-02 16:13:43', interval 1 hour)

  AND  request_date_time > DATE_ADD('2024-04-02 16:13:43', INTERVAL -1 hour);

index  iux_trans_no X locks rec but not gap

UPDATE  user_ACCOUNT

SET

   UNSETTLE_AMOUNT = UNSETTLE_AMOUNT + 24750.00

WHERE

   user_NO = '010003';

2024-04-02 17:14:45.083

2024-04-02 17:14:45.083

UPDATE TRANS_COLLECT SET TOTAL_AMONT =  TOTAL_AMONT + 50000.00,TRANS_COUNT = TRANS_COUNT + 1

 WHERE

 id =  212

and SETTLE_NO is null 

 AND STATUS = '100';

X locks rec but not gap waiting

index PRIMARY of table  `trans_collect`

update  
trans_order

SET

   collect_code = '202404020100001'

where

  status = '02'

  and`trans_no` = '2024040200300003215'

  AND  request_date_time < DATE_ADD('2024-04-02 16:13:42', interval 1 hour)

  AND  request_date_time > DATE_ADD('2024-04-02 16:13:42', INTERVAL -1 hour);

2024-04-02 17:14:45.084

X locks rec but not gap waiting
 
iux_trans_no

根据对比 事务1执行了4个更新语句, 事务2执行了2个更新语句.就产生了死锁.其实事务1和事务2执行的语句都是一样的,也就是4个更新语句,只是事务2执行到第2条更新语句时候就与事务1发生了死锁.

这4条UPDATE语句功能是这样的
1 更新订单为成功的,根据状态和订单号以及最近1小时间隔
2 更新汇总表,根据ID=212 进行金额累加,以及计数器++
3 更新商户账户待结算金额
4 更新订单表的,把汇总号更新到具体订单对应的位置

事务1

1 更新订单为成功的,根据状态和订单号以及最近1小时间隔 
   持有订单表IUX_TRANS_NO的锁
2 更新汇总表,根据ID=212 进行金额累加,以及计数器++
   持有汇总表的主键索引锁   持有中
3 更新商户账户待结算金额
  持有对应的锁
4 更新订单表的,把汇总号更新到具体订单对应的位置
    要持有订单表IUX_TRANS_NO的锁  等待状态

事务2

1 更新订单为成功的,根据状态和订单号以及最近1小时间隔 
   持有订单表IUX_TRANS_NO的锁  持有中
2 更新汇总表,根据ID=212 进行金额累加,以及计数器++
   持有汇总表的主键索引锁  等待状态
3 更新商户账户待结算金额   
   未执行
4 更新订单表的,把汇总号更新到具体订单对应的位置
    未执行

这些我们很清楚了,
事务1 
 第2条SQL 持有主键索引 汇总表的. 
 第4条SQL等待IUX_TRANS_NO 索引锁,

事务2   
第1条持有 IUX_TRASN_NO 锁
第2条 等待汇总表 主键锁

再看一下具体SQL 得知
事务1 第2条 主键索引记录锁 ID=212 ; 第4条 TRASN_NO="...3215"
事务2 第1条 唯一索引 锁定 TRASN_NO="....3216" 第2条 想锁定 ID=212

似乎接近了真相,业务上一个订单包含多个商品, 订单包就是212 ,商品就是交易号. 很多情况下业务叫法和表命名是无法对齐的. 实际上订单表应该叫商品订单表... 不说了! 这里ID=212就是总订单, TRASN就是具体商品交易订单. LTS 把ID=212的总订单里面的商品交易分拆到不同的线程中并发执行.导致死锁的发生.

下面是唯一索引结构,由时间和交易号组成,只所以要做成唯一,原本是非分区表,是有唯一索引的,不过是TRASN_NO字段.现在要做成分区表,原本另外个项目跟开发沟通,分区无法保证全局唯一性,要保证的话只能开发应用端确保这个逻辑.后来这个新的开发人员水平不高,新业务强烈建议数据库去保证.

  UNIQUE KEY `iux_trans_no` (`trans_no`,`request_date_time`)   USING BTREE,

这里我们回顾下 事务1的第1条SQL也是要持有IUX_TRASNO,
按理说 事务2的第1条SQL应该等待IUX_TRASNO,然后它获得了,奇怪不?
应该说 事务1要么释放了IUX_TRASNO的锁,否则事务2是无法加到锁的.
不过按理论来说 事务必须等待结束后才会去释放锁的.
违背了理论,实际上又释放了. 这很矛盾啊?
要么是MYSQL的BUG ,有这可能,去年一个网络直播,辩论MYSQL和PG谁是第一开源数据库? 姜老师和冯若航 怼骂! 其中MYSQL事务的部分提交.

也许是我认知不到位,听说MYSQL是两层架构,SERVER层和引擎层,引擎层找数据,加锁,返回服务层,服务层不要,引擎就释放该记录锁.

有这条规矩,大概率是这条了.事务1 对TRASN_NO="....3215" 加锁,同时对TRASN_NO="...3216"加锁.然后返回给服务层,服务层判断不需要,就要求引擎去释放"...3216" 所以事务2才能加上TRASN_NO='3216'锁
事务1 第4条 更新3215,顺便要加一下3216记录锁来判断下. 结果被事务2阻塞了. 为此就发生了死锁.

有什么优化办法?
1 我们调整SQL执行顺序,把两个更新唯一索引放在1和2的位置,把主键索引放在第3个位置.这样加锁时间非常短, 不过只是暂时型解决,只要两个事务的时间间隔越短,还是会爆发死锁.

2 原来业务单表唯一索引,次新业务分区表普通索引,新业务分区表唯一索引为什么原理的业务不会,次新的业务不会,反而新业务就会?
问题就出在组合字段的唯一索引. 实时上把分区表唯一索引 改成普通索引就没有了死锁发生了.

奇怪不奇怪?
MYSQL 8.0.X RC隔离级别下 二级组合字段唯一索引会

按照理论来说只加X锁,顶多加个下一个不符记录的记录锁. 
新旧业务索引都会加下一个不符合记录的X锁,偏偏组合唯一索引就会.

所以我们要请出丁奇,丁老师出马..........................................

我也从极客时间购买并学习过丁奇老师的<MYSQL45讲>,说真的挺不错,从原理上了解MYSQL的通俗易读的课程. 不过很多生产实践遇到的问题,并不能全部从该课程获得解释!
正好丁奇老师开课培训主讲生产实践中遇到的问题,并且大家可以选择自己感兴趣的单元学习.

《MySQL进阶单元课》每单元50¥,按需按单元选。

报名方式:+私聊 丁奇

1. 近期直播课:

11月2日(本周六) 19:00

第14单元《读写分离:扩展性和一致性》

2. 前14单元大纲:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/58910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring】Spring Boot 日志(8)

本系列共涉及4个框架&#xff1a;Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点&#xff0c;根据序号学习即可。 目录 本系列共涉及4个框架&#xff1a;Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点&#xff0c;根据序号学习即可。 …

饿了么数据库表设计

有商家表、商品表、商品规格表、购物车表&#xff0c;不难分析出表是不够全面的。 (1)首先分析需要补充的表 1.对于购物车而言肯定有对应的用户&#xff0c;因此要添加一个用户表。 2.商品规格是冷&#xff0c;热&#xff0c;半分糖、全糖&#xff0c;对于冷热和半分糖是可以分…

C++模拟实现list

C教学总目录 C模拟实现list 1、成员变量2、迭代器3、insert函数4、erase函数5、pop_back、push_front、pop_front函数6、size和clear函数7、析构函数8、拷贝构造函数9、赋值运算符重载完整代码&#xff08;包含测试代码&#xff09; 1、成员变量 先来看看SGI版本STL中list的实…

【STM32】SD卡

(一)常用卡的认识 在学习这个内容之前&#xff0c;作为生活小白的我对于SD卡、TF卡、SIM卡毫无了解&#xff0c;晕头转向。 SD卡&#xff1a;Secure Digital Card的英文缩写&#xff0c;直译就是“安全数字卡”。一般用于大一些的电子设备比如&#xff1a;电脑、数码相机、AV…

品牌怎么找到用户发的优质内容,进行加热、复制?

在&#xff0c;相对传统媒体来说&#xff0c;社交媒体营销具有更高的成本效益。品牌可以通过相对较低的成本达到大量潜在客户&#xff0c;尤其是通过口碑营销和内容分享&#xff0c;可以实现倍增的传播效果。在社媒营销的过程中&#xff0c;去找到与品牌有关的优质、正向内容&a…

物联网设备如何助力实现高效远程老人监护

在发达国家&#xff0c;老龄化进程加速&#xff0c;老年人常需医疗、行动辅助、安全保障及个人卫生护理&#xff0c;费用高昂。传统老人监护依赖护士或助理现场照料&#xff0c;而物联网远程监控方案能有效改进此模式。它通过运用传感器等技术&#xff0c;实现全天候低成本实时…

如何使用和打开jconsole

配置: spring.jmx.enabledtrue spring.jmx.default-domainmybatiesdemo management.endpoints.jmx.exposure.include* 启动参数: -Dcom.sun.management.jmxremote.port9000 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse 启动项…

残差块(Residual Block)

1. **残差块的定义与作用**&#xff1a; 残差块通过引入跳跃连接&#xff08;skip-connection&#xff09;或称为快捷连接&#xff08;shortcut connection&#xff09;&#xff0c;允许网络学习输入与输出之间的残差映射&#xff0c;即学习函数&#xff0c;其中 是期望的底层映…

Sigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导

SSigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导 Sigrity Power SI的VR noise Metrics check模式本质上是用来评估和观测器件的电源网络的耦合对于信号的影响,输出S参数以及列出具体的贡献值。 以下图为例

讲个故事-HTTP/HTTPS 协议访问逻辑

一、HTTP/HTTPS 协议基本概念 1、协议 HTTP与 HTTPS 协议都是客户端 浏览器和服务器间的一种约定,约定如何将服务器中的信息下载到本地 ,并通过浏览器显示出来。 不同的是, HTTP 协议是一种明文传输协议,其对传输的数据不提供任何加密措施。而HTTPS 协议则是通过 SSL/TL…

生信入门第八课:RNA-seq比对、定量和差异分析

生信入门合集&#xff1a; 生信入门第一课&#xff1a;VirtualBox安装Ubuntu虚拟机 生信入门第二课&#xff1a;RNA-seq生信分析环境搭建-conda及常用软件安装 生信入门第三课&#xff1a;Linux操作系统简介及生信分析常用30个命令 生信入门第四课&#xff1a;生物信息学常…

打响反对人工智能的第一枪

序言&#xff1a;人工智能的讨论不能只有一片叫好的声音&#xff0c;一味的追捧反而可能隐藏巨大的危机。因此&#xff0c;必须有反对的声音&#xff0c;且越强烈越能激发深入思考。本篇文章的作者就以犀利的视角&#xff0c;漂亮地打响了反对人工智能应用的第一枪。 我以前一…

HR为什么都开始使用智能招聘系统?

数字化时代到来&#xff0c;人力资源管理领域正经历着前所未有的变革。 众所周知&#xff0c;今年的招聘市场&#xff0c;HR们正面临着越来越繁重的招聘任务。传统的招聘方式&#xff0c;如手动筛选简历、安排面试等&#xff0c;耗时费力极易出错。而且&#xff0c;传统的招聘…

机器人大模型GR2——在大规模视频数据集上预训练且机器人数据上微调,随后预测动作轨迹和视频(含清华RDT详解)

前言 上个月的24年10.9日&#xff0c;我在朋友圈看到字节发了个机器人大模型GR2&#xff0c;立马去看了下其论文(当然了&#xff0c;本质是个技术报告) 这次也是我头一次看paper&#xff0c;不看正文&#xff0c;而是直奔其References&#xff0c;​看有没有我预想中的文献&a…

【VSCode】配置

安装插件 C vscode-icons gdb调试 https://www.bilibili.com/video/BV15U4y1x7b2/?spm_id_from333.999.0.0&vd_sourcedf0ce73d9b9b61e6d4771898f1441f7f https://www.bilibili.com/video/BV1pU4y1W74Z?spm_id_from333.788.recommend_more_video.-1&vd_sourcedf0…

客服宝快捷回复软件:客服工作的得力助手

在从事客服工作的这段漫长时间里&#xff0c;响应率和满意度一直是我最为头疼的绩效指标。这两个指标就如同两座大山&#xff0c;压得我时常喘不过气来。 然而&#xff0c;幸运的是&#xff0c;最近我安装了客服宝这个快捷回复软件&#xff0c;这一举措如同为我打开了一扇新的…

Hive的数据存储格式

目录 一、前言 二、存储格式 2.1、文本格式&#xff08;TextFile&#xff09; 2.1.1、定义与特点 2.1.2、存储与压缩 2. 1.3、使用场景 2.2、行列式文件&#xff08;ORCFile&#xff09; 2.2.1、ORC的结构 2.2.2、ORC的数据类型 2.2.3、ORC的压缩格式 2.2.3、ORC存储…

蓝牙BLE开发——红米手机无法搜索蓝牙设备?

解决 红米手机&#xff0c;无法搜索附近蓝牙设备 具体型号当时忘记查看了&#xff0c;如果你遇到有以下选项&#xff0c;记得打开~ 设置权限

华为自研仓颉编程语言官网上线 首个公测版本开放下载

仓颉编程语言官网正式公开上线&#xff0c;同时首个公测版本开放下载。本次仓颉编程语言官网上线了首页、在线体验、文档、学习、下载、动态以及三方库共六个模块&#xff0c;可供开发和学习和体验。 据悉&#xff0c;仓颉编程语言是在今年6月的华为开发者大会上正式公布&…

【AI换脸整合包及教程】AI换脸技术新贵:Rope换脸工具全面解析

随着人工智能技术的快速发展&#xff0c;AI换脸技术逐渐走入大众视野&#xff0c;成为一种既有趣又实用的技术。从早期的DeepFace到后来的Faceswap&#xff0c;再到如今的Rope&#xff0c;每一次技术的革新都带来了更高效、更自然的换脸体验。Rope作为当前市场上最炙手可热的AI…