Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >故障分析 | 查询 ps.data_locks 导致 MySQL hang 住

故障分析 | 查询 ps.data_locks 导致 MySQL hang 住

作者头像
爱可生开源社区
发布于 2024-09-25 08:29:55
发布于 2024-09-25 08:29:55
16500
代码可运行
举报
运行总次数:0
代码可运行
作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:[简书 | 轻松的鱼],欢迎讨论。

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

本文约 1500 字,预计阅读需要 8 分钟

1问题描述

MySQL 版本:8.0.26

跑批执行到 insert into t1 select * from t2 时,有一个定时任务运行 MySQL 巡检脚本,巡检脚本执行到 select * from performance_schema.data_locks、select * from performance_schema.data_lock_waits 会导致 MySQL hang,一开始只是某些 SQL 执行无响应,最终 MySQL 无法登录。

2分析过程

1. 开始 hang 时的线程状态

下图标记的两个线程中:

  • 第一个线程完整的 SQL 是 insert into t1 select * from t2
  • 第二个线程完整的 SQL 是 select * from performance_schema.data_lock_waits,这是巡检脚本里的 SQL,上一句是 select * from performance_schema.data_locks
  • 其余线程全部都卡住了

2. 分析堆栈

分析等锁和互斥量的线程持有和正在等待的锁情况如下:

  • Thread 285 持有 LOCK_status,被 Thread 21 持有的 srv_innodb_monitor_mutex 阻塞
  • Thread 21 持有 srv_innodb_monitor_mutex,被未知线程持有的 trx_sys_mutex_enter()trx_sys->mutex 阻塞
  • 大量线程被 Thread 285 持有的 LOCK_status 阻塞
  • 大量线程(包括 insert into..select 和查询 ps.data_lock_wait)阻塞在 trx_sys_mutex_enter()

现在的问题是没有找到哪个线程持有了 trx_sys->mutex 互斥量。

<<< 左右滑动见更多 >>>

3. 本地复现

调用存储过程,当执行到 insert into ... select... 时,另外一个 session 执行 select * from performance_schema.data_locks

反复测试了很多次,后面找到了复现的必要条件:

执行 select * from performance_schema.data_locks 报错内存分配异常:ERROR 3044 (HY000): Memory allocation error: while scanning data_locks table in function rnd_next.

然后才能观察到 insert into ... select 卡住,堆栈显示这个线程在等 trx_sys->mutex

复现截图:

insert into ... select 线程堆栈如下,不过分析所有线程堆栈后仍然找不到谁持有了 trx_sys->mutex

4. 代码分析 trx_sys->mutex 结构

由于堆栈信息里找不到 trx_sys->mutex 互斥锁的持有者,想到的另外一个方法是用 gdb 打印出 trx_sys->mutex 结构,看其中是否有线程 ID 信息。

发现只有 debug 模式下才有 线程 ID 信息,普通模式下没有,因此需要编译一个 debug 版本进行复现,然后用 gdb 打印出 trx_sys->mutex 互斥锁的持有者。

5. debug版本复现

当查询 ps.data_locks 触发内存分配报错后,通过 gdb 打印 insert into 线程的堆栈,卡在了 mutex_enter_inline

然后打印查询 ps.data_locks 的线程堆栈,堆栈是正常的,但是打印 trx_sys->mutex 时发现持有者竟是它自己:

_M_thread 的值转换为 16 进制,就可以用来核对 gdb info thread 输出的的线程号:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gdb) p/x 140316922181376
$2 = 0x7f9e144d7700

6. 合理推测&找证据

经过 debug 版本上复现,发现查询 ps.data_locks 时触发内存分配错误,但是没有释放 trx_sys->mutex 互斥量。

于是合理推测:存在 bug,查询 ps.data_locks 时触发内存分配错误后,不会释放 trx_sys->mutex,导致内部死锁。

找到了这个 bug:https://github.com/mysql/mysql-server/commit/d6be2f8d23b1fe41f10c7147957faf68b117abb2

7. bug 解释

performance_schame.data_locks 的实现中,使用了 C++ try-catch 机制来处理读取 data_locks 记录时内存分配失败的异常情况。

table_data_locks.cc 文件的 table_data_locks::rnd_next 函数中:

代码解释:catch (const std::bad_alloc &) 用来捕获 std::bad_alloc 类型的异常,当 try 块中抛出 std::bad_alloc 异常时,调用 my_error 打印错误信息,并返回 ER_STD_BAD_ALLOC_ERROR,结束执行。

try 模块中的 iterator_done = it->scan(&m_container, true); 是实际执行的业务逻辑代码,it->scan 进行某种迭代扫描操作,结果存储在 iterator_done 变量中。catch 模块捕获的异常就是由这里抛出的。

it->scan 的定义在 p_s.cc 源码文件的 Innodb_data_lock_iterator::scan 函数中,执行逻辑很清晰:

  1. trx_sys_mutex_enter() 先加 trx_sys->mutex 互斥锁
  2. 调用 scan_trx_list 扫码 rw_trx_listmysql_trx_list 两个事务列表
  3. trx_sys_mutex_exit() 释放 trx_sys->mutex 互斥锁

