2022-10-27 347
前几天跟一位朋友分析了一个死锁问题,所以有了这篇图文详细的博文,哈哈~
发生死锁了,如何排查和解决呢?本文将跟你一起探讨这个问题
数据库隔离级别:
mysql>select@@tx_isolation; +-----------------+ |@@tx_isolation| +-----------------+ |REPEATABLE-READ| +-----------------+ 1rowinset,1warning(0.00sec)
自动提交关闭:
mysql>setautocommit=0; QueryOK,0rowsaffected(0.00sec) mysql>select@@autocommit; +--------------+ |@@autocommit| +--------------+ |0| +--------------+ 1rowinset(0.00sec)
表结构:
//id是自增主键,name是非唯一索引,balance普通字段 CREATETABLE`account`( `id`int(11)NOTNULLAUTO_INCREMENT, `name`varchar(255)DEFAULTNULL, `balance`int(11)DEFAULTNULL, PRIMARYKEY(`id`), KEY`idx_name`(`name`)USINGBTREE )ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8;
表中的数据:
开启两个终端模拟事务并发情况,执行顺序以及实验现象如下:
1)事务A执行更新操作,更新成功
mysql>updateaccountsetbalance=1000wherename='Wei'; QueryOK,1rowaffected(0.01sec)
2)事务B执行更新操作,更新成功
mysql>updateaccountsetbalance=1000wherename='Eason'; QueryOK,1rowaffected(0.01sec)
3)事务A执行插入操作,陷入阻塞~
mysql>insertintoaccountvalues(null,'Jay',100);
这时候可以用
select*frominformation_schema.innodb_locks;
查看锁情况:
4)事务B执行插入操作,插入成功,同时事务A的插入由阻塞变为死锁error。
mysql>insertintoaccountvalues(null,'Yan',100); QueryOK,1rowaffected(0.01sec)
在分析死锁日志前,先做一下锁介绍,哈哈~
主要介绍一下兼容性以及锁模式类型的锁:
共享锁与排他锁
InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)。
如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:
如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容。
意向锁
比如:事务1在表1上加了S锁后,事务2想要更改某行记录,需要添加IX锁,由于不兼容,所以需要等待S锁释放;如果事务1在表1上加了IS锁,事务2添加的IX锁与IS锁兼容,就可以操作,这就实现了更细粒度的加锁。
InnoDB存储引擎中锁的兼容性如下表:
记录锁(Record Locks)
记录锁的事务数据(关键词:lock_mode X locks rec butnotgap),记录如下:
RECORDLOCKSspaceid58pageno3nbits72index`PRIMARY`oftable`test`.`t` trxid10078lock_modeXlocksrecbutnotgap Recordlock,heapno2PHYSICALRECORD:n_fields3;compactformat;infobits0 0:len4;hex8000000a;asc;; 1:len6;hex00000000274f;asc'O;; 2:len7;hexb60000019d0110;asc;;
间隙锁(Gap Locks)
间隙锁的事务数据(关键词:gap before rec),记录如下:
RECORDLOCKSspaceid177pageno4nbits80indexidx_nameoftable`test2`.`account` trxid38049lock_modeXlocksgapbeforerec Recordlock,heapno6PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len3;hex576569;ascWei;; 1:len4;hex80000002;asc;;
Next-Key Locks
插入意向锁(Insert Intention)
事务数据类似于下面:
RECORDLOCKSspaceid31pageno3nbits72index`PRIMARY`oftable`test`.`child` trxid8731lock_modeXlocksgapbeforerecinsertintentionwaiting Recordlock,heapno3PHYSICALRECORD:n_fields3;compactformat;infobits0 0:len4;hex80000066;ascf;; 1:len6;hex000000002215;asc";; 2:len7;hex9000000172011c;ascr;;...
锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):
show engine innodb status
可以用 show engine innodb status,查看最近一次死锁日志哈~,执行后,死锁日志如下:
0x243c ***(1)TRANSACTION: TRANSACTION38048,ACTIVE92secinserting mysqltablesinuse1,locked1 LOCKWAIT4lockstruct(s),heapsize1136,4rowlock(s),undologentries2 MySQLthreadid53,OSthreadhandle2300,queryid2362localhost::1rootupdate insertintoaccountvalues(null,'Jay',100) ***(1)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid177pageno4nbits80indexidx_nameoftable`test2`.`account` trxid38048lock_modeXlocksgapbeforerecinsertintentionwaiting Recordlock,heapno6PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len3;hex576569;ascWei;; 1:len4;hex80000002;asc;; ***(2)TRANSACTION: TRANSACTION38049,ACTIVE72secinserting,threaddeclaredinsideInnoDB5000 mysqltablesinuse1,locked1 5lockstruct(s),heapsize1136,4rowlock(s),undologentries2 MySQLthreadid52,OSthreadhandle9276,queryid2363localhost::1rootupdate insertintoaccountvalues(null,'Yan',100) ***(2)HOLDSTHELOCK(S): RECORDLOCKSspaceid177pageno4nbits80indexidx_nameoftable`test2`.`account` trxid38049lock_modeXlocksgapbeforerec Recordlock,heapno6PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len3;hex576569;ascWei;; 1:len4;hex80000002;asc;; ***(2)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid177pageno4nbits80indexidx_nameoftable`test2`.`account` trxid38049lock_modeXinsertintentionwaiting Recordlock,heapno1PHYSICALRECORD:n_fields1;compactformat;infobits0 0:len8;hex73757072656d756d;ascsupremum;; ***WEROLLBACKTRANSACTION(1)
我们如何分析以上死锁日志呢?
第一部分
1)找到关键词TRANSACTION,事务38048
2)查看正在执行的SQL
insertintoaccountvalues(null,'Jay',100)
3)正在等待锁释放(WAITING FOR THIS LOCK TO BE GRANTED),插入意向排他锁(lockmode X locks gap before rec insert intention waiting),普通索引(idxname),物理记录(PHYSICAL RECORD),间隙区间(未知,Wei);
第二部分
1)找到关键词TRANSACTION,事务38049
2)查看正在执行的SQL
insertintoaccountvalues(null,'Yan',100)
3)持有锁(HOLDS THE LOCK),间隙锁(lockmode X locks gap before rec),普通索引(index idxname),物理记录(physical record),区间(未知,Wei);
4)正在等待锁释放(waiting for this lock to be granted),插入意向锁(lockmode X insert intention waiting),普通索引上(index idxname),物理记录(physical record),间隙区间(未知,+∞);
5)事务1回滚(we roll back transaction 1);
查看日志结果
查看日志可得:
这里面,有些朋友可能有疑惑,
我们接下来一小节详细分析一波,一个一个问题来~
死锁死循环四要素
事务A持有什么锁呢?它又想拿什么样的插入意向排他锁呢?
为了方便记录,例子用W表示Wei,J表示Jay,E表示Eason哈~
我们先来分析事务A中update语句的加锁情况~
updateaccountsetbalance=1000wherename='Wei';
间隙锁:
记录锁
Next-Key锁
综上所述,事务A执行完update更新语句,会持有锁:
我们再来分析一波事务A中insert语句的加锁情况
insertintoaccountvalues(null,'Jay',100);
间隙锁:
插入意向锁(Insert Intention)
因此,事务A的update语句和insert语句执行完,它是持有了 (E,W]的 Next-Key锁,(W,+∞)的Gap锁,想拿到 (E,W)的插入意向排它锁,等待的锁跟死锁日志是对上的,哈哈~
事务B拥有了什么间隙锁?它为什么也要拿插入意向锁?
同理,我们再来分析一波事务B,update语句的加锁分析:
updateaccountsetbalance=1000wherename='Eason';
间隙锁:
记录锁
Next-Key锁
综上所述,事务B执行完update更新语句,会持有锁:
我们再来分析一波B中insert语句的加锁情况
insertintoaccountvalues(null,'Yan',100);
间隙锁:
插入意向锁(Insert Intention)
所以,事务B的update语句和insert语句执行完,它是持有了 (-∞,E]的 Next-Key锁,(E,W)的Gap锁,想拿到 (W,+∞)的间隙锁,即插入意向排它锁,加锁情况跟死锁日志也是对上的~
死锁真相还原
接下来呢,让我们一起还原死锁真相吧~哈哈~
最后,遇到死锁问题,我们应该怎么分析呢?
原文链接:https://77isp.com/post/9846.html
=========================================
https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。
数据库技术 2022-03-28
网站技术 2022-11-26
网站技术 2023-01-07
网站技术 2022-11-17
Windows相关 2022-02-23
网站技术 2023-01-14
Windows相关 2022-02-16
Windows相关 2022-02-16
Linux相关 2022-02-27
数据库技术 2022-02-20
抠敌 2023年10月23日
嚼餐 2023年10月23日
男忌 2023年10月22日
瓮仆 2023年10月22日
簿偌 2023年10月22日
扫码二维码
获取最新动态