为什么MySQL没有负载,但交易却跑不动?

news/2025/1/31 2:38:30/

在这里插入图片描述
在MySQL的数据库中,我们有时会发现MySQL数据库明明没有负载,CPU、硬盘、内存和网络等资源都很空闲,但很多SQL都pending在哪儿,MySQL数据库无法处理交易。这是怎么回事呢?

在数据库系统中出现这种情况通常是锁竞争造成的,MySQL数据库更加容易出现这种情况,因为它的存储层和服务层是分开的,我们来看锁竞争在MySQL 5.7和8.0里的表现和解决办法。

5.7版本

查询锁竞争

在 MySQL 8.0 之前,必须SET GLOBAL innodb_status_output_locks=ON后才能在SHOW ENGINE INNODB STATUS中查到数据锁,例如下面这个事务:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from t1 where intcol1=0 for update;
...
900 rows in set (0.00 sec)

在SHOW ENGINE INNODB STATUS中查到

---TRANSACTION 7827, ACTIVE 11 sec
222 lock struct(s), heap size 24784, 5720 row lock(s)
MySQL thread id 9912, OS thread handle 139967683151616, query id 11123 localhost root
TABLE LOCK table `mysqlslap`.`t1` trx id 7827 lock mode IX
RECORD LOCKS space id 25 page no 37 n bits 1056 index in_1 of table `mysqlslap`.`t1` trx id 7827 lock_mode X...

修改了900行,却锁住了5720行。查询space id为25对应的对象:

mysql> select * from information_schema.INNODB_SYS_DATAFILES where space=25;
+-------+--------------------+
| SPACE | PATH               |
+-------+--------------------+
|    25 | ./mysqlslap/t1.ibd |
+-------+--------------------+
1 row in set (0.00 sec)

在另外一个session里执行

mysql> update t1 set intcol1=1 where intcol1=0;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

查询锁的情况


mysql> select * from information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 7829              | 7829:25:4:2       | 7827            | 7827:25:4:2      |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)mysql> select * from information_schema.innodb_locks;
+-------------+-------------+-----------+-----------+------------------+-----------------+------------+-----------+----------+----------------+
| lock_id     | lock_trx_id | lock_mode | lock_type | lock_table       | lock_index      | lock_space | lock_page | lock_rec | lock_data      |
+-------------+-------------+-----------+-----------+------------------+-----------------+------------+-----------+----------+----------------+
| 7829:25:4:2 | 7829        | X         | RECORD    | `mysqlslap`.`t1` | GEN_CLUST_INDEX |         25 |         4 |        2 | 0x000000000200 |
| 7827:25:4:2 | 7827        | X         | RECORD    | `mysqlslap`.`t1` | GEN_CLUST_INDEX |         25 |         4 |        2 | 0x000000000200 |
+-------------+-------------+-----------+-----------+------------------+-----------------+------------+-----------+----------+----------------+
2 rows in set, 1 warning (0.00 sec)

查询阻塞的线程:

SELECT b.trx_mysql_thread_id             AS 'blocked_thread_id' ,b.trx_query                      AS 'blocked_sql_text' ,c.trx_mysql_thread_id             AS 'blocker_thread_id',c.trx_query                       AS 'blocker_sql_text',( Unix_timestamp() - Unix_timestamp(c.trx_started) ) AS 'blocked_time' 
FROM   information_schema.innodb_lock_waits a INNER JOIN information_schema.innodb_trx b ON a.requesting_trx_id = b.trx_id INNER JOIN information_schema.innodb_trx c ON a.blocking_trx_id = c.trx_id 
WHERE  ( Unix_timestamp() - Unix_timestamp(c.trx_started) ) > 4; 
+-------------------+-----------------------------------------+-------------------+------------------+--------------+
| blocked_thread_id | blocked_sql_text                        | blocker_thread_id | blocker_sql_text | blocked_time |
+-------------------+-----------------------------------------+-------------------+------------------+--------------+
|              9921 | update t1 set intcol1=1 where intcol1=0 |              9917 | NULL             |          782 |
+-------------------+-----------------------------------------+-------------------+------------------+--------------+
1 row in set, 1 warning (0.00 sec)

根据线程号查询执行的SQL

SELECT a.sql_text, c.id, d.trx_started 
FROM   performance_schema.events_statements_current a join performance_schema.threads b ON a.thread_id = b.thread_id join information_schema.processlist c ON b.processlist_id = c.id join information_schema.innodb_trx d ON c.id = d.trx_mysql_thread_id 
where c.id=9917
ORDER  BY d.trx_started\G
*************************** 1. row ***************************sql_text: select * from t1 where intcol1=0 for updateid: 9917
trx_started: 2023-05-26 13:24:59
1 row in set (0.00 sec)

注意这里查询出的SQL是阻塞事务的最后一条SQL,并不一定是阻塞的源头。

解决锁竞争

解决方法是针对where中的条件增加索引,使MySQL服务层的过滤能在存储层完成,例如

