MySQL死锁如何处理

转载自  MySQL死锁如何处理

前提

笔者负责的一个系统最近有新功能上线后突然在预警模块不定时报出MySQL死锁导致事务回滚。幸亏,上游系统采用了异步推送和同步查询结合的方式,感知到推送失败及时进行了补偿。于是,笔者争取了一点时间详细分析了导致死锁的多个事务的执行时序,分析并且得出解决方案。

死锁场景复现

首先,MySQL的服务端版本是5.7(小版本可以基本忽略),使用了InnoDB。有一张用户数据表的schema设计如下(无关字段已经屏蔽掉):

CREATE TABLE `t_user_data`
(id      BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',data_id VARCHAR(50)     NOT NULL COMMENT '数据ID',INDEX idx_user_id (user_id),INDEX idx_data_id (data_id)
) COMMENT '用户数据表';

业务代码中发生死锁的伪代码如下:

process_method(dataId,userDataDtoList){start transaction:userDataDao.deleteByDataId(dataId);for dto in userDataDtoList:UserData userData = convert(dto);userDataDao.insert(dto);commit;
}

这里的逻辑是,如果已经存在对应dataId的数据要先进行删除,然后写入新的用户数据。

尝试用两个Session提交两个事务重现死锁问题:

时间序列Tx-Session-1Tx-Session-2
T1START TRANSACTION; 
T2 START TRANSACTION;
T3DELETE FROM t_user_data WHERE data_id = ‘xxxxx’; 
T4 DELETE FROM t_user_data WHERE data_id = ‘yyyyy’;
T5INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (1, ‘xxxxx’); 
T6 INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (2, ‘yyyyy’);
T7 Deadlock found when trying to get lock; try restarting transaction(Rollback)
T8COMMIT; 

这里会出现两个现象:

  1. Tx-Session-2会话T4执行完毕之后,Tx-Session-1会话T5执行的时候,Tx-Session-1会话客户端会处于阻塞状态。

  2. Tx-Session-2会话T6执行完毕之后,MySQL提示死锁事务被回滚,此时,Tx-Session-1会话客户端会解除阻塞。

导致死锁的原因

后面会写一篇专门的文章学习和理解MySQL的InnoDB数据引擎的锁相关知识,这里直接排查InnoDB的死锁日志。

mysql> show engine innodb status;

输出的死锁日志如下:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-05-11 19:16:04 0x5804
*** (1) TRANSACTION:
TRANSACTION 3882, ACTIVE 13 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 32, OS thread handle 9876, query id 358 localhost ::1 doge update
INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (1, 'xxxxx')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 33 page no 6 n bits 72 index idx_data_id of table `test`.`t_user_data` trx id 3882 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 00: len 8; hex 73757072656d756d; asc supremum;;*** (2) TRANSACTION:
TRANSACTION 3883, ACTIVE 9 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 11, OS thread handle 22532, query id 359 localhost ::1 doge update
INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (2, 'yyyyy')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 33 page no 6 n bits 72 index idx_data_id of table `test`.`t_user_data` trx id 3883 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 00: len 8; hex 73757072656d756d; asc supremum;;*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 33 page no 6 n bits 72 index idx_data_id of table `test`.`t_user_data` trx id 3883 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 00: len 8; hex 73757072656d756d; asc supremum;;*** WE ROLL BACK TRANSACTION (2)

这里要参考MySQL关于InnoDB锁的关于next-key锁描述那一节,注意死锁日志关键字supremum的意义:

next-key锁将gap锁定在索引中最大值之上,而supremum伪记录的值高于索引中实际的任何值。supremum不是真正的索引记录,因此,实际上,此next-key锁仅锁定最大索引值之后的间隙。

两个事务的锁属性可以通过select * from information_schema.innodb_locks;进行查询,数据如下表:

