记一次MySQL占用CPU飙升问题

线上的接口服务这两天开始频繁的报502和504, 经过排查发现连接的MySQL从库的CPU占用一度达到99%.
几乎全部都是MySQL占用的.(该服务器配置为32核+64G)
通过top -H查看MySQL进程的线程情况,发现有300+的子线程在同时占用CPU,且每个线程的占用在10%以下.
说明应该不是慢查询导致的,否则个别线程的CPU占用应该较高,应该是QPS过高导致.
通过show global status;进行确认

show global status结果中主要看QPS和TPS, 先了解一下如何获取这两个关键信息:

QPS指标:

先来认识一下以下几个有关查询的指标:

  1. Questions:MySQL从上一次启动到当前客户端发送给MySQL的查询数量,不包括存储过程内部的查询。它不统计COM_PING,COM_STATISTICS,COM_STMT_PREPARE,COM_STMT_CLOSE,COM_STMT_RESET数量,但它会把show命令计算到指标当中。
  2. Queries:MySQL从上一次启动到当前客户端发送给MySQL的查询数量,包括存储过程内部的查询,它不统计COM_PING和COM_STATISTICS两个命令。
  3. Com_select:MySQL从上一次启动到当前所执行的查询语句总数量。
    由上可见,在数据库中执行show命令会使questions值加1,而com_select则不记录,而且对于数据库的监控,经常会用到show命令,所以在用questions方式计算时,数据其实是被污染的。
    而且questions的值在设置环境变量的时候,也是一直在增长的,而com_select的值在此过程中,并不增长。
    所以在使用question方式进行计算时,人为拉高了qps的结果,相对来说,使用com_select此种方式来计算qps,相对比较帖近真实情况一些,也就是说,在同等条件下,拉高了qps的值。

以下将介绍通过Questions方式以及Com_select方式计算QPS

Questions方式计算QPS

1.Questions方式计算QPS公式
questions = show global status where variable_name='Questions';
uptime = show global status like 'Uptime';
qps=questions/uptime

如上,拿当前Questions值除去Uptime=QPS,这个QPS的意义为从MySQL上一次启动到当前并且包含show命令平均每秒的QPS值,假如某个时间段的查询数量特别高,但是通过除Uptime时间,也会被拉下来。并且此questions值包含了show命令及环境变量所造成的数据污染。

2.Questions方式计算QPS指标SQL

注:以下SQL在MySQL8.0中进行测试,5.6或5.7中的稍有差异。

select round(sum(if(variable_name='Questions',variable_value,0))/sum(if(variable_name='Uptime',variable_value,0)),1) as 'QPS' from performance_schema.global_status where variable_name in ('Questions','Uptime');

Com_select方式计算QPS

通过Com_select来计算QPS,可以连续获取两次Com_select指标,拿新指标减去老指标后再除于间隔时长的出间隔时间内的每秒平均值。这种方式更贴近真实一些。

1.Com_Select方式计算QPS脚本
#!/usr/bin/env bash
OLD_QPS=`echo "show global status where Variable_name='Com_select';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
sleep $1
NEW_QPS=`echo "show global status where Variable_name='Com_select';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
echo "($NEW_QPS-$OLD_QPS) / $1" | bc

TPS指标

获取TPS指标的方式也有两种:

  1. 基于com_commitcom_rollback计算tps
  2. 基于com_insertcom_deletecom_update的status,变量计算tps
基于com_commitcom_rollback计算tps

相关指标介绍:

  • Com_commit:MySQL从上一次启动到当前所执行的提交语句总数量
  • Com_rollback:MySQL从上一次启动到当前所执行的回退语句总数量
1.基于com_commit和com_rollback方式计算TPS公式

这样计算出来的TPS也是MySQL从上次启动到当前平均每秒的TPS指标

com_commit = show global status where variable_name='com_commit';
com_rollback = show global status where variable_name='com_rollback';
uptime = show global status where Variable_name='Uptime';
tps=(com_commit + com_rollback)/uptime
2.计算脚本
#!/usr/bin/env bash
COM_COMMIT=`echo "show global status where Variable_name='Com_commit';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
COM_ROLLBACK=`echo "show global status where Variable_name='Com_rollback';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
UPTIME=`echo "show global status where Variable_name='Uptime';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`

echo "($COM_COMMIT + $COM_ROLLBACK) / $UPTIME" | bc
基于com_insert、com_delete、com_update的status计算tps

相关指标介绍:

  • Com_update: MySQL从上一次启动到当前所执行的更新语句总数量
  • Com_delete:MySQL从上一次启动到当前所执行的删除语句总数量
  • Com_insert:MySQL从上一次启动到当前所执行的插入语句总数量
1.计算脚本

指定间隔时间内取值两次,然后新指标减去老指标后三个指标相加再除以间隔时间得出间隔时间内每秒平均TPS

#/usr/bin/env bash
OLD_COM_INSERT=`echo "show global status where Variable_name='Com_insert';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
OLD_COM_UPDATE=`echo "show global status where Variable_name='Com_update';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
OLD_COM_DELETE=`echo "show global status where Variable_name='Com_delete';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
sleep $1
NEW_COM_INSERT=`echo "show global status where Variable_name='Com_insert';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
NEW_COM_UPDATE=`echo "show global status where Variable_name='Com_update';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`
NEW_COM_DELETE=`echo "show global status where Variable_name='Com_delete';"|mysql --defaults-file=./.my.cnf -N|awk '{print $2}'`

echo "(($NEW_COM_INSERT - $OLD_COM_INSERT) + ($NEW_COM_UPDATE - $OLD_COM_UPDATE) + ($NEW_COM_DELETE - $OLD_COM_DELETE)) / $1" | bc

通过以上查询发现QPS是平常的10倍左右,使用 show full processlist;查看主要都是某个查询语种在占用线程.

开启排查日志,进一步确认问题:

  1. 查询开启状态:show global variables like '%general%';
Variable_name Value
general_log OFF
general_log_file /var/lib/mysql/iZ8vbhhl8axxpiqez8o39eZ.log
  1. 开启日志:set global general_log=on;

  2. /var/lib/mysql/iZ8vbhhl8axxpiqez8o39eZ.log目录下查看日志
    确认是某个查询语种异常,不断重试导致CPU沾满

  3. 关闭日志: set global general_log=off;

随后根据show full processlist;结果中的Host定位到发出异常查询的服务,修改代码后问题解决.

令附上查询慢SQL的语句:

  • 查看是否开启
show global variables like '%slow_query_log%'
  • 查看默认的超时时间配置
show variables like 'long_query_time%';
  • 超时时间修改为 1 秒
set global long_query_time=1;
  • 开启慢查询日志
set global slow_query_log=1;
  • 关闭慢查询日志
set global slow_query_log=0;

参考:MySQL监控

你可能感兴趣的:(记一次MySQL占用CPU飙升问题)