故障分析 | innodb_thread_concurrency 导致数据库异常的问题分析

news2024/11/17 20:28:18

作者通过分析源码定位数据库异常,梳理参数 innodb_thread_concurrency 设置的注意事项。

作者:李锡超

一个爱笑的江苏苏宁银行数据库工程师,主要负责数据库日常运维、自动化建设、DMP 平台运维。擅长 MySQL、Python、Oracle,爱好骑行、研究技术。

  • 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

一、问题现象

研发同学反馈某测试应用系统存在异常,分析应用的错误日志、CPU、内存和磁盘 IO 等指标后,未发现相关异常。请求配合确认数据库运行情况。

关键配置

配置项
数据库版本MySQL 8.0
数据库架构单机
CPU 个数8C
内存16G
参数 innodb_thread_concurrency16
参数 innodb_concurrency_tickets5000

二、初步分析

此类问题,一般是由于 SQL 的效率低下,导致服务器的 CPU、IO 等资源耗尽,然后应用发起新的 SQL 请求,会由于无法获取系统资源,导致 SQL 请求被堵塞。

为此,检查 CPU、IO 等资源,发现 CPU 使用率约 5%,IO 几乎没有压力。登陆数据库检查连接状态,发现很多连接的状态都在 executing。部分结果如下:
在这里插入图片描述

根据上述结果分析:

有 28 个会话状态为 executing,1 个会话状态为 updating。如果这些会话都真正在 executing,CPU 压力应该会很高,但实际情况仅占用很少的 CPU。

1 系统有报错或者某其它异常?

随后,对 MySQL 错误日志、磁盘使用率、磁盘 Inode 使用率、系统 messages 等信息进行确认,都未发现有相关异常!

2 SQL 语句存在特殊性?

对连接中的 SQL 进行了初步分析,发现除了表 t01 所在的 SQL 较为复杂,其它 SQL 都非常简单,且访问的都是数据表(不是视图)。表 t02t03 的数据仅 1 行,应该瞬间执行完成!

由于是测试环境,且问题导致测试阻断,于是执行如下命令收集了诊断数据:

诊断项执行 SQL
连接状态show processlist;
线程状态select * from performance_schema.threads where processlist_info\G
事务信息select * from information_schema.innodb_trx\G
InnoDB statusshow engine innodb status\G
堆栈信息pstack <mysqld-pid>

随后对数据库执行了重启,重启完成后,应用系统恢复正常。

三、堆栈与源码分析

综合收集的信息,对连接状态、线程状态和堆栈信息进行关联分析,发现被堵塞的 29 个连接中,有 13 个都被卡在函数 nanosleep 中,比较奇怪。其堆栈关键信息如下:

#0  in nanosleep from /lib64/libpthread.so.0
#1  in srv_conc_enter_innodb
#2  in ha_innobase::index_read
#3  in ha_innobase::index_first
#4  in handler::ha_index_first
#5  in IndexScanIterator<false>::Read
#6  in Query_expression::ExecuteIteratorQuery
#7  in Query_expression::execute
#8  in Sql_cmd_dml::execute
#9  in mysql_execute_command
#10 in dispatch_sql_command
#11 in dispatch_command
#12 in do_command
#13 in handle_connection

其中 index_read ⼀般是⾸次访问 index,去找 WHERE ⾥的记录。更关键的,看到了 srv_conc_enter_innodb 函数,并由他调用了 nanosleep,执行了类似**“睡眠”** 的操作。为此,结合对应版本的源码进行分析。总结如下:

