GROUP BY 是分析型查詢的核心算子,MySQL 優化器提供三種執行策略,效能差異可達數量級。本文深度解析 Loose Index Scan(鬆散索引掃描)、Tight Index Scan(緊湊索引掃描)、Temporary Table + Filesort(臨時表回退) 三大機制的觸發條件、執行原理與 SQL 改寫實戰。
所有索引掃描優化的基礎:GROUP BY 欄位必須完全匹配某個 B-Tree 索引的最左連續前綴。Hash 索引、全文索引、空間索引均不支援排序特性,無法用於 GROUP BY 優化。
GROUP BY 欄位構成索引的最左連續前綴(如索引 (a,b,c),支援 GROUP BY a、GROUP BY a,b,不支援 GROUP BY a,c)DISTINCT、無其他聚合函數、無 HAVING 複雜條件優化器直接在索引樹上「跳躍」:定位到每個 GROUP 的第一筆滿足條件的記錄,讀取所需欄位後直接跳到下一組的第一筆,完全跳過組內其餘記錄。讀取的索引鍵數量 ≈ 分組數量 ≪ 總行數。
Extra: Using index for group-by
-- 索引:idx_gid_uid_gc (group_id, user_id, gmt_create) EXPLAIN SELECT group_id, user_id, MAX(gmt_create) FROM group_message WHERE group_id BETWEEN 1 AND 4 GROUP BY group_id, user_id; -- 滿足條件:GROUP BY 為索引前綴,聚合為 MAX(),WHERE 為範圍但 GROUP BY 欄位在索引最左 -- 實測 Extra: Using index for group-by
GROUP BY 欄位不連續或非最左,但 WHERE 提供常數等值填補缺口優化器掃描所有滿足 WHERE 條件的索引記錄(而非跳躍),在記憶體中完成分組聚合。讀取鍵值數量 = 條件過濾後的行數。
Extra: Using where; Using index (無 Using index for group-by)
-- 索引:(group_id, user_id, gmt_create) -- GROUP BY user_id, gmt_create 缺失 group_id(索引最左) -- WHERE group_id = 2 提供常數填補 → 構成完整前綴 (2, user_id, gmt_create) EXPLAIN SELECT MAX(gmt_create) FROM group_message WHERE group_id = 2 GROUP BY user_id, gmt_create; -- Extra: Using where; Using index
GROUP BY 欄位來自不同索引或無索引group_id > 1 AND group_id < 10 配合 GROUP BY user_id)SUM()、COUNT()、AVG() 等非 MIN/MAX 聚合Filesort 排序Extra: Using where; Using temporary; Using filesort
Created_tmp_disk_tables 計數器飆升)sort_buffer_size 不足導致多輪合併排序-- 1. 確認索引存在性 SHOW INDEX FROM group_message; -- 2. 觀察 EXPLAIN Extra 欄位 -- 目標:Using index for group-by > Using index > Using temporary; Using filesort -- 3. SQL 改寫策略 -- (A) 調整 GROUP BY 欄位順序匹配索引最左前綴 -- (B) 新增複合索引覆蓋 GROUP BY + WHERE 常數欄位 -- (C) 將範圍條件轉為等值(如業務允許分批處理) -- (D) 強制索引:FORCE INDEX(idx_xxx) 避免優化器誤判 -- 4. 參數調優(臨時表回退場景) SET SESSION sort_buffer_size = 16M; -- 線程級,謹慎調大 SET SESSION tmp_table_size = 256M; -- 內存臨時表上限 SET SESSION max_heap_table_size = 256M; -- 配合 tmp_table_size
| 查詢模式 | Extra | 耗時 | 備註 |
|---|---|---|---|
| GROUP BY gid, uid + MAX(gc) + WHERE gid BETWEEN | Using index for group-by | 12 ms | 鬆散掃描,讀取 ~分組數 |
| WHERE gid=2 GROUP BY uid, gc + MAX(gc) | Using where; Using index | 45 ms | 緊湊掃描,讀取 gid=2 全部行 |
| WHERE gid BETWEEN 1-10 GROUP BY uid + MAX(gc) | Using temporary; Using filesort | 1,850 ms | 臨時表+排序,磁碟 I/O 主導 |
WHERE 常數等值欄位 + GROUP BY 欄位 + 聚合函數欄位sort_buffer_size、tmp_table_size、max_heap_table_size 三聯調,防磁碟臨時表Created_tmp_disk_tables / Created_tmp_tables < 25%,Sort_merge_passes = 0掌握三種掃描機制的邊界條件,將 GROUP BY 優化從「玄學調參」轉化為「索引設計與 SQL 改寫的確定性工程」。