MySQL死锁分析

一、表结构,死锁日志,事务隔离级别

表结构:

CREATE TABLE `ep_lucky_bag_activity_user_task_schedule` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `user_id` char(32) NOT NULL COMMENT '用户id',
  `task_id` char(32) NOT NULL COMMENT '任务id',
  `rate` int(11) NOT NULL DEFAULT '0' COMMENT '任务完成的进度',
  `use_rate` int(11) NOT NULL DEFAULT '0' COMMENT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否被删',
  PRIMARY KEY (`id`),
  UNIQUE KEY `udx_user_id_task_id` (`user_id`,`task_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务进度表'

死锁日志:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-11-09 13:09:42 0x7f8e4379e700
*** (1) TRANSACTION:
TRANSACTION 29466672039, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 2
MySQL thread id 18422134, OS thread handle 140514547791616, query id 73544985667 XXXX XXXX update
insert into
            ep_lucky_bag_activity_user_task_schedule (user_id, task_id, rate, use_rate)
        values
            ('pqtjtyylwovtrqjghpkwrtpy972s6641', 'ab67f3ed87b141f29132a5f94ce50233', 1, 0)
        on duplicate key update
            rate = rate + 1,
            use_rate = use_rate + 0,
            update_time = now()
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466672039 lock_mode X waiting
*** (2) TRANSACTION:
TRANSACTION 29466671925, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 4
MySQL thread id 18422851, OS thread handle 140248994146048, query id 73544985718 XXX XXX update
insert into
            ep_lucky_bag_activity_user_task_schedule (user_id, task_id, rate, use_rate)
        values
            ('pqtjtyylwovtrqjghpkwrtpy972s6641', '7e8fb79190fd435cad78242bf090a1b1', 1, 0)
        on duplicate key update
            rate = rate + 1,
            use_rate = use_rate + 0,
            update_time = now()
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466671925 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466671925 lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (1)

事务隔离级别为读已提交。

二、insert on duplicate key update语句读已提交隔离级别的加锁分析

通过个人网上学习 + 简单实验得出,不保证正确。

  1. 判断插入的间隙有没有gap锁。
    有gap锁:加插入意向锁并等待,直到该间隙的gap锁释放。之后进入第2步。
    没有gap锁:直接进入第2步。

  2. 判断是否有唯一键冲突
    有冲突:
    (1)如果该唯一键被加了隐式锁,隐式锁会升级为记录锁,锁被实际插入这条记录的事务持有。
    (2)获取唯一键的next-key锁(先获取gap锁,再获取记录锁)后,执行更新

    没有冲突:插入记录,加隐式锁。

三、死锁时事务执行的SQL

_ 事务29466671925 事务29466672039
1 insert ('qpt...', 'ab6...') on duplicate key update _
2 _ insert ('qpt...', 'ab6...') on duplicate key update
3 insert ('qpt...', '7e8...') on duplicate key update -> 死锁 _

四、日志加锁分析

(1) TRANSACTION:
insert into
            ep_lucky_bag_activity_user_task_schedule (user_id, task_id, rate, use_rate)
        values
            ('pqtjtyylwovtrqjghpkwrtpy972s6641', 'ab67f3ed87b141f29132a5f94ce50233', 1, 0)
        on duplicate key update
            rate = rate + 1,
            use_rate = use_rate + 0,
            update_time = now()
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466672039 lock_mode X waiting

事务29466672039在等待唯一索引('qpt...', 'ab6...')的X型next-key锁。

(2) TRANSACTION:
insert into
            ep_lucky_bag_activity_user_task_schedule (user_id, task_id, rate, use_rate)
        values
            ('pqtjtyylwovtrqjghpkwrtpy972s6641', '7e8fb79190fd435cad78242bf090a1b1', 1, 0)
        on duplicate key update
            rate = rate + 1,
            use_rate = use_rate + 0,
            update_time = now()
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466671925 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1316 page no 5085 n bits 288 index udx_user_id_task_id of table `XXX`.`ep_lucky_bag_activity_user_task_schedule` trx id 29466671925 lock_mode X locks gap before rec insert intention waiting

事务29466671925持有了唯一索引('qpt...', 'ab6...')的的X型记录锁,
在等待获取唯一索引('qpt...', 'ab6...')前的插入意向锁。

五、死锁时事务持有锁分析

_ 事务29466671925 事务29466672039
1 insert ('qpt...', 'ab6...') on duplicate key update _
2 持有('qpt...', 'ab6...')的隐式锁 _
3 _ insert ('qpt...', 'ab6...') on duplicate key update
4 ('qpt...', 'ab6...')的隐式锁升级为X型记录锁 _
5 _ 获取('qpt...', 'ab6...')的X型gap锁
6 _ 获取('qpt...', 'ab6...')的X型记录锁失败,进入等待
7 insert ('qpt...', '7e8...') on duplicate key update _
8 要插入的('qpt...', '7e8...') 间隙被加了gap锁,加插入意向锁并等待gap锁释放。两个事务持有对方想要的锁,等待对方持有的锁,死锁出现 _

六、解决方式

查看业务代码后,发现事务29466671925在insert ('qpt...', 'ab6...')时就可以把事务提交了,就不会出现这个死锁问题。修改代码后上线,死锁告警不再出现。

七、总结

1.有些时候,光靠死锁日志很难分析出死锁出现的原因,需要结合业务逻辑分析。例如这次的死锁日志显示,两个事务插入唯一键不同的记录,却互相持有并等待同一唯一键的锁,刚看到死锁日志时是挺懵的。
2.写代码时仔细考虑事务覆盖的范围,还是很有用处的。例如这次死锁就是因为事务没有及时提交造成的。

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