從查詢到配置:線上排查與調(diào)優(yōu)實(shí)戰(zhàn)指南)
接手過線上MySQL的人多半都經(jīng)歷過那種“數(shù)據(jù)庫突然連不上了”的瞬間。應(yīng)用日志里刷著一排Too many connectionsDBA群里瞬間炸鍋第一反應(yīng)就是敲show variables like max_connections看上限再敲show status like Threads_connected看當(dāng)前占用。這套動(dòng)作我做過無數(shù)次但老實(shí)說早期我對(duì)連接數(shù)的理解也就停留在“滿了就調(diào)大”這個(gè)層面直到被幾次故障教育之后才真正把連接數(shù)的查詢、配置、監(jiān)控和治理串成一條線。這篇就把MySQL連接數(shù)從查看到配置、從參數(shù)到實(shí)戰(zhàn)的完整脈絡(luò)梳理一遍全是踩過坑之后沉淀下來的東西。1. 先搞懂連接數(shù)到底是怎么回事1.1 連接的本質(zhì)和開銷MySQL的“連接數(shù)”本質(zhì)上是客戶端與服務(wù)端之間建立的會(huì)話數(shù)量。每一次連接服務(wù)端都要為它分配線程、內(nèi)存、臨時(shí)資源連接建立時(shí)還要經(jīng)歷TCP握手、認(rèn)證、權(quán)限校驗(yàn)這一整套流程。你可以把每個(gè)連接想象成餐廳里的一把椅子——椅子數(shù)量有限每個(gè)客人坐下來都要占地方服務(wù)員線程要招呼他后廚存儲(chǔ)引擎要為他備菜??腿俗吡艘巫硬拍芸粘鰜淼巫颖旧聿粫?huì)自己變多。這個(gè)類比解釋了為什么連接數(shù)不是可以無限漲的。每個(gè)連接占用的內(nèi)存通常在幾百KB到幾MB之間取決于會(huì)話級(jí)變量、排序緩沖區(qū)、臨時(shí)表內(nèi)存等配置。如果有幾百個(gè)連接同時(shí)存在光會(huì)話內(nèi)存就是一筆不小的開銷。更重要的是MySQL的線程調(diào)度能力有限連接數(shù)堆積到一定程度CPU會(huì)大量消耗在線程切換上導(dǎo)致整體吞吐下降——這時(shí)候數(shù)據(jù)庫不是“滿”了而是“癱”了。1.2 連接被誰占用了排查連接數(shù)問題的第一步不是看總數(shù)而是看構(gòu)成。一個(gè)典型的MySQL實(shí)例上連接主要來自幾個(gè)方向應(yīng)用連接池絕大多數(shù)連接來自業(yè)務(wù)側(cè)。Java應(yīng)用里常見的連接池如Druid、HikariCP每個(gè)實(shí)例會(huì)維護(hù)一組連接微服務(wù)規(guī)模一大連接數(shù)自然水漲船高。監(jiān)控腳本和定時(shí)任務(wù)很多人會(huì)忽略這些。監(jiān)控采集、報(bào)表任務(wù)、批量腳本如果寫在循環(huán)里每次new一個(gè)連接又沒釋放連接數(shù)會(huì)被慢慢“吃”掉。管理端和手工查詢Navicat、Workbench這類GUI工具每個(gè)打開的查詢窗口對(duì)應(yīng)一個(gè)連接。開發(fā)環(huán)境人手一把看起來不多但疊加起來也可能占掉幾十個(gè)連接。閑置連接這是最陰險(xiǎn)的占用。應(yīng)用獲取連接后忘了歸還或者連接池最小空閑數(shù)配置過高大量連接處于Sleep狀態(tài)既不干活也不釋放。分類清楚了才能對(duì)癥下藥。這也是為什么我們查連接數(shù)時(shí)永遠(yuǎn)要追問一句“這些連接都是誰”。1.3 連接上限從哪來MySQL對(duì)連接數(shù)的限制最直接的是max_connections參數(shù)。它控制的是整個(gè)實(shí)例允許同時(shí)存在的客戶端連接總數(shù)包括所有庫、所有用戶。除此之外還有一個(gè)max_user_connections單獨(dú)限制單個(gè)賬號(hào)的最大連接數(shù)默認(rèn)是0也就是不限制。值得說明的是max_connections是實(shí)例級(jí)別的硬上限但“生效”的方式不是寫死而是動(dòng)態(tài)判斷當(dāng)已有連接數(shù)達(dá)到上限后再來的新連接請求會(huì)直接被拒絕錯(cuò)誤碼是ER_CON_COUNT_ERROR也就是我們熟悉的Too many connections。生產(chǎn)環(huán)境最常見的誤區(qū)是把max_connections當(dāng)成一個(gè)需要“夠用就行”的配置結(jié)果上線時(shí)夠用業(yè)務(wù)一漲直接被打穿。正確做法是按最壞情況估算同時(shí)留出緩沖余量。2. 查詢連接數(shù)從命令行到SQL的全套姿勢2.1 第一件事先看全局狀態(tài)最基礎(chǔ)也是最高頻的查詢命令是這兩條SHOW VARIABLES LIKE max_connections; SHOW STATUS LIKE Threads_connected;SHOW VARIABLES查的是配置值SHOW STATUS查的是實(shí)時(shí)狀態(tài)。Threads_connected表示當(dāng)前已建立的連接數(shù)這兩個(gè)值一比就能看出連接水位。比如配置上限是500當(dāng)前已經(jīng)480那就得警惕了。除了Threads_connected還有幾個(gè)狀態(tài)值值得一起看SHOW STATUS LIKE Threads_running; SHOW STATUS LIKE Threads_created; SHOW STATUS LIKE Aborted_connects; SHOW STATUS LIKE Connection_errors_max_connections;Threads_running當(dāng)前正在執(zhí)行的連接數(shù)。這個(gè)值最能反映數(shù)據(jù)庫真實(shí)壓力。連接數(shù)高不一定有問題但Threads_running持續(xù)走高說明SQL真的在跑要關(guān)注慢查詢和鎖等待。Threads_created為處理連接而創(chuàng)建的線程數(shù)。如果這個(gè)值在持續(xù)快速增加說明系統(tǒng)在頻繁創(chuàng)建線程通常意味著連接在頻繁建立和斷開——連接池沒用好。Aborted_connects連接建立失敗的次數(shù)可以用來判斷是否有連接被拒。Connection_errors_max_connections因?yàn)檫_(dá)到max_connections上限而被拒絕的連接次數(shù)。這個(gè)值只要大于0就說明被拒過。2.2 查當(dāng)前活躍連接information_schema 三板斧全局狀態(tài)只能看到總數(shù)想知道“誰占著連接”得上information_schema.processlistSELECT id, user, host, db, command, time, state, info FROM information_schema.processlist ORDER BY time DESC;這個(gè)查詢會(huì)列出所有當(dāng)前連接重點(diǎn)看幾列commandSleep表示空閑連接Query表示正在執(zhí)行查詢Connect表示正在建立連接。time當(dāng)前狀態(tài)持續(xù)的時(shí)間。某個(gè)Query的 time 特別大基本就是慢查詢或者鎖等待。state執(zhí)行狀態(tài)比如Sending data、Waiting for table metadata lock、Locked不同狀態(tài)對(duì)應(yīng)不同問題。info正在執(zhí)行的SQL語句不過只有CommandQuery時(shí)才有內(nèi)容。排查連接數(shù)異常我通常先跑這個(gè)SQL把結(jié)果按command分組統(tǒng)計(jì)再按host分組看來源SELECT command, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY command; SELECT host, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY host ORDER BY cnt DESC;這兩刀下去大頭在哪基本就清楚了全是Sleep那就是連接池沒回收集中在某個(gè)host那就是那個(gè)應(yīng)用實(shí)例有問題全是Query且 time 很大那就不用管連接數(shù)了先查SQL吧。2.3 performance_schema 更細(xì)的視角information_schema.processlist在連接數(shù)非常多比如上千或者大量被占用時(shí)查詢本身會(huì)有些開銷而且只能看到當(dāng)前瞬間的狀態(tài)。如果要做歷史趨勢分析或者更細(xì)維度的統(tǒng)計(jì)用performance_schema更合適。SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;這個(gè)用來定位消耗最多的SQL模式。連接數(shù)的場景下更常用的是看performance_schema.threadsSELECT TYPE, PROCESSLIST_COMMAND, COUNT(*) AS cnt FROM performance_schema.threads GROUP BY TYPE, PROCESSLIST_COMMAND;performance_schema.threads會(huì)包含后臺(tái)線程和前臺(tái)連接線程能更精確地區(qū)分哪些是系統(tǒng)內(nèi)部線程哪些是真實(shí)客戶端連接。不過說實(shí)話日常排查我還是優(yōu)先用information_schema.processlist它直觀、覆蓋面夠用。performance_schema更適合做監(jiān)控采集和深度分析因?yàn)樗臍v史累計(jì)數(shù)據(jù)是現(xiàn)成的不用自己額外記錄。2.4 把連接狀態(tài)做成監(jiān)控手工查只能解決“當(dāng)時(shí)發(fā)生了什么”生產(chǎn)環(huán)境更需要的是“連接數(shù)趨勢”。我比較推薦的做法是把核心指標(biāo)接入監(jiān)控系統(tǒng)至少采集這幾個(gè)值max_connections配置上限變更時(shí)要同步更新監(jiān)控里的閾值。Threads_connected當(dāng)前連接數(shù)按百分比設(shè)置告警閾值。比如達(dá)到上限的80%告警達(dá)到90%緊急告警。Threads_running當(dāng)前執(zhí)行連接數(shù)這個(gè)值超過幾十就要警惕SQL性能問題了。Aborted_connects增量異常時(shí)說明連接被拒絕過。采集方式可以直接用SHOW GLOBAL STATUS輪詢也可以用performance_schema的表。之前我見過一個(gè)坑監(jiān)控腳本自身也使用了數(shù)據(jù)庫連接去采集狀態(tài)結(jié)果數(shù)據(jù)庫連接打滿時(shí)監(jiān)控反而連不上采集任務(wù)失敗告警沒發(fā)出來。后來我們把監(jiān)控采集改成直連本地socket并單獨(dú)留一條管理通道才徹底避免了這個(gè)尷尬。3. 配置連接數(shù)參數(shù)詳解與調(diào)優(yōu)思路3.1 max_connections 到底該設(shè)多大關(guān)于max_connections網(wǎng)上流傳著“設(shè)個(gè)1000、2000就完事”的說法這是典型的頭疼醫(yī)頭。連接數(shù)上限不是拍腦袋定的要結(jié)合三個(gè)因素算因素一是內(nèi)存。每個(gè)連接至少會(huì)占用thread_stack加net_buffer_length等固定開銷但真正的大頭是會(huì)話級(jí)的排序緩沖、臨時(shí)表、join緩沖等。粗略估算時(shí)可以按每個(gè)連接1MB到2MB來算。假設(shè)實(shí)例內(nèi)存32GB預(yù)留一半給InnoDB緩沖池和操作系統(tǒng)剩下16GB給連接那理論上可以支持8000到16000個(gè)連接。但這只是內(nèi)存約束不是推薦值。因素二是CPU。每個(gè)連接的活躍查詢都會(huì)消耗CPU。一個(gè)4核8線程的實(shí)例同時(shí)能高效運(yùn)行的查詢也就十幾個(gè)。就算連接數(shù)堆到幾千絕大多數(shù)連接也只能排隊(duì)等CPU數(shù)據(jù)庫的TPS不會(huì)因此提高反而線程切換開銷會(huì)把性能拖垮。因素三是業(yè)務(wù)形態(tài)。max_connections 預(yù)估活躍連接峰值 緩沖余量比如一個(gè)應(yīng)用實(shí)例連接池配置20個(gè)連接總共50個(gè)實(shí)例那理論上限是1000再加上監(jiān)控、管理、備用流量建議設(shè)到1200。注意這里說的是“連接池配置的總額”不是當(dāng)前實(shí)際活躍連接數(shù)——因?yàn)檫B接池一般會(huì)維持比活躍請求更多的空閑連接。我見過一份配置調(diào)優(yōu)記錄某電商系統(tǒng)在促銷前把max_connections從500調(diào)到2000結(jié)果促銷還沒開始數(shù)據(jù)庫先卡死了。原因就是連接數(shù)上來了但每連接的內(nèi)存和CPU開銷也上來了系統(tǒng)資源根本扛不住。后來把連接池最大值壓下去配合排隊(duì)機(jī)制系統(tǒng)反而穩(wěn)定了。這就是典型的“連接數(shù)配置不是開得越大越好”。3.2 連接生命周期wait_timeout 與 interactive_timeout連接數(shù)為什么會(huì)越積越多很多時(shí)候不是應(yīng)用故意不釋放而是連接變成了“僵尸連接”——既沒關(guān)閉也不干活就那么一直躺著??刂七@個(gè)現(xiàn)象的關(guān)鍵參數(shù)是wait_timeout和interactive_timeout。SHOW VARIABLES LIKE wait_timeout; SHOW VARIABLES LIKE interactive_timeout;wait_timeout非交互式連接的空閑超時(shí)時(shí)間默認(rèn)28800秒8小時(shí)。interactive_timeout交互式連接的空閑超時(shí)時(shí)間同樣默認(rèn)28800秒。這個(gè)默認(rèn)值在云上環(huán)境經(jīng)常成為“連接數(shù)泄漏”的幫兇。應(yīng)用獲取連接后不歸還連接池又不主動(dòng)踢掉連接就會(huì)一直占著。8小時(shí)后才能被服務(wù)端強(qiáng)制斷開——這個(gè)節(jié)奏太慢了。建議線上把wait_timeout調(diào)小比如600秒10分鐘甚至300秒5分鐘。但對(duì)交互式連接不要一刀切因?yàn)镈BA自己用命令行連上去可能掛著看個(gè)長查詢這時(shí)候interactive_timeout需要保持足夠長。這里有個(gè)容易踩的坑修改wait_timeout只對(duì)之后新建的連接生效不是的這個(gè)參數(shù)對(duì)已存在的連接立即生效MySQL會(huì)周期性檢測空閑連接并強(qiáng)制斷開。但也正因如此如果連接池里的連接在空閑狀態(tài)被服務(wù)端切掉了應(yīng)用連接池如果沒有做好連接有效性檢測第一次使用這個(gè)連接時(shí)會(huì)拋異常走一次重連流程業(yè)務(wù)上表現(xiàn)為偶發(fā)報(bào)錯(cuò)。所以調(diào)wait_timeout時(shí)一定要確認(rèn)應(yīng)用連接池的testOnBorrow或validationQuery是開著的。3.3 連接回收與復(fù)用thread_cache_size每次新建連接MySQL都要?jiǎng)?chuàng)建一個(gè)線程連接斷開時(shí)線程也要銷毀。頻繁創(chuàng)建銷毀線程是有成本的。thread_cache_size就是用來緩存空閑線程的作用類似連接復(fù)用加速器。SHOW VARIABLES LIKE thread_cache_size;這個(gè)參數(shù)的值表示MySQL可以緩存多少個(gè)空閑線程用于復(fù)用。設(shè)置多大合適可以看狀態(tài)值SHOW STATUS LIKE Threads_created; SHOW STATUS LIKE Connections;Threads_created除以Connections可以得到線程創(chuàng)建率。如果這個(gè)比例很低說明大部分連接都在復(fù)用已有線程thread_cache_size夠用如果比例偏高說明線程頻繁創(chuàng)建銷毀可以適當(dāng)調(diào)大thread_cache_size。不過要強(qiáng)調(diào)一點(diǎn)thread_cache_size治標(biāo)不治本。如果連接在頻繁創(chuàng)建和銷毀真正的問題通常是應(yīng)用連接池參數(shù)不合理——比如連接池最大生命周期太短、空閑回收太激進(jìn)。這個(gè)參數(shù)只能緩解癥狀不該把它當(dāng)成主力優(yōu)化手段。3.4 連接數(shù)與連接池的聯(lián)動(dòng)配置前面說的都是MySQL服務(wù)端的參數(shù)但線上連接數(shù)問題的根源往往不在服務(wù)端而在應(yīng)用側(cè)的連接池配置。這里把兩邊的指標(biāo)對(duì)應(yīng)起來看連接池參數(shù)作用與MySQL的對(duì)應(yīng)關(guān)系maximumPoolSize連接池最大連接數(shù)對(duì)應(yīng)單實(shí)例對(duì)MySQL的最大占用minimumIdle連接池最小空閑數(shù)決定常駐連接數(shù)maxLifetime連接最大存活時(shí)間應(yīng)小于MySQL的wait_timeoutconnectionTimeout獲取連接的超時(shí)時(shí)間超過后應(yīng)用側(cè)直接報(bào)錯(cuò)不等MySQL拒絕validationTimeout連接有效性驗(yàn)證超時(shí)用于檢測被服務(wù)端斷開的連接這里有個(gè)黃金法則連接池的maxLifetime必須小于 MySQL 的wait_timeout。這樣連接在MySQL側(cè)被強(qiáng)制斷開之前應(yīng)用連接池會(huì)主動(dòng)把老連接淘汰并新建避免拿到失效連接。比如MySQLwait_timeout設(shè)為600秒連接池maxLifetime設(shè)為480秒留出120秒的安全余量。另一個(gè)容易忽略的是連接池大小的設(shè)計(jì)。很多團(tuán)隊(duì)直接套用“max50、min10”這類默認(rèn)值完全不評(píng)估實(shí)際并發(fā)。連接池太大堆出來的都是空閑連接白占MySQL的連接數(shù)上限連接池太小高峰期請求都在排隊(duì)等連接。一個(gè)靠譜的起步值是連接池大小 業(yè)務(wù)并發(fā)峰值 × (單個(gè)請求占用連接的平均時(shí)間 / 請求平均響應(yīng)時(shí)間)這個(gè)公式本質(zhì)上就是Little‘s Law按需求出穩(wěn)態(tài)下需要的連接數(shù)。實(shí)際項(xiàng)目中單實(shí)例10到20個(gè)連接對(duì)絕大多數(shù)業(yè)務(wù)都?jí)蛴脤幙勺屔倭空埱笈抨?duì)也不要把連接池飆到幾十上百。3.5 防呆設(shè)計(jì)max_user_connections 與賬號(hào)拆分有時(shí)候不是所有業(yè)務(wù)都值得給滿額連接配額。某個(gè)慢業(yè)務(wù)、某個(gè)批量任務(wù)、某個(gè)定時(shí)腳本它們?nèi)绻加昧诉^多連接可能擠掉核心業(yè)務(wù)的連接名額。這種情況可以用max_user_connections做賬號(hào)級(jí)別的連接上限控制。比如有一個(gè)報(bào)表賬號(hào)專門用來跑數(shù)據(jù)分析GRANT SELECT ON mydb.* TO report_user% IDENTIFIED BY password; ALTER USER report_user% WITH MAX_USER_CONNECTIONS 20;這樣就算報(bào)表任務(wù)寫得很爛、連接池配得很大它最多也就占用20個(gè)連接不會(huì)影響主業(yè)務(wù)的連接供給。還有一種更精細(xì)的做法核心業(yè)務(wù)用獨(dú)立的MySQL賬號(hào)不同微服務(wù)使用不同的賬號(hào)前綴這樣既隔離了連接配額又能在processlist里一眼看出連接歸屬。3.6 連接數(shù)滿之后的應(yīng)急逃生通道連接數(shù)打滿最尷尬的處境是你想連上去查狀態(tài)、殺連接結(jié)果連不上了因?yàn)槟阋彩恰斑B接”之一。這里有幾個(gè)務(wù)實(shí)的做法第一預(yù)留管理連接。MySQL始終為具備CONNECTION_ADMIN權(quán)限或MySQL 5.7及之前版本中的SUPER權(quán)限的賬號(hào)保留一條連接通道。所謂“保留”不是完全不受max_connections限制而是在連接數(shù)達(dá)到上限后管理員賬號(hào)仍然可以建立額外的連接。上限是max_connections 1這條額外的通道就是應(yīng)急逃生的關(guān)鍵。所以生產(chǎn)環(huán)境的DBA賬號(hào)一定要配置為超級(jí)權(quán)限賬號(hào)并且千萬別把這個(gè)賬號(hào)給應(yīng)用用。平時(shí)用普通賬號(hào)連接出了問題用管理員賬號(hào)連接。第二緊急調(diào)大連接數(shù)。如果是max_connections真的不夠管理員能連上之后立即執(zhí)行SET GLOBAL max_connections 2000;這是動(dòng)態(tài)參數(shù)不用重啟立刻生效。但如上所述這只能緩解不能根除。緊急調(diào)整后必須排查連接的去向。第三清理僵尸連接。找到真正的空閑連接后可以直接killKILL 12345;這里有個(gè)細(xì)節(jié)要注意KILL只能殺Admin連接不用想KILL權(quán)限足夠就可以。但注意如果有正在執(zhí)行的查詢直接KILL會(huì)中斷這個(gè)查詢業(yè)務(wù)側(cè)可能報(bào)錯(cuò)。對(duì)于Sleep狀態(tài)的連接殺掉是安全的。批量清理時(shí)可以先把要?dú)⒌倪B接拼出來確認(rèn)無誤后執(zhí)行SELECT CONCAT(KILL , id, ;) FROM information_schema.processlist WHERE command Sleep AND time 600;把結(jié)果復(fù)制出來執(zhí)行也可以配合存儲(chǔ)過程循環(huán)處理但保險(xiǎn)起見手工執(zhí)行更穩(wěn)妥省得誤殺活躍連接。4. 線上實(shí)戰(zhàn)連接數(shù)問題的排查與規(guī)避4.1 一個(gè)典型的連接數(shù)打滿案例某天下午一個(gè)內(nèi)部系統(tǒng)的線上數(shù)據(jù)庫突然告警Threads_connected曲線直線拉升半小時(shí)內(nèi)從200漲到1500直接觸頂。我連上去后第一件事就是查processlistSELECT command, state, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY command, state ORDER BY cnt DESC;結(jié)果觸目驚心大部分連接是Sleep狀態(tài)占比超過80%而且host全部集中在同一個(gè)應(yīng)用服務(wù)網(wǎng)段。再查這個(gè)應(yīng)用對(duì)應(yīng)的連接池配置發(fā)現(xiàn)兩個(gè)問題第一連接池的minimumIdle設(shè)置得很大每個(gè)實(shí)例維持35個(gè)空閑連接20個(gè)實(shí)例就是700個(gè)常駐連接——這本身就已經(jīng)占了很大額度。第二wait_timeout是默認(rèn)的8小時(shí)即便程序偶爾釋放連接失敗MySQL也不會(huì)主動(dòng)回收。當(dāng)時(shí)做了三件事SET GLOBAL wait_timeout 300; SET GLOBAL max_connections 2000;然后寫了一條SQL把所有Sleep超過300秒的連接全部殺掉SELECT CONCAT(KILL , id, ;) FROM information_schema.processlist WHERE command Sleep AND time 300;執(zhí)行完連接數(shù)立刻降下來了。但既然是慢性的就要處理根因。后面協(xié)調(diào)應(yīng)用團(tuán)隊(duì)改了兩處連接池minimumIdle降到5maxLifetime調(diào)到480秒并開啟連接有效性檢測。修改后觀察一周連接數(shù)穩(wěn)定在200到300之間水位健康。這個(gè)案例可以提煉出一條通用排查路徑先看狀態(tài)總量再看構(gòu)成command分組再定位來源host分組最后對(duì)癥處理參數(shù)調(diào)整連接清理。4.2 慢查詢和鎖等待怎么偽裝成連接數(shù)問題有一種場景極具迷惑性表面看是連接數(shù)暴漲實(shí)際是慢查詢和鎖等待把連接拖住了。具體來說一個(gè)慢查詢執(zhí)行幾十秒連接池里等待這個(gè)操作釋放的請求就會(huì)排隊(duì)每個(gè)排隊(duì)請求實(shí)際上占用了連接池的一個(gè)連接。連接池耗盡后新的請求只能等待connectionTimeout超時(shí)表現(xiàn)和MySQL連接數(shù)打滿幾乎一樣——應(yīng)用日志里都是一堆連接超時(shí)報(bào)錯(cuò)。但此時(shí)MySQL側(cè)的Threads_connected可能并沒有超限問題出在應(yīng)用側(cè)的連接池被“餓死”了。排查這種問題要先看MySQL側(cè)的Threads_running。如果Threads_running很高比如持續(xù)超過幾十說明確實(shí)有查詢在執(zhí)行。這時(shí)候要去看慢查詢?nèi)罩径ㄎ痪唧w是哪條SQL在拖后腿同時(shí)查鎖等待SELECT * FROM sys.innodb_lock_waits;sys.innodb_lock_waits是MySQL 5.7之后提供的一個(gè)視圖直接能看到誰在等鎖、誰持有鎖、等待多久。大部分情況下找到持有鎖的事務(wù)并評(píng)估是否要kill比盲目處理連接數(shù)有效得多。見過不少團(tuán)隊(duì)在這種場景下誤判成“連接數(shù)不夠”反復(fù)調(diào)大max_connections結(jié)果只是把問題推遲了慢查詢還在新連接來了繼續(xù)排隊(duì)整個(gè)系統(tǒng)卡到崩潰。記住一句話連接數(shù)是結(jié)果不是原因。排查時(shí)永遠(yuǎn)要問一句這些連接為什么存在4.3 連接數(shù)監(jiān)控告警的最佳實(shí)踐最后系統(tǒng)性地聊一下連接數(shù)的監(jiān)控和告警建設(shè)。別小看這部分一套好用的告警體系能在故障發(fā)生前就把問題按住。建議至少設(shè)置三級(jí)告警告警級(jí)別觸發(fā)條件響應(yīng)動(dòng)作關(guān)注連接數(shù)利用率 60%觀察趨勢確認(rèn)是否有新業(yè)務(wù)上線警告連接數(shù)利用率 80%排查連接構(gòu)成清理異常連接嚴(yán)重連接數(shù)利用率 90% 或出現(xiàn)拒絕連接立即介入準(zhǔn)備擴(kuò)容或kill連接除了比例告警還要監(jiān)控Aborted_connects的增量。這個(gè)值平時(shí)基本是0一旦往上走說明連接已經(jīng)開始被拒了這時(shí)候再處理就不如提前處理。采集方式上建議用獨(dú)立的監(jiān)控通道采集不占用業(yè)務(wù)連接配額??梢允褂帽镜豷ocket方式連接MySQL也可以在連接串里顯式指定一個(gè)專門的監(jiān)控賬號(hào)這個(gè)賬號(hào)只有PROCESS、SHOW DATABASES等輕量權(quán)限不會(huì)對(duì)業(yè)務(wù)造成影響。另外提醒一句Threads_running的監(jiān)控比Threads_connected更早暴露性能問題。連接數(shù)緩慢上升不可怕可怕的是Threads_running突然升高——那往往意味著線上有慢查詢或鎖風(fēng)暴正在發(fā)酵。4.4 常見問題速查表現(xiàn)象可能原因排查方法解決方案連接數(shù)緩慢持續(xù)上升連接池未回收連接 / 連接泄漏查看Sleep狀態(tài)連接數(shù)和來源host修復(fù)連接池調(diào)小wait_timeout連接數(shù)瞬間打滿連接風(fēng)暴 / 應(yīng)用重啟 / 流量尖峰查看processlist出現(xiàn)時(shí)間點(diǎn)緊急調(diào)大max_connectionskill多余連接檢查應(yīng)用側(cè)連接池配置應(yīng)用報(bào)連接超時(shí)但MySQL連接數(shù)未滿應(yīng)用連接池排隊(duì) / 慢SQL占用連接查看應(yīng)用日志、MySQLThreads_running優(yōu)化慢SQL調(diào)整連接池參數(shù)連接頻繁建立和斷開連接池配置不當(dāng) / 超時(shí)過短對(duì)比Threads_created與Connections調(diào)整連接池最小/最大連接數(shù)調(diào)大thread_cache_size某些賬號(hào)占用過多連接賬號(hào)沒有配額限制按user統(tǒng)計(jì)連接數(shù)設(shè)置max_user_connections連接被殺后應(yīng)用報(bào)錯(cuò)應(yīng)用使用失效連接查看應(yīng)用連接池日志開啟連接池的testOnBorrow或啟用validationQuery調(diào)整wait_timeout后應(yīng)用偶發(fā)異常連接池未感知服務(wù)端斷開應(yīng)用連接池配置設(shè)置連接池maxLifetime小于wait_timeout4.5 我最后想分享的一點(diǎn)經(jīng)驗(yàn)連接數(shù)這件事說起來是MySQL的一個(gè)小參數(shù)但背后牽扯到應(yīng)用架構(gòu)、連接池配置、運(yùn)維監(jiān)控、SQL質(zhì)量等多個(gè)層面。我踩過最深的坑就是“只管MySQL不管應(yīng)用”參數(shù)調(diào)了一輪又一輪問題卻反復(fù)出現(xiàn)。后來我才意識(shí)到MySQL側(cè)能做的其實(shí)很有限——設(shè)好上限、設(shè)好超時(shí)、做好監(jiān)控、留好逃生通道剩下的功夫全在應(yīng)用側(cè)。尤其想提醒剛接手?jǐn)?shù)據(jù)庫運(yùn)維的同學(xué)連接數(shù)查詢不是背幾條SQL而是要理解每個(gè)狀態(tài)值背后的含義以及它跟應(yīng)用行為之間的關(guān)系。等你遇到一次連接數(shù)打滿把整個(gè)過程從頭跟到尾捋一遍你對(duì)數(shù)據(jù)庫連接機(jī)制的理解會(huì)有一個(gè)質(zhì)的飛躍。