MySQL數(shù)據(jù)庫CPU飆升到500%的原因和解決方案
CPU 500%意味著MySQL吃掉了好幾個核,基本上是某些SQL在瘋狂消耗計算資源。這種情況下不要急著重啟——重啟只是把問題藏起來了,過一會兒還會炸。
先滅火,再查因,最后防復發(fā)。按這個順序來。
滅火:先找到罪魁禍首
登上服務(wù)器,第一件事不是看MySQL,是看操作系統(tǒng)。
top -Hp $(pidof mysqld)
這條命令列出mysqld進程下所有線程的CPU占用。找到CPU最高的那幾個線程,記下它們的LWP(輕量級進程ID)。
然后進MySQL:
SHOW PROCESSLIST;
或者更詳細的版本:
SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command != 'Sleep' ORDER BY time DESC;
重點看 command 不是Sleep的連接——Sleep的連接是空閑的,不吃CPU???time 列,跑了幾百秒甚至幾千秒的查詢大概率就是兇手。info 列顯示正在執(zhí)行的SQL。
找到了問題SQL之后,如果業(yè)務(wù)允許,直接殺掉:
KILL <process_id>;
這一步的目的是止血。CPU降下來之后,你才有余裕去分析根因。如果不先殺掉問題查詢,服務(wù)器可能連登錄都卡。
查因:為什么這條SQL吃這么多CPU
CPU飆升的原因,90%以上是這幾種情況。
全表掃描。 一條查詢沒走索引,掃了幾百萬行甚至幾千萬行。每一行都要從磁盤讀到內(nèi)存(如果Buffer Pool裝不下的話),然后逐行比較WHERE條件。CPU的消耗主要在"逐行比較"這一步。
拿到問題SQL之后,用EXPLAIN看一下:
EXPLAIN SELECT ... ;
如果 type 列是 ALL,rows 列是幾百萬,那就是全表掃描???key 列是不是NULL——NULL說明沒用上任何索引。
鎖等待引發(fā)的連鎖反應(yīng)。 一條慢SQL持有行鎖,后面的請求全部排隊等鎖。等待的連接越來越多,每個連接都占一個線程,MySQL的線程調(diào)度開銷就上去了。這種情況下CPU高不是因為在"計算",而是因為在"調(diào)度"。
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started ASC;
這條命令列出所有活躍事務(wù),按開始時間排序。跑了最久的那個事務(wù)大概率是罪魁禍首——它持有鎖不釋放,后面的事務(wù)全部堵住了。
SELECT * FROM performance_schema.data_lock_waits;
這條(MySQL 8.0+)能看到誰在等誰的鎖。如果 BLOCKING_ENGINE_TRANSACTION_ID 指向的事務(wù)已經(jīng)跑了很久,考慮殺掉它。
排序和臨時表。 帶 ORDER BY 或 GROUP BY 的查詢,如果排序字段沒有索引,MySQL會在內(nèi)存里(或者磁盤上)建臨時表做排序。數(shù)據(jù)量一大,排序本身就是CPU密集型操作。
EXPLAIN里看到 Extra 列有 Using filesort 或 Using temporary,就是這個情況。
大量短連接。 某些應(yīng)用沒用連接池,每次請求都新建MySQL連接、用完就斷開。MySQL創(chuàng)建和銷毀連接的開銷不?。ㄒ稣J證、分配線程、初始化會話變量)。如果QPS很高,光是連接管理就能把CPU吃滿。
SHOW GLOBAL STATUS LIKE 'Threads_created'; SHOW GLOBAL STATUS LIKE 'Connections';
如果 Threads_created 的值很高且在快速增長,說明在頻繁創(chuàng)建新線程。正常情況下,連接池會復用線程,Threads_created 應(yīng)該增長很慢。
常見場景的具體處理
場景一:某條慢SQL導致CPU飆升
這是最常見的情況。處理步驟:
殺掉問題查詢 → EXPLAIN分析 → 加索引或改寫SQL → 驗證。
加索引的時候注意:在生產(chǎn)環(huán)境給大表加索引,MySQL 5.6之前會鎖表,5.6之后支持Online DDL,但仍然會消耗大量IO。如果表有幾千萬行,加索引可能要跑幾分鐘到幾十分鐘,期間會影響寫入性能。
建議在業(yè)務(wù)低峰期操作,或者用 pt-online-schema-change 工具:
pt-online-schema-change --alter "ADD INDEX idx_user_status(user_id, status)" \ D=mydb,t=orders --execute
這個工具的原理是創(chuàng)建一張新表、加上索引、通過觸發(fā)器同步數(shù)據(jù)、最后原子性地rename。對線上業(yè)務(wù)的影響比直接ALTER TABLE小很多。
場景二:大事務(wù)持有鎖導致連鎖堵塞
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx WHERE trx_started < NOW() - INTERVAL 60 SECOND;
找到跑了超過60秒的事務(wù),看它在干什么。如果是一個忘記提交的事務(wù)(trx_query 為NULL說明當前沒在執(zhí)行SQL,但事務(wù)還開著),直接殺掉對應(yīng)的連接:
KILL <trx_mysql_thread_id>;
然后去排查應(yīng)用代碼——大概率是某個地方開了事務(wù)忘記commit/rollback,或者事務(wù)里做了不該做的事(比如在事務(wù)里調(diào)了外部HTTP接口,接口超時導致事務(wù)一直掛著)。
場景三:突發(fā)流量導致CPU飆升
不是某條SQL有問題,而是正常的SQL突然來了十倍的量。這種情況加索引沒用,因為每條SQL本身都很快,只是量太大了。
短期應(yīng)對:
SET GLOBAL max_connections = 500;
限制最大連接數(shù),超出的請求直接拒絕,保護MySQL不被打死。比讓所有請求都卡住要好——至少一部分請求能正常處理。
中期方案:應(yīng)用層加限流、加緩存。把熱點查詢的結(jié)果緩存到Redis,大部分請求不打到MySQL。
長期方案:讀寫分離,讀請求分散到從庫。
預防:別等CPU飆了再處理
開慢查詢?nèi)罩尽?/strong> 這是最基本的。
SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;
long_query_time 設(shè)成1秒。很多人設(shè)成10秒,那等于只能抓到"已經(jīng)嚴重影響用戶體驗"的查詢。1秒的閾值能讓你提前發(fā)現(xiàn)潛在問題。
監(jiān)控線程狀態(tài)。 定期檢查活躍連接數(shù)和長時間運行的查詢:
SELECT COUNT(*) FROM information_schema.processlist WHERE command != 'Sleep';
活躍連接數(shù)突然飆升,往往是CPU飆升的前兆。配合Prometheus + Grafana做監(jiān)控告警,活躍連接超過閾值就報警。
定期審查慢查詢。 每周跑一次 pt-query-digest,看看有沒有新出現(xiàn)的慢查詢。很多CPU飆升事故不是突然發(fā)生的,而是某條SQL隨著數(shù)據(jù)量增長越來越慢,從100ms慢到1秒,從1秒慢到10秒,最后某天數(shù)據(jù)量過了臨界點,直接把CPU打滿。
檢查Buffer Pool命中率。
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
用 Innodb_buffer_pool_read_requests(邏輯讀)和 Innodb_buffer_pool_reads(物理讀,即磁盤讀)算 命中率:1 - 物理讀/邏輯讀。正常應(yīng)該在99%以上。如果低于95%,說明Buffer Pool太小,大量數(shù)據(jù)要從磁盤讀,CPU花在IO等待上的時間就多了。調(diào)大 innodb_buffer_pool_size,一般設(shè)成物理內(nèi)存的60%-80%。
一個容易忽略的點
MySQL的CPU飆升有時候不是MySQL本身的問題。
檢查一下服務(wù)器上是不是還跑著別的東西——有些運維圖省事,把應(yīng)用服務(wù)和MySQL部署在同一臺機器上。應(yīng)用服務(wù)突然吃了大量CPU,MySQL分到的CPU時間片就少了,本來100ms能跑完的查詢變成了500ms,連接堆積,惡性循環(huán)。
top 命令看一眼整體CPU分布,如果mysqld不是CPU占用最高的進程,那問題可能根本不在MySQL。
還有一種情況是OOM Killer。Linux內(nèi)核在內(nèi)存不足的時候會殺掉占內(nèi)存最多的進程,MySQL經(jīng)常是第一個被殺的。殺完之后MySQL重啟,Buffer Pool是冷的,所有查詢都要從磁盤讀數(shù)據(jù),CPU和IO同時飆升。看 dmesg | grep -i oom 能確認是不是被OOM Killer干掉過。
線上數(shù)據(jù)庫出問題的時候,最重要的不是你知道多少優(yōu)化技巧,而是能不能在壓力下保持冷靜、按步驟排查。先止血、再查因、最后防復發(fā)——這三步的順序不能亂。CPU飆到500%的時候最怕的操作就是慌了直接重啟,重啟完發(fā)現(xiàn)問題還在,又開始亂改配置,越改越亂。
以上就是MySQL數(shù)據(jù)庫CPU飆升到500%的原因和解決方案的詳細內(nèi)容,更多關(guān)于MySQL CPU飆升到500%的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Navicat中導入mysql大數(shù)據(jù)時出錯解決方法
這篇文章主要介紹了Navicat中導入mysql大數(shù)據(jù)時出錯解決方法,需要的朋友可以參考下2017-04-04
MySQL數(shù)據(jù)庫全方位優(yōu)化指南(從硬件到架構(gòu)的深度調(diào)優(yōu))
MySQL作為全球最流行的開源關(guān)系型數(shù)據(jù)庫,廣泛應(yīng)用于電商、論壇、博客等各類業(yè)務(wù)場景,MySQL優(yōu)化是一個從硬件到軟件、從配置到架構(gòu)的系統(tǒng)性工程,本文介紹MySQL數(shù)據(jù)庫全方位優(yōu)化指南,感興趣的朋友一起看看吧2025-11-11
踩坑MySQL UNION和ORDER BY混用的問題及解決
MySQL中UNION合并多個子集時,內(nèi)部ORDER BY可能失效,解決方法:各子集添加LIMIT,外層再包裹SELECT并使用ORDER BY,確保整體排序正確2025-09-09
MySQL數(shù)據(jù)庫中表的查詢實例(單表和多表)
查詢數(shù)據(jù)是數(shù)據(jù)庫操作中最常用,也是最重要的操作,下面這篇文章主要介紹了MySQL數(shù)據(jù)庫中表的查詢的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-03-03
使用SQL語句統(tǒng)計數(shù)據(jù)時sum和count函數(shù)中使用if判斷條件的講解
今天小編就為大家分享一篇關(guān)于使用SQL語句統(tǒng)計數(shù)據(jù)時sum和count函數(shù)中使用if判斷條件的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02
淺析CentOS6.8安裝MySQL8.0.18的教程(RPM方式)
這篇文章主要介紹了CentOS6.8安裝MySQL8.0.18(RPM方式)的詳細教程,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11