|-index_read(buf, nullptr, 0, HA_READ_AFTER_KEY) // 入口函数
  |-ret = innobase_srv_conc_enter_innodb(m_prebuilt)
    |-err = DB_SUCCESS
    // STEP-1: 判断 innodb_thread_concurrency 是否为0, 不为0则进一步判断。否则直接返回(即不限制进入innodb的线程数)
    |-if (srv_thread_concurrency): 
      // STEP-2: 判断事务拥有的 ticket(该值初始为:0) 个数是否大于0,如成立则 --ticket,然后返回 DB_SUCCESS 至上层函数;否则继续判断
      |-if (trx->n_tickets_to_enter_innodb > 0):  --trx->n_tickets_to_enter_innodb
      |-else: err = srv_conc_enter_innodb(prebuilt)
        |-return srv_conc_enter_innodb_with_atomics(trx)
          |-for (;;):
            |-ulint sleep_in_us 
            |-if (srv_thread_concurrency == 0): return DB_SUCCESS // 再次判断 innodb_thread_concurrency 是否为0, 满足则直接返回 DB_SUCCESS
            /* STEP-3: 判断进入 innodb 的事务是否小于 innodb_thread_concurrency 。
                如小于(进入innodb):则调整innodb中活动线程个数、标记事务进入了innodb、设置事务的ticket个数,然后返回 DB_SUCCESS 至上层函数; 
            */
            |-if (srv_conc.n_active.load(std::memory_order_relaxed) < srv_thread_concurrency): 
              |-n_active = srv_conc.n_active.fetch_add(1, std::memory_order_acquire) + 1
              |-if (n_active <= srv_thread_concurrency):
                |-srv_enter_innodb_with_tickets(trx): // Note that a user thread is entering InnoDB.
                  |-trx->declared_to_be_inside_innodb = TRUE
                  |-trx->n_tickets_to_enter_innodb = srv_n_free_tickets_to_enter
                |- // 调整 srv_thread_sleep_delay/
                |-return DB_SUCCESS
              |-srv_conc.n_active.fetch_sub(1, std::memory_order_release)
            /* STEP-4: 否则(未进入innodb),执行:
                   a. 设置事务的状态(information_schema.innodb_trx.trx_operation_state)为"sleeping before entering InnoDB"
                   b. 根据 innodb_thread_sleep_delay 设置sleep时间
                   c. 判断 sleep 时间是否超过上限 innodb_adaptive_max_sleep_delay, 如超过则设置睡眠时间为 innodb_adaptive_max_sleep_delay(1.5s)
                   d. 调用 nanosleep 进行指定时间的 sleep
                   e. 设置事务状态为 “”
                   f. 自增 sleep 此时
                   h. 自增睡眠时间 
                   i. 进行下一次for 循环   ------------------ > for
            */
            |-trx->op_info = "sleeping before entering InnoDB"
            |-sleep_in_us = srv_thread_sleep_delay
            |-if (srv_adaptive_max_sleep_delay > 0 && sleep_in_us > srv_adaptive_max_sleep_delay):
              |-sleep_in_us = srv_adaptive_max_sleep_delay
              |-srv_thread_sleep_delay = sleep_in_us
            |-std::this_thread::sleep_for(std::chrono::microseconds(sleep_in_us))
              |-nanosleep
            |-trx->op_info = ""
            |-++n_sleeps
            |-if (srv_adaptive_max_sleep_delay > 0 && n_sleeps > 1):
              |-++srv_thread_sleep_delay
            |-if (trx_is_interrupted(trx)):
              |-return DB_INTERRUPTED
    |-return err
  |-ret = row_search_mvcc(buf, mode, m_prebuilt, match_mode, 0) // 执行查询操作
  |-innobase_srv_conc_exit_innodb(m_prebuilt);
    // STEP-5: 判断是否进入了innodb,且ticket为0(ticket 被耗尽)
    |-if (trx->declared_to_be_inside_innodb && trx->n_tickets_to_enter_innodb == 0):
      |-srv_conc_force_exit_innodb(trx)
        // STEP-6: 标记事务为未进入innodb状态。以避免不必要的函数调用
        |-srv_conc_exit_innodb_with_atomics(trx)
          |-trx->n_tickets_to_enter_innodb = 0
          |-trx->declared_to_be_inside_innodb = FALSE
          |-srv_conc.n_active.fetch_sub(1, std::memory_order_release)

