Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MySQL 升级到 8.0 变慢问题分析

MySQL 升级到 8.0 变慢问题分析

原创
作者头像
爱可生开源社区
发布于 2023-05-11 10:02:36
发布于 2023-05-11 10:02:36
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

1. 背景介绍

前段时间,客户线上 MySQL 版本从 5.7.29 升级到 8.0.25。

升级完成之后,放业务请求进来,没到一分钟就开始出现慢查询,然后,慢查询越来越多,业务 SQL 出现堆积。

整个过程持续了大概一个小时,直到给某条业务 SQL 对应的表加上索引,问题才得到解决。

有一个比较奇怪的现象是:问题持续的过程中,服务器的系统负载、CPU 使用率、磁盘 IO、网络都处于低峰时期的水平,也就是说,问题很可能不是因为硬件资源不够用导致的。

那么,根本原因到底是什么?让我们一起来揭晓答案~

2. 原因分析

客户线上环境有一个监控脚本,每分钟执行一次,这个脚本执行的 SQL 如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select ... from sys.innodb_lock_waits w 
inner join information_schema.innodb_trx b
  on b.trx_id = w.blocking_trx_id 
inner join information_schema.innodb_trx r
  on r.trx_id = w.waiting_trx_id;

对几个监控脚本的日志、SAR 日志、MySQL 的慢查询日志 & 错误日志,以及死锁的源码,进行了全方位无死角的分析,发现了可疑之处。

经过测试验证,最终确认罪魁祸首是 sys.innodb_lock_waits 视图引用的某个基表

这个基表的名字和 MySQL 5.7 中不一样了,它的行为也发生了变化,就是这个行为的变化在某些场景下阻塞了业务 SQL,导致大量业务 SQL 执行变慢。

揭露这个罪恶的基表之前,我们先来看一下 sys.innodb_lock_waits 视图的定义:

  • MySQL 5.7 中简化的视图定义
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CREATE VIEW sys.innodb_lock_waits AS
  SELECT ... FROM information_schema.innodb_lock_waits w
  JOIN information_schema.innodb_trx b
    ON b.trx_id = w.blocking_trx_id
  JOIN information_schema.innodb_trx r
    ON r.trx_id = w.requesting_trx_id
  JOIN information_schema.innodb_locks bl
    ON bl.lock_id = w.blocking_lock_id
  JOIN information_schema.innodb_locks rl
    ON rl.lock_id = w.requested_lock_id
  ORDER BY r.trx_wait_started
  • MySQL 8.0 中简化的视图定义
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CREATE VIEW sys.innodb_lock_waits (...) AS
  SELECT ... FROM performance_schema.data_lock_waits w 
  JOIN information_schema.INNODB_TRX b 
    ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID
  JOIN information_schema.INNODB_TRX r
    ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID
  JOIN performance_schema.data_locks bl
    ON bl.ENGINE_LOCK_ID = w.BLOCKING_ENGINE_LOCK_ID
  JOIN performance_schema.data_locks rl
    ON rl.ENGINE_LOCK_ID = w.REQUESTING_ENGINE_LOCK_ID 
  ORDER BY r.trx_wait_started

5.7 中 sys.innodb_lock_waits 涉及 3 个基表:

  • information_schema.innodb_lock_waits
  • information_schema.innodb_locks
  • information_schema.innodb_trx

8.0 中 sys.innodb_lock_waits 也涉及 3 个基表:

  • performance_schema.data_lock_waits
  • performance_schema.data_locks
  • information_schema.INNODB_TRX

揭晓答案:引发问题的罪魁祸首就是 8.0 中的 performance_schema.data_locks 表。

从两个版本的视图定义对比可以看到,performance_schema.data_locks 的前身是 information_schema.innodb_locks

我们再来看看这两个表的行为有什么不一样?

MySQL 5.7 中,information_schema.innodb_locks 包含这些数据:

  • InnoDB 事务已申请但未获得的锁。
  • InnoDB 事务已持有并且阻塞了其它事务的锁。

官方文档描述如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
The INNODB_LOCKS table provides information
about each lock that an InnoDB transaction
has requested but not yet acquired, 
and each lock that a transaction holds
that is blocking another transaction.

MySQL 8.0 中,performance_schema.data_locks 包含这些数据:

  • InnoDB 事务已申请但未获得的锁。
  • InnoDB 事务正在持有的锁。

官方文档描述如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
The data_locks table
shows data locks held and requested

