GROUP BY 實質包含「排序 + 分組 + 聚合」。MySQL 優化器會嘗試利用索引避免額外排序與臨時表,分為三種執行策略,效能由高到低:
GROUP BY 欄位為**同一複合索引的最左連續前綴** (如 idx(a,b,c) 群組 a,b 或 a)。MIN() 或 MAX()**。IN 列表)。WHERE 條件,或條件僅涉及索引前綴常數。
EXPLAIN SELECT user_id, MAX(gmt_create)
FROM group_message
WHERE group_id < 10
GROUP BY group_id, user_id;
-- Extra: Using where; Using index for group-by
Using index for group-by 即標識鬆散掃描。
索引 (group_id, user_id, gmt_create) 順序: group_id=1, user_id=1, gmt_create=10:00 ← 讀取此行 (組 1 的 MAX) group_id=1, user_id=1, gmt_create=10:05 ← 跳過 group_id=1, user_id=2, gmt_create=09:00 ← 讀取 (組 2 的 MAX) group_id=2, user_id=1, gmt_create=11:00 ← 讀取 (組 3 的 MAX) ... 只需讀取每組的「第一筆」或「最後一筆」,跳過中間行,I/O 極少。
GROUP BY 欄位**不連續**或**非索引前綴** (如 GROUP BY user_id 但索引為 (group_id, user_id)),無法直接跳躍。WHERE 提供了缺失前綴的**常數值** (如 WHERE group_id = 2),補齊前綴 → 形成完整索引前綴 (2, user_id)。
EXPLAIN SELECT MAX(gmt_create)
FROM group_message
WHERE group_id = 2
GROUP BY user_id;
-- Extra: Using where; Using index
-- (無 Using index for group-by)
需掃描 group_id=2 範圍內**所有**索引鍵,再記憶體中分組聚合。
索引 (group_id, user_id, gmt_create): WHERE group_id=2 定位到範圍起始 掃描該範圍所有 user_id 的所有 gmt_create 在臨時記憶體結構中按 user_id 聚合 MAX(gmt_create)
GROUP BY 欄位順序。GROUP BY 欄位來自不同索引或非索引欄位。GROUP BY 欄位非索引前綴且無常數補齊 (如 WHERE group_id > 1 AND group_id < 10 GROUP BY user_id)。
EXPLAIN SELECT MAX(gmt_create)
FROM group_message
WHERE group_id > 1 AND group_id < 10
GROUP BY user_id;
-- Extra: Using where; Using index; Using temporary; Using filesort
流程:索引範圍掃描 → 寫入臨時表 (記憶體/磁碟) → 排序 → 分組聚合。
1. 索引範圍掃描 group_id IN (2..9) 取出所有行 2. 寫入臨時表 (Memory -> MyISAM/InnoDB on disk) 3. 對臨時表按 user_id 排序 4. 掃描排序後臨時表執行分組聚合
IF (滿足鬆散掃描條件)
USE Loose Index Scan
ELSE IF (可構建完整索引前綴 + 滿足緊湊掃描條件)
USE Tight Index Scan
ELSE
USE Temporary Table + Filesort
| 問題現象 | 優化手段 | 目標策略 |
|---|---|---|
| Extra 出現 Using temporary; Using filesort | 建立符合 GROUP BY 順序的複合索引 (最左前綴) | Tight / Loose Index Scan |
| GROUP BY a,b 但索引為 (b,a) | 重建索引為 (a,b) 或調整 GROUP BY 順序 | Loose Index Scan |
| 需 COUNT/SUM 但想用 Loose Scan | 不可行,Loose Scan 僅支援 MIN/MAX | 改用 Tight Scan 或臨時表 |
| WHERE 範圍條件導致回退 | 將範圍拆分為多個等值查詢 UNION ALL (視基數) | 嘗試 Loose Scan |
| 大表 GROUP BY 慢 | 預先聚合表 / 物化視圖 / ClickHouse 等 OLAP 引擎 | 避免即時 GROUP BY |
MySQL GROUP BY 優化核心在於**索引欄位順序與常數條件的配合**。掌握三種策略觸發條件,針對性調整索引與 SQL 寫法,可將分組查詢效能提升數量級。