記一次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監控

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容