MySQL的index merge(索引合并)导致数据库死锁分析与解决方案 | 京东云技术团队

news2024/11/25 20:31:30

背景

在DBS-集群列表-更多-连接查询-死锁中,看到9月22日有数据库死锁日志,后排查发现是因为mysql的优化-index merge(索引合并)导致数据库死锁。

定义

index merge(索引合并):该数据库查询优化的一种技术,在mysql 5.1之后进行引入,它可以在多个索引上进行查询,并将结果合并返回。

mysql数据库的锁机制

在排查问题之前,首先讲一下mysql数据库的锁机制:

1 加锁的基本单位是 next-key lock(记录锁+间隙锁),当记录锁或者间隙锁能够解决幻读的问题,就会退化为记录锁(行锁),间隙锁。

2 加锁是将锁加在了索引之上,而不是数据之上。

3 对于当前读,索引进行加锁,当前读语句包括了(select … from. … for update,select…from … lock in share mode,update…,delete…)。

4 加锁根据唯一性索引、非唯一性索引进行了区分,根据查询条件分为了等值查询、范围查询,根据是否能够查到数据又分为了记录存在和不存在的情况。

本次死锁问题使用的索引是非唯一性索引的等值查询中记录存在的情况,因此本文仅仅详细介绍这种情况,其它情况可以查看最下面的参考文档1:

加锁情况是:会依次扫描,首先扫描到条件匹配的数据,加一个next-key lock,然后接下来扫描到第一个记录不匹配的数据,增加一个间隙锁,最后对查到记录的主键增加一个记录锁,

针对以上情况加了三种锁,加锁的目的是为了防止幻读的发生。

针对二级索引的锁进行分析:

表结构:

CREATE TABLE `jdi_roster_apply_detail` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `apply_id` varchar(100) NOT NULL COMMENT '申请单号',
  `status` tinyint(10) NOT NULL COMMENT '状态',
  PRIMARY KEY (`id`),
  KEY `idx_status` (`status`),
  KEY `idx_apply_id` (`apply_id`)
) ENGINE=InnoDB AUTO_INCREMENT=984483 DEFAULT CHARSET=utf8 COMMENT='黑白名单申请单明细'



表数据:

idapply_idstatus
95965116953692205220689981
96073816953692275761736901
96131916953730476739033261
96136516953731224478652281

通过 idx_apply_id建立的b+树:

因为索引是二级索引,所以叶子节点存储的数据是主键值。

执行sql:

select * from jdi_roster_apply_detail where apply_id='1695369227576173690' for update



执行数据扫描过程

1 查到符合条件的记录,增加next-key 锁,因此锁是(1695369220522068998,1695369227576173690]

2 找到第一个不符合记录的数据增加间隙锁,因此锁是 (1695369227576173690,1695373047673903326)

3 对符合条件的主键索引增加记录锁,因此对 id=960738,增加记录锁。

针对三种锁解决的幻读:

1 如果没有第一条的next-key锁, 另一个事务增加一个apply_id=1695369227576173690, id<960738 时,该事务在进行查询时,会多一条记录,因此会造成幻读。

2 如果没有第二条的 间隙锁,另一个事务增加一个apply_id=1695369227576173690, id>960738是,该事务在进行查询时,会多一条记录,因此会造成幻读。

3 如果没有第三条的记录锁,另一条事务删除一条 id=960738的记录,该事务进行查询时,会少一条数据,因此会造成幻读。

实际问题分析

数据库死锁日志

以上日志两个事务分别执行了update语句:

#事务1
update jdi_roster_apply_detail set `status` = 10 where `status` = 1 and apply_id = '1695369220522068998'
#事务2
update jdi_roster_apply_detail set `status` = 10 where `status` = 1 and apply_id = '1695369227576173690' 



这个sql是用于将某个申请单id待审批的数据改为已审批。

因为在泰山里不能执行update语句 ,因此执行了select语句查看用的索引情况:

explain select * from  jdi_roster_apply_detail  where `status` = 1 and apply_id = '1695369220522068998'



执行的结果:

通过结果可以看出两个update语句都使用了两个索引,分别是idx_status,idx_apply_id,然后将查到的结果进行合并,因此在模拟的过程中,可以将其拆成两个查询语句。

死锁模拟

