「慢查詢只是響應變慢」是危險的幻覺。本文復盤一起真實故障:一條 70s 慢查詢掃描 790 萬行,引發應用服務 OOM、Full GC 風暴、線程阻塞、數據庫負載飆升至 50+,最終導致全站不可用。完整記錄定位、止血、根因分析、長效治理全過程。
# Query_time: 70.472013 Lock_time: 0.000078
# Rows_sent: 7915203 Rows_examined: 15984089 Rows_affected: 0
# Bytes_sent: 1258414478
USE js_sku;
SELECT ss_id, ss_sa_id, ss_si_id, ss_av_zid, ss_av_fid, ss_artno,
ss_av_zvalue, ss_av_fvalue, ss_av_zpic, ss_av_fpic, ss_number,
ss_sales, ss_cprice, ss_price, ss_stock, ss_orderid, ss_status,
ss_add_time, ss_lastmodify
FROM js_sgoods_sku
WHERE ss_si_id = 0 AND ss_status > 0
ORDER BY ss_orderid DESC, ss_av_fid ASC;
+----+-------------+---------------+------+---------------+----------+---------+-------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+------+---------------+----------+---------+-------+---------+-----------------------------+
| 1 | SIMPLE | js_sgoods_sku | ref | ss_si_id | ss_si_id | 4 | const | 9516091 | Using where; Using filesort |
+----+-------------+---------------+------+---------------+----------+---------+-------+---------+-----------------------------+
type=ref、key=ss_si_id 看似走索引,但 rows=951 萬、Using filesort 揭示真相:索引區分度極低,導致幾乎全表掃描 + 文件排序。
-- 索引統計信息
SHOW INDEX FROM js_sgoods_sku;
+---------------+------------+----------+--------------+-------------+-----------+-------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality |
+---------------+------------+----------+--------------+-------------+-----------+-------------+
| js_sgoods_sku | 1 | ss_si_id | 1 | ss_si_id | A | 1551779 |
+---------------+------------+----------+--------------+-------------+-----------+-------------+
-- 實際數據分佈
SELECT ss_si_id, COUNT(*) FROM js_sgoods_sku GROUP BY ss_si_id;
+----------+-----------+
| ss_si_id | COUNT(*) |
+----------+-----------+
| 0 | 7994788 | -- 佔 41.9%!
| ... | ... |
+----------+-----------+
結論:ss_si_id=0 是業務默認值,佔比近 42%,Cardinality 統計信息(155 萬)嚴重高估區分度,優化器誤判索引優於全表掃描。
790 萬行 × 1.2GB 網絡包,佔滿帶緩衝FGC 每 10s 一次、線程全部 BLOCKED、CPU 100%Load 0.x → 50+,險些拖垮數據庫// 伪代碼:Service 層攔截
if (param.siId == 0) {
log.warn("Forbidden query: siId=0");
return Collections.emptyList(); // 直接返回空,不打 DB
}
# 幹掉運行超過 30s、查 js_sgoods_sku 表的查詢
pt-kill --host=localhost --user=root --password=xxx \
--busy-time=30 --match-command=Query \
--match-info="js_sgoods_sku" --kill --print --victims=all
效果:秒級清理殘留慢查詢,DB 負載從 50+ 降回 0.x,爭取修復窗口。
| 維度 | 措施 | 驗收標準 |
|---|---|---|
| SQL 規範 | 禁止查詢默認值/全表掃描;Code Review 強制 EXPLAIN | 慢查詢日誌零增長 |
| 結果集限制 | 強制 LIMIT N;中間件攔截超大結果集 | 單查詢返回 < 1000 行 |
| 索引重構 | 針對高頻查詢建聯合索引 (ss_status, ss_si_id, ss_orderid, ss_av_fid) 覆蓋排序 | EXPLAIN rows < 1000 |
| 統計信息 | 定時 ANALYZE TABLE;大表啟用 innodb_stats_persistent_sample_pages 增大採樣 | Cardinality 偏差 < 10% |
| 熔斷機制 | pt-kill 守護進程 + ProxySQL 查詢規則限流 | 故障影響時間 < 30s |
| 壓測預案 | 全鏈路壓測驗證「默認值查詢」抗壓能力 | QPS 2倍峰值無宕機 |
慢查詢≠無害。大結果集 + 低區分度索引 + 無 LIMIT + 高併發 = 核彈。建立「慢查詢日審計 → 週度索引健康檢查 → 月度架構演進評審」機制,讓優化成為持續交付流程的一環,而非救火行動。