lock_idlock_tx_idlock_modelock_typelock_tablelock_indexlock_spacelock_pagelock_reclock_data
3882:33:6:13882XRECORDtest.t_user_dataidx_data_id3361supremum pseudo-record
3883:33:6:13883XRECORDtest.t_user_dataidx_data_id3361supremum pseudo-record
DELETE FROM t_user_data WHERE data_id = '不存在的索引值';

上面的SQL执行时候,如果条件刚好是索引列,并且查询的值是当前表(索引)中不存在的数据,根据next-key锁的描述和死锁日志中的asc supremum关键字,执行该DELETE语句的时候,会锁定目标值和高于目标值的任何值,如果条件是"xxxxx",那么相当于锁定区间为(“xxxxx”,最大上界]。

next-key锁是索引记录上的记录锁(Record Lock)和索引记录之前的间隙上的间隙锁(Gap Lock)定的组合。间隙锁有两个特点:

  1. 两个事务即使锁定的区间一致(或者有部分重合),不会影响它们之间获取到锁(可以参考行锁的兼容性矩阵)。

  2. 间隙锁G会阻止非持有G的其他事务向锁定的区间中插入数据,以避免产生冲突数据。

分析到这里,就很好解释上面出现死锁的执行时序:

  1. 两个事务的DELETE语句都可以正确执行,这个时候,两者的间隙锁锁定的区域分别是(‘xxxxx’,最大上界]和(‘yyyyy’,最大上界]。

  2. 事务1执行INSERT语句的时候阻塞,是因为事务2的间隙锁不允许事务1插入索引值’xxxxx’。

  3. 事务2执行INSERT语句的时候阻塞,是因为事务1的间隙锁不允许事务1插入索引值’yyyyy’,执行到这一步,MySQL的死锁检查模块应该起效了,因为两个事务依赖的锁资源已经成环(或者成有向图)。

  4. 事务2的优先级比较低,于是抛出死锁异常并且被回滚了。

之前曾经和DBA同事聊过,发生死锁的事务是怎么衡量优先级或者怎么确定哪个事务需要回滚(释放锁资源让另一个事务可以正常提交),但是后来没有收到很好的答复,这一点有时间再研究一下。

解决方案

参考MySQL的文档,解决方案有两个:

  1. 方案一:降低数据库的事务隔离级别,需要降低到READ COMMITED,这样子可以关闭间隙锁的扫描。(<== 并不推荐这种做法,修改事务隔离级别有可能出现新的问题)

  2. 方案二:针对对应的原因修改业务代码。

这里方案二只需要把伪代码逻辑修改如下:

process_method(dataId,userDataDtoList){List<UserData> userDataList = userDataDao.selectByDataId(dataId);start transaction:if userDataList is not empty: List<Long> ids = collectIdList(userDataList);userDataDao.deleteByIds(ids);       for dto in userDataDtoList:UserData userData = convert(dto);userDataDao.insert(dto);commit;
}

就是先根据dataId进行查询,如果存在数据,聚合主键列表,通过主键列表进行删除,然后再进行数据插入。

小结

这并非是第一次在生产环境中出现MySQL死锁,只是这次的案例相对简单。InnoDB提供的死锁日志其实并没有提供完整的事务提交的SQL,所以对于复杂的场景需要细致结合代码和死锁日志进行排查,很多时候对应的代码逻辑是多处的。这里列举一下笔者处理死锁问题的一些步骤:

  1. 及时止损,如果可以回滚导致死锁的代码,那么最好果敢地回滚;如果重试可以解决问题并且出现死锁问题的规模不大,可以尝试短时间内进行问题排查。

  2. 通过业务系统日志迅速定位到发生死锁的代码块,JVM应用一般底层是依赖JDBC,出现死锁的时候会抛出一个SQLException的子类,异常栈的信息中带有"Deadlock"字样。

  3. 分析InnoDB的死锁日志,一般会列出竞争锁的多个事务的相对详细的信息,这些信息是排查死锁问题的第一手资料。

  4. 修复问题上线后注意做好监控和预警,确定问题彻底解决。

