據(jù)庫(kù)連接池爆滿(mǎn)排查指南:從Threads_connected到根因定位與調(diào)優(yōu))
凌晨?jī)牲c(diǎn)手機(jī)被連續(xù)告警震醒。某個(gè)內(nèi)部系統(tǒng)的數(shù)據(jù)庫(kù)連接池在十分鐘內(nèi)被拉滿(mǎn)應(yīng)用層開(kāi)始連環(huán)報(bào)錯(cuò)用戶(hù)頁(yè)面卡成白屏部分接口直接 500。打開(kāi)監(jiān)控一看MySQL 的Threads_connected已經(jīng)頂?shù)綆浊нB接池那邊的空閑連接趨近于零所有新請(qǐng)求都在排隊(duì)等連接。這類(lèi)“數(shù)據(jù)庫(kù)連接池爆滿(mǎn)”的問(wèn)題在業(yè)務(wù)高峰期太常見(jiàn)了但每次發(fā)生都讓人手心冒汗。表面上是“連接不夠用”實(shí)際上背后藏著完全不同的根因可能是連接泄漏可能是慢 SQL 拖住了連接也可能是事務(wù)邊界太長(zhǎng)把連接占死了甚至可能是連接池參數(shù)和數(shù)據(jù)庫(kù)參數(shù)互相打架。這篇文章就把我多次排查連接池爆滿(mǎn)的經(jīng)驗(yàn)完整梳理一遍從表象到根因再到止血、治根、防復(fù)發(fā)一步步講清楚。適合剛接觸數(shù)據(jù)庫(kù)和后端開(kāi)發(fā)的朋友也適合每次遇到連接告警都不知道從哪下手的人。1. 連接池爆滿(mǎn)的典型信號(hào)先從現(xiàn)象避開(kāi)調(diào)參誤區(qū)1.1 應(yīng)用層和數(shù)據(jù)庫(kù)層的異常表現(xiàn)連接池爆滿(mǎn)時(shí)最先報(bào)警的通常是連接池客戶(hù)端。用 HikariCP 的應(yīng)用日志里會(huì)出現(xiàn)類(lèi)似Connection is not available, request timed out after 30000ms用 DBCP 或 C3P0 的則是Cannot get a connection, pool exhausted或者是Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available這些錯(cuò)誤都有一個(gè)共同點(diǎn)應(yīng)用側(cè)拿不到可用連接后續(xù)請(qǐng)求只能排隊(duì)等待等到超時(shí)就直接拋異常。用戶(hù)看到的現(xiàn)象就是頁(yè)面轉(zhuǎn)圈、接口超時(shí)、部分功能不可用。數(shù)據(jù)庫(kù)側(cè)的指標(biāo)更直接。登錄 MySQL 執(zhí)行SHOW STATUS LIKE Threads_connected; SHOW STATUS LIKE Threads_running;正常情況下Threads_connected應(yīng)該穩(wěn)定在幾十到幾百的量級(jí)爆滿(mǎn)時(shí)會(huì)直接逼近甚至頂?shù)絤ax_connections此時(shí)再新進(jìn)連接就會(huì)收到“Too many connections”的報(bào)錯(cuò)。這就是為什么很多人在這個(gè)時(shí)間點(diǎn)會(huì)下意識(shí)去改max_connections。1.2 最大的坑一上來(lái)就調(diào)大連接池和 max_connections遇到連接爆滿(mǎn)人的第一反應(yīng)往往是“連接不夠用那就加連接”。于是把 HikariCP 的maximumPoolSize從 50 調(diào)到 200再把 MySQL 的max_connections從 500 調(diào)到 2000重啟應(yīng)用。這種做法在低峰期可能管用但高峰期大概率會(huì)“死得更快”。原因很簡(jiǎn)單連接數(shù)提高每個(gè)連接都要占用 MySQL 服務(wù)端的內(nèi)存和線(xiàn)程資源數(shù)據(jù)庫(kù)的負(fù)擔(dān)反而更重。如果根因是連接泄漏新的連接池上限只是讓池子從“2小時(shí)爆滿(mǎn)”變成“4小時(shí)爆滿(mǎn)”問(wèn)題還在時(shí)間問(wèn)題而已。如果根因是慢 SQL那么連接從 50 加到 200慢 SQL 占用的連接也同比放大并且會(huì)在數(shù)據(jù)庫(kù)端制造更多的鎖競(jìng)爭(zhēng)和上下文切換。所以遇到連接爆滿(mǎn)千萬(wàn)不要急著動(dòng)參數(shù)。先把“連接都消耗到哪里去了”查清楚再?zèng)Q定怎么處理。工具其實(shí)很簡(jiǎn)單MySQL 自帶的一張進(jìn)程列表就是最初的突破口。2. 用 show processlist 和線(xiàn)程堆棧給連接“畫(huà)像”2.1 SHOW PROCESSLIST 字段怎么看Command 分布決定排查方向在數(shù)據(jù)庫(kù)側(cè)執(zhí)行SHOW FULL PROCESSLIST;這是排查連接問(wèn)題的第一板斧。它會(huì)把當(dāng)前數(shù)據(jù)庫(kù)上每一個(gè)連接列出來(lái)核心字段如下字段含義排查價(jià)值Id連接線(xiàn)程 ID后面 kill 連接時(shí)用User賬號(hào)來(lái)源區(qū)分是業(yè)務(wù)賬號(hào)還是運(yùn)維賬號(hào)Host客戶(hù)端 IP 和端口找到連接來(lái)自哪個(gè)應(yīng)用節(jié)點(diǎn)db當(dāng)前連接的默認(rèn)庫(kù)確認(rèn)是不是目標(biāo)庫(kù)Command連接當(dāng)前的狀態(tài)Query、Sleep 等是關(guān)鍵Time當(dāng)前狀態(tài)持續(xù)秒數(shù)數(shù)字越大越可疑State具體執(zhí)行狀態(tài)Sending data、Waiting for lock 等Info正在執(zhí)行的 SQL慢 SQL 的現(xiàn)場(chǎng)證據(jù)這里最重要的一步是看 Command 列的分布。如果連接里有大量Sleep說(shuō)明連接是被“占住但沒(méi)干活”的狀態(tài)帶爆的——重點(diǎn)懷疑連接泄漏、事務(wù)未提交、連接未歸還。如果大量是Query且Time很長(zhǎng)說(shuō)明是慢 SQL 或鎖等待拖住了連接導(dǎo)致后續(xù)請(qǐng)求全部排隊(duì)。簡(jiǎn)單說(shuō)Sleep 多查代碼漏連接Query 多查 SQL 和鎖。這兩條方向基本決定了后面所有排查動(dòng)作。2.2 從應(yīng)用線(xiàn)程堆棧追蹤連接獲取點(diǎn)如果懷疑連接泄漏光在數(shù)據(jù)庫(kù)側(cè)看到一堆 Sleep 連接還不夠必須找出是應(yīng)用代碼里哪個(gè)位置拿了連接沒(méi)還。這時(shí)需要把 Java 線(xiàn)程堆棧倒出來(lái)看jstack 應(yīng)用PID thread.log然后在日志里找連接池等待和獲取連接的棧。例如 HikariCP 下搜索grep -n HikariCP thread.log | head -100或者看是否存在at com.zaxxer.hikari.pool.HikariPool.getConnection如果大量業(yè)務(wù)線(xiàn)程同時(shí)卡在getConnection等待并且你看到業(yè)務(wù)代碼的調(diào)用鏈停在一個(gè)具體 Service 方法上那么那個(gè)方法附近大概率就是連接發(fā)生泄漏的位置。再配合查看這段代碼里是否手寫(xiě)了Connection獲取和關(guān)閉基本就能鎖定來(lái)源。2.3 用連接池監(jiān)控與 leakDetection 坐實(shí)泄漏多數(shù)連接池組件都提供活躍連接數(shù)的指標(biāo)。HikariCP 的監(jiān)控里可以看到active、idle、pending的實(shí)時(shí)值。如果在流量平穩(wěn)甚至下降時(shí)active仍然不斷上升那就是泄漏的典型曲線(xiàn)。HikariCP 還提供了泄漏檢測(cè)閾值參數(shù)leak-detection-threshold: 60000單位是毫秒。含義是一個(gè)連接從池中借出超過(guò) 60 秒未歸還就記錄一條泄漏告警日志。典型輸出是Apparent connection leak detected需要注意這個(gè)參數(shù)不應(yīng)設(shè)得過(guò)短否則一些本來(lái)耗時(shí)較長(zhǎng)的合法請(qǐng)求也會(huì)誤報(bào)一般設(shè)成 30 秒到 60 秒比較合理。它不能自動(dòng)修復(fù)泄漏只能幫你把問(wèn)題代碼暴露出來(lái)在下一節(jié)我們會(huì)展開(kāi)幾種最常見(jiàn)的泄漏寫(xiě)法。3. 四個(gè)真實(shí)根因每種都有不同的“爆法”3.1 連接泄漏池被“僵尸連接”填滿(mǎn)連接泄漏是連接池爆滿(mǎn)的第一大戶(hù)也是最容易在代碼評(píng)審中漏掉的場(chǎng)景。最常見(jiàn)的寫(xiě)法是// 錯(cuò)誤寫(xiě)法finally 里沒(méi)有 close public ListUser queryUsers() throws Exception { Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT * FROM user); while (rs.next()) { // ... } return list; // conn、stmt、rs 都沒(méi)有關(guān)閉 }這里面只要stmt或rs沒(méi)有關(guān)閉連接歸還池子就會(huì)延遲如果異常發(fā)生在中途連接就直接“丟”了。等連接池里的連接被這種代碼借走但不還池子很快就會(huì)被耗干。正確做法是用 Java 的 try-with-resources讓連接、語(yǔ)句、結(jié)果集都自動(dòng)關(guān)閉// 正確寫(xiě)法 public ListUser queryUsers() throws Exception { String sql SELECT * FROM user; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { // ... } return list; } }排查技巧也很實(shí)用先搜全工程里所有g(shù)etConnection()的調(diào)用點(diǎn)逐個(gè)確認(rèn)是否有配套的 close然后重點(diǎn)檢查 try-catch-finally 結(jié)構(gòu)看 finally 里是否真的關(guān)閉了。很多老項(xiàng)目用的是手寫(xiě)的 JDBC 工具類(lèi)這種地方最容易藏連接泄漏需要重點(diǎn)盯。3.2 慢 SQL 與長(zhǎng)事務(wù)一個(gè)查詢(xún)占住窗口不放連接池就像銀行柜臺(tái)每個(gè)窗口同一時(shí)間只能服務(wù)一個(gè)客戶(hù)。一條 SQL 跑了 30 秒就相當(dāng)于一個(gè)客戶(hù)占著柜臺(tái) 30 秒后面排隊(duì)的人只能干等。慢 SQL 觸發(fā)連接池爆滿(mǎn)的典型表現(xiàn)就是Threads_connected沒(méi)到頂?shù)玊hreads_running很高且有大量連接卡在同一個(gè) SQL 上。在SHOW FULL PROCESSLIST里你會(huì)看到 Info 列是同一個(gè)查詢(xún)模板Time 字段都是幾十秒甚至幾分鐘State 常見(jiàn)為Sending data、Creating sort index、Copying to tmp table等。這類(lèi)問(wèn)題的處理順序是先拿這條 SQL 執(zhí)行EXPLAIN看是否走了索引、掃描行數(shù)是不是太大??词欠褚?yàn)樯罘猪?yè)導(dǎo)致掃描了大量數(shù)據(jù)例如LIMIT 1000000, 20。看是否在查詢(xún)里做了大范圍的排序或臨時(shí)表操作。一個(gè)很常見(jiàn)的例子是給大表做統(tǒng)計(jì)報(bào)表SQL 里寫(xiě)法是SELECT province, COUNT(*) FROM order_table WHERE create_time NOW() - INTERVAL 1 DAY GROUP BY province;如果create_time上沒(méi)有索引這個(gè)查詢(xún)會(huì)全表掃描上百萬(wàn)行跑個(gè)幾十秒很正常。一個(gè)這樣的查詢(xún)就能讓整個(gè)連接池的可用連接迅速下降。長(zhǎng)事務(wù)和慢 SQL 經(jīng)常成對(duì)出現(xiàn)。一個(gè)事務(wù)里包了多次查詢(xún)用戶(hù)中途不操作也不提交事務(wù)連接就一直掛在那里表現(xiàn)同樣是連接池被占滿(mǎn)。3.3 鎖等待連接在排隊(duì)中耗干鎖等待是連接池爆滿(mǎn)里最隱蔽的一種。前面兩類(lèi)是“連接被代碼占用”或“被 SQL 跑滿(mǎn)”鎖等待則是“連接在等鎖前端一直占著連接不釋放”。比如庫(kù)存扣減場(chǎng)景UPDATE inventory SET stock stock - 1 WHERE product_id 123;如果這條語(yǔ)句在某個(gè)事務(wù)里執(zhí)行而另一個(gè)事務(wù)已經(jīng)鎖住了這行記錄沒(méi)提交后續(xù)所有更新同一行的連接都會(huì)卡在Waiting for lock狀態(tài)。數(shù)據(jù)庫(kù)側(cè)看到的是大量連接都在等待連接池側(cè)看到的是所有連接都處于活躍但“假死”狀態(tài)。排查鎖等待需要看三張表SELECT * FROM information_schema.innodb_trx; SELECT * FROM information_schema.innodb_lock_waits; SELECT * FROM information_schema.innodb_locks;重點(diǎn)看trx_state是RUNNING但trx_started時(shí)間很長(zhǎng)的舊事務(wù)那個(gè)事務(wù)基本上就是持鎖大戶(hù)。處理方式通常是先把這個(gè)舊事務(wù)處理掉鎖一釋放后面的連接就會(huì)繼續(xù)跑連接池爆滿(mǎn)現(xiàn)象自然緩解。防止這類(lèi)問(wèn)題的核心是做短事務(wù)UPDATE 之前先確認(rèn) WHERE 條件用了索引不要在事務(wù)里做遠(yuǎn)程接口調(diào)用不要在一個(gè)事務(wù)里更新大量行。這些習(xí)慣比事后調(diào)參重要得多。3.4 連接池參數(shù)和 MySQL 參數(shù)互毆還有一類(lèi)爆滿(mǎn)是“配置打架”造成的。最典型的是連接池的maxLifetime大于 MySQL 的wait_timeout。MySQL 默認(rèn)wait_timeout是 28800 秒也就是 8 小時(shí)。如果連接池讓一個(gè)連接活過(guò) 8 小時(shí)后數(shù)據(jù)庫(kù)端已經(jīng)把它斷掉了但連接池不知道還會(huì)繼續(xù)拿這個(gè)連接去查詢(xún)。表現(xiàn)就是應(yīng)用運(yùn)行一段時(shí)間后偶發(fā)性地報(bào)連接失敗之后連接數(shù)突然暴漲那是因?yàn)檫B接池在瘋狂重建連接一下子把連接數(shù)頂了上去。正確的參數(shù)協(xié)同思路如下參數(shù)推薦方向MySQL wait_timeout28800 秒默認(rèn)或按實(shí)際情況調(diào)整連接池 maxLifetime應(yīng)小于 wait_timeout比如 180 秒到 1400 秒之間connectionTimeout獲取連接超時(shí)默認(rèn) 30 秒可按響應(yīng)要求收緊idleTimeout / minIdle空閑連接回收策略避免大量空閑連接占住 DB這類(lèi)問(wèn)題排查時(shí)有個(gè)特點(diǎn)不是穩(wěn)定復(fù)現(xiàn)而是“隔一段時(shí)間爆一次”。如果連接池是 HikariCP它的默認(rèn)maxLifetime是 1800000 毫秒30 分鐘小于 MySQL 的 8 小時(shí)所以一般不容易踩到但用某些連接池組件時(shí)maxLifetime默認(rèn)是 0代表永不超時(shí)這時(shí)就很容易和數(shù)據(jù)庫(kù)的wait_timeout沖突。4. MySQL 側(cè)的連接上限到底該怎么定4.1 max_connections 不是第一個(gè)要?jiǎng)拥男o不少人一看到Too many connections就急著去改max_connections。實(shí)際上max_connections更像是一個(gè)“風(fēng)險(xiǎn)防火墻”而不是性能調(diào)優(yōu)參數(shù)。合理的做法是正常情況下max_used_connections應(yīng)該離max_connections有足夠余量一旦距離被拉近就要警惕而不是把墻后移。要查看歷史峰值占用SHOW STATUS LIKE Max_used_connections;如果這個(gè)值和當(dāng)前Threads_connected都快貼到max_connections了那連接池爆滿(mǎn)是必然結(jié)果因?yàn)閿?shù)據(jù)庫(kù)端根本沒(méi)有余量給新連接。4.2 Threads_connected 和 Threads_running 要分開(kāi)看這兩個(gè)指標(biāo)被很多人混用其實(shí)含義完全不同Threads_connected當(dāng)前打開(kāi)的連接數(shù)。它高說(shuō)明“有很多連接掛著”。Threads_running當(dāng)前正在執(zhí)行查詢(xún)的線(xiàn)程數(shù)。它高說(shuō)明“數(shù)據(jù)庫(kù)真的在忙”。如果Threads_connected高但Threads_running低多半是連接閑置或泄漏數(shù)據(jù)庫(kù)本身不忙是連接管理出了問(wèn)題。如果兩者都高說(shuō)明數(shù)據(jù)庫(kù)處理能力到瓶頸了要么優(yōu)化 SQL要么對(duì)讀寫(xiě)做拆分和限流。如果Threads_running高但Threads_connected不高那反而是健康的——表明連接都被有效利用只是數(shù)據(jù)庫(kù)確實(shí)在超負(fù)荷工作。這兩者結(jié)合起來(lái)看才能判斷“連接爆滿(mǎn)”到底是資源問(wèn)題還是代碼問(wèn)題。4.3 連接數(shù)與內(nèi)存、線(xiàn)程調(diào)度之間的聯(lián)動(dòng)每個(gè) MySQL 連接都有成本。連接建立后服務(wù)端要給它分配線(xiàn)程、內(nèi)存尤其要考慮各類(lèi) buffer。例如sort_buffer_sizejoin_buffer_sizetmp_table_size如果這些參數(shù)設(shè)置得偏大比如sort_buffer_size是 2MB那么 2000 個(gè)連接在極端情況下可能同時(shí)占用數(shù) GB 內(nèi)存。這就是為什么把小內(nèi)存機(jī)器上的max_connections盲目調(diào)大反而可能直接把數(shù)據(jù)庫(kù) OOM 拖垮。正確的做法是先估算機(jī)器內(nèi)存再看全庫(kù)連接數(shù)所需的 buffer 總量最后反向推導(dǎo)合理上限。例如 16GB 內(nèi)存的實(shí)例InnoDB buffer pool 分走 8GB給每個(gè)連接的平均 buffer 按 2MB 估那連接數(shù)控制在 1000 以?xún)?nèi)都不算激進(jìn)但如果機(jī)器只有 4GB連接數(shù)就得壓到 300 以下。這個(gè)計(jì)算不需要很精確目的是讓你心里有數(shù)連接數(shù)從來(lái)不是越大越好它是有資源約束的。連接池的作用是“復(fù)用”而不是“無(wú)腦堆量”。5. 止血、治根、驗(yàn)證一套能落地的完整流程5.1 先止血臨時(shí) kill 的取舍連接已經(jīng)爆滿(mǎn)的現(xiàn)場(chǎng)首要任務(wù)不是排查而是讓業(yè)務(wù)先恢復(fù)。最直接的手段是殺掉一批可疑連接。殺連接之前必須確認(rèn)兩點(diǎn)連接是誰(shuí)的、連接在干什么。前面用SHOW FULL PROCESSLIST看到的結(jié)果就是依據(jù)??梢?xún)?yōu)先殺掉Command Sleep且Time超過(guò) 60 秒的連接這類(lèi)連接大概率是空閑的即使是事務(wù)未提交kill 之后事務(wù)也會(huì)回滾寧可損失極小范圍的數(shù)據(jù)一致性也不能讓整個(gè)服務(wù)持續(xù)不可用。執(zhí)行格式KILL 12345;生產(chǎn)環(huán)境更穩(wěn)妥的方法是寫(xiě)腳本按條件批量處理但要設(shè)置白名單排除管理賬號(hào)連接。不推薦一上來(lái)就重啟數(shù)據(jù)庫(kù)或者重啟應(yīng)用。重啟只能讓當(dāng)前連接清零如果根因是連接泄漏或慢 SQL重啟之后很快又會(huì)滿(mǎn)。它只適合在完全無(wú)法定位問(wèn)題時(shí)的應(yīng)急手段。5.2 治根代碼修復(fù)、SQL 治理、事務(wù)邊界止血只能維持幾個(gè)小時(shí)的穩(wěn)定真正要處理的是根因。連接泄漏把所有 JDBC 操作統(tǒng)一改成 try-with-resources可以做一個(gè)封裝的 DAO 基類(lèi)避免團(tuán)隊(duì)里各寫(xiě)各的。數(shù)據(jù)源獲取統(tǒng)一走 Spring 的Autowired或標(biāo)準(zhǔn)池 API不要再寫(xiě)“自己 new Connection”的工具類(lèi)。慢 SQL先開(kāi)慢查詢(xún)?nèi)罩緎low_query_log 1 long_query_time 1把超過(guò) 1 秒的 SQL 全部收集起來(lái)挑出高頻和高耗時(shí)的逐條優(yōu)化。常見(jiàn)手段包括加索引、改寫(xiě)查詢(xún)條件、把深分頁(yè)改成游標(biāo)或基于上次最大 ID 的分頁(yè)方式。事務(wù)邊界事務(wù)盡量只包裹核心的寫(xiě)操作不要在事務(wù)內(nèi)做遠(yuǎn)程 HTTP 調(diào)用、循環(huán)查詢(xún)等慢操作。如果做批量更新控制在合理行數(shù)內(nèi)并分批提交長(zhǎng)事務(wù)是連接占用的隱形殺手。5.3 驗(yàn)證壓測(cè) 監(jiān)控告警 灰度發(fā)布修復(fù)之后不能直接宣布結(jié)束必須驗(yàn)證。先做一輪壓力測(cè)試模擬高于平時(shí)的 QPS觀察連接池曲線(xiàn)是否平穩(wěn)、Threads_running是否處于合理范圍、連接獲取時(shí)間 P99 是否達(dá)標(biāo)。我常用的做法是壓測(cè)時(shí)把連接池的active、idle、pending三個(gè)指標(biāo)都打出來(lái)。如果流量上升時(shí)active同步上升、流量平穩(wěn)后active能降回去說(shuō)明池子是健康的。如果active只升不降說(shuō)明還會(huì)漏繼續(xù)找代碼問(wèn)題。監(jiān)控告警也要跟上。至少給這幾個(gè)指標(biāo)配告警Threads_connected與max_connections的比值超過(guò) 80%。連接池獲取連接等待時(shí)間超過(guò) 1 秒。連接池pending長(zhǎng)期大于 0。慢 SQL 數(shù)量突增。6. 用 QPS 反推連接池大小連接數(shù)規(guī)劃的計(jì)算方法6.1 連接占用公式與計(jì)算示例連接池大小不該拍腦袋可以用一個(gè)粗算公式來(lái)估算連接池大小 ≈ 平均 QPS × 單請(qǐng)求平均數(shù)據(jù)庫(kù)耗時(shí) × (1 排隊(duì)系數(shù))舉例說(shuō)明。某個(gè)業(yè)務(wù)接口每秒請(qǐng)求量 500這個(gè)接口每次請(qǐng)求訪問(wèn)數(shù)據(jù)庫(kù)的平均耗時(shí)是 20 毫秒。那么單請(qǐng)求占用的連接時(shí)間就是 0.02 秒。同一時(shí)刻需要的連接數(shù)大約是500 × 0.02 10再考慮一部分請(qǐng)求可能因?yàn)榈却i、結(jié)果集處理等原因多占一點(diǎn)時(shí)間加一個(gè) 30%50% 的排隊(duì)系數(shù)10 × 1.5 15也就是說(shuō)這個(gè)接口的預(yù)留連接池大小 1520 已經(jīng)夠用。如果實(shí)測(cè)連接一直在 20 以上而不下降說(shuō)明耗時(shí)估算偏低或存在泄漏而不是配置不夠。6.2 三種典型負(fù)載下的池大小設(shè)計(jì)不同業(yè)務(wù)形態(tài)對(duì)連接池的需求差異很大業(yè)務(wù)形態(tài)特征池大小建議低頻管理類(lèi)QPS 低、單次查詢(xún)簡(jiǎn)單1020高頻 OLTP 類(lèi)QPS 高、單次查詢(xún)幾毫秒到幾十毫秒核心接口單獨(dú)估算池總大小 50100報(bào)表/批量類(lèi)單次查詢(xún)秒級(jí)、并發(fā)不高池不用太大但 SQL 必須優(yōu)化否則池再大也堵這里面最關(guān)鍵的理念是連接池解決的是“復(fù)用連接”的問(wèn)題不是“讓更多請(qǐng)求同時(shí)跑”的問(wèn)題。單個(gè) MySQL 連接的查詢(xún)速度取決于索引和執(zhí)行計(jì)劃而不是池的大小。池子設(shè)得再大SQL 本身要跑 10 秒照樣拖垮。6.3 需要長(zhǎng)期盯的監(jiān)控指標(biāo)最后建議把這幾個(gè)指標(biāo)做成日常巡檢項(xiàng)連接池活躍連接數(shù)看漲跌趨勢(shì)是否與流量一致。連接獲取等待時(shí)長(zhǎng) P99超過(guò) 500ms 就要警惕。連接創(chuàng)建/銷(xiāo)毀速率如果頻繁創(chuàng)建銷(xiāo)毀說(shuō)明maxLifetime或idleTimeout配置不合適。MySQLThreads_connected與Threads_running的比值。這些指標(biāo)可以在看板上做成兩張圖一張展示連接池狀態(tài)一張展示數(shù)據(jù)庫(kù)狀態(tài)。連接池爆滿(mǎn)不是突發(fā)事故大多是緩慢惡化的過(guò)程。有了這些圖基本都能在用戶(hù)感知之前提前處理。最后分享一個(gè)印象深刻的排查經(jīng)歷。某次連接池持續(xù)告警我把應(yīng)用、數(shù)據(jù)庫(kù)、參數(shù)都翻了個(gè)遍仍然時(shí)不時(shí)漲滿(mǎn)。最后發(fā)現(xiàn)是一個(gè)第三方 SDK 內(nèi)置了自己的連接池每次調(diào)用都新建一個(gè)池實(shí)例而不復(fù)用流量稍高就把數(shù)據(jù)庫(kù)連接數(shù)頂上天。那次之后我學(xué)會(huì)了排查連接問(wèn)題時(shí)要把中間件、SDK、框架的隱藏連接全部考慮進(jìn)去不能只盯業(yè)務(wù)代碼。希望這篇排查順序能幫你少熬幾個(gè)夜少掉幾根頭發(fā)。