为便于理解,将以上源码逻辑总结为 4 个场景:

  • 场景 1:innodb_thread_concurrency == 0, 执行逻辑:

  • 场景 2:innodb_thread_concurrency != 0、事务拥有 ticket, 执行逻辑:

  • **场景 3:innodb_thread_concurrency != 0、事务没有 ticket、进入 innodb 的事务小于 innodb_thread_concurrency , 执行逻辑: **

  • 场景 4:innodb_thread_concurrency != 0、事务没有 ticket、进入 innodb 的事务大于 innodb_thread_concurrency , 执行逻辑:

根据堆栈信息,收影响的会话都被堵塞在 nanosleep 函数;同时,通过事务信息,看到对应的会话的 ticket 为 0、事务状态为 sleeping before entering InnoDB,与上述场景 4 基本相符。

小结

故障数据库配置 innodb_thread_concurrency=16,问题时刻由于数据库中慢 SQL 持有并发资源,且并发较高(超过 innodb_thread_concurrency),导致其它事务需要进行 nanosleep 以等待 InnoDB 并发资源。

同时,结合源码不难看出,对于慢 SQL,其自身也需要频繁从 innodb 中进出,而当其拥有的 ticket(5000)用完之后,也需要重新进入排队已等待并发资源,导致执行 SQL 性能进一步降低,形成劣性循环。

四、问题解决

问题发生后,已通过重启的方式临时解决。但通过与研发同学的沟通,还存在如下问题:

1 如何根本解决解决问题?

综合以上分析过程,我们可以看到导致此次故障的根本原因就是问题时刻数据库存在慢 SQL,耗尽了 InnoDB 的并发资源,因此需要对问题 SQL 进行优化(由于篇幅有限,不在此讨论)。

此外,测试数据库设置了 innodb_thread_concurrency=16 是导致发生该现象的直接原因。对于该参数设置建议,简要总结如下(完整说明参考 MySQL 官方文档):

  • 如果数据库的活动并发用户线程数小于 64,则设置 innodb_thread_concurrency=0
  • 如果压力一直很重或偶尔出现峰值,首先设置 innodb_thread_concurrency=128,然后将该值降低到 96、80、64,以此类推,直到找到提供最佳性能的线程数;
  • Innodb_thread_concurrency 值过高会导致性能下降,因为这会增加系统内部和资源的争用。

因此,建议将 innodb_thread_concurrency=0 从数据库层面解决。该参数为动态参数,发生问题后可立即修改,并会立即生效,以避免不必要的重启操作。同时,需要尽快对慢 SQL 进行优化,以从根本解决该问题。

2 如何影响到那些本身执行会很快的其它 SQL?

根据源码分析结果:由于耗尽的是 InnoDB 全局并发线程资源,类似于进入 InnoDB “连接” 被耗尽了一样。因此会影响所有的其它线程。

3 影响的会话到底会被堵塞多久?

对于线上系统,当 InnoDB 并发资源被耗尽后,新发起的 SQL 会进入 nanosleep,直至已进入 InnoDB 事务的 ticket 被耗尽后,才有可能进入 InnoDB(而且好像是最后新发起的 SQL 请求,由于 sleep 时间越短,越容易进入)。除非源头的慢 SQL 快速执行完成,但由于慢 SQL 在此状态下,当 ticket 用完后也需要参与排队,因此其执行时间会进一步加长,导致源头 SQL 无法快速完成。因此对于大多数 SQL 请求,都需要参与堵塞,且堵塞的时间会越来越长。问题发生后,建议尽快处理。

4 再次发生后,如何快速确认是该问题?

  • 对于该数据库版本,检查是否大量的数据库会话处于 executing, 且部分会话执行的 SQL 可能非常简单;
  • 检查数据库事务的状态,判断是否有处于 sleeping before entering InnoDB 的事务,且基本满足:
    sleeping before entering InnoDB 的事务个数 = 总的事务个数 - innodb_thread_concurrency
  • 检查 innodb 输出,示例输出结果如下:
