MySQL 死锁案例解析一则

原文链接:https://www.modb.pro/db/448666

一、问题背景
某业务模块反馈数据库最近出现过几次死锁告警的情况,本文总结了这次死锁排查的全过程,并分析了导致死锁的原因及解决方案。
希望给大家提供一个死锁的排查及解决思路。基础环境:

  • 主机类型:x3850 X6
  • 操作系统:DB:CentOS Linux release 7.4.1708、APP:CentOS Linux release 7.2.1511 (Core)
  • 存储:IBM存储,2TB,MULTIPATH
  • 内存:64 G
  • CPU型号:E7-4830 v3 @ 2.10GHz ( 4 U * 12 core)
  • CPU核数:32CORE
  • 数据库环境:5.7.27
  • 事务隔离级别:READ-COMMITED
问题现象:MySQL 死锁告警
告警日志:
{"errorCode":"SYSTEM_ERROR","errorMsg":"nested exception is org. apache. ibatis.exceptions.PersistenceException:  
Error updating database. Cause: ERR-CODE: [TDDL-4614 [ERR_EXECUTE_ON_MYSQL]
Deadlock found when trying to get lock;
The error occurred while setting parameters\n### SQL:
update A xxx   
二、分析说明
  • 通过分析日志定位、分析死锁原因;
  • 追溯历史数据,分析关键指标的历史波动,这些关键指标可以用来做为数据库健康度参考指标。
  • 用实际数据来验证推断,排除掉其它干扰因素,定位数据库问题的根本原因,帮助快速修复。
三、MySQL加锁机制
在深入探究问题之前,我们先了解一下 MySQL 的加锁机制。
首先要明确的一点是 MySQL 加锁实际上是给索引加锁,而非给数据加锁。我们先看下MySQL 索引的结构。
MySQL 索引分为主键索引(或聚簇索引)和二级索引(或非主键索引、非聚簇索引、辅助索引,包括各种主键索引外的其他所有索引)。不同存储引擎对于数据的组织方式略有不同。
对InnoDB而言,主键索引和数据是存放在一起的,构成一颗B+树(称为索引组织表),主键位于非叶子节点,数据存放于叶子节点。示意图如下:

而MyISAM是堆组织表,主键索引和数据分开存放,叶子节点保存的只是数据的物理地址,示意图如下:

二级索引的组织方式对于InnoDB和MyISAM是一样的,保存了二级索引和主键索引的对应关系,二级索引列位于非叶子节点,主键值位于叶子节点,示意图如下:

那么在MySQL 的这种索引结构下,我们怎么找到需要的数据呢?
以select * from t where name='aaa'为例,MySQL Server对sql进行解析后发现name字段有索引可用,于是先在二级索引(图2-2)上根据name='aaa'找到主键id=17,然后根据主键17到主键索引上(图2-1)上找到需要的记录。
了解 MySQL 利用索引对数据进行组织和检索的原理后,接下来看下MySQL 如何给索引枷锁。
需要了解的是索引如何加锁和索引类型(主键、唯一、非唯一、没有索引)以及隔离级别(RC、RR等)有关。本例中限定隔离级别为RC,RR情况下和RC加锁基本一致,不同的是RC为了防止幻读会额外加上间隙锁。
2.1  根据主键进行更新
update t set name='xxx' where id=29;只需要将主键上id=29的记录加上X锁即可(X锁称为互斥锁,加锁后本事务可以读和写,其他事务读和写会被阻塞)。如下:

2.2  根据唯一索引进行更新
update t set name='xxx' where name='ddd';这里假设name是唯一的。InnoDB现在name索引上找到name='ddd'的索引项(id=29)并加上加上X锁,然后根据id=29再到主键索引上找到对应的叶子节点并加上X锁。
一共两把锁,一把加在唯一索引上,一把加在主键索引上。这里需要说明的是加锁是一步步加的,不会同时给唯一索引和主键索引加锁。这种分步加锁的机制实际上也是导致死锁的诱因之一。示意如下:

2.3 根据非唯一索引进行更新
update t set name='xxx' where name='ddd';这里假设name不唯一,即根据name可以查到多条记录(id不同)。和上面唯一索引加锁类似,不同的是会给所有符合条件的索引项加锁。示意如下:

这里一共四把锁,加锁步骤如下:

1、在非唯一索引(name)上找到(ddd,29)的索引项,加上X锁;

2、根据(ddd,29)找到主键索引的(29,ddd)记录,加X锁;

3、在非唯一索引(name)上找到(ddd,37)的索引项,加上X锁;

4、根据(ddd,29)找到主键索引的(37,ddd)记录,加X锁;


从上面步骤可以看出,InnoDB对于每个符合条件的记录是分步加锁的,即先加二级索引再加主键索引;其次是按记录逐条加锁的,即加完一条记录后,再加另外一条记录,直到所有符合条件的记录都加完锁。那么锁什么时候释放呢?答案是事务结束时会释放所有的锁。
小结:MySQL 加锁和索引类型有关,加锁是按记录逐条加,另外加锁也和隔离级别有关。 四、疑问点排查及分析思路 1、发生死锁的表结构及索引情况(隐去了部分无关字段和索引):
如下:
CREATE TABLE `A` (  
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `create_date` datetime NOT NULL , `modified_date` datetime NOT NULL ,  `pay_name` varchar(256) NOT NULL ,  `pay_version` varchar(256) DEFAULT NULL , 
`identifier` varchar(256) NOT NULL , `seller_id` varchar(64) NOT NULL , 
`state` varchar(64) DEFAULT NULL , `fund_transfer_ order_ no` varchar(256) DEFAULT NULL,  PRIMARY KEY (`id`),UNIQUE KEY `uk_scene_identifier`(KEY `idx_seller` (`seller_id`), 
KEY `idx_seller_transNo` (`seller_id`,`fund_transfer_order_no`(20)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ; 
该表共有三个索引,1个主键索引,2个普通索引。 2、分析死锁日志
发生死锁,第一时间查看死锁日志,内容如下:
Transactions deadlock detected, dumping detailed information. 
2021-05-19T21:44:23.516263+08:00 5877341 [Note] InnoDB:  
*** (1) TRANSACTION: 
TRANSACTION 173268495, ACTIVE 0 sec fetching rows 
mysql tables in use 1, locked 1
LOCK WAIT 304 lock struct(s), heap size 41168, 6 row lock(s), undo log entries 1
MySQL thread id 5877358, OS thread handle 47356539049728, query id 557970181 11.183.244.150 fin_instant_app updating 
update 死锁语句
2021-05-19T21:44:23.516321+08:00 5877341 [Note] InnoDB:  
*** (1) HOLDS THE LOCK(S): 
RECORD LOCKS space id 173 page no 13726 n bits 248 index idx_seller_transNo of table `xxx`.`fund_transfer_stream` trx id 173268495 lock_mode X locks rec but not gap 
Record lock, heap no 168 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
2021-05-19T21:44:23.516565+08:00 5877341 [Note] InnoDB:  
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 173 page no 12416 n bits 128 index PRIMARY of table `xxx`.`fund_transfer_stream` trx id 173268495 lock_mode X locks rec but not gap waiting 
Record lock, heap no 56 PHYSICAL RECORD: n_fields 17; compact format; info bits 0
2021-05-19T21:44:23.517793+08:00 5877341 [Note] InnoDB:  
*** (2) TRANSACTION: 
TRANSACTION 173268500, ACTIVE 0 sec fetching rows, thread declared inside InnoDB 81
mysql tables in use 1, locked 1
302 lock struct(s), heap size 41168, 2 row lock(s), undo log entries 1
MySQL thread id 5877341, OS thread handle 47362313119488, query id 557970189 11.131.81.107 fin_instant_app updating 
update 死锁语句
分析下死锁日志,可以得到以下信息:
  1. 导致死锁的两条SQL语句。
  2. 事务1,持有索引idx_seller_transNo的锁,在等待获取PRIMARY的锁。
  3. 事务2,持有PRIMARY的锁,在等待获取idx_seller_transNo的锁。
  4. 因事务1和事务2之间发生循环等待,故发生死锁。
  5. 事务1和事务2当前持有的锁均为:lock_mode X locks rec but not gap
两个事务对记录加的都是X 锁,No Gap锁,即对当行记录加锁(Record Lock),并未加间隙锁。 3、常见锁类型
X锁:排他锁、又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
与之对应的是S锁:共享锁,又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
根据目前掌握的信息,可以做一些简单的推断。
首先,此次死锁一定是和Gap锁以及Next-Key Lock没有关系的。因为数据库隔离级别是RC(READ-COMMITED)的,这种隔离级别是不会添加Gap锁的。前面的死锁日志也提到这一点。
然后,就要翻代码了,看看代码中事务到底是怎么做的。核心代码及SQL如下:
@Transactional(rollbackFor = Exception.class)
public int doProcessing(String sellerId, Long id, String fundTransferOrderNo) { fundTreansferStreamDAO.updateFundStreamId(sellerId, id, fundTransferOrderNo); 
return fundTreansferStreamDAO.updateStatus(sellerId, fundTransferOrderNo,"PROCESSING"); 
}
该代码的目的是先后修改同一条记录的两个不同字段,同一个事务中执行了两条Update语句,再分别查看下两条SQL的执行计划:分别用到了PRIMARY索引和idx_seller_transNo索引。
有了以上这些已知信息,就可以开始排查死锁原因及其背后的原理了。
通过分析死锁日志,再结合代码以及建表语句,发现主要问题出在idx_seller_transNo索引上面:
KEY `idx_seller_transNo` (`seller_id`,`fund_transfer_order_no`(20))
索引创建语句中,使用了前缀索引,为了节约索引空间,提高索引效率,只选择了fund_transfer_order_no字段的前20位作为索引值。
因为fund_transfer_order_no只是普通索引,而非唯一性索引。又因为在一种特殊情况下,会有同一个用户的两个fund_transfer_order_no的前20位相同,这就导致两条不同的记录的索引值一样(因为seller_id 和fund_transfer_order_no(20)都相同 )。 那么为什么fund_transfer_order_no的前20位相同会导致死锁呢?
我们知道,在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
  • 主键索引的叶子节点存的是整行数据。在InnoDB中,主键索引也被称为聚簇索引(clustered index)。
  • 非主键索引的叶子节点的内容是主键的值,在InnoDB中,非主键索引也被称为非聚簇索引(secondary index)。

死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
事务在以非主键索引为where条件进行Update的时候,会先对该非主键索引加锁,然后再查询该非主键索引对应的主键索引都有哪些,再对这些主键索引进行加锁。 五、解决方案 解决方案至此,我们分析清楚了导致死锁的根本原理以及其背后的原理。那么这个问题解决起来就不难了。
可以从两方面入手,分别是修改索引和修改代码(包含SQL语句)。
修改索引:只要我们把前缀索引 idx_seller_transNo中fund_transfer_order_no的前缀长度修改下就可以了。比如改成50。即可避免死锁。
但是,改了idx_seller_transNo的前缀长度后,可以解决死锁的前提条件是update语句真正执行的时候,会用到fund_transfer_order_no索引。如果MySQL查询优化器在代价分析之后,决定使用索引 KEY idx_seller(seller_id),那么还是会存在死锁问题。原理和本文类似。
所以,根本解决办法就是改代码:
  • 所有update都通过主键ID进行。
  • 在同一个事务中,避免出现多条update语句修改同一条记录。
其他思考
在死锁发生之后的一周内,前前后后做过很多中种推断及假设,最终还是要靠实践来验证自己的想法。遇到问题,不要想当然,亲手复现下问题,然后再来分析。
通过本文,大家可以掌握死锁分析的基本理论和一般方法,希望能为大家工作中快速解决实际出现的死锁问题提供思路。

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

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

相关文章

一.NODE MCU(ESP8285,ESP8286)开发环境搭建

一.序言: 1.esp8285长什么样? 2.esp8285是什么,能做什么? 通过上面图片,看到上面的芯片,是带有多个阵脚的单片机。实际上,看着该芯片很小,但是却具有完整的wifi无线蓝牙功能,它本身可以运行一个极简的linux小系统,并且该极简的小linux系统具备无线蓝牙功能。。它同…

54岁前港姐与好友因一事反目成仇,20年后方破冰

现年54岁的前「金牌司仪」陈淽菁(前名:陈芷菁)是1994年落选港姐,之后加入TVB参演电视剧《天地男儿》、《壹号皇庭》入屋,后因口齿伶俐而转战主持界。2017年陈淽菁离巢,外出以个人名义成立「陈芷菁工作室」&…

每日学习笔记:C++ STL算法之容器元素转换、结合、互换

本文API 转换元素 transform(sourceBeg,sourceEnd,destBeg, op) 结合元素 transform(source1Beg,source1End,source2Beg,destBeg, op) 互换元素 swap_ranges(sourceBeg,sourceEnd,destBeg) 转换元素 结合元素 互换元素

深度学习驱动的流体力学计算与应用

在深度学习与流体力学深度融合的背景下,科研边界不断拓展,创新成果层出不穷。从物理模型融合到复杂流动模拟,从数据驱动研究到流场智能分析,深度学习正以前所未有的力量重塑流体力学领域。近期在Nature和Science杂志上发表的深度学…

ARM_day8:温湿度数据采集应用

1、IIC通信过程 主机发送起始信号、主机发送8位(7位从机地址1位传送方向(0W,1R))、从机应答、发数据、应答、数据传输完,主机发送停止信号 2、起始信号和终止信号 SCL时钟线,SDA数据线 SCL高电平,SDA由高到低——起始信号 SC…

汽车零部件制造迎来智能化升级,3D视觉定位系统助力无人化生产线建设

随着新能源汽车市场的蓬勃发展,汽车零部件制造行业正面临着前所未有的机遇与挑战。为了提高产能和产品加工精度,某专业铝合金汽车零部件制造商决定引进智能生产线,其中,对成垛摆放的变速箱壳体进行机床上料成为关键一环。 传统的上…

SpringBootSpringCloud升级可能会出现的问题

1.背景 之前负责过我们中台的SpringBoot和Cloud的升级,特次记录分享一下项目中可能出现的问题,方便后续的人快速定位问题。以及下述选择的解决方案都是基于让升级的服务影响和改动最小以及提供通用的解决方案的提前进行选择的。 1.1版本说明 升级前&a…

陇剑杯 省赛 攻击者1 CTF wireshark 流量分析

陇剑杯 省赛 攻击者1 题目 链接:https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码:haek ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志分析 │ │ acce…

Vue3项目 网易严选_学习笔记

Vue3项目 网易严选_第一天 主要内容 项目搭建vuex基础路由设计首页顶部和底部布局 学习目标 知识点要求项目搭建掌握vuex基础掌握路由设计掌握首页顶部和底部布局掌握 一、项目搭建 1.1 创建项目 vue create vue-wangyi选择vue3.0版本 1.2 目录调整 大致步骤&#xff…

Workerman开启ssl方法如下

参考地址 Workerman开启ssl方法如下-遇见你与你分享 准备工作: 1、Workerman版本不小于3.3.7 2、PHP安装了openssl扩展 3、已经申请了证书(pem/crt文件及key文件)放在了/etc/nginx/conf.d/ssl下 4、配置文件 location /wss { proxy_set…

unity制作拼接地图

前段时间有个朋友问我想要制作一款地图编辑器,最开始我还想着在一个平面用节点切割制作地图编辑器这倒是也行,但不太好控制每一个点,如果未来项目大了,更加不好维护。 偶然间翻到一篇文章:unity地图边缘检测 或许我们…

基于数字孪生的城市建模和仿真

近年来,数字孪生概念几乎呈爆炸式增长,利用该概念的科学文章数量呈指数级增长就证明了这一点。 这一概念源自制造业,使用 CAD 模型可以创建组件和产品的精确数字复制品。 该术语最早的使用可以追溯到 2003 年,通常归功于 Grieves …

vue3第二十节(新增编译宏defineModel)

为什么会需要使用defineModel() 注意:defineModel() 需要在3.4及以上版本才可使用; 组件之间通讯,通过 props 和 emits 进行通讯,是单向数据流,比如:props是自上而下的(父组件数据修改导致子组件更新&…

生成人工智能体:人类行为的交互式模拟论文与源码架构解析(2)——架构分析 - 核心思想环境搭建技术选型

4.架构分析 4.1.核心思想 超越一阶提示,通过增加静态知识库和信息检索方案或简单的总结方案来扩展语言模型。 将这些想法扩展到构建一个代理架构,该架构处理检索,其中过去的经验在每个时步动态更新,并混合与npc当前上下文和计划…

【动态规划】切割钢条详解python

1. 问题介绍和应用场景 切割钢条问题是运筹学和算法设计中的一个经典问题,涉及如何最优化切割有限资源以最大化收益。这个问题经常用作动态规划教学的入门案例,同时在工业生产中也有实际应用,比如在金属加工业中如何切割原材料以减少浪费并增…

EelasticSearch是什么?及EelasticSearch的安装

一、概述 Elasticsearch 是一个基于 Apache Lucene 构建的开源分布式搜索引擎和分析引擎。它专为云计算环境设计,提供了一个分布式的、高可用的实时分析和搜索平台。Elasticsearch 可以处理大量数据,并且具备横向扩展能力,能够通过增加更多的…

Jmeter三个常用组件

Jmeter三个常用组件 一、线程组二、 HTTP请求三、查看结果树 线程组:jmeter是基于线程来运行的,线程组主要用来管理线程的数量,线程的执行策略。 HTTP请求:HTTP请求是jmeter接口测试的核心部分,主要使用HTTP取样器来发…

Android 12 如何加载 native 原生库

在 Android 7.0 及更高版本中,系统库与应用库是分开的。 图1. 原生库的命名空间 原生库的命名空间可防止应用使用私有平台的原生 API(例如使用 OpenSSL)。该命名空间还可以避免应用意外使用平台库(而非它们自己的库)的…

ES源码四:网络通信层流程

听说ES网络层很难?今天来卷它😄 前言 ES网络层比较复杂,分为两个部分: 基于HTTP协议的REST服务端基于TCP实现的PRC框架 插件化设计的网络层模块(NetworkModule) 入口还是上一章的创建Node构造方法的地方…

【MySQL 安装与配置】Window简单安装MySQL,并配置局域网连接

文章日期:2024.04.17 系统:Window10 || Window11 类型:安装与配置MySQL数据库 文章全程已做去敏处理!!! 【需要做的可联系我】 AES解密处理(直接解密即可)(crypto-js.js…