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

