16 innDB死锁检测

一、准备工作

死锁检测一般采用Wait-For-Graph算法,本例所述实现为递归式(深度优先搜索),后续改为非递归实现(栈)。

参考资料:http://www.gpfeng.com/?p=426

准备两个session如下:

start transaction                                            start transaction;
update nkeys set c5=0 where c1=50000;			
                                             update nkeys set c5=1 where c1=50001;
update nkeys set c5=11 where c1=50001;		
							          update nkeys set c5=10 where c1=50000;
可以看出,session2在执行第二个更新操作时,会发现死锁。

实际上,死锁检测发生在加锁请求无法立即满足需要进入所等待时,lock_deadlock_occurs(它进一步调用lock_deadlock_recursive)被两个函数锁调用:lock_rec_enqueue_waiting和lock_table_enqueue_waiting。所以,session1的第二条语句也会触发死锁检测,因为它需要等待,但是此时还没有死锁存在。


二、lock_deadlock_recursive源码分析

InnoDB在实现Wait-For-Graph时基于性能方面的考虑,定义了两个变量:

LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK(默认200):深度优先遍历的层数超过此值,即认为发生死锁,回滚当前事务。LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK(默认1000000):lock_deadlock_recursive递归调用的次数超过此值,即认为发生死锁,回滚当前事务。

几个关键点:

1、该函数中的start表示顶层调用该函数的事务,在递归过程中,如果出现lock_trx == start,则说明发生死锁。(lock_trx为某递归深度时的事务)

2、该函数中的wait_lock是指该递归深度下在等待的锁,lock是在该递归深度下与wait_lock冲突的锁。

3、注意:在加锁时,会把锁放在哈希表链表中的最后位置,这样在遍历这条链表时,会先发现有冲突(锁了同一行)的锁,而不是自己(如果只发现自己,则说明现在还没有死锁。)

// trx-sys中所有事务trx->deadlock_mark在lock_deadlock_occurs中被置为0
    if (trx->deadlock_mark == 1) {
        /* We have already exhaustively searched the subtree starting from this trx */
        return(0);
    }
 
    // cost 增加1
    *cost = *cost + 1;
 
    // 等待的锁为行锁,找到lock_sys中相同行上的第一个锁
    if (lock_get_type_low(wait_lock) == LOCK_REC) {
 
        space = wait_lock->un_member.rec_lock.space;
        page_no = wait_lock->un_member.rec_lock.page_no;
        lock = lock_rec_get_first_on_page_addr(space, page_no);
 
        /* Position the iterator on the first matching record lock. */
        while (lock != NULL
               && lock != wait_lock
               && !lock_rec_get_nth_bit(lock, heap_no)) {
 
            lock = lock_rec_get_next_on_page(lock);
        }
        // 找到自身,不需要继续找lock_sys中相同行上的下一个锁
        if (lock == wait_lock) {
            lock = NULL;
        }
    } else {  // 等待的锁为表锁
        lock = wait_lock;
    }
 
    /* Look at the locks ahead of wait_lock in the lock queue */
    for (;;) {
        // lock为表锁,定位到table对应表锁链表中lock前一个表锁
        /* Get previous table lock. */
        if (heap_no == ULINT_UNDEFINED) {
            lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock);
        }
        // 没有找到前一个表锁,即lock为最后一个表锁请求,不会造成死锁,返回FALSE
	// 或者,lock_sys中找到相同行上第一个锁lock == wait_lock,不会造成死锁,返回FALSE
	// 死锁检测时,不需要关心是否与之后加的锁有冲突,Wait-For-Graph节点之间是单向关系
        if (lock == NULL) {
            /* We can mark this subtree as searched */
            trx->deadlock_mark = 1;
            return(FALSE);
        }
        // Wait-For-Graph中找到了一条边,需要检查是否死锁
        if (lock_has_to_wait(wait_lock, lock)) {
            // 判断是否调用层次太深,或者调用次数过多
            ibool    too_far
                = depth > LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK
                || *cost > LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK;
 
            lock_trx = lock->trx;
            // Wait-For-Graph中找到了回路,出现死锁!
            if (lock_trx == start) {
                // 判断事务权重,若start权重较小,返回LOCK_VICTIM_IS_START,start将被回滚
                if (trx_weight_ge(wait_lock->trx, start)) {
                        return(LOCK_VICTIM_IS_START);
                }
                // wait_lock->trx权重较小,需要回滚,返回LOCK_VICTIM_IS_OTHER
                lock_deadlock_found = TRUE;
                wait_lock->trx->was_chosen_as_deadlock_victim = TRUE;
                lock_cancel_waiting_and_release(wait_lock);
 
                return(LOCK_VICTIM_IS_OTHER);
            }
            // 调用层次太深,或者调用次数过多,返回LOCK_EXCEED_MAX_DEPTH
            if (too_far) {
                return(LOCK_EXCEED_MAX_DEPTH);
            }
            // 目前还没有出现死锁,继续在Wait-For-Graph寻找可能的边,继续深度搜索
            if (lock_trx->que_state == TRX_QUE_LOCK_WAIT) {
 
                ret = lock_deadlock_recursive(start, lock_trx, lock_trx->wait_lock, cost, depth + 1);
		// 只有当检测出死锁或者发现调用层次(次数)过深(过多)才返回,否则为Wait-For-Graph选择下一个节点
                if (ret != 0) {
                    return(ret);
                }
            }
        }
	// wait_lock与lock没有冲突,继续找lock_sys中相同行上的下一个锁
        /* Get the next record lock to check. */
        if (heap_no != ULINT_UNDEFINED) {//只对于行锁,而不需要考虑表锁
            do {
                lock = lock_rec_get_next_on_page(lock);
            } while (lock != NULL && lock != wait_lock && !lock_rec_get_nth_bit(lock, heap_no));
            // 找到自身,不需要继续找lock_sys中相同行上的下一个锁
            if (lock == wait_lock) {
                lock = NULL;
            }
        }
    }/* end of the 'for (;;)'-loop */

lock_deadlock_recursive返回值的处理:

1. LOCK_VICTIM_IS_OTHER,发生死锁,需要回滚其它事务,但可能多个事务与当前事务发生死锁,因此需要再次调用lock_deadlock_recursive 

2. LOCK_EXCEED_MAX_DEPTH,调用层次(次数)过深(过多),回滚当前事务 

3. LOCK_VICTIM_IS_START,发生死锁,需要回滚当前事务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值