很显然,调用 scan_trx_list 扫描时如果发生 std::bad_alloc 内存异常,会直接被 catch 模块获取,抛出异常结束执行,无法执行到 trx_sys_mutex_exit() 释放互斥锁,导致了 trx_sys->mutex 互斥锁的残留。

这一点可以在 debug 版本复现时打印的 trx_sys->mutex 信息中得到证实:是在 p_s.cc 文件的第 592 行加上的。

3复现步骤

简化的复现步骤如下:

  1. 准备一个小内存的虚拟机,比如 2-4G,方便触发内存分配异常
  2. 造一张 500 万行的表 t1
  3. 执行 begin;select * from t1 for update;
  4. 执行 select * from performance_schema.data_locks; 触发报错 ERROR 3044 (HY000): Memory allocation error: while scanning data_locks table in function rnd_next.
  5. 继续查询 performance_schema.data_lock_waits 会被阻塞。

4结论

该故障成因如下:

  1. 跑批中 INSERT INTO t1 SELECT * FROM t2; 会对 t2 表所有行加 S Lock(原因:RR 隔离级别,并且无法走索引)。t2 表很大,有几亿行,会导致 performance_schema.data_locks 中有几亿个锁记录;
  2. 查询 ps.data_locks 时,由于记录数太多,消耗大量内存,触发内存分配异常。在 ps.data_locks 的实现中,内存分配异常是由 C++ try-catch 机制处理的,但是这个异常处理发生在 mutex lock 和 mutex unlock 之间,导致 mutex 不释放。本次故障就是执行 trx_sys_mutex_enter() 后迭代扫描锁记录时发生了内存分配异常,trx_sys_mutex_exit() 释放互斥锁的操作未执行,残留了下来;
  3. 由于 trx_sys->mutex 互斥锁在 InnoDB 中被大量使用,接下来巡检脚本查询 data_lock_waits 被阻塞,后台 purge 线程、innodb monitor 线程、元数据刷新进程、用户线程执行的业务 SQL 都被阻塞,最终会导致死锁。