--------------
ROW OPERATIONS
--------------
16 queries inside InnoDB, 22 queries in queue
....
----------------------------
  • 根据前面提供的信息采集步骤,保存相关信息,并结合堆栈和源码进行确认。

本文关键字:#MySQL# #源码#

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/554843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【coding加油站】vue单页面手机商城设计

1、引言 设计结课作业,课程设计无处下手&#xff0c;网页要求的总数量太多&#xff1f;没有合适的模板&#xff1f;数据库&#xff0c;java&#xff0c;python&#xff0c;vue&#xff0c;html作业复杂工程量过大&#xff1f;毕设毫无头绪等等一系列问题。你想要解决的问题&am…

【Python】判断语句 ② ( if else 语句 | if else 语句语法 | Python 中的空格缩进 | 代码示例 )

文章目录 一、if else 语句语法二、Python 中的空格缩进三、代码示例 一、if else 语句语法 if else 语句语法 : if 条件判定:满足条件要执行的代码1满足条件要执行的代码2满足条件要执行的代码3 else:不满足条件要执行的代码1不满足条件要执行的代码2不满足条件要执行的代码3…

佳能打印机删掉又会自动加载的原因及解决方案

驱动人生分析出现佳能打印机删掉又会自动加载的原因可能是因为在系统中&#xff0c;存在着佳能打印机的自动驱动程序。将打印机删除后&#xff0c;系统会自动重新安装该驱动程序&#xff0c;导致打印机重新加载。 特别在一台新的佳能打印机设备到位时&#xff0c;也会出现电脑…

2023自动化测试工具还有什么新鲜事?

我们准备了一份详细指南&#xff0c;介绍了在一个好的测试自动化工具中应该寻找什么&#xff0c;以及哪些工具在 2023 年值得考虑。 尽管手动测试仍然是软件质量保证的强大工具&#xff0c;正如我们在最近关于手动测试与自动测试的长期阅读中再次确立的那样&#xff0c;越来越…

1700页,卷S人的 软件测试《八股文》PDF手册,涨薪跳槽拿高薪就靠它了

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

【Linux】权限管理,谁动了我代码?!

目录 一&#xff0c;shell命令以及运行原理 二 &#xff0c;Linux用户权限 1. su —— 用户切换 三&#xff0c;权限管理 1. 理解 2. 用户 3. 文件类型 4. 文件基本权限 5. 设置文件权限方法 1. chmod —— 修改文件访问权限 2. chown —— 修改文件拥有者 3. chg…

2023年5大风口行业

今天就来和大家分享一下&#xff0c;在时代的洪流下&#xff0c;普通人如何顺应大势抓住机遇&#xff01; 实现人在风口上&#xff0c;猪都会飞起来。 根据对市场的观察及各平台数据分析结果&#xff0c;小编总结了了2023年将会迎来大爆发的5个行业&#xff0c;带大家看看新的…

最近公司招人面试了一位5年的测试,一问三不知,最后还反怼我...

最近看了很多简历&#xff0c;很多候选人年限不小&#xff0c;但是做的都是一些非常传统的项目&#xff0c;想着也不能通过简历就直接否定一个人&#xff0c;何况现在大环境越来 越难&#xff0c;大家找工作也不容易&#xff0c;于是就打算见一见。 在沟通中发现&#xff0c;由…

xxl-sso 单点登录

目录 1 项目启动修改Host文件运行路径SSO登录/注销流程验证 2 分析登录流程 单点登录原理及简单实现&#xff1a;https://www.cnblogs.com/ywlaker/p/6113927.html xxl-sso是一款基于redis轻量级分布式高可用的SSO实现组件,支持web端(Cookie实现)和app端(Token实现)两种方式,两…

[WGAN] Wasserstein GAN

看这个解析讲的也挺好的&#xff1a;令人拍案叫绝的Wasserstein GAN - 知乎 1、背景 GAN的训练是delicate和unstable的。需要定义一个连续的距离&#xff0c;来衡量模型distribution和真实distribution之间的差异。 2、贡献 提出了Wasserstein-GAN&#xff0c;用Earth Mover (…

