mysql抓取数据卡顿_数据库引起的网站访问缓慢卡顿排查经历

本文记录了一次排查网站访问缓慢卡顿的问题,问题源头指向存储引擎为 MyISAM 的表。通过查看慢查询日志、分析锁机制,发现表锁(尤其是Waiting for table metadata lock)严重。原因在于MyISAM表的表级锁及高频率读写操作。解决方案包括调整并发插入设置和考虑切换到InnoDB存储引擎。
部署运行你感兴趣的模型镜像

问题描述

有个项目网站访问异常缓慢卡顿,有时候甚至报404,网站代码确定没有问题,那只好检查数据库,看数据库那边是否合理或有待优化。

数据库排查

排查之前基本确定是一张存储引擎为 MyISAM 的表的问题(在这里取表名为 A),所有的访问都卡在了 A 表的查询上面,那么为什么会卡在这里呢???接下来就开始分析排查

查看慢查询日志

一般正式环境中,都会开启慢查询日志,方便后期的维护。

因为表 A 有 100 万条左右的记录,所以虽然对应的列有做索引,但第一反应还是以为是数据太多查询慢导致的网站访问缓慢,所以去查看慢查询日志,结果发现基本上没有 A 表的慢查询记录。

基于慢查询日志查看结果的思考

这就有点离谱了,为什么明明卡在了 A 表,但关于 A 表的慢查询却没有呢??难道是慢查询记录有问题?还是说 A 表查询其实是不慢的?

排除疑惑,确定排查方向

有了上面的疑惑,我就在网上查看相关的慢查询资料,后来看到了下面这一段话:

只有当一个 SQL 的执行时间(不包括锁等待的时间 lock_time)大于 long_query_time 的时候,才会判定为慢查询 SQL;但是判定为慢查询 SQL 之后,输出的 Query_time 包括了(执行时间+锁等待时间),并且也会输出 Lock_time 时间。当一个 SQL 的执行时间(排除 lock_time)小于 long_query_time 的时候(即使他锁等待超过了很久),也不会记录到慢查询日志当中的。

看了上面这段话,我恍然大悟。既然卡在了 A 表,又没有关于 A 表的慢查询,那唯一的可能就是锁定时间 lock_time 太长了。那就往这个方向分析吧。

关于 MyISAM 表的锁机制与分析

MyISAM 表的锁机制

MySQL 数据表存储引擎不同,锁机制也不同。如:MyISAM 和 MEMORY 存储引擎采用的是表级锁table-level locking;BDB 存储引擎采用的是页面锁 page-level locking,但也支持表级锁;InnoDB 存储引擎既支持行级锁 row-level locking,也支持表级锁,但默认情况下是采用行级锁。

所以 MyISAM 的锁是表级锁,一旦锁定就是整张表锁定。MyISAM 有读锁和写锁,具体的锁定机制如下图:

eb562db1ae4cadf209c0dacffee5336f.png

MyISAM 有 表共享读锁 table read lock 和表独占写锁 table write lock 两种,有以下几点特性:

MyISAM 表的读操作,不会阻塞其他用户对同一个表的读请求,但会阻塞对同一个表的写请求;

MyISAM 表的写操作,会阻塞其他用户对同一个表的读和写操作;

MyISAM 表的读、写操作之间、以及写操作之间是串行的

一些有助于排查的语句

SHOW STATUS LIKE ‘table%’

通过语句 SHOW STATUS LIKE 'table%'; 可以查看到如下图的查询结果:

43605105b98b74f0886ce7e3ba193a92.png

Table_locks_immediate:能够立即获得表级锁的锁请求次数

Table_locks_waited:不能立即获取表级锁而需要等待的锁请求次数

如果 Table_locks_waited 值较高,且存在性能问题,则说明存在着较严重的表级锁争用情况

当初执行以上语句时,确实看到 Table_locks_waited 非常大,好像有几千~~~

SHOW OPEN TABLES

这个语句的作用是 列举在表缓存中当前被打开的非TEMPORARY表,会返回一下字段:

Database:含有该表的数据库。

Table:表名称。

In_use:表当前被查询使用的次数。如果该数为零,则表是打开的,但是当前没有被使用。

Name_locked:表名称是否被锁定。名称锁定用于取消表或对表进行重命名等操作。

如果有很多表,执行该语句后一般会有好多条数据的,但是大部分表 In_use 都是 0,所以我们可以在语句上加个条件 show open tables where in_use >=1;,如果 In_use 有大于等于 1 的,结果会如下:

mysql> show open tables where in_use >=1;

+----------+-------+--------+-------------+

| Database | Table | In_use | Name_locked |

+----------+-------+--------+-------------+

| MyDB | test | 1 | 0 |

+----------+-------+--------+-------------+

1 row in set (0.00 sec)

我当时执行该语句后,只返回 A 表的相关记录,而且 In_use 的值竟然达到上百,Name_locked 的值具体是几忘了,但肯定不是 0!那就是说 A 表的表锁很严重啊。。。

SHOW PROCESSLIST

PROCESSLIST 命令的输出结果显示了有哪些线程在运行,不仅可以查看当前所有的连接数,还可以查看当前的连接状态帮助识别出有问题的查询语句等。

如果是 root 帐号,能看到所有用户的当前连接。如果是其他普通帐号,则只能看到自己占用的连接。SHOW PROCESSLIST只能列出当前 100 条;如果想全部列出,可以使用 SHOW FULL PROCESSLIST 命令。执行语句会有类似下面的结果:

mysql> SHOW PROCESSLIST;