从官方文档的描述可以看到两个表的不同之处

  • 5.7 的 innodb_locks 记录 InnoDB 事务已持有并且阻塞了其它事务的锁。
  • 8.0 的 data_locks 记录 InnoDB 事务正在持有的锁。

正是因为这个不同之处,导致 8.0 的 data_locks 表的数据量可能会非常大。

我们再深挖一层,看看 data_locks 表的数据量大是怎么导致其它业务 SQL 阻塞的。

MySQL 线程读取 data_locks 表时,会持有全局事务对象互斥量(trx_sys->mutex),直到读完表中的所有数据,才会释放这个互斥量。

实际上,直到读完表中的所有数据,才会释放 trx_sys->mutex 互斥量的说法不准确。 为了避免展开介绍读取 data_locks 表实现逻辑,我们暂且使用这个说法。

data_locks 表的数据量越大,从表里读取数据花费的时间就越长,读取这个表的线程持有 trx_sys->mutex 互斥量的时间也就越长。

从 data_locks 表里读取数据的线程长时间持有 trx_sys->mutex 互斥量会有什么问题?

这个问题就大了,因为 trx_sys->mutex 互斥量非常吃香。

涉及 InnoDB 的所有 SQL 都在事务中运行,每个事务启动成功之后,都需要加入全局事务链表,而全局事务链表需要 trx_sys->mutex 互斥量的保护。

也就是说,InnoDB 中每个事务加入全局事务链表之前,都需要持有 trx_sys->mutex 互斥量。

从 data_locks 表里读取数据的线程长时间持有 trx_sys->mutex 互斥量,就会长时间阻塞其它 SQL 执行,导致其它 SQL 排队等待,出现堆积,表现出来的状态就是 MySQL 整体都变慢了。

介绍清楚逻辑之后,我们回归现实,来看看客户线上的问题。

1. 背景介绍小节中提到的那条业务 SQL 在执行过程中会对 300 万条记录加锁。

这条 SQL 只要执行一次,事务结束之前,data_locks 表中会有 300 万条加锁记录。

从 data_locks 表中读取记录之前,需要持有 trx_sys->mutex 互斥量,再读取 300 万条记录,最后释放互斥量。互斥量释放之前,其它业务 SQL 就得排队等着这个互斥量。

监控脚本执行一次的过程中,一堆业务 SQL 只能排队等待 trx_sys->mutex 互斥量,然后到了周期执行时间,监控脚本又执行了一次,也在等待 trx_sys->mutex 互斥量,不幸的是,又来了一堆业务 SQL。

就这样,监控脚本和业务 SQL 相互影响,恶性循环,SQL 执行越来越慢...,直到 DBA 在 1. 背景介绍小节中提到的那条业务 SQL 对应的表上创建了一个索引。

在那个表上创建索引之后,那条业务 SQL 执行过程中就不需要对 300 万条记录加锁了,而是只会对少量记录加锁,data_locks 表中的数据量也就变的很少了,不需要长时间持有 trx_sys->mutex 互斥量,消除了堵点,MySQL 整体就变的通畅了。

3. 测试验证

在 MySQL 5.7 和 8.0 的 test 库中都创建 t1 表,事务隔离级别为:READ-COMMITTED。

表结构如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CREATE TABLE `t4` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `e1` enum('长春', '沈阳', '福州', '成都', '杭州', '南昌', '苏州', '德清', '北京') NOT NULL DEFAULT '北京',
  `i1` int unsigned NOT NULL DEFAULT '0',
  `c1` char(11) DEFAULT '',
  `d1` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

数据如下:

3.1 MySQL 5.7 测试

第 1 步,在 session 1 中执行一条 SQL,锁住全表记录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test.t1
    -> for update;
+----+--------+-------+---------------+------------+
| id | e1     | i1    | c1            | d1         |
+----+--------+-------+---------------+------------+
|  1 | 长春   | 99999 | 1 测试char    | 1760407.11 |
|  2 | 沈阳   |     2 | 2 测试char    | 3514530.95 |
|  3 | 福州   |     3 | 3 测试char    | 2997310.90 |
|  4 | 成都   |     4 | 4 测试char    | 8731919.55 |
|  5 | 杭州   |     5 | 5 测试char    | 2073324.31 |
|  6 | 南昌   |     6 | 6 测试char    | 3258837.89 |
|  7 | 苏州   |     7 | 7 测试char    | 2735011.35 |
|  8 | 德清   |     8 | 8 测试char    |  145889.60 |
|  9 | 杭州   |     9 | 9 测试char    | 2028916.63 |
| 10 | 北京   |    10 | 10 测试char   | 3222960.80 |
+----+--------+-------+---------------+------------+
10 rows in set (0.00 sec)

