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,发生死锁,需要回滚当前事务

### MySQL 中事务、索引和锁的概念及用法 #### 1. MySQL 事务管理 MySQL 支持 ACID 属性的事务处理,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability)。为了实现这些特性,在 InnoDB 存储引擎下提供了多种级别的锁定机制来控制并发访问。 - **BEGIN/START TRANSACTION**: 显式开启一个新的事务。 - **COMMIT**: 提交当前事务的所有更改并使其永久生效;提交之后不能再回滚该次操作前的状态。 - **ROLLBACK**: 取消未保存的数据变更并将数据库恢复至最近一次成功提交后的状态。 对于长时间运行的大规模数据修改任务,建议分批次执行以减少持有排他锁的时间长度,从而降低与其他会话发生冲突的可能性[^1]。 ```sql -- 开始新事物 START TRANSACTION; -- 执行一系列 SQL 操作... UPDATE table_name SET column='value' WHERE condition; INSERT INTO another_table (columns...) VALUES (...); -- 结束事物并提交更改 COMMIT; ``` #### 2. MySQL 索引优化 合理创建索引能够显著提升查询效率,但也需要注意过度使用可能导致维护成本增加甚至性能下降。以下是几种常见的索引类型及其适用场景: - **主键索引**:用于唯一标识每条记录,默认情况下自动建立于 `AUTO_INCREMENT` 字段上。 - **唯一索引**:确保列内不允许存在重复值,常应用于邮箱地址等需要保持唯一的属性之上。 - **普通索引**:加速特定条件下的检索速度而不施加额外约束。 - **全文索引**:针对大文本字段提供高效的关键词匹配能力。 - **组合索引**:通过多个字段共同构建复合型索引来满足复杂查询需求。 应当谨慎评估业务逻辑实际所需,并定期审查现有索引的有效性和必要性,及时删除不再使用的冗余项[^2]。 ```sql CREATE INDEX idx_column ON table_name(column); ALTER TABLE table_name ADD FULLTEXT(fulltext_index_columns); DROP INDEX index_name ON table_name; ``` #### 3. MySQL 锁定策略 InnoDB 表默认采用行级锁定方式,允许不同客户端同时对同一张表的不同部分实施读取或写入动作而互不干扰。然而,在某些特殊情形下仍可能出现资源争抢现象进而引发死锁错误。为了避免这种情况的发生,除了遵循前述关于批量作业拆解的原则外,还应考虑如下几点: - 尽量缩短单个事务持续周期; - 对涉及多张关联表格的操作按照固定顺序获取相应对象上的锁; - 当检测到潜在风险时主动释放不必要的占用权柄以便让位给更高优先级的任务继续推进工作流程。 此外,适当调整配置参数如 innodb_lock_wait_timeout 来设定最大等待时限也有助于缓解此类问题带来的负面影响[^3]。 ```sql SET SESSION innodb_lock_wait_timeout = 50; -- 设置会话级别超时时间为50秒 SHOW VARIABLES LIKE 'innodb_lock%'; -- 查看有关 InnDB 锁的相关变量设置 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值