常用五大类RFID系统,实践领域广泛,加强现代化管理

随着信息技术的不断进步&#xff0c;RFID技术已逐渐成为企业管理及社会服务领域中不可或缺的一种重要技术手段。根据其不同的应用场景&#xff0c;RFID技术广泛应用于药品监管、固定资产管理、仓储管理、智慧工厂和消费服务等领域。本文将从五个方面介绍常用的RFID系统。 一、…

Linux Ubuntu配置Anaconda与Python的方法

本文介绍在Linux Ubuntu操作系统的电脑中&#xff0c;安装Anaconda环境与Python语言的方法。 在之前的文章Win10中Anaconda及Python的下载与安装方法&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/122642187&#xff09;中&#xff0c;我们介绍了在Win…

【资料分享】高边、低边晶体管开关及电路解析

高边和低边晶体管开关 电路中&#xff0c;晶体管常常被用来当做开关使用。晶体管用作开关时有两种不同的接线方式&#xff1a;高边&#xff08;high side&#xff09;和低边(low side)。 高边和低边是由晶体管在电路中的位置决定的。晶体管可以是双极性晶体管&#xff08;BJT…

云性能测试的主要意义是什么?

云性能测试是一种基于云计算技术的性能测试方法&#xff0c;其通过在云端部署测试环境和测试工具&#xff0c;将测试结果反馈给用户&#xff0c;从而帮助用户评估系统的稳定性、性能和可靠性&#xff0c;那云性能测试的主要意义是什么&#xff1f; 一、作用 云性能测试可以帮助…

测试跳槽一次涨4k,我5年跳了3次...

最近有人说&#xff0c;现在测试岗位初始工资太低了&#xff0c;有些刚刚入行的程序员朋友说自己工资连5位数都没有.....干了好几年也没怎么涨。看看别人动辄月薪1.5到2万&#xff0c;其实我想说也没那么难。 说下如何高效地拿到2万的工资&#xff0c;总体来说&#xff0c;就靠…

了解预测性维护:从哪些方面入手?

预测性维护&#xff08;Predictive Maintenance&#xff09;是一种基于数据分析和先进技术的维护策略&#xff0c;旨在提前预测设备故障&#xff0c;并在故障发生之前采取适当的维护措施。相比传统的定期维护或纠正性维护&#xff0c;预测性维护能够降低维护成本、提高设备可用…

在行 | 唱响钢铁冶金行业绿色发展进行曲

在行业现场解析行业难题&#xff0c; 用主题方案创新数智价值。 中国是世界第一钢铁冶金大国&#xff0c;钢铁产量接近全球的一半&#xff0c;高产量也带来了高碳排&#xff0c;仅钢铁行业的碳排量&#xff0c;已占全球钢铁碳排放总量的60%以上&#xff0c;占全国碳排放总量的…

DID-M3D 论文学习

1. 解决了什么问题&#xff1f; 单目 3D 检测成本低、配置简单&#xff0c;对一张 RGB 图像预测 3D 空间的 3D 边框。最难的任务就是预测实例深度&#xff0c;因为相机投影后会丢失深度信息。以前的方法大多直接预测深度&#xff0c;本文则指出 RGB 图像上的实例深度不是一目了…

从《流浪地球2》看多团队任务管理

《流浪地球2》作为春节档热门电影&#xff0c;讲述了地球因为太阳系内其他恒星的影响而不断向外逃离&#xff0c;人类必须采取行动拯救地球和自己的故事&#xff0c;是中国科幻电影的里程碑式影片。影片中充满各种科技元素&#xff0c;令人印象深刻&#xff0c;量子计算机550系…

【libcurl 】win32 构建 Release版本 修改cmakelist 链接openssl1.1.*

以下库均已MD的构建以vs2019 V142构建MD构建 直接换用了一个openssl库,libcurl连接报错 $(ProjectDir)..\..\..\3rdparty\openssl\xdw_openssl1_1_1\lib\win32\libcrypto.lib