事务1事务2锁的范围
beginbegin
select * from jdi_roster_apply_detail where apply_id = ‘1695369220522068998’ for updateidx_apply_id所以锁住了(-∞,1695369220522068998],(1695369220522068998,1695369227576173690) 主键id索引锁住了 id=959651
select * from jdi_roster_apply_detail where apply_id = ‘1695369227576173690’ for updateidx_apply_id所以锁住了(1695369220522068998,1695369227576173690],(1695369227576173690,1695373047673903326) 主键id索引锁住了 id=960738
select * from jdi_roster_apply_detail where status = 1 for update会对idx_status上加next-key锁和间隙锁,但是在对主键959651,960738,961319,961365进行加记录锁时,其中事务2 对960738已经加了记录锁,所以该事务1进行了阻塞。
select * from jdi_roster_apply_detail where status = 1 for update会对idx_status上加next-key锁和间隙锁,但是在对主键959651,960738,961319,961365进行加记录锁时,其中事务1对959651已经加了记录锁,所以该事务2进行了阻塞。
deadlock

两个事务分别想要两个主键id的记录锁,造成相互等待,形成了死锁。

以上是先执行idx_apply_id的索引查询再执行idx_status索引查询,如果先执行idx_status索引查询,再执行idx_apply_id的索引查询,也会因为主键的记录锁造成死锁。

解决方案

1 利用force index(idx_apply_id)强制走某个索引,这样InnoDB就会忽略index merge,避免多个索引同时加锁的情况。

2 禁用Index Merge,用命令禁用Index Merge:SET GLOBAL optimizer_switch=‘index_merge=off,index_merge_union=off,index_merge_sort_union=off,index_merge_intersection=off’;

3 Index Merge同时使用了2个独立索引,因此新建一个包含这两个索引所有字段的联合索引,这样InnoDB就只会走这个单独的联合索引。

第三种方案相较于第一种查询性能更好,相对于第二种仅仅作用于该表,影响范围小,因此本次也是采用了该方案。

总结

该死锁问题是因为优化器使用了合并索引问题导致的,最终通过新建一个联合索引来解决这个问题。

参考文档:

1 https://www.xiaolincoding.com/mysql/lock/how_to_lock.html

作者:京东工业 李小辉

来源:京东云开发者社区 转载请注明来源

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

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

相关文章

每日leetcode_775全局倒置与局部倒置

每日leetcode_755全局倒置与局部倒置 记录自己的成长&#xff0c;加油。 题目出处&#xff1a;775. 全局倒置与局部倒置 - 力扣&#xff08;LeetCode&#xff09; 题目 题目简要&#xff1a; 全局倒置&#xff1a;左边的大于右边的&#xff08;不需要紧挨着&#xff09; 局部…

Chrome插件精选 — 网页截图插件

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件记录下比较好用的一款或几款&#xff0c;便于节省尝试的时间和精力。 捕捉网页截图 - FireShot 下载地址 (访问密码: 8276) Fireshot是一款浏览器插件&#xff0c…

2023.09全国青少年软件编程(scratch图形化)等级考试试卷(一级)

2023年9月全国青少年软件编程 scratch图形化 等级考试试卷&#xff08;一级&#xff09; 第 1 题 下列哪项内容是不可以修改的&#xff1f;&#xff08; &#xff09; A&#xff1a;角色名称 B&#xff1a;造型名称 C&#xff1a;舞台名称 D&#xff1a;背景名称 第 2 题…

11款新编程工具!

导读作为一名开发人员&#xff0c;对于工具的需求是一直都有的。工具使得开发人员只需要关注那些重要的事情就可以了&#xff0c;因此工作也更有效率&#xff0c;生活也更轻松了。对于开发人员来说&#xff0c;由于很难找到更好的替代方案&#xff0c;因此只能一直使用那些熟悉…

Oracle update 关联更新优化方法

关联更新顾名思义就是指&#xff0c;更新的数据从关联的表中获取并update到目标表。并且该SQL将会是一个天然的嵌套循环。有两种优化思路解决&#xff1a; 1、PLSQL 根据rowid更新 是否需要加order by rowid的考量&#xff1a; 如果buffer cache足够大&#xff0c;能够放得下要…

PyTorch 深度学习之卷积神经网络(基础篇)Basic CNN(九)

0. Revision: Fully connected Neural Network 全连接 1. Convolution Neural Network 保留空间信息 1.1 Convolution Convolution-Single Input Channel 单通道 数乘 3 input Channels 3通道 N input Channels N input Channels and M output channel M 个卷积核 1.2 conv…

jmeter性能测试过程中遇到的问题及解决方法

问题1 如何在大并发测试下&#xff0c;让登录或者后续接口只执行一次&#xff1f; 分析 这个问题网上的答案其实很多&#xff0c;但是大多不靠谱。 比如推荐使用仅一次控制器&#xff0c;但是仅一次控制器对线程组无效&#xff1b;比如推荐跨线程组调用&#xff0c;但是这样…

让ChatGPT等模型学会自主思考!开创性技术“自主认知”框架

