Mysql-innodb中死锁检测的代码

本文主要简单记录一下关于mysql innodb引擎中关于死锁检测与处理的代码的阅读。

1. 概述
innodb中检测与处理死锁的代码入口在:
storage/innobase/lock/lock0lock.c 的lock_deadlock_occurs函数 (大约3484行)。
这个函数调用了lock_deadlock_recursive函数 迭代地检查死锁。

当事务尝试获取(请求)加一个锁,并且需要等待时,innodb会开始进行死锁检测。

innodb中对于死锁的判断主要有3个情况:
1. 事务间形成锁的循环等待
2. 在检测死锁过程中检测的事务数超过了 LOCK_MAX_N_STEPS
3. 在检测死锁的过程中迭代的层数超过了 LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK
关于2和3会在后面具体介绍。

检测到死锁后有两种可能的处理:
1. 牺牲当前锁的事务
2. 牺牲检测到死锁时的另一个锁的事务(这个锁等待当前锁)
牺牲的选择是基于事务的weight。 (关于事务weight的计算会在后面介绍)

2. 背景知识
1. 事务的状态
一个事务的状态有4大类:
TRX_STATE_NOT_STARTED,
TRX_STATE_ACTIVE,
  TRX_STATE_PREPARED,                                       
TRX_STATE_COMMITTED_IN_MEMORY
意思很明显, 当事务处于激活(ACTIVE)的状态时,它可能有4种子状态:
TRX_QUE_RUNNING,                             
TRX_QUE_LOCK_WAIT,                         
  TRX_QUE_ROLLING_BACK,                   
  TRX_QUE_COMMITTING                         

TRX_QUE_LOCK_WAIT在死锁中用于判断循环等待。

2. innodb中锁的类型标识位
锁的类型标识位保存在锁结构 “lock_struct” 中的 “ulint    type_mode” 属性中, 在innodb中常叫它bitmap。
它主要使用了12位的字节来表示锁的类型
这12个字节主要包含了4个部分的内容 mode, type, 等待标识 ,和 具体的行锁类型。
Mysql-innodb中死锁检测的代码
上表为mode和type的掩码。 0xFUL是15(1111),用于获取mode。0xF0UL是240(11110000),用于获取type,目前已经定义的type有表锁和行锁2类

前4位为mode mode主要有5个: IS        IX                            AI (用于自增字段)
Mysql-innodb中死锁检测的代码
上表为这5个mode的兼容性关系。

类型之后就是一个bit位的等待标识:
Mysql-innodb中死锁检测的代码
掩码为256(100000000),第9位。
之后为行锁具体的锁类型:
Mysql-innodb中死锁检测的代码
LOCK_ORDINARY 为正常锁,即与gap没有关系的锁。
LOCK_GAP 只锁记录前的gap,阻止对该记录进行修改,包括在该记录前进行插入。
在gap上innodb是允许有冲突的事务。
LOCK_REC_NOT_GAP  锁记录而不锁gap,阻止修改,但允许在该记录前进行插入。
LOCK_INSERT_INTENTION 插入意向锁,一般配合LOCK_GAP使用,当gap上的冲突消失时进行插入。



3. 死锁的检测
lock_deadlock_occurs函数调用lock_deadlock_recursive 来迭代地检测死锁。
lock_deadlock_recursive检测的过程大概是:
1. 首先把待判断的锁设置为初始事务start等待的新锁
2. 如果要待判断的锁是行锁的话,从第一个开始循环取该行锁所在页中的锁为锁B;表锁的话跳到步骤6
3. 如果待判断的锁是否等待这个锁B
4. 如果等待, 并且锁B的事务是初始事务start,说明产生死锁了,要判定是牺牲这个锁B的事务还是牺牲新锁的事务start
5. 如果等待,并且迭代深度或总消耗超过了上限(每次调用lock_deadlock_recursive时消耗加1)时,也判定发生死锁
6. 如果等待,判断这个锁B是否处于等待其他锁的状态
7. 如果处于等待其他锁的状态,那么迭代地调用lock_deadlock_recursive 来判断这个锁是否等待该页上的其他锁,同时迭代层数加1。 这样迭代判断是否最终会形成循环等待。
8. 如果是表锁,那么这次迭代就没有死锁

Mysql-innodb中死锁检测的代码
上图lock_deadlock_recursive是迭代的主函数。 start为初始事务, wait_lock为待判断锁, cost和depth为累积的消耗和迭代深度。
Mysql-innodb中死锁检测的代码
如果是行锁的话把变量lock设置为页上的第一个锁,如果是表锁的话就直接把lock设置为待判断的锁。
Mysql-innodb中死锁检测的代码
开始循环, 首先判断变量lock是否已经等于待判定锁(表锁,或页中其他所有的行锁都判断完了),那么没有死锁发生。
Mysql-innodb中死锁检测的代码
接着判断待判断的锁是否等待lock变量指定的锁。 如果等待,首先判断是否迭代深度或总消耗是否已经达到上限。 接着判断lock的事务是否是初始事务,如果是,进入死锁处理。
Mysql-innodb中死锁检测的代码
如果lock的事务是否不是初始事务, 并且总消耗和迭代深度已经达到上限,也按死锁处理了。
Mysql-innodb中死锁检测的代码
如果lock的事务是否不是初始事务,接着递归调用去判断是否有循环等待。
Mysql-innodb中死锁检测的代码
最后取页中的下一个锁,进入下一个循环。

4. 判断锁1是否等待锁2
对于锁的判断主要在lock_has_to_wait函数(1017行)
Mysql-innodb中死锁检测的代码
如果锁1与锁2 属于同一个事务,或者两个锁的mode之间没有冲突的话,那么锁之间没有等待关系。否则调用lock_rec_has_to_wait函数来判断等待。
Mysql-innodb中死锁检测的代码
判断的主要把所有非等待的情况列出来:
1. 如果锁1类型为gap 并且没有LOCK_INSERT_INTENTION
2. 如果锁1类型不是LOCK_INSERT_INTENTION,而锁2是gap
3. 如果锁1类型是gap,而锁2类型不是gap
4. 如果锁2是LOCK_INSERT_INTENTION
那么锁1 不需要 等待锁2, 否则锁1需要等待锁2

5. 死锁牺牲者的判断
死锁的处理主要在lock_deadlock_occurs中。 即判断牺牲哪个事务。
入口函数为trx_weight_ge (storage/innobase/trx/trx0trx.c  1479行)。 把较轻的那个事务判定为牺牲者。
Mysql-innodb中死锁检测的代码

Mysql-innodb中死锁检测的代码

Mysql-innodb中死锁检测的代码
一个事务的weight的计算是基于修改的行数与锁定的行数来计算的。

6. 总结
innodb中的死锁判断主要作用与一个页内的行锁, 这也与数据库i/o以页为单位进行操作相一致。 所以innodb的行锁更多的是从事务的层面上而言, 具体到底层其实也是锁整个页。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值