故障分析 | MySQL:5.6 大事务 show engine innodb status 故障一例

做者:高鹏(网名八怪)
文章末尾有他著做的《深刻理解 MySQL 主从原理 32 讲》,深刻透彻理解 MySQL 主从,GTID 相关技术知识。
本文来源:转载自公众号-老叶茶馆,
(做者简书: https://www.jianshu.com/p/40b...
*爱可生开源社区出品,原创内容未经受权不得随意使用,转载请联系小编并注明来源。

背景

今天遇到一个朋友的线上问题,大概意思就是说,我有一个线上的大事务大概 100G 左右,正在作回滚,当前看起来彷佛影响了线上的业务,而且回滚很慢,是否能够减轻对线上业务的影响。而且朋友已经取消了双 1 设置,可是没有任何改观。版本:MySQL 5.6
首先咱们须要知道的是,MySQL 并不适合大事务,大概列举一些 MySQL 中大事务的影响:php

  • binlog 文件做为一次写入,会在 sync 阶段消耗大量的 IO,会致使全库 hang 主,状态大多为 query end。
  • 大事务会形成致使主从延迟。
  • 大事务可能致使某些须要备份挂起,缘由在于 flush table with read lock,拿不到 MDL GLOBAL 级别的锁,等待状态为 Waiting for global read lock。
  • 大事务可能致使更大 Innodb row 锁加锁范围,致使 row 锁等待问题。
  • 回滚困难。

基于如上一些不彻底的列举,咱们应该在线上尽量的避免大事务。好了咱们下面来进行问题讨论。html

分析

1、问题

前面已经说了,咱们已经取消了双 1 设置,所谓的双 1 就是 sync_binlog=1 和 innodb_flush_log_at_trx_commit=1。这两个参数线上要保证为 1,前者保证 binlog 的安全,后者保证 redo 的安全,它们在数据库 crash recovery 的时候起到了关键做用,不设置为双 1 可能致使数据丢失。具体的参数含义不作过多讨论。可是这里的问题是即使取消了双 1,没有任何改观,所以彷佛说明 IO问题不是主要瓶颈呢?mysql

下面咱们来看几个截图:ios

  • vmstat 截图

1.jpg

  • iostat 截图

2.jpg

  • top -Hu截图

3.jpg

咱们重点观察 vmstat 的 r 和 b 列发现,IO 队列没有什么问题,而且 wa% 并不大。咱们观察 iostat 中的 %util 和读写数据大小来看问题不大,而且 tps 远没达到极限(SSD 盘)。咱们 top -Hu 能够观察到 %us 不小,而且有线程已经打满了(99.4%CPU)一个 CPU 核。 所以咱们能够将方向转为研究 CPU 瓶颈的产生,但愿可以对问题有帮助,而后从提供的 perf top 中咱们有以下发现:算法

4.jpg

好了咱们将问题先锁定到 lock_number_of_rows_locked 这个函数上。sql

2、函数 lock_number_of_rows_locked 的做用

朋友用的 5.6,可是我这里以 5.7.26 的版本进行描述。而后下一节描述 5.6 和 5.7 算法上的关键差别。不知道你们是否注意过 show engine innodb status 中的这样一个标志:数据库

5.jpg

这个标记就来自函数 lock_number_of_rows_locked,含义为当前事务加行锁的行数。而这个函数包裹在函数 lock_print_info_all_transactions 下面,lock_print_info_all_transactions 函数是打印咱们一般看到 show engine innodb status 中事务部分的核心参数。咱们来看一下简单的流程:安全

PrintNotStarted print_not_started(file);//创建一个结构体,目的是作not start 事务的打印
ut_list_map(trx_sys->mysql_trx_list, print_not_started); //这个地方打印出那些事务状态是no start的事务。mysql_trx_list是全事务。

consttrx_t* trx;
TrxListIterator trx_iter; //这个迭代器是trx_sys->rw_trx_list 这个链表的迭代器
consttrx_t* prev_trx = 0;

/* Control whether a block should be fetched from the buffer pool. */
bool load_block = true;
bool monitor = srv_print_innodb_lock_monitor && (srv_show_locks_held != 0);

while((trx = trx_iter.current()) != 0) { //经过迭代器进行迭代 ,显然这里不会有只读事务的信息,所有是读写事务。

...
/* If we need to print the locked record contents then we
need to fetch the containing block from the buffer pool. */
if(monitor) {

/* Print the locks owned by the current transaction. */
TrxLockIterator& lock_iter = trx_iter.lock_iter();

if(!lock_trx_print_locks( //打印出锁的详细信息
file, trx, lock_iter, load_block))

简单的说就是先打印哪些处于 not start 的事务,而后打印那些读写事务的信息,固然咱们的回滚事务确定也包含在其中了,须要注意的是只读事务 show engine 不会打印。对于处于回滚状态的事务咱们能够在 show engine 中观察到以下信息:函数

6.jpg

函数 trx_print_low 能够看到大部分的信息,这里就不详细解释了。既然如此咱们须要明白 lock_number_of_rows_locked 是如何计算的,下面进行讨论。工具

3、函数 lock_number_of_rows_locked 的算法变化

上面咱们说了函数 lock_number_of_rows_locked 函数会打印出当前事务加行锁的行数。那么咱们来看一下 5.6 和 5.7 算法的不一样。

5.7.26

实际上只有以下一句话:

return(trx_lock->n_rec_locks);

咱们能够看到这是返回了一个计数器,而这个计数器的递增就是在每行记录加锁后完成的,在函数 lock_rec_set_nth_bit 的末尾能够看到 ++lock->trx->lock.nreclocks,所以这是一种预先计算的机制。所以这样的计算代价很低,也不会因为某个事务持有了大量的锁,而致使计算代价太高。

5.6.22

随后我翻了一下 5.6.22 的代码,发现彻底不一样以下:

for(lock= UT_LIST_GET_FIRST(trx_lock->trx_locks); //使用for循环每一个获取的锁结构
lock!= NULL;
lock= UT_LIST_GET_NEXT(trx_locks, lock)) {
if(lock_get_type_low(lock) == LOCK_REC) { //过滤为行锁
ulint n_bit;
ulint n_bits = lock_rec_get_n_bits(lock);

for(n_bit = 0; n_bit < n_bits; n_bit++) {//开始循环每个锁结构的每个bit位进行统计
if(lock_rec_get_nth_bit(lock, n_bit)) {
n_records++;
}
}
}
}

return(n_records);

咱们知道循环自己是一种 CPU 密集型的操做,这里使用了嵌套循环实现。所以若是在 5.6 中若是出现大事务操做了大量的行,那么获取行锁记录的个数的时候,将会出现高耗 CPU 的状况。

4、缘由总结和解决

有了上面的分析咱们很清楚了,触发的缘由有以下几点:

  • MySQL 5.6 版本
  • 有大事务的存在,大概 100G 左右的数据加行锁了
  • 使用了 show engine innodb status

这样当在统计这个大事务行锁个数的时候,就会进行大量的循环操做。从现象上看就是线程消耗了大量的 CPU 资源,而且处于 perf top 的第一位。

知道了缘由就很简单了,找出为频繁使用 show engine innodb status 的监控工具,随后业务所有恢复正常,IO 利用率也上升了以下:

7.jpg

固然若是可以使用更新的版本好比 5.7 及 8.0 版本将不会出现这个问题,能够考虑使用更高版本。分析性能问题须要首先找到性能的瓶颈而后进行集中突破,好比本例中 CPU 资源消耗更加严重。也许解决问题就在一瞬间。

5、其余

最后经过朋友后面查询的 bug 以下:https://bugs.mysql.com/bug.ph... 发现印风(翟卫翔)已经在多年前提出过了这个问题,而且作出了修改意见,而且这个修改意见官方采纳了,也就是上面咱们分析的算法改变。通过印风(翟卫翔)的测试有 bug 中有以下描述:

  • From perf top, function locknumberofrowslocked may occupy more than 20% of CPU sometimes

也就是 CPU 消耗会高达 20%。

下面是 5.7.26 调用栈帧:

8.jpg


最后推荐高鹏的专栏《深刻理解 MySQL 主从原理 32 讲》,想要透彻了解学习 MySQL 主从原理的朋友不容错过。

相关文章
相关标签/搜索