5解决方案

  1. 官方在 8.0.37 中修复了这个 bug,可升级到 8.0.37 解决。 修复方式是在 ps.data_locksdata_lock_waits 的实现中去掉了 try-catch 这段代码,防止发生在 mutex lock 和 mutex unlock 之间处理异常,导致 mutex unlock 无法执行残留 mutex。
  2. 不要在行锁很多的情况下查询 ps.data_locks
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
MySQL 升级到 8.0 变慢问题分析
升级完成之后,放业务请求进来,没到一分钟就开始出现慢查询,然后,慢查询越来越多,业务 SQL 出现堆积。
爱可生开源社区
2023/05/11
1.2K0
MYSQL 8 从锁开始 监控你的锁,死锁,死锁的详细信息
MYSQL 中有一个重要的特性就是锁,如何认识到锁的概念对于使用MYSQL有着重要的意义,针对与锁的认识,以及发现我们需要通过MYSQL本身的performance_schema 中的表来了解,不熟悉这一个系列的同学可以去从之前的performance_schema 系列里面去了解performance_schema的日常使用。MYSQL的锁可以从 metadata 和 表锁开始。
AustinDatabases
2022/04/05
2.2K0
MYSQL  8  从锁开始  监控你的锁,死锁,死锁的详细信息
MySQL的锁
MySQL的锁包括服务器级别的锁,存储引擎级别的锁,及互斥锁。服务器级别的锁包括表锁和元数据锁,存储引擎的锁是行级别的锁,由InnoDB引擎控制。互斥锁是低级别的锁,适用于内部的资源,用于同步低级别代码的操作,确保一次只有一个线程能够访问,例如,日志文件、自增列的计数器,及InnoDB buffer pool的互斥。
MySQLSE
2023/12/13
1470
MySQL的锁
故障分析 | 一则 MySQL 从节点 hung 死问题分析
近期,发现一个 MySQL 从节点提示同步异常。执行 show replica status 都被挂起。
爱可生开源社区
2024/04/11
3960
故障分析 | 一则 MySQL 从节点 hung 死问题分析
万字硬核实战分析MySQL死锁
本文先完整介绍MySQL的各种锁类型及加锁机制,之后通过一个案例带大家了解如何分析排查死锁问题。最后,再介绍几种预防死锁的方法。以下是示例表的表结构
会玩code
2022/04/24
9580
万字硬核实战分析MySQL死锁
Innodb加锁信息查看
mysql提供一套INNODB监控机制,用于周期性(每15钞)输出INNODB运行相关状态(INNODB运行状态、表空间状态、表状态等)到mysqld服务标准错误输出。另外,INNODB标准监控和锁监控,也可以通过命令:show engine innodb status输出到控制台。
十毛
2021/07/27
1.2K0
InnoDB数据锁–第2.5部分“锁”(深入研究)
现在,我们将InnoDB数据锁-第2部分“锁”中了解到的所有知识放在一起,进行深入研究:
MySQLSE
2020/12/08
1.4K0
InnoDB数据锁–第2部分“锁”
在InnoDB Data Locking –第1部分“简介”中,我们通过同时编辑电子表格的比喻描述了锁能够解决的难题。虽然通过比喻可以获得直观的感觉,但是我们需要将解决方案与现实进行匹配。在这篇文章中,将讨论我们之前看到的语句如何映射到InnoDB的表,行,锁,锁队列等实际情况,例如“ Alice请求对文件A的读取访问,但必须等待Basil首先释放其写权限”。
MySQLSE
2020/10/29
9980
InnoDB如何快速杀掉堵塞会话的思考
我们在运维MySQL的过程中,肯定多多少少遇到过Innodb row lock的问题,如果在线上遇到我们可能会看到一大片的session处于堵塞状态通常我们在show processlist中会看到如下:
老叶茶馆
2021/01/12
1K0
InnoDB如何快速杀掉堵塞会话的思考
Percona 8.0.30中"show engine innodb status"导致coredump排查及分析
GreatSQL合并Percona-Server 8.0.30的Beta版测试中,QA报了一个crash的bug:
老叶茶馆
2023/09/01
2770
Percona 8.0.30中"show engine innodb status"导致coredump排查及分析
MySQL 8.0:Performance Schema 中锁相关的表
在MySQL 8.0 中,Performance Schema 已经成为监控和分析数据库锁状态的首选方法。 在本文中,我们将探讨Performance Schema中与锁相关的表,并通过实例介绍如何使用这些表来发现当前会话的锁、识别哪些锁被阻塞、以及确定谁持有锁。
用户1278550
2024/01/25
1.2K0
MySQL 8.0:Performance Schema 中锁相关的表
故障分析 | MySQL死锁案例分析
死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。
爱可生开源社区
2022/12/15
8110
应用示例荟萃 | performance_schema全方位介绍(上)
经过前面6个篇幅的学习,相信大家对什么是performance_schema,已经初步形成了一个整体认识,但我想很多同行看完之前的文章之后可能还是一脸懵逼,今天就为大家带来performance_schema系列的最后一个篇章(全系共7个篇章),在这一期里,我们将为大家列举数十个performance_schema应用示例。下面,请跟随我们一起开始performance_schema系统的学习之旅吧。
老叶茶馆
2020/11/26
5880
会话和锁信息查询视图 | 全方位认识 sys 系统库
在上一篇《等待事件统计视图 | 全方位认识 sys 系统库》中,我们介绍了sys 系统库中的等待事件统计视图,本期的内容先给大家介绍会话信息和锁等待信息查询视图,通过这些视图我们可以清晰地知道每个会话正在做什么事情,是否存在锁等待。下面请跟随我们一起开始 sys 系统库的系统学习之旅吧~
沃趣科技
2018/09/04
1.6K0
会话和锁信息查询视图 | 全方位认识 sys 系统库
MySQL锁概述
本文介绍比较重要的一些锁,基于这些锁,对于理解MySQL的其他特性是大有帮助。部分锁例如AUT0-INC Locks有兴趣请自己发掘。
凯哥的Java技术活
2022/07/08
4510
MySQL锁概述
故障分析 | 从 Insert 并发死锁分析 Insert 加锁源码逻辑
即,死锁问题具有业务关联、机制复杂、类型多样等特点,导致当数据库发生死锁问题时,不是那么容易分析。
爱可生开源社区
2023/04/23
1.1K0
mysql锁表和解锁语句_db2查看是否锁表
锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是Mysql在服务器层和存储引擎层的的并发控制。
全栈程序员站长
2022/09/25
3.3K0
mysql锁表和解锁语句_db2查看是否锁表
来,看看MySQL 5.6, 5.7, 8.0的新特性
对于MySQL的历史,相信很多人早已耳熟能详,这里就不要赘述。下面仅从产品特性的角度梳理其发展过程中的里程碑事件。
星哥玩云
2022/08/17
1.6K0
来,看看MySQL 5.6, 5.7, 8.0的新特性
故障分析 | show processlist 引起的性能问题
业务监控发现交易的平均响应时间比之前慢了近一倍,需要排查一下数据库是不是响应慢了。生产MySQL版本为8.0.18,一主3从半同步复制。
爱可生开源社区
2022/07/11
3900
MySQL并发插入导致死锁
某天下午组里有一个对外提供创建租户的接口突然产生了MySQL死锁的报警。 该服务是一个老服务,至少有一年没有人改动过该接口,并且租户这个场景只支持创建和查询,其他能力都不支持。收到报警的一刻,内心充满了疑惑:"这也能死锁?"
公众号 云舒编程
2024/01/25
4050
MySQL并发插入导致死锁
相关推荐
MySQL 升级到 8.0 变慢问题分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验