浅析MySQL死锁检测
MySQL发生死锁时,通过show engine innodb status;命令并不能看到事务中引起死锁的所有SQL语句。
死锁排查起来就比较麻烦,需要查询events_statements_%表,来获取SQL,同时需要对业务也比较熟悉,这样能分析出造成死锁的语句。
本着探究的目的,来看下MySQL死锁检测实现及为何无法打印出触发死锁的所有SQL语句。
Lock bitmap
截取show engine innodb status;命令查询锁信息时一段内容:
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 124 page no 4 n bits 80 index idx_id of table `dhy`.`t` trx id 45909 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 320: len 4; hex 80000002; asc ;;1: len 6; hex 000000000601; asc ;;
space id、page no、n bits(lock_rec_t结构体) 通过这三个可以定位到某一条记录,这里对应的就是heap_no = 2这条。n_bits是一个bitmap,用来记录行记录上是否持有Lock。
lock_t 结构用来描述锁信息的,通过n_bits可以将lock(lock_t类型的变量,下文中都将这样描述)与行记录的对应起来。lock_rec_t结构如下:
struct lock_rec_t {ib_uint32_t space; /*!< space id */ib_uint32_t page_no; /*!< page number */ib_uint32_t n_bits; /*!< number of bits in the lockbitmap; NOTE: the lock bitmap isplaced immediately after thelock struct */ /** Print the record lock into the given output stream@param[in,out] out the output stream@return the given output stream. */std::ostream& print(std::ostream& out) const;
};
n_bits 的大小分配为 : (1+ (记录锁+ 64) / 8) * 8
size大小:
static size_t lock_size(const page_t* page) {ulint n_recs = page_dir_get_n_heap(page);/* Make lock bitmap bigger by a safety margin */return(1 + ((n_recs + LOCK_PAGE_BITMAP_MARGIN) / 8));}
这么大的内存空间分配在lock内存之后的:
ulint n_bytes = size + sizeof(*lock);
mem_heap_t* heap = trx->lock.lock_heap;lock = reinterpret_cast<lock_t*>(mem_heap_alloc(heap, n_bytes));
在lock_rec_set_nth_bit中,设置bitmap位图信息:
lock_rec_set_nth_bit(
/*=================*/lock_t* lock, /*!< in: record lock */ulint i) /*!< in: index of the bit */
{ulint byte_index;ulint bit_index;byte_index = i / 8; //标识第几个字节bit_index = i % 8; //标识字节中的第几位((byte*) &lock[1])[byte_index] |= 1 << bit_index; //将对应byte上相应的bit位设置为1++lock->trx->lock.n_rec_locks;
}
创建好的lock都会被添加到HASH表中,space_no与page_no相同的lock会被分配到同一个HASH桶中
ulint key = m_rec_id.fold();++lock->index->table->n_rec_locks;HASH_INSERT(lock_t, hash, lock_hash_get(m_mode), key, lock);/**@return the "folded" value of {space, page_no} */
ulint fold() const
{return(m_fold);
}
判断lock上bitmap对应的bit位是否存在锁:
UNIV_INLINE
ibool
lock_rec_get_nth_bit( /*=================*/const lock_t* lock, /*!< in: record lock */ulint i) /*!< in: index of the bit */ {const byte* b;if (i >= lock->un_member.rec_lock.n_bits) {return(FALSE);}b = ((const byte*) &lock[1]) + (i / 8); //根据i(也就是heap_no)计算出是lock之后的第几个字节return(1 & *b >> (i % 8)); //判断对应的bit位上是否为1
}
死锁检测
先介绍几个重要点:
变量:
const lock_t* m_wait_lock; // 想持有的锁
const trx_t* m_start // 直译过来是:正在以不兼容模式请求锁定的联接事务,举例说明下比较好理解:
session1 | session2 |
---|---|
begin; | begin; |
lock:a | |
lock:b | |
lock:b | |
lock:a |
m_start 就是对应session2这个事务
ulint heap_no // 记录对应的物理位置号
函数get_first_lock // 获取m_wait_lock对应记录上的第一个lock, 例如上面例子中,就是获取session1中对a这条记录持有的lock信息。
精简后流程如下:
DeadlockChecker::get_first_lock(ulint* heap_no) const
{const lock_t* lock = m_wait_lock;if (lock_get_type_low(lock) == LOCK_REC) {hash_table_t* lock_hash;lock_hash = lock->type_mode & LOCK_PREDICATE? lock_sys->prdt_hash: lock_sys->rec_hash;/* We are only interested in records that match the heap_no. */*heap_no = lock_rec_find_set_bit(lock); // 查找lock对应的heap_no/* Find the locks on the page. */lock = lock_rec_get_first_on_page_addr(lock_hash,lock->un_member.rec_lock.space,lock->un_member.rec_lock.page_no); //找出page上的第一个lock/* Position on the first lock on the physical record.*/if (!lock_rec_get_nth_bit(lock, *heap_no)) { //如果lock的bitmap对应bit位上存在锁,则返回lock,否则查找下一个lock,直到对应bit位上存在锁lock = lock_rec_get_next_const(*heap_no, lock);}} else {/* Table locks don't care about the heap_no. */*heap_no = ULINT_UNDEFINED;dict_table_t* table = lock->un_member.tab_lock.table;lock = UT_LIST_GET_FIRST(table->locks);}return(lock);
}
死锁检测核心函数在DeadlockChecker::search()中,会做以下处理:
- 首先获取m_wait_lock对应记录上的第一个lock
const lock_t* lock = get_first_lock(&heap_no);
- 进入一个循环,这里代码比较多,直接贴代码不太直观,放一张流程图
流程较长,举个例子对照上图看下:
session1 | session2 |
---|---|
begin; | begin; |
lock:a | |
lock:b | |
lock:b //blocking | |
lock:a |
进入死锁检测时:
m_start:session2事务信息
lock:m_wait_lock对应记录上的第一个lock,对应session1对a记录持有的锁
m_wait_lock:session2中对a想要持有的锁
进入循环后,只满足lock->trx->lock.que_state == TRX_QUE_LOCK_WAIT这个条件(也代表m_wait_lock和lock是发生锁等待了),步骤7中将m_wait_lock被赋值后即为session2中对b想要持有的锁,lock变为session2对b持有的锁,再次进入循环。
这时lock->trx == m_start(都为session2),即检测出死锁。如下图所示:
死锁日志
死锁日志只能看到事务中最后一个SQL语句,因为每次执行完语句后m_query_string变量都会被reset_query(),要实现就需要一个SQL语句和lock的对应关系,将每次执行的SQL保留起来。
这块还涉及到死锁日志的一个参数:
- innodb_print_all_deadlocks :会将死锁信息打印到errorlock中,最好将此参数设置下,能够保留死锁日志,方便查看因为show engine innodb status;只会保留最后一个死锁日志的信息,原因是mysql会在tmp目录下创建一个ib开头的临时文件,每次重启后都会重建。
结语
这里梳理了下死锁检测的流程,由于水平有限,文章可能存在不正确地方,望指正。
浅析MySQL死锁检测相关推荐
- mysql死锁检测算法_MySQL InnoDB如何应付死锁
死锁是事务处理型数据库系统的一个经典问题,但是它们并不是很危险的, 除非它们如此地频繁以至于你根本处理不了几个事务. 当因死锁而产生了回滚时,你通常可以在你的应用程序中重新发出一个事务即可. Inno ...
- mysql死锁检测算法_MySQL 8 死锁检测脚本
MySQL 8 记录死锁关的几张表有所变化,重新写一个脚本,便于在出现问题的时候快速处置问题. 死锁示意图 死锁相关的表 information_schema.INNODB_TRX `performa ...
- php 检测死锁,MySQL 死锁检测
对于死锁,MySQL并没有提供提供直接的变量来表示.对于5.5版本之后的performance_shcema可以提供锁的详细信息(但我们还是5.0呢) 对于死锁,MySQL并没有提供提供直接的变量来表 ...
- 如何阅读MySQL死锁日志
现象描述 客户在夜间批量执行数据处理时发生了死锁现象,是由不同的会话并发删除数据引起的,这个问题原因是比较简单,但想通过这个案例让大家熟悉如何去排查死锁问题,如何去阅读死锁日志这才是目的.通过模拟用户 ...
- mysql 死锁监控_mysql 死锁
MySQL复制slave服务器死锁案例 原文:MySQL复制slave服务器死锁案例 MySQL复制刚刚触发了一个bug,该bug的触发条件是slave上Xtrabackup备份的时候执行flushs ...
- mysql死锁介绍以及解决
什么是死锁 死锁是2+个线程在执行过程中, 因争夺资源而造成的相互等待的现象,若无外力作用,它们将无法推进下去. 死锁产生的4个必要条件 互斥条件 指进程对所分配的资源进行排他性使用,即一段时间内某资 ...
- 10、MySQL锁等待,死锁,死锁检测
使用数据库时,有时会出现死锁.对于实际应用来说,就是出现系统卡顿. 死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象.就是所谓的锁资源请求产生了回路现象,即死循环,此时称 ...
- mysql二级封锁协议_MySQL 行锁、两阶段锁协议、死锁以及死锁检测
行锁 MySQL的行锁都是在引擎层实现的,但是 MyISAM 不支持行锁,意味着并发控制只能使用表锁,同一张表任何时刻只能被一个更新在执行,影响到业务并发度.InnoDB 是支持行锁的,这也是 MyI ...
- mysql 命令 kill_MySQL之死锁检测
最近,笔者在查看线上服务日志时,发现spring大量异常,异常中都显示了同样的报错信息,信息如下. Deadlock found when trying to get lock; try restar ...
最新文章
- 2022-2028年中国基因工程药物产业市场研究及前瞻分析报告
- python游戏程序-python游戏程序
- SSM整合pom.xml和导包
- ES6箭头函数中的this指向
- 轻松实现深度Clone | Source Generators方式
- Rsync+Inotify操作文档
- C语言宏定义中#define中的井号#的使用
- Dictionary 泛型类 [转]
- Stream介绍及简单操作!
- 书海拾贝|开发艺术探索之 android 的消息机制
- GET和POST本质区别
- DIRECTSHOW中的视频捕捉
- 解决连接远程服务器MySQL“ACCESS DENIED FOR USER‘ROOT‘@‘IP地址‘“问题
- Datawhale 202210 Excel | 第九、十章 Excel数据可视化
- Android五大数据存储
- 武侠末世(真香游戏V2.0)
- 分布式系统原理-分布式事务方案那么多,到底该选哪一个
- 如何关闭系统自带的屏幕放大125%
- 惠普 High Definition Audio 总线上的调制解调器设备 音频设备 装不上的解决方法
- SAE J2534协议 车辆诊断编程接口