ChatGPT、百度文心一言、Bard等大语言模型展现出了超强的创造能力&#xff0c;加速了生成式AI的应用进程。但AI模型只能基于训练数据执行各种任务&#xff0c;无法像人类一样利用生活知识、过往经验用于复杂的推理和决策。 例如&#xff0c;在玩游戏时&#xff0c;人类可以利用…

mysql中的几种排名函数

mysql中的排名函数 mysql里面的排名函数&#xff0c;涉及有以下几个&#xff1a; rank()、dense_rank()、row_number() 1、rank() 函数 RANK() OVER (PARTITION BY <expression>[{,<expression>...}]ORDER BY <expression> [ASC|DESC], [{,<expression…

如何用 ChatGPT 的 Advanced Data Analysis 帮你采集数据?

&#xff08;注&#xff1a;本文为小报童精选文章&#xff0c;已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 想采集网页数据却不会写 Python 爬虫&#xff1f;不会就不会吧&#xff0c;ChatGPT 会就可以了 &#x1f602; 问题描述 朋友最近遇到了一点儿…

thinkphp6 获取url路径中的应用名、控制器名、操作名

如果使用了多应用模式&#xff0c;可以通过下面的方法来获取当前应用 app(http)->getName(); 获取当前控制器 Request::controller(); 获取当前操作 Request::action(); 请求信息 ThinkPHP6.0完全开发手册 看云 ThinkPHP6.0基于精简核心和统一用法两大原则在5.1的基础上…

Ansible基础及模块

Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;能批量配置、部署、管理上千台主机。比如以前需要切换到每个主机上执行的一或多个操作&#xff0c;使用Ansible只需在固定的一台Ansible控制节点上去完成所有主机的操作 Ansible是基于模块工作的&#xff0c;它…

JZ64 求1+2+3+...+n

JZ64 求123…n //实例化对象时会自动调用构造函数 class Solution { public:class Sum{public://每调用一次构造函数 _sum加等一个数Sum(){_sum _i;_i;}};//Solution类的成员函数int Sum_Solution(int n){_i 1;_sum 0;//数n是几 我就构造几个对象//构造几个对象就会调用…

SVN报错fail to run the WC Db work queue associated with,清理失败,乱码的解决方式

替换掉 wc.db 文件即可 SVN报错fail to run the WC Db work queue associated with&#xff0c;清理失败&#xff0c;乱码的解决方式_svn failed to run the wc db-CSDN博客

Kubernetes 集群部署 Prometheus 和 Grafana

Kubernetes 集群部署 Prometheus 和 Grafana 文章目录 Kubernetes 集群部署 Prometheus 和 Grafana一.部署 node-exporter1.node-exporter 安装2.部署 node-exporter 二.部署Prometheus1.Prometheus 安装和配置&#xff08;1&#xff09;创建 sa 账号&#xff0c;对 sa 做 rbac…

智能物联网解决方案:蓝牙IOT主控模块打造高效监测和超低功耗

物联网蓝牙模块&#xff0c;无论单模&#xff0c;还是双模&#xff0c;或者双模音频的选择&#xff0c;如下文说描述&#xff1a; 蓝牙芯片模块市场的百花齐放&#xff0c;也带来的工程师在选型时碰到很大的困难&#xff0c;但是无论是做半成品&#xff0c;还是做成品&#xf…

Java GC 算法

一、概述 理解Java虚拟机垃圾回收机制的底层原理&#xff0c;是成为一个高级Java开发者的基本功。本文从底层的垃圾回收算法开始&#xff0c;着重去阐释不同垃圾回收器在算法设计和实现时的一些技术细节&#xff0c;去探索「why」这一部分&#xff0c;通过对比不同的垃圾回收算…

BAT020:将文本文档中多行文本拼接为;分隔的单行文本

引言&#xff1a;编写批处理程序&#xff0c;实现将文本文档中多行文本拼接为;分隔的单行文本。 一、新建Windows批处理文件 参考博客&#xff1a; CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件&#xff0c;点击【…

[软考中级]软件设计师-uml

事物 uml中有4中事物&#xff0c;结构事物&#xff0c;行为事物&#xff0c;分组事物和注释事物 结构事物是uml模型中的名词&#xff0c;通常是模型的静态部分&#xff0c;描述概念或物理元素 行为事物是uml的动态部分&#xff0c;是模型中的动词&#xff0c;描述了跨越时间…

记一次Hbase2.1.x历史数据数据迁移方案

查看待迁移的表 list_namespace_tables vaas_dwm2. 制作待迁移表“DWM_TRIP_PART”的快照 snapshot vaas_dwm:DWM_TRIP_PART,dwm_trip_part_snapshot3. 统计待迁移表数据总数 hbase org.apache.hadoop.hbase.mapreduce.RowCounter vaas_dwm:DWM_TRIP_PART