第 2 步,在 session 2 中,执行另一条 SQL:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select * from test.t1
    -> where id >= 5
    -> for update;

第 3 步,session 2 的 SQL 等待获取锁的过程中,在 session 3 中查询锁的情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select * from information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 263231            | 263231:473:3:6    | 263229          | 263229:473:3:6   |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.04 sec)

mysql> select
    ->  lock_id, lock_trx_id, lock_table, lock_data
    -> from information_schema.innodb_locks;
+----------------+-------------+-------------+-----------+
| lock_id        | lock_trx_id | lock_table  | lock_data |
+----------------+-------------+-------------+-----------+
| 263231:473:3:6 | 263231      | `test`.`t1` | 5         |
| 263229:473:3:6 | 263229      | `test`.`t1` | 5         |
+----------------+-------------+-------------+-----------+
2 rows in set, 1 warning (0.01 sec)

从 innodb_lock_waits 的查询结果可以看到,事务 263231 申请持有锁被事务 263229 阻塞了。

innodb_locks 表中有 2 条记录:

  • lock_trx_id = 263231, lock_data = 5 的记录表示事务 263231 正在申请对 id = 5 的记录加锁。
  • lock_trx_id = 263229,lock_data = 5 的记录表示事务 263229 正在持有 id = 5 的记录上的锁,阻塞了事务 263231 对 id = 5 的记录加锁。

这和官方文档对 innodb_locks 表的行为的描述一致(前面已介绍过)。

3.2 MySQL 8.0 测试

第 1 步,在 session 1 中执行一条 SQL,锁住全表记录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test.t1
    -> for update;
+----+--------+-------+---------------+------------+
| id | e1     | i1    | c1            | d1         |
+----+--------+-------+---------------+------------+
|  1 | 长春   | 99999 | 1 测试char    | 1760407.11 |
|  2 | 沈阳   |     2 | 2 测试char    | 3514530.95 |
|  3 | 福州   |     3 | 3 测试char    | 2997310.90 |
|  4 | 成都   |     4 | 4 测试char    | 8731919.55 |
|  5 | 杭州   |     5 | 5 测试char    | 2073324.31 |
|  6 | 南昌   |     6 | 6 测试char    | 3258837.89 |
|  7 | 苏州   |     7 | 7 测试char    | 2735011.35 |
|  8 | 德清   |     8 | 8 测试char    |  145889.60 |
|  9 | 杭州   |     9 | 9 测试char    | 2028916.63 |
| 10 | 北京   |    10 | 10 测试char   | 3222960.80 |
+----+--------+-------+---------------+------------+
10 rows in set (0.00 sec)

第 2 步,在 session 2 中,执行另一条 SQL:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select * from test.t1
    -> where id >= 5
    -> for update;

第 3 步,session 2 的 SQL 等待获取锁的过程中,在 session 3 中查询锁的情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select
    ->   engine_transaction_id as trx_id,
    ->   lock_status, lock_data
    -> from performance_schema.data_locks
    -> where lock_type = 'RECORD';
+--------+-------------+-----------+
| trx_id | lock_status | lock_data |
+--------+-------------+-----------+
|  19540 | WAITING     | 5         |
|  19522 | GRANTED     | 1         |
|  19522 | GRANTED     | 2         |
|  19522 | GRANTED     | 3         |
|  19522 | GRANTED     | 4         |
|  19522 | GRANTED     | 5         |
|  19522 | GRANTED     | 6         |
|  19522 | GRANTED     | 7         |
|  19522 | GRANTED     | 8         |
|  19522 | GRANTED     | 9         |
|  19522 | GRANTED     | 10        |
+--------+-------------+-----------+
11 rows in set (0.00 sec)

从以上查询结果可以看到,data_locks 表里包含事务 19522 正在持有的 10 把锁(对应 10 条锁记录),以及事务 19539 已申请但未获得的 id = 5 的记录上的锁,这个行为也和官方文档的描述一致(前面介绍过)。

4. 总结

performance_schema.data_locks 表会记录所有事务正在持有的锁,如果某些 SQL 写的有问题,锁定记录非常多,这个表里的锁记录数量就会非常多。

data_locks 表里的锁记录数量非常多,读取这个表的线程就会长时间持有 trx_sys->mutex 互斥量,这会阻塞其它 SQL 执行。