+--------+-------------+-----------------------+-----------+-------------+-------+---------------------------------------------------------------+------------------+

| Id | User | Host | db | Command | Time | State | Info |

+--------+-------------+-----------------------+-----------+-------------+-------+---------------------------------------------------------------+------------------+

| 1 | system user | | NULL | Connect | 75478 | Waiting for master to send event | NULL |

| 2 | system user | | NULL | Connect | 15681 | Slave has read all relay log; waiting for more updates | NULL |

| 154517 | dbtb | 129.227.126.102:36766 | NULL | Binlog Dump | 17682 | Master has sent all binlog to slave; waiting for more updates | NULL |

| 256957 | root | 61.104.40.211:58206 | db_name | Query | 0 | starting | SHOW PROCESSLIST |

+--------+-------------+-----------------------+-----------+-------------+-------+---------------------------------------------------------------+------------------+

4 rows in set

各字段的含义如下:

Id:用户登录 mysql 时,系统分配的 connection_id,可以使用函数 connection_id() 查看;

User:显示当前用户,如果不是 root,这个命令就只显示用户权限范围的 sql 语句;

Host:显示这个语句是从哪个 IP 的哪个端口上发的,可以用来跟踪出现问题语句的用户;

db:显示这个进程目前连接的是哪个数据库;

Command:显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等;

Time:显示这个状态持续的时间,单位是 秒;

State:显示使用当前连接的 sql 语句的状态,很重要的列。state 描述的是语句执行中的某一个状态。一个 sql 语句,以查询为例,可能需要经过 copying to tmp table、sorting result、sending data 等状态才可以完成;

Info:显示这个 sql 语句,是判断问题语句的一个重要依据

State 的状态有很多,可以根据具体值去网上查找相关的信息

我当时执行这个语句时,竟然有一大堆 State 为 Waiting for table metadata lock 的记录,而且都是 A 表的。那说明 A 表的表锁真的相当严重啊。。。

确认问题所在

通过以上的查看与分析,可以知道网站访问缓慢卡顿的原因就是 A 表的表锁太严重了,至于为什么表锁严重,有以下两点原因:

A 表的存储引擎为 MyISAM

A 表的读写太频繁(因为业务需要,该表有时候会有增删操作)

解决方案

既然知道是 A 表的表锁太严重,而且 A 表的存储引擎为 MyISAM 。那就往这方面解决就是了,主要有两个大方向:

在存储引擎不变的情况下,尽量减少表锁的发生

因为有读也有写,有时候写操作也会很频繁,所以可以考虑修改存储引擎为 InnoDB

存储引擎不变

concurrent_insert

通常来说,在 MyISAM 里读写操作是串行的,但当对同一个表进行查询和插入操作时,为了降低锁竞争的频率,根据 concurrent_insert 的设置,MyISAM 是可以并行处理查询和插入的:

当 concurrent_insert=0 时,不允许并发插入功能。

当 concurrent_insert=1 时,允许对没有空洞的表使用并发插入,新数据位于数据文件结尾。

当 concurrent_insert=2 时,不管表有没有空洞,都允许在数据文件结尾并发插入。

所谓空洞,就是行记录被删除以后,只是被标记为“已删除”,其存储空间没有被回收,也就是说没有被物理删除。由另外一个进程,异步对这个数据进行删除。因为空间长度问题,删除以后的物理空间不能被新的记录所使用,从而形成了空洞。MyISAM 的空洞可以通过命令 OPTIMIZE TABLE table_name 来删除,但是该命令执行时会锁表,且效率较低,所以要谨慎使用。

根据上面的说明,把 concurrent_insert 设置为 2 是一个不错的选择,至于由此产生的数据空洞,可以定期使用 OPTIMIZE TABLE 语法优化。

max_write_lock_count

默认情况下,写操作的优先级要高于读操作的优先级,即便是先发送的读请求,后发送的写请求,此时也会优先处理写请求,然后再处理读请求。这就造成一个问题:一旦我发出若干个写请求,就会堵塞所有的读请求,直到写请求全都处理完,才有机会处理读请求。此时可以考虑设置 max_write_lock_count,如:

SET GLOBAL max_write_lock_count = 1;

有了这样的设置,当系统处理一个写操作后,就会暂停写操作,给读操作执行的机会。

low-priority-updates

我们还可以更干脆点,直接降低写操作的优先级,给读操作更高的优先级。

SET GLOBAL low-priority-updates=1

小结

综合来看,concurrent_insert=2 是绝对推荐的,至于 max_write_lock_count=1 和 low-priority- updates=1,则视情况而定,如果可以降低写操作的优先级,则使用 low-priority-updates=1,否则使用 max_write_lock_count=1。

我尝试把设置 concurrent_insert=2 和 max_write_lock_count=1 后,网站访问缓慢卡顿的问题并没有得到改善,不知道是因为我操作设置不当,还是因为这样操作对当前问题没有效果

修改存储引擎

既然无法在保持 MyISAM 引擎不变的情况下解决问题,那我只好把存储引擎修改成 InnoDB 了。因为是正式环境的数据,修改的时候要谨慎点。以下是应该的步骤:

找个用户操作比较少的时间点来进行修改

修改前禁止掉所有的相关用户操作,如果可以的话直接关闭整个网站

备份 A 表的数据

删除 A 表,并新建 A 表,新建时记得修改存储引擎

导入备份好的数据到 A 表

结语

整个排查的过程是非常迷茫与痛苦的,但是通过这个过程还是学到了很多,所以在此记录一下。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值