记一次Index-Merge造成的死锁

起因

前一段时间一个没有多少量的项目突然线上出错报警,第一时间查到异常日志

报错信息比较明显,数据库产生死锁。

分析

分析代码之前让我们来复习一下什么是死锁以及产生死锁的原因是什么

死锁产生原因是什么❓

当两个及以上的事务,都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源。

举个我们最常见的例子,A 事务持有X ,申请Y,B 事务持有Y锁,申请X锁。A和B 事务持有锁并且申请对方持有的锁,这样就会造成死锁。

翻译成代码:

// 在隔离级别RR,ID为主键索引的情况下。 (画外音:不谈隔离级别与索引情况下分析加锁都是耍流氓)                                

session1:update name = “a” where id =1;update name = “b” where id =4; session2:   update name = “a” where id = 4;update name = “b” where id = 1;

在并发的情况下,假设请求顺序是这样的1. session1先拿id=1的行锁2. session2拿id=4的行锁3. session1请求id=4的行锁(等待session2释放)4. session2请求id=1的行锁(等待session1释放)5. 循环等待,造成死锁

了解了死锁产生的基本原因之后,让我们去看下源码,看是不是有类似这样的代码逻辑。

但是奇怪的是,我们翻了源码(这里不把源码放出来了),但是源码并没有类似这样的逻辑,更神奇的是代码里根本就没有@Transaction注解,也就意味着没有应用到事务,也就是说单表语句造成了死锁❓

现在看来问题比较诡异,单条语句造成了死锁。接下来我们去跟DBA要一下死锁日志

根据死锁日志,发现确实仅仅是因为 

update g_growth_free_activity_product    SET buy_count = 1079    where product_id = 79550 and activity_id = 2062 and deleted = 0

这条语句产生了死锁。

转机

之后就是各种google,百度的时候了,终于我们发现了一些和我们比较像的案例,https://blog.csdn.net/zheng0518/article/details/54695605 ,链接里的例子和我们的现象比较接近,文章里更是贴出了MySQL官方bug的地址https://bugs.mysql.com/bug.php?id=77209

上面的图就bug中描述的内容,大意是update时使用index merge增加了死锁风险。

我们需要先去看看【index-merge】是什么。
我们翻一下官方文档:https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html,文档里有多种情况的介绍,不赘述。
翻译一下大概就是对单个表的多个索引分别进行扫描并将结果交并集处理。

那我们再来看业务SQL,针对
update g_growth_free_activity_product    SET buy_count = 1079    where product_id = 79550 and activity_id = 2062 and deleted = 0
如果有index merge,意味着【product_id】和【activity_id】是索引列。(deleted字段应该没人加索引吧)
我们去看下表中的索引结构:

【product_id】和【activity_id】确实都是普通二级索引。
虽然都是单列索引,但是我们还不能确定优化器在执行SQL的时候一定会选择【index merge】,还需要查看下执行计划。
为了保证我们的数据和线上一致,我们把线上数据拉了下来,并创建了一个test表,表结构相同,索引结构相同,把数据导进去。并查询到发生死锁时的请求日志

我们通过执行计划能看到,update时确实使用了 【index-merge】进行优化,extra列显示的是使用了【交集】类型。

到这时候,所有的条件就都能对的上了,但是是否真的是因为这个原因发生死锁我们还需要还原一下案发现场,尝试在neibu环境进行复现。
我们在上面已经建好的test表上做测试。

10个线程并发执行更新,查看结果。

确实是很容易就发生了死锁。
到这里我们问题就已经基本定位了:由于索引设置不合理的缘故,where条件两个单列普通二级索引在查询的时候MySQL进行了index-merge优化,引发死锁。

分析

找到原因之后,我们再来分下下使用index-merge为什么会发生死锁。

我们先拿到死锁的数据。
查询【activity_id=2062】,对应MySQL主键记录是在 【88-234】
查询【product_id=79550】,对应MySQL主键记是【218,186】
查询【product_id=79466】,对应MySQL主键记是【219,183】我们根据执行计划与加锁流程拆分下成如下几个过程

再结合的死锁日志,我们分析下加锁流程

session1等待【activity_id】的锁,session2等待的是主键锁,产生循环等待,发生死锁。

解决方案

最后说一下解决方案,建议使用方案2,本身就是因为索引使用不合理导致,优化之后再也没有死锁的问题了。

1、关闭【index merge】
2、建立联合索引
3、优化代码
4、强制走单列索引

最后提几个关于MySQL建议阅读的文档:
官方文档:https://dev.mysql.com/doc/
淘宝数据库内核月报:http://mysql.taobao.org/monthly/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335