如果只想要获取锁的阻塞情况,可以查询 performance_schema.data_lock_waits。

本文关键字:#MySQL# #升级# #慢查询#

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
故障分析 | 查询 ps.data_locks 导致 MySQL hang 住
跑批执行到 insert into t1 select * from t2 时,有一个定时任务运行 MySQL 巡检脚本,巡检脚本执行到 select * from performance_schema.data_locks、select * from performance_schema.data_lock_waits 会导致 MySQL hang,一开始只是某些 SQL 执行无响应,最终 MySQL 无法登录。
爱可生开源社区
2024/09/25
2870
故障分析 | 查询 ps.data_locks 导致 MySQL hang 住
应用示例荟萃 | performance_schema全方位介绍(中)
表级锁对应的instruments(wait/lock/table/sql/handler)默认启用,对应的consumers表为performance_schema.table_handles在setup_consumers只受全局配置项global_instrumentation控制,默认启用。所以,默认情况下只需要设置系统配置参数performance_schema=ON即可,下面我们通过一个示例演示如何找出谁持有表级锁
老叶茶馆
2020/11/26
4550
应用示例荟萃 | performance_schema全方位介绍(上)
经过前面6个篇幅的学习,相信大家对什么是performance_schema,已经初步形成了一个整体认识,但我想很多同行看完之前的文章之后可能还是一脸懵逼,今天就为大家带来performance_schema系列的最后一个篇章(全系共7个篇章),在这一期里,我们将为大家列举数十个performance_schema应用示例。下面,请跟随我们一起开始performance_schema系统的学习之旅吧。
沃趣科技
2018/07/02
1.4K0
应用示例荟萃 | performance_schema全方位介绍(上)
MySQL 8.0:Performance Schema 中锁相关的表
在MySQL 8.0 中,Performance Schema 已经成为监控和分析数据库锁状态的首选方法。 在本文中,我们将探讨Performance Schema中与锁相关的表,并通过实例介绍如何使用这些表来发现当前会话的锁、识别哪些锁被阻塞、以及确定谁持有锁。
用户1278550
2024/01/25
1.6K0
MySQL 8.0:Performance Schema 中锁相关的表
InnoDB如何快速杀掉堵塞会话的思考
我们在运维MySQL的过程中,肯定多多少少遇到过Innodb row lock的问题,如果在线上遇到我们可能会看到一大片的session处于堵塞状态通常我们在show processlist中会看到如下:
老叶茶馆
2021/01/12
1.1K0
InnoDB如何快速杀掉堵塞会话的思考
MySQL的锁
MySQL的锁包括服务器级别的锁,存储引擎级别的锁,及互斥锁。服务器级别的锁包括表锁和元数据锁,存储引擎的锁是行级别的锁,由InnoDB引擎控制。互斥锁是低级别的锁,适用于内部的资源,用于同步低级别代码的操作,确保一次只有一个线程能够访问,例如,日志文件、自增列的计数器,及InnoDB buffer pool的互斥。
MySQLSE
2023/12/13
2090
MySQL的锁
Mysql 数据库 超时和锁定
昨天项目中遇到部分服务一直是pending状态,排查了代码和重启了服务都没能解决问题,于是从数据库开始排查。
chuchur
2022/10/25
5.4K0
一次死锁的过程分析和MySQL8.0版本记录了更完整的死锁日志
事务隔离级别为RC读已提交MySQL版本为 5.7.26先介绍一下数据表情况,因为涉及到公司内部真实的数据,所以以下都做了模拟,但不会影响具体的分析。
老叶茶馆
2020/06/24
2.2K1
mysql锁表和解锁语句_db2查看是否锁表
锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是Mysql在服务器层和存储引擎层的的并发控制。
全栈程序员站长
2022/09/25
3.7K0
mysql锁表和解锁语句_db2查看是否锁表
MySQL的insert会阻塞update?
某银行客户在从Oracle迁移到MySQL的开发中,MySQL在READ-COMMITTED隔离级别下,出现了insert阻塞update的情况,但同样的情况下,Oracle的insert则不会阻塞update。本文通过复现该问题,分析MySQL的锁信息,确认是MySQL与Oracle在并发事务处理上的差异,在进行数据库迁移改造的程序开发应予以关注。
bisal
2023/04/06
2.2K0
Innodb加锁信息查看
mysql提供一套INNODB监控机制,用于周期性(每15钞)输出INNODB运行相关状态(INNODB运行状态、表空间状态、表状态等)到mysqld服务标准错误输出。另外,INNODB标准监控和锁监控,也可以通过命令:show engine innodb status输出到控制台。
十毛
2021/07/27
1.3K0
MySQL 死锁的详细分析方法
用数据库的时候,偶尔会出现死锁,针对我们的业务系统,出现死锁的直接结果就是系统卡顿、客户找事儿,所以我们也在想尽全力的消除掉数据库的死锁。出现死锁的时候,如果只是想解锁,用show full processlist看下kill掉就好了,如果想查找到详细的问题,一个办法是用show engine innodb status来查看简略信息或者开死锁日志,后期在MySQL日志里面慢慢分析。以上这写方法我们都用过,最近在看Innodb的书的时候发现另一种实时的分析方法,能最大限度的分析死锁的原因。
星哥玩云
2022/08/17
6000
MySQL Cases-MySQL找出谁持有行锁(RR)
一个事务所做的修改,对其他事务是不可见的,好似是串行执行的。多个事务并行执行的,好似他是串行执行的,事务并发执行,但是效果和串行效果一致,一个事务所做的修改对其他事务是不可见的,好似是串行执行的。
姚崇
2021/08/30
1.5K0
innodb存储引擎锁的实现
通常,我们在95%以上的MySQL使用场景中,从一定程度上来讲,就是在使用InnoDB存储引擎,很多时候我们选择使用InnoDB存储引擎的原因,就是因为它支持高并发,而高并发的实现很大程度上得益于细粒度的锁实现(行级锁),不仅如此,MySQL 还支持更多灵活多变的锁类型,例如:按照锁类型可分为共享锁(S锁)和排它锁(X锁),按照锁范围可分为记录锁(record lock)、间隙锁(gap lock)、next-key lock。以及一些其他特殊用途的锁。这些锁在不同的隔离级别下存在着不同的表现形式。尤其在RR隔离级别下锁的类型最为丰富、灵活多变。下面我们将为大家一一介绍这些锁的含义和用途。
沃趣科技
2018/08/20
1.2K0
innodb存储引擎锁的实现
万字硬核实战分析MySQL死锁
本文先完整介绍MySQL的各种锁类型及加锁机制,之后通过一个案例带大家了解如何分析排查死锁问题。最后,再介绍几种预防死锁的方法。以下是示例表的表结构
会玩code
2022/04/24
1K0
万字硬核实战分析MySQL死锁
InnoDB数据锁–第2部分“锁”
在InnoDB Data Locking –第1部分“简介”中,我们通过同时编辑电子表格的比喻描述了锁能够解决的难题。虽然通过比喻可以获得直观的感觉,但是我们需要将解决方案与现实进行匹配。在这篇文章中,将讨论我们之前看到的语句如何映射到InnoDB的表,行,锁,锁队列等实际情况,例如“ Alice请求对文件A的读取访问,但必须等待Basil首先释放其写权限”。
MySQLSE
2020/10/29
1.1K0
MySQL FAQ 系列 :如何查看当前最新事务 ID
该文介绍了如何查看 MySQL 事务日志,包括查看事务日志位置、查看事务日志内容、使用 SQL 查询事务日志、利用 Percona 分支的特性查看当前最新事务 ID,以及通过查看事务日志排查性能问题等。
叶金荣
2017/05/15
4.9K0
MySQL锁等待与死锁问题分析
在 MySQL 运维过程中,锁等待和死锁问题是令各位 DBA 及开发同学非常头痛的事。出现此类问题会造成业务回滚、卡顿等故障,特别是业务繁忙的系统,出现死锁问题后影响会更严重。本篇文章我们一起来学习下什么是锁等待及死锁,出现此类问题又应该如何分析处理呢?
MySQL技术
2021/04/13
2.2K3
故障分析 | 一则 MySQL 从节点 hung 死问题分析
近期,发现一个 MySQL 从节点提示同步异常。执行 show replica status 都被挂起。
爱可生开源社区
2024/04/11
5390
故障分析 | 一则 MySQL 从节点 hung 死问题分析
MySQL/MariaDB的锁超详细讲解
使用begin或者start transaction来显式开启一个事务,显式开启的事务必须使用commit或者rollback显式提交或回滚。几种特殊的情况除外:行版本隔离级别下的更新冲突和死锁会自动回滚。
星哥玩云
2022/08/16
1.3K0
MySQL/MariaDB的锁超详细讲解
推荐阅读
相关推荐
故障分析 | 查询 ps.data_locks 导致 MySQL hang 住
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验