参考资料:

  • MySQL5.7官方文档

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

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

相关文章

(十)IDEA添加mybatis-mapp.xml文件

1.点击file–Settings–Editor–file and Code Templates 2.配置mybatis-mapper.xml的网址,点击file–Settings–ages& Frameworks–Schmas and DTDs 3.创建mapper.xml文件

g4e基础篇#2 Git分布式版本控制系统的优势

1. 基础篇&#xff1a;为什么要使用版本控制系统Git 分布式版本控制系统的优势Git 安装和设置初始化Git存储库(Repo)起步 1 – 创建分支和保存代码起步 2 – 了解Git历史记录起步 3 – 拉取请求 Pull Request 工作机制Git是当前最棒的版本控制系统&#xff0c;已经迅速成为了事…

P4989-二进制之谜【堆,贪心】

正题 题目链接:https://www.luogu.org/problemnew/show/P4989 题目大意 一个二进制数两两配对&#xff0c;要求 配对的数不能交叉(用同一个区间但不包含)0在前1在后 要求配对最多的情况下所有配对的距离之和最远。 解题思路 将0视为左括号&#xff0c;1视为右括号&#xf…

案例分析 | 由Decimal操作计算引发的Spark数据丢失问题

转载自 案例分析 | 由Decimal操作计算引发的Spark数据丢失问题 供稿 | Hadoop Team 编辑 | 顾欣怡 本文3058字&#xff0c;预计阅读时间10分钟 导读 eBay的Hadoop集群上面每天运行着大量Spark计算任务。对于数据计算任务&#xff0c;其计算性能十分重要&#xff0c;数据质…

入门干货之Electron的.NET实现-Electron.NET

0x01、Electron.NET1、介绍Electron是由Github上的一支团队和一群活跃贡献者维护。用HTML&#xff0c;CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.Js合并到同一个运行时环境中&#xff0c;并将其打包为Mac&#xff0c;Windows和Linu…

通过Chocolatey软件包管理器安装.NET Core

在Linux的世界里&#xff0c;有了yum/apt-get百分之九十的软件都可以通过它来安装管理。但是在Windows系统上&#xff0c;装个软件还是挺折腾的。比如我要装个Chrome浏览器&#xff0c;我先得打开IE浏览器吧&#xff0c;我还打不开Chrome的官网吧&#xff0c;得百度吧&#xff…

(十三)RabbitMQ使用详解

RabbitMQ是基于AMQP的一款消息管理系统。AMQP(Advanced Message Queuing Protocol)&#xff0c;是一个提供消息服务的应用层标准高级消息队列协议&#xff0c;其中RabbitMQ就是基于这种协议的一种实现。 常见mq&#xff1a; ActiveMQ&#xff1a;基于JMSRabbitMQ&#xff1a;…

ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

在ASP.NET Core Web API下事件驱动型架构的实现&#xff08;一&#xff09;&#xff1a;一个简单的实现中&#xff0c;我介绍了事件驱动型架构的一种简单的实现&#xff0c;并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了&#xff0c;百十行代码就展示了一个…

和某ZYC巨佬和XXY巨佬的随机挑战2总结

前言 一切的起点在那个炎热的酷暑&#xff0c;菜的一批的WYCWYCWYC坐在最容易被∗*∗的左下角。这时他永远都想不到&#xff0c;他与巨佬之间的挑战&#xff0c;即将开始。 正题 规则 随机跳333到蓝题&#xff0c;然后写完。 完成记录 题目博客 T1:P3100−[USACO14JAN]T1:P31…

(十四)消息中间件MQ详解及四大MQ比较

一、消息中间件相关知识 1、概述 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件&#xff0c;如老牌的ActiveMQ、RabbitMQ&a…

g4e基础篇#3 Git安装与配置

