首页 > 数据库 >MySQL的index merge(索引合并)导致数据库死锁分析与解决方案

MySQL的index merge(索引合并)导致数据库死锁分析与解决方案

时间:2023-10-13 10:57:27浏览次数:42  
标签:status index idx merge update 索引 死锁 apply id

背景

在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='黑白名单申请单明细'


表数据:

id apply_id status
959651 1695369220522068998 1
960738 1695369227576173690 1
961319 1695373047673903326 1
961365 1695373122447865228 1

通过 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 锁的范围
begin begin
select * from jdi_roster_apply_detail where apply_id = '1695369220522068998' for update idx_apply_id所以锁住了(-∞,1695369220522068998],(1695369220522068998,1695369227576173690) 主键id索引锁住了 id=959651
select * from jdi_roster_apply_detail where apply_id = '1695369227576173690' for update idx_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

作者:京东工业 李小辉

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

标签:status,index,idx,merge,update,索引,死锁,apply,id
From: https://www.cnblogs.com/jingdongkeji/p/17761388.html

相关文章

  • z-index 点击 不生效 遮挡 如何让被遮挡的下层元素能被点击到(且上层也能) 上层下层都能
    https://stackoverflow.com/questions/64052019/make-element-with-smaller-z-index-clickable一种方案是在上层元素的点击事件调用的函数中,通过js代码,选择器选中下层元素,然后.click()代码触发下层元素的点击事件 (如果上层不需要被点击,可以用pointer-events:none;)......
  • 记一次Redis Cluster Pipeline导致的死锁问题
    作者:vivo互联网服务器团队-LiGang本文介绍了一次排查Dubbo线程池耗尽问题的过程。通过查看Dubbo线程状态、分析Jedis连接池获取连接的源码、排查死锁条件等方面,最终确认是因为使用了clusterpipeline模式且没有设置超时时间导致死锁问题。一、背景介绍RedisPipeline是一种高效......
  • 记一次Redis Cluster Pipeline导致的死锁问题
    作者:vivo互联网服务器团队-LiGang本文介绍了一次排查Dubbo线程池耗尽问题的过程。通过查看Dubbo线程状态、分析Jedis连接池获取连接的源码、排查死锁条件等方面,最终确认是因为使用了clusterpipeline模式且没有设置超时时间导致死锁问题。一、背景介绍RedisPipeline是一......
  • 巧用指标平台DataIndex,五步法轻松实现指标管理
    开发部门在做指标加工的全流程中,是否经常出现如下问题:·业务部门看指标数据的时候,看到两个名称相似的指标,不清楚两个指标的差异性,来咨询开发部门指标计算口径,开发部门配合业务部门翻找代码,找出指标口径差异性,影响工作效率·业务部门看指标数据的时候,总会出现不同页面的同一指......
  • 处理机调度与死锁
    一、处理机调度的层次概念按什么原则分配CPU:调度算法。何时分配CPU:调度时机。如何分配CPU:调度过程。周转时间:完成时间-进入时间。(注意:从进入系统到执行完成包括在后备队列中等待调度、在就绪队列中等待进程调度、执行以及等待I/O操作完成四部分时间,作业进入是指作业准备好被......
  • [abc302f] Merge Set
    F-MergeSet显然要建图首先,我们有一个粗略的想法,对于同一集合\(S_i\)内的元素,\(S_{i,j}\)与\(S_{i,j+1}\)间连一条无向的标号为\(i\)的边那么题目显然是要我们跑最短路,若到达\(x\)的边为\(i\),然后从\(x\)向外走到点\(y\),走的边若还为\(i\),那么代价为\(0\),否则代价为\(1\)也......
  • 整理常见问题一死锁条件
    1、死锁的条件死锁是两个或两个以上的进程在执行过程中,由于竞争资源或进程推进顺序非法造成的阻塞现象,若无外力作用将无法推进下去。四个必要条件1)互斥条件:一个资源每次只能被一个进程使用(涉及的资源是非共享的)2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不......
  • [903] Concatenate (merge) multiple dictionaries in Python
    Toconcatenate(merge)multipledictionariesinPython,youcanusevariousmethodsdependingonyourPythonversionandpreferences.Herearesomecommonapproaches:1.Usingtheupdate()Method:Youcanusetheupdate()methodofdictionariestomergeo......
  • 死锁
    1、什么是死锁?死锁是一组互相竞争资源的线程,因为互相等待,导致的永久阻塞。2、产生死锁的原因?互斥:共享资源x和y只能被一个线程占用占有且等待:线程t1已经取得资源x,在等待资源y的时候不释放资源x不可抢占:其他线程不能强行抢占线程t1占有的资源循环等待:线程t1等待线程t2占有的......
  • 死锁和Lock锁
    死锁就是两个线程都有着一个对象的锁 然后下一步都想去拿另外一个线程的锁,因为两个线程有的锁还没解开,形成循环僵持,谁都想要另外一个线程的锁,但是又没解开自己拿到的锁。 解决办法示例: 就是可以等另外一个线程解开了锁然后再去拿锁。 Lock锁:和synchonized锁是一样的......