记一次mysql死锁问题的排查

最近在工作中遇到一些死锁的问题,所以简单研究了一下后,写下一篇文章分享一下。

1.如何查看看mysql中出现的死锁?

通过show engine innodb status 查看的日志是最新一次记录死锁的日志。

通过查看死锁日志可以看到如下格式的日志

------------------------

LATEST DETECTED DEADLOCK

------------------------

2020-08-27 10:59:02 0x7f0a2a44d700

*** (1) TRANSACTION:

TRANSACTION 40124869845, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)

MySQL thread id 23518445, OS thread handle 139681814148864, query id 159005367992 127.0.0.1 table updating

/*id:da7f25fb*/delete from table

    where A = 1537169 and D = 10

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 3053 page no 264692 n bits 392 index idx_deal of table `database`.`table` trx id 40124869845 lock_mode X waiting

*** (2) TRANSACTION:

TRANSACTION 40124869844, ACTIVE 0 sec inserting

mysql tables in use 1, locked 1

3 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1

MySQL thread id 23515886, OS thread handle 139681635555072, query id 159005367991 10.27.218.212 table update

/*id:a4f70f31*/insert into table (A, B, C, D)

    VALUES  (1537167, 150539097,2427520, 10)

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 3053 page no 264692 n bits 392 index idx_deal of table `database`.`table` trx id 40124869844 lock mode S

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 3053 page no 264692 n bits 392 index idx_deal of table `database`.`table` trx id 40124869844 lock_mode X locks gap before rec insert intention waiting

*** WE ROLL BACK TRANSACTION (1)

------------

TRANSACTIONS

------------

2.如何读懂死锁日志

TRANSACTION 40124869845, ACTIVE 0 sec starting index read

事务编号为 40124869845 ,活跃0秒,starting index read 表示事务状态为根据索引读取数据。常见的其他状态:

fetching rows 表示事务状态在row_search_for_mysql中被设置,表示正在查找记录。

updating or deleting 表示事务已经真正进入了Update/delete的函数逻辑(row_update_for_mysql)

thread declared inside InnoDB 说明事务已经进入innodb层。通常而言 不在innodb层的事务大部分是会被回滚的。

(1)WAITING FOR THIS LOCK TO BE GRANTED: 表示事务(1)正在等待lock_mode X waiting 此处就是X锁的排它锁。

(2) HOLDS THE LOCK(S):表示事务40124869844 持有的lock mode s锁(S锁),同时(2) WAITING FOR THIS LOCK TO BE GRANTED:表示事务(2)正在等到lock_mode X locks gap before rec insert intention waiting锁也就是插入意向锁。

3.mysql死锁日志中出现的锁解释

3.1基本锁

首先我们要知道对于MySQL有两种常规锁模式

LOCK_S(读锁,共享锁)

LOCK_X(写锁,排它锁)

最容易理解的锁模式,读加共享锁,写加排它锁.

3.2 意向锁(Intention Locks)

InnoDB为了支持多粒度(表锁与行锁)的锁并存,引入意向锁。意向锁是表级锁,可分为意向共享锁(IS锁)和意向排他锁(IX锁)。

事务在请求S锁和X锁前,需要先获得对应的IS、IX锁。

意向锁产生的主要目的是为了处理行锁和表锁之间的冲突,用于表明“某个事务正在某一行上持有了锁,或者准备去持有锁”。

3.3 记录锁(也叫行锁)

记录锁(Record Locks)

记录锁, 仅仅锁住索引记录的一行。

单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的。

3.4 间隙锁(Gap Lock)

区间锁, 仅仅锁住一个索引区间(开区间)。

在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。

3.5 next-key锁(Next-Key Locks)

record lock + gap lock, 左开右闭区间。

默认情况下,innodb使用next-key locks来锁定记录。

但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。

3.6插入意向锁(Insert Intention Locks)

Gap Lock中存在一种插入意向锁(Insert Intention Lock),在insert操作时产生。在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。

假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。

锁之间的冲突与兼容

4.insert加锁过程

简单的insert会在insert的行对应的索引记录上加一个排它锁,这是一个record lock,并没有gap,所以并不会阻塞其他session在gap间隙里插入记录。

不过在insert操作之前,还会加一种锁,官方文档称它为insertion intention gap lock,也就是意向的gap锁。这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock。

假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。

假设发生了一个唯一键冲突错误,那么将会在重复的索引记录上加读锁(lock s)。当有多个session同时插入相同的行记录时,如果另外一个session已经获得该行的排它锁,那么将会导致死锁。

5.insert死锁场景分析

创建一张表

CREATE TABLE `aa` (

`id` int(10) unsigned NOT NULL COMMENT '主键',

`name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',

`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',

`stage` int(11) NOT NULL DEFAULT '0' COMMENT '关卡数',

PRIMARY KEY (`id`),

UNIQUE KEY `udx_name` (`name`),

KEY `idx_stage` (`stage`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表数据
事务执行顺序

如果T1未rollback,而是commit的话,T2和T3会报唯一键冲突:ERROR 1062 (23000): Duplicate entry ‘6’ for key ‘PRIMARY’。

事务T1 提交以后等到的死锁日志如下:

------------------------

LATEST DETECTED DEADLOCK

------------------------

2020-08-28 12:28:34 700000a3f000

*** (1) TRANSACTION:

TRANSACTION 36831, ACTIVE 17 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1

MySQL thread id 38, OS thread handle 0x700000b0b000, query id 953 localhost root update

insert into t values (4,5)

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 25 page no 4 n bits 72 index `idx_b` of table `test`.`t` trx id 36831 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000016; asc ;;

1: len 4; hex 8000000b; asc ;;

*** (2) TRANSACTION:

TRANSACTION 36832, ACTIVE 13 sec inserting

mysql tables in use 1, locked 1

3 lock struct(s), heap size 360, 2 row lock(s)

MySQL thread id 39, OS thread handle 0x700000a3f000, query id 954 localhost root update

insert into t values (4,5)

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 25 page no 4 n bits 72 index `idx_b` of table `test`.`t` trx id 36832 lock_mode X locks gap before rec

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000016; asc ;;

1: len 4; hex 8000000b; asc ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 25 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 36832 lock mode S locks rec but not gap waiting

Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0

0: len 4; hex 80000004; asc ;;

1: len 6; hex 000000008fdf; asc ;;

2: len 7; hex 8d000001d00110; asc ;;

3: len 4; hex 80000005; asc ;;

*** WE ROLL BACK TRANSACTION (2)

死锁成因分析:

事务T1成功插入记录,并获得索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP)。

紧接着事务T2、T3也开始插入记录,请求排他插入意向锁(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION);但由于发生重复唯一键冲突,前文提到过,如果插入出现重复键冲突时,各自请求的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP)转成共享记录锁(LOCK_S | LOCK_REC_NOT_GAP)。此时T2和T3分别持有了不冲突的lock s 的共享记录锁。

T1回滚释放索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),T2和T3都要请求索引id=6上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP)。

由于X锁与S锁互斥,对于T2来讲,需要获取id=6 的排它锁,由于T3此时获取了id=6 的S锁,所以T2要等到T3把锁释放了,同样的道理T3要等T2把S锁释放了。T2和T3都等待对方释放S锁。于是,死锁便产生了。

如果此场景下,只有两个事务T1与T2或者T1与T3,则不会引发如上死锁情况产生。

最后:打一个小广告,后续的文章会在微信公众号“程序员之家QAQ”推送,欢迎大家搜索关注~~

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