mysql> create index in_1 on t1(intcol1);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0mysql> show create table t1\G
*************************** 1. row ***************************Table: t1
Create Table: CREATE TABLE `t1` (`intcol1` int(32) DEFAULT NULL,`intcol2` int(32) DEFAULT NULL,`charcol1` varchar(128) DEFAULT NULL,`charcol2` varchar(128) DEFAULT NULL,`charcol3` varchar(128) DEFAULT NULL,KEY `in_1` (`intcol1`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

再锁住同样的行

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from t1 where intcol1=0 for update;
...
900 rows in set (0.00 sec)

在SHOW ENGINE INNODB STATUS中查到

---TRANSACTION 7841, ACTIVE 15 sec
155 lock struct(s), heap size 24784, 1801 row lock(s)
MySQL thread id 9917, OS thread handle 139966608627456, query id 11227 localhost root
TABLE LOCK table `mysqlslap`.`t1` trx id 7841 lock mode IX
RECORD LOCKS space id 25 page no 37 n bits 1056 index in_1 of table `mysqlslap`.`t1` trx id 7841 lock_mode X...

被锁住的记录从之前的5720条减少到1801条。

有索引后执行计划也不同,加索引之前的执行计划

mysql> explain select * from t1 where intcol1=0 for update;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 6143 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

加了索引后的执行计划是:

mysql> explain select * from t1 where intcol1=0 for update;
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | in_1          | in_1 | 5       | const |  900 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

对比一下可以发现后者的Extra字段中没有“Using where”,因为过滤再存储层已经完成了。

生产中可以先 select 出 主键id,再用 主键id 去 update

8.0版本

从 MySQL 8.0 开始,performance_schema.data_locks显示 InnoDB 数据锁。具体参见拙作《MySQL 8.0运维于优化》第18章第3节“优化索引”。


http://www.ppmy.cn/news/1185744.html

相关文章

[Linux C] signal 的使用

前言: signal 是一种通信机制,可以跨进程发送,可以同进程跨线程发送,可以不同进程向指定线程发送。 信号的创建有两套api,一个是signal,一个是sigaction,signal缺陷很多,比如没有提…

【python数据结构】DFS

# 695&#xff1a;最大岛屿面积 class Solution:def maxAreaOfIsland(self, grid: List[List[int]]) -> int:m, n len(grid), len(grid[0])def dfs(x,y):if x < 0 or y < 0 or x > m or y > n or grid[x][y] ! 1:return 0grid[x][y] 0ans 1for idx, idy in …

ElasticSearch 统计搜索热词

实际开发中,我们会统计某个模块下的搜索热词,这个在elasticsearch中特别好用,也比较简单, 使用可以使用 "terms aggregation" 来统计热词 terms 是代表的elasticSerach中的Term Query,统计的就是Term Query, Term Query是一种最基本的查询方式,它用于在Ela…

半导体设备:碳化硅气浮导轨

碳化硅陶瓷气浮导轨 碳化硅陶瓷的特点&#xff1a; 1、硬度仅次于金刚石&#xff1b;耐久性更好&#xff1b; 2、膨胀系数小&#xff1b;精度更可靠&#xff1b; 3、强度高&#xff1b; 1、非接触式气浮导轨结构 2、高直线度平面度指标 3、行程可达 1m,可定制 4、微动步…

yum 常见错误解决方案

虚拟机内&#xff0c;yum 下载时出现以下错误的解决方案 1. “Couldn’t resolve host ‘mirrors.zju.edu.cn’” Trying other mirror. 出现这个错误的原因是没有配置 DNS 服务器 cd 进入 /etc 目录&#xff0c;vim 进入 resolv.conf &#xff0c;在文件内添加配置 namese…

UG\NX二次开发 获取当前NX所有打开的部件tag、名称​

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 感谢粉丝订阅 感谢 人间小说家 订阅本专栏,非常感谢。 简介 当打开一个部件或者新建一个部件时,必须检测当前的窗口是否已经打开的同名组件。有两种方法可以避免。 方法1:先获取窗口中的所有部件名,打开新的部件时,…

[SHCTF 2023新生赛] web题解

文章目录 [WEEK1]babyRCE1zzphpez_serialize登录就给flag飞机大战方法一方法二 ezphp生成你的邀请函吧~ [WEEK2]serializeno_wake_upMD5的事就拜托了Hashpumphash_ext_attack脚本 ez_sstiEasyCMS [WEEK3]sseerriiaalliizzeegogogo [WEEK1] babyRCE 源码 <?php$rce $_GE…

DC电源模块如何承受超负荷电流的能力

BOSHIDA DC电源模块如何承受超负荷电流的能力 DC电源模块是现代电子设备中必不可少的部件&#xff0c;它们通常被用来将交流电转换为稳定的直流电&#xff0c;为电子设备提供所需的电力。在某些情况下&#xff0c;DC电源模块可能会遇到超负荷电流的情况&#xff0c;如启动过程…