MySQL查詢優化完整指南:從理論到實踐
本文從MySQL查詢的基礎機制出發,深入探討單表查詢訪問方法、聯表查詢策略、成本計算原理、基于規則的優化技術,最后通過實際案例展示慢SQL的診斷和優化過程。
目錄
- 一、單表查詢的訪問方法
- 二、聯表查詢機制
- 三、查詢成本計算
- 四、基于規則的查詢優化
- 五、執行計劃分析
- 六、慢SQL治理實戰案例
一、單表查詢的訪問方法
MySQL 的訪問方法定義了查詢語句的執行方式,類似于從起點到終點的路線選擇。查詢優化器根據條件、索引和統計信息選擇最優訪問方法,以最小化執行成本(I/O、CPU、內存等)。雖然查詢結果相同,但不同訪問方法的效率差異顯著。
1.1 訪問方法性能對比
不同訪問方法的性能差異如下圖所示,從左到右效率遞減:
1.2 具體訪問方法詳解
1.2.1 const - 最高效訪問
- 描述: 通過主鍵或唯一二級索引與常數等值比較,定位單條記錄
- 性能: 效率最高(“坐火箭”),成本為常數級別,無需掃描或回表
- 適用場景: 主鍵或唯一索引的等值查詢,聯合索引需所有列等值匹配
- 注意: 唯一索引查詢 NULL 值(如
key IS NULL
)無法使用const
,因 NULL 可重復 - 示例:
SELECT * FROM table WHERE id = 100;
使用主鍵,定位單條記錄
1.2.2 ref - 普通索引等值查詢
- 描述: 使用普通二級索引進行等值查詢,可能匹配多條記錄,需回表獲取完整記錄
- 性能: 效率次于
const
(“坐高鐵”),適合選擇性高的索引,成本隨匹配記錄數增加 - 適用場景: 普通二級索引或聯合索引最左連續列等值匹配,包括 NULL 值查詢
- 示例:
SELECT * FROM table WHERE key1 = 'abc';
使用普通索引idx_key1
1.2.3 ref_or_null - 擴展NULL查詢
- 描述: 擴展
ref
,額外查找索引列為 NULL 的記錄 - 性能: 效率略低于
ref
,因需處理 NULL 檢查 - 適用場景: 等值查詢結合 NULL 檢查,如
WHERE key1 = 'abc' OR key1 IS NULL
- 注意: 若無 NULL 值,可能降級為
ref
1.2.4 range - 范圍查詢
- 描述: 使用索引(聚簇或二級索引)進行范圍查詢,條件對應數軸上的區間(單點或連續區間)
- 性能: 效率依賴范圍大小,選擇性高時接近
ref
,需回表 - 適用場景: 支持
>
,<
,IN
,BETWEEN
,LIKE
(前綴匹配)等操作符 - 示例:
SELECT * FROM table WHERE key1 IN ('a', 'b');
生成單點區間
1.2.5 index_merge - 多索引合并
-
描述: 使用多個二級索引合并結果,分為三種算法:
- Intersection: 取索引結果交集,適用于等值匹配或主鍵范圍匹配,需按主鍵排序
- Union: 取索引結果并集,適用于 OR 連接的等值匹配
- Sort-Union: 范圍查詢結果按主鍵排序后取并集,成本略高
-
性能: 效率低于單索引查詢(
range
/ref
),因涉及多索引操作和回表,但優于全表掃描 -
適用場景: 復雜條件涉及多個索引,如
WHERE key1 = 'a' OR key2 = 'b'
-
注意: 優化器根據成本選擇是否使用,聯合索引可替代以降低成本
1.2.6 index - 索引全掃描
- 描述: 直接掃描二級索引全部葉子節點,查詢列和條件均包含在索引中,免回表
- 性能: 優于全表掃描,因索引記錄小,但仍需全索引掃描
- 適用場景: 查詢列和條件全在索引中,如
SELECT key1 FROM table WHERE key1 = 'a';
- 注意: 需確保查詢不涉及非索引列
1.2.7 all - 全表掃描
- 描述: 掃描整個聚簇索引,逐行檢查條件
- 性能: 效率最低(“坐烏龜”),掃描全表記錄,I/O 成本高
- 適用場景: 無索引可用或條件選擇性低
- 示例:
SELECT * FROM table WHERE non_indexed_col = 1;
1.3 重要注意事項
二級索引與回表
通常使用單個二級索引,先定位記錄,再回表獲取完整數據。非索引條件在回表后過濾。
范圍區間確定
AND
連接取交集,OR
連接取并集- 無法使用索引的條件替換為
TRUE
,簡化區間計算 - 復雜條件需逐一分析,提取有效區間
聯合索引優化
多列查詢可通過聯合索引替代 index_merge
,但需平衡其他查詢對單列索引的需求。
優化器決策
優化器基于統計信息(ANALYZE TABLE
更新)選擇最低成本訪問方法,通過合理設計索引和查詢,MySQL 優化器能選擇高效訪問方法,顯著提升查詢性能。
二、聯表查詢機制
2.1 聯表查詢的本質
聯表查詢將多個表的記錄組合成笛卡爾積,通過過濾條件生成結果集。笛卡爾積是各表記錄逐一匹配形成的組合,記錄數為各表行數的乘積(如兩表各 100 行,生成 100×100=10000 行)。MySQL 語法簡單,在 FROM
子句列出表名即可,但需通過過濾條件(涉及表間比較,如 table1.col1 = table2.col2
)控制結果集規模。
2.2 聯表查詢的分類
2.2.1 內連接(INNER JOIN)
- 僅保留符合連接條件和過濾條件的記錄組合,驅動表與被驅動表可互換
- 示例:
SELECT * FROM table1 INNER JOIN table2 ON table1.col1 = table2.col2;
僅返回匹配記錄(默認的聯表查詢)
2.2.2 外連接(OUTER JOIN)
- 驅動表記錄即使無匹配也保留,被驅動表字段補
NULL
- 左外連接: 左表為驅動表,
SELECT * FROM table1 LEFT JOIN table2 ON ... [WHERE ...]
- 右外連接: 右表為驅動表,
SELECT * FROM table1 RIGHT JOIN table2 ON ...
2.3 聯表查詢執行過程
聯表查詢由優化器驅動,執行過程如下圖所示:
查詢驅動表:
- 選擇第一個表(驅動表),使用單表訪問方法(如
const
,ref
,all
)執行查詢,獲取符合單表條件的記錄 - 示例:
SELECT * FROM table1, table2 WHERE table1.col1 > 10 AND table1.col1 = table2.col2;
先查詢table1
滿足col1 > 10
的記錄。
查詢被驅動表:
- 對驅動表結果集的每條記錄,查詢被驅動表,應用兩表條件(如
table1.col1 = table2.col2
)和被驅動表單表條件 - 被驅動表訪問次數等于驅動表結果集記錄數
2.4 連接算法與優化
2.4.1 嵌套循環連接(Nested Loop Join)
- 驅動表查詢一次,被驅動表查詢多次(次數等于驅動表結果集記錄數)
- 多表連接時,上一輪結果集作為新驅動表,重復過程
- 特點: 簡單但效率低,被驅動表可能多次全表掃描
- 性能: 成本隨驅動表記錄數和被驅動表訪問方式增加
2.4.2 索引優化策略
- 被驅動表查詢可利用索引加速,參考常見訪問方法
- 建議: 為被驅動表的關鍵列(如連接條件中的列)添加索引,確保選擇性高,查詢列表僅包含必要列以觸發
index
2.4.3 基于塊的嵌套循環連接(Block Nested Loop Join)
- 被驅動表數據量大時,多次全表掃描導致高 I/O 成本
- 采用
join buffer
(默認 256KB,可通過join_buffer_size
調整)緩存驅動表結果集記錄 - 具體過程:
- 將驅動表查詢列和條件放入
join buffer
- 掃描被驅動表,記錄一次性與
join buffer
中多條記錄匹配 - 減少被驅動表 I/O,最佳情況為
join buffer
容納所有驅動表記錄,僅需掃描被驅動表一次
- 將驅動表查詢列和條件放入
三、查詢成本計算
3.1 MySQL 查詢成本構成
MySQL 查詢的執行成本主要分為I/O成本和CPU成本:
3.1.1 I/O 成本
從磁盤加載數據或索引頁面到內存的耗時,以頁面(page)為基本單位,默認頁面大小為 16KB,成本常數默認為 1.0
,出現在涉及表數據或索引的加載這樣的場景中,例如全表掃描或索引范圍查詢。
3.1.2 CPU 成本
讀取記錄、檢測搜索條件、排序等操作的耗時,讀取并檢測一條記錄是否符合條件的默認成本為 0.2
(無論是否需要條件檢測),主要包括條件比較、結果集排序等。
3.1.3 成本常數
1.0
(頁面讀取)和 0.2
(記錄檢測)是默認值,硬編碼在 MySQL 源碼中,部分微調值(如 1.1
或 0.01
)用于調整成本估算。
3.2 單表查詢成本計算流程
MySQL 查詢優化器在執行單表查詢前會評估所有可能執行方案的成本,選擇成本最低的方案作為執行計劃:
具體步驟包括:
- 根據搜索條件,找出可能使用的索引
- 計算全表掃描的成本
- 計算使用索引的成本
- 考慮索引合并
- 選擇成本最低的執行方案
3.3 聯表查詢成本計算
總成本公式:
總成本 = 單次訪問驅動表的成本 + 驅動表扇出 × 單次訪問被驅動表的成本
其中:
- 驅動表扇出: 驅動表查詢結果的記錄數
- 當無法直接確定扇出時,優化器通過啟發式規則(heuristic)估算剩余條件的過濾效果
3.3.1 聯表查詢成本評估示例
以內連接方式舉例:
SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.common_field
WHERE s1.key2 > 10 AND s1.key2 < 1000 AND s2.key2 > 1000 AND s2.key2 < 2000;
MySQL 優化器評估兩種連接順序(s1驅動s2或s2驅動s1),計算對應的總成本。
3.4 成本計算總結
MySQL 查詢優化器通過比較全表掃描和索引訪問的 I/O 和 CPU 成本,選擇成本最低的執行計劃。聯表查詢中,優化器評估不同連接順序和訪問方法,結合條件過濾和統計數據提高估算準確性。
四、基于規則的查詢優化
MySQL 查詢優化器通過基于規則的優化手段,將用戶編寫的復雜或低效查詢語句重寫為更高效的形式(包括條件化簡、外連接消除和子查詢優化等),以提升查詢性能。
4.1 條件化簡
MySQL 查詢優化器會對查詢中的搜索條件(表達式)進行化簡,以減少計算復雜度和提高執行效率。
4.1.1 移除不必要的括號
優化器會移除查詢中多余的括號,簡化表達式,例如:
-- 原始查詢
((a = 5 AND b = c) OR ((a > c) AND (c < 5)))-- 優化后
(a = 5 AND b = c) OR (a > c AND c < 5)
4.1.2 常量傳遞
當某個列與常量等值匹配,并且與其他表達式通過 AND 連接時,優化器會將常量值傳遞到其他表達式中:
-- 原始查詢
a = 5 AND b > a-- 優化后
a = 5 AND b > 5
4.1.3 等值傳遞
當多個列之間存在等值關系時,優化器會將等值條件傳遞:
-- 原始查詢
a = b AND b = c AND c = 5-- 優化后
a = 5 AND b = 5 AND c = 5
4.1.4 移除無用條件
對于恒為 TRUE 或 FALSE 的條件,優化器會直接移除:
-- 原始查詢
(a < 1 AND b = b) OR (a = 6 OR 5 != 5)-- 優化后
a < 1 OR a = 6
4.1.5 表達式計算
對于只包含常量的表達式,優化器會在查詢執行前計算其值:
-- 原始查詢
a = 5 + 1-- 優化后
a = 6
但對于涉及復雜函數或非單獨列的表達式(如 ABS(a) > 5
),優化器不會進行化簡。
4.1.6 HAVING 和 WHERE 子句合并
如果查詢不包含聚合函數(如 SUM、MAX)或 GROUP BY 子句,優化器會將 HAVING 子句合并到 WHERE 子句中。
4.2 外連接消除
外連接(LEFT/RIGHT JOIN)與內連接(INNER JOIN)的區別在于,驅動表的記錄即使在被驅動表中找不到匹配記錄,也會被保留(被驅動表字段填充為 NULL)。
4.2.1 空值拒絕(Null Rejection)
如果 WHERE 子句中指定被驅動表的列不為 NULL(如 t2.n2 IS NOT NULL
),外連接效果等價于內連接:
-- 原始查詢
SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.n2 IS NOT NULL;-- 等價于
SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2;
這種轉換允許優化器調整連接順序,降低查詢成本。
4.3 子查詢優化
子查詢是嵌套在查詢中的子查詢,優化器通過物化表和半連接等策略優化其執行效率。
4.3.1 子查詢出現位置
- SELECT 子句:如
(SELECT m1 FROM t1 LIMIT 1)
- FROM 子句:作為派生表,如
(SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2) AS t
- WHERE 或 ON 子句:如
WHERE m1 IN (SELECT m2 FROM t2)
- ORDER BY 或 GROUP BY 子句:較少使用
4.3.2 子查詢分類
按返回結果集分類:
- 標量子查詢:返回單一值(如
(SELECT m1 FROM t1 LIMIT 1)
) - 行子查詢:返回單條記錄,包含多列(如
(SELECT m2, n2 FROM t2 LIMIT 1)
) - 列子查詢:返回單列多行(如
(SELECT m2 FROM t2)
) - 表子查詢:返回多行多列(如
(SELECT m2, n2 FROM t2)
)
按與外層查詢關系分類:
- 不相關子查詢:獨立執行,不依賴外層查詢值
- 相關子查詢:執行依賴外層查詢值,如
WHERE m1 IN (SELECT m2 FROM t2 WHERE n1 = n2)
4.3.3 子查詢在布爾表達式中的使用
- 比較操作符(=、>、<、等):與標量子查詢或行子查詢結合
- IN/NOT IN、ANY/SOME、ALL:與列子查詢或表子查詢結合
- EXISTS/NOT EXISTS:判斷子查詢結果集是否為空
4.3.4 子查詢優化策略
MySQL優化器通過不同的策略優化各類子查詢,主要優化路徑如下:
主要優化策略說明:
物化表(Materialization):
- 將不相關 IN 子查詢結果集寫入臨時表(物化表),對物化表記錄去重,建立哈希索引(內存)或 B+ 樹索引(磁盤)
- 將外層查詢與物化表進行內連接,由優化器評估連接順序成本
- 適用于子查詢結果集較大時,物化表通過索引加速 IN 判斷
半連接(Semi-join):
- 將 IN 子查詢轉換為半連接,僅保留外層查詢表的記錄,不關心被驅動表匹配的記錄數
- 適用于子查詢在 WHERE/ON 子句中,與 IN 語句結合,或者子查詢為單一查詢(無 UNION、GROUP BY、HAVING 等)
- 不適用于子查詢與 OR 連接、使用 NOT IN或者子查詢在 SELECT 子句中
執行策略包括:
- 子查詢查詢列表為主鍵/唯一索引列時,將子查詢表上拉到外層查詢的 FROM 子句
- 使用臨時表記錄外層查詢記錄 ID,消除重復
- 對子查詢表使用松散索引掃描,僅取索引值相同的首條記錄
- 逐條檢查外層查詢記錄,找到第一條匹配的子查詢記錄即停止
- 物化子查詢后與外層查詢表連接
4.4 優化規則總結
MySQL 查詢優化器通過條件化簡、外連接消除和子查詢優化等基于規則的優化手段,將用戶編寫的低效查詢轉換為高效形式。這些規則包括移除冗余條件、合并子句、將外連接轉為內連接、以及通過物化表和半連接優化 IN 子查詢。開發者可以通過編寫簡潔的 SQL 語句、利用索引和主鍵、避免冗余操作等方式,進一步配合優化器提升查詢性能。
五、執行計劃分析
MySQL采用EXPLAIN
分析SQL查詢的執行計劃,幫助優化查詢性能。它展示MySQL如何執行查詢,包括表訪問順序、索引使用情況等。
5.1 EXPLAIN 輸出字段詳解
運行EXPLAIN
語句后,會返回一個結果集,包含多個字段:
5.1.1 查詢標識字段
- id:表示查詢中每個子查詢的執行順序。值越大,優先級越高,越先執行。相同
id
表示同一執行層級 - select_type:查詢類型,常見值包括:
SIMPLE
:簡單查詢,無子查詢或聯合PRIMARY
:最外層查詢SUBQUERY
:子查詢DERIVED
:派生表(如FROM子句中的子查詢)UNION
:UNION操作中的查詢
5.1.2 表和分區信息
- table:顯示查詢涉及的表名或別名
- partitions:顯示查詢涉及的分區(如果表分區了)。無分區表時為空
5.1.3 訪問方法和索引信息
-
type:訪問類型(即前面所說的訪問方法),反映查詢效率,常見值從優到劣:
system
:表只有一行數據const
:通過主鍵或唯一索引直接定位一行eq_ref
:通過主鍵或唯一索引進行等值匹配ref
:通過非唯一索引進行等值匹配range
:索引范圍掃描(如>
、<
、IN
)index
:全索引掃描ALL
:全表掃描(效率最低)
-
possible_keys:MySQL可能使用的索引列表。為空表示沒有可用索引
-
key:實際使用的索引。為空表示未使用索引
-
key_len:使用索引的長度(字節),幫助判斷聯合索引的使用情況。值越小,索引選擇性越高
5.1.4 查詢條件和統計信息
- ref:顯示與
key
索引比較的列或常量。例如,const
表示常量值,表名.列名表示關聯表的列 - rows:估計掃描的行數,值越小越好,反映查詢效率
- filtered:表示過濾后的行數百分比(0-100%)。值越高,表示過濾效果越好
5.1.5 額外信息
- Extra:額外信息,常見值:
Using index
:僅使用索引完成查詢(覆蓋索引)Using where
:在WHERE條件中過濾Using temporary
:使用了臨時表(可能影響性能)Using filesort
:需要額外排序(可能影響性能)Using join buffer
:使用了連接緩沖區
5.2 執行計劃分析要點
執行計劃分析時,需要重點關注以下幾個關鍵字段:
分析時著重點:
- 關注
type
,盡量避免ALL
或index
,優先const
、eq_ref
、ref
- 檢查
key
和possible_keys
,確保使用合適的索引 - 觀察
rows
和filtered
,減少掃描行數 - 注意
Extra
中的Using temporary
和Using filesort
,可能需要優化
5.3 實際分析示例
通過分析這些字段,可定位查詢瓶頸,優化索引或重寫SQL以提高性能。
六、慢SQL治理實戰案例
以下是5個經過脫敏處理的實際慢SQL案例,展示了從問題發現到優化解決的完整過程。
6.1 慢SQL治理流程圖
慢SQL治理是一個系統性的過程,需要遵循科學的流程來確保優化效果:
慢SQL治理的關鍵在于系統性思考和漸進式優化:
- 發現階段:建立完善的監控體系,及時發現性能問題
- 分析階段:深入分析根本原因,避免頭痛醫頭、腳痛醫腳
- 方案階段:制定多層次的優化方案,從SQL到架構全方位考慮
- 實施階段:謹慎實施,確保系統穩定性
- 驗證階段:客觀評估優化效果,確保問題真正解決
- 監控階段:持續觀察,防止問題反彈
6.2 案例一:模糊匹配優化
問題SQL(脫敏后)
SELECT count(*)
FROM `脫敏表`
WHERE (xxx_id = XXX) AND (`name` LIKE '%關鍵詞%')
執行計劃分析
字段 | 值 | 含義 |
---|---|---|
id | 1 | 單表查詢 |
select_type | SIMPLE | 簡單查詢 |
table | 脫敏表 | 查詢目標表 |
type | ref | 使用了索引,但非最優 |
key | idx_xxxid_xxxuserid | 使用的索引不含 name |
rows | 589328 | 預計掃描 58 萬行 |
filtered | 11.11 | 低過濾率 |
Extra | Using where | 沒有使用 name 字段索引,存在回表操作 |
性能瓶頸分析
問題點 | 描述 |
---|---|
%關鍵詞% 前模糊匹配 | 無法使用 B+Tree 索引 |
name 無索引 | 即使改為前綴匹配 LIKE ‘xxx%’ 也無法優化 |
使用了錯誤索引 | 當前索引 (xxx_id, xxxuserid) 無法支持 name 查詢 |
高數據量下回表掃描 | xxx_id = 177 的數據量大(58w),過濾性能差 |
優化方案
去掉前模糊匹配,改成 LIKE '關鍵詞%'
,并為 name 字段建立索引。
6.3 案例二:復雜條件查詢優化
問題SQL(脫敏后)
SELECT count(*)
FROM `脫敏表`
WHERE creator IN ('ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7') AND (xxx_id = YYY) AND (status = 1) AND (`name` LIKE '%活動關鍵詞%')
性能瓶頸分析
字段 | 值 | 解讀 |
---|---|---|
type | ref | 使用了 corp_id 索引,但效率不高 |
key | idx_xxxid_xxxuserid | 說明使用的是 (corp_id, userid) 的索引 |
rows | 518218 | 預估要掃描 50 萬條數據(即 corp_id=156 的數據量) |
filtered | 0.56 | 超低過濾率,說明大多數數據都是"掃了但沒用上" |
Extra | Using where | 多條件聯合過濾 + 無法使用 name 的索引,導致回表嚴重 |
問題分析表
問題點 | 描述 |
---|---|
%模糊匹配% | LIKE ‘%素質學習活動%’ 無法走索引,依舊是性能殺手 |
無 name 索引 | 和慢 SQL 1 一樣,name 字段未建立索引 |
使用錯誤索引 | 當前使用的是 (xxx_id, xxxuserid),和查詢條件不匹配 |
多個 WHERE 條件 | creator IN (…) + status=1 + xxx_id=156 增加了查詢復雜度 |
掃描行數巨大 | 掃描了 50 萬行,過濾率不到 1%,極其低效 |
優化方案
使用搜索引擎(如Elasticsearch)進行模糊匹配,將復雜的文本搜索從數據庫中分離出來。
6.4 案例三:URL字段查詢優化
問題SQL(脫敏后)
SELECT `id`, `media_id`
FROM `脫敏表`
WHERE (xxx_id = XXX) AND (user_id = ZZZ) AND (url = 'https://example.com/feed?...') LIMIT 1
執行計劃分析
字段 | 值 | 含義 |
---|---|---|
type | ref | 使用了索引但是模糊匹配級別,不夠高效 |
key | idx_xxxid_xxxuserid | 當前使用的是 (corp_id, user_id) 索引 |
rows | 27622 | 即便用了索引,預估掃描仍達 2 萬+ 行 |
filtered | 10% | 表示大部分數據被過濾掉了(說明 url 沒有參與索引) |
Extra | Using where | 表示 MySQL 仍需逐行判斷 url=xxx 條件 |
優化方案
添加新字段(如哈希值),改寫SQL:
SELECT id, media_id
FROM 脫敏表
WHERE xxx_id = XXXAND xxxuser_id = ZZZAND url_hash = '哈希值'AND url = 'https://example.com/feed?...'LIMIT 1
6.5 案例四:高并發系統級性能問題
問題SQL(脫敏后)
SELECT id, xxxuser_id1, xxxuser_id2, xxx_id, task_id, ...
FROM table_task
WHERE xxxuser_id2 = '用戶ID'AND xxx_id = XXXAND create_time >= 時間戳AND task_status = 2;
執行計劃分析
字段 | 值 | 含義 |
---|---|---|
type | range | 范圍掃描,說明部分使用了索引 |
key | idx_wx_user_time | 使用了某個復合索引(猜測為 xxx_userid + create_time) |
rows | 68794 | 預估需要掃描約 6.8 萬行 |
filtered | 1.00 | 過濾率很低(接近全掃描) |
Extra | Using index condition; Using where | 沒有使用覆蓋索引,部分條件未被索引消化 |
問題分析
這條 SQL 單條執行并不慢,索引命中了(走的是 (xxx_userid, create_time)
),本質的問題是高并發下的系統級退化。
問題類型 | 原因 |
---|---|
? 數據庫連接池耗盡 | 協程數超出 max_open_conns,大量阻塞 |
? 單連接響應慢 | 數據量大,每條查詢雖然不慢,但處理行數多,連接占用時間長 |
? Goroutine 累積 | 協程爆炸,CPU、內存調度競爭,影響系統吞吐 |
? MySQL CPU 使用高 | 并發下 10w+ 次篩選,造成 DB CPU 持續高壓 |
? 索引雖走,但行數多 | rows=68794 表示每次查近 7 萬條,I/O 和網絡傳輸壓力大 |
優化方案
- 限制協程并發度(從4降到2)
- 查詢范圍從10天減少到3天
- 執行頻率從10分鐘一次改為30分鐘一次
6.6 案例五:數據傾斜與并發優化
問題SQL(脫敏后)
SELECT count(*)
FROM `脫敏表`
WHERE (xxx_id = YYY) AND (user_id = AAA) AND (state = '狀態值') AND (del_way != 0) AND (delete_time >= 時間戳1) AND (delete_time < 時間戳2)
執行計劃分析
字段 | 值 | 含義 |
---|---|---|
key | idx_xxxuserid_followtime | 使用了 (xxxuser_id, follow_time) 聯合索引,但不完全匹配查詢條件 |
key_len | 8 | 實際只使用了索引的第一列 xxxuser_id(類型為 BIGINT,占用 8 字節) |
ref | const | xxxuser_id = 51967 為常量匹配,用于索引過濾 |
rows | 53472 | 預估掃描行數約為 5.3 萬,說明該 xxxuser_id 擁有大量數據 |
filtered | 0.00 | 預估過濾率為 0%,說明幾乎所有行都需進一步篩選,回表嚴重 |
Extra | Using where | 表示需回表做完整 WHERE 條件判斷,未能使用覆蓋索引或索引條件下推 |
問題分析
問題 | 描述 |
---|---|
??數據傾斜 | 部分 xxxuser_id 擁有極多數據(幾十萬級),SQL執行時間呈不均衡狀態 |
??并發導致放大 | 每個用戶都執行這條 SQL,量一大就撐爆連接池或 DB 資源 |
??索引不命中 delete_time / state | 這兩個高過濾條件字段只能靠回表判斷,掃描行數增多 |
??邏輯層并發掃描 | 多個用戶并發執行時,每人掃幾萬,系統瞬間承壓(4核但代碼設置了10個協程數) |
優化方案
理想索引:
CREATE INDEX idx_xxxuserid_delway_state_deltime
ON 脫敏表(xxxuser_id, del_way, state, delete_time);
實際采用的3種優化:
優化手段 | 背景/動機 | 預期收益 |
---|---|---|
清理數據庫表中的雙刪數據 | 當前 del_way != 0 過濾范圍太大,無效數據長期滯留影響查詢效率;部分舊數據可歸檔或物理刪除 | 降低數據量,減少每次掃描行數,提升過濾效率 |
降低協程并發數 | 當前每個 xxxuser_id 并發觸發多條 SQL,容易造成連接池耗盡、系統抖動 | 控制系統資源消耗,更穩定運行、更平滑負載 |
將時間范圍由 3 天縮小到 1 天 | 原先掃描范圍廣、數據量大,導致執行時間不可控;改為每日定時執行或滾動聚合 | 明確可控的數據窗口,降低查詢壓力、支持更頻繁調度 |
6.7 慢SQL治理總結
類型 | 內容 |
---|---|
數據優化 | 清理無效數據、減少掃描量 |
查詢優化 | 改寫 SQL 表達式、提升執行計劃 |
系統優化 | 限制并發、平滑調度 |
窗口優化 | 縮小時間范圍、減少數據量 |
總結
本文從MySQL查詢的基礎機制出發,系統性地介紹了查詢優化的理論基礎和實踐方法:
- 訪問方法:7種不同的訪問方法為查詢提供了從最高效的
const
到最低效的all
的性能選擇 - 聯表查詢:理解笛卡爾積本質和連接算法,合理設計索引和選擇驅動表
- 成本計算:掌握I/O成本和CPU成本的計算原理,理解優化器的決策依據
- 規則優化:利用條件化簡、外連接消除等規則,讓查詢更高效
- 執行計劃:通過
EXPLAIN
分析瓶頸,指導索引設計和SQL優化 - 實戰案例:5個真實案例展示了從問題發現到解決的完整流程
通過理論學習和實踐應用相結合,可以更好地理解MySQL查詢優化的精髓,在實際開發中寫出高性能的SQL語句