现在你已经对Git有了最基本的了解&#xff0c;现在让我们开始动手开始安装和配置Git环境。Git工具包括Git命令行工具&#xff0c;图形化工具和服务器环境&#xff1b;在我们这个教程中&#xff0c;我们会使用以下软件配置我们的环境&#xff1a;• Windows 操作系统&#xff08…

[认证授权] 6.Permission Based Access Control

在前面5篇博客中介绍了OAuth2和OIDC&#xff08;OpenId Connect&#xff09;&#xff0c;其作用是授权和认证。那么当我们得到OAuth2的Access Token或者OIDC的Id Token之后&#xff0c;我们的资源服务如何来验证这些token是否有权限来执行对资源的某一项操作呢&#xff1f;比如…

微软发布PowerShell Core第一个版本:支持多平台开发

微软旗下的PowerShell团队正式宣布推出PowerShell Core 6.0&#xff0c;非常诡异的是这明明是Core的第一个版本&#xff0c;但是却用了一个6.0后缀的版本号。“这是我们对PowerShell做出的最大最重要的改变&#xff01;”微软技术研究员兼PowerShell创始人Jeffrey Snover在Twit…

.NET Core单文件发布静态编译AOT CoreRT

.NET Core单文件发布静态编译AOT CoreRT&#xff0c;将.NET Core应用打包成一个可执行文件并包含运行时。支持Windows, MacOS and Linux x64 w/ RyuJIT codegen。示例项目&#xff1a;https://github.com/dotnet/corert/tree/master/samples/WebApi下面来实际体验。首先确保安装…

2019纪中暑假游记+总结

Travels总篇\texttt{Travels总篇}Travels总篇 7/4\texttt{7/4}7/4 下午才去纪中&#xff0c;早上就一大早和同学出去玩&#xff0c;看了蜘蛛侠然后到3点多才出发。 因为走南沙大桥所以很快就到了(具体有多快忘了&#xff0c;反正路上一点都不塞车)。就愉快的去整理宿舍洗个早…

使用xUnit为.net core程序进行单元测试(上)

一. 导读为什么要编写自动化测试程序&#xff08;Automated Tests&#xff09;&#xff1f;可以频繁的进行测试可以在任何时间进行测试&#xff0c;也可以按计划定时进行&#xff0c;例如&#xff1a;可以在半夜进行自动测试。肯定比人工测试要快。可以更快速的发现错误。基本上…

select2删除选中项,allowClear设置

转载自 select2删除选中项&#xff0c;allowClear设置 在使用select2过程中&#xff0c;有时候需要删除我们选中的选项&#xff0c;如下图&#xff1a; 这时候就需要设置select2的allowClear属性了。 有两种方法&#xff1a; 第一种&#xff1a; 直接用select2定义的一个c…

LeetCode算法总结-回溯法与深度优先搜索

转载自 LeetCode算法总结-回溯法与深度优先搜索 回溯法&#xff08;探索与回溯法&#xff09;是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探索到某一步时&#xff0c;发现原先选择并不优或达不到目标&#xff0c;就…

入门干货之用DVG打造你的项目主页-Docfx、Vs、Github

由于这三项技术涉及到的要点以及内容较多&#xff0c;希望大家有空能自己挖掘一下更多更深的用法。0x01、介绍VS&#xff0c;即VS2017以及以上版本&#xff0c;宇宙最好的IDE&#xff0c;集成了宇宙最有前景的平台&#xff0c;前阶段也支持了宇宙最好的语言。Github&#xff0c…

ASP.NET Core中使用IOC三部曲(一.使用ASP.NET Core自带的IOC容器)

前言本文主要是详解一下在ASP.NET Core中,自带的IOC容器相关的使用方式和注入类型的生命周期.这里就不详细的赘述IOC是什么 以及DI是什么了.. emm..不懂的可以自行百度.正文今天我们主要讲讲如何使用自带IOC容器,emm..虽然自带的功能不是那么强大,但是胜在轻量级..而且..不用引…