最通用做法:全表掃描建臨時表分組。但若滿足條件,優化器可直接用索引避免臨時表。關鍵前提:所有 GROUP BY 列來自同一有序索引前綴。
定義:僅讀取索引中每組首鍵,跳過重複鍵值。
必要條件:
GROUP BY 列為同一索引最左連續前綴MAX()/MIN() 且指向同一列MIN/MAX 參數除外)EXPLAIN 識別:Extra: Using index for group-by
SELECT c1, c2 FROM t1 GROUP BY c1, c2; SELECT DISTINCT c1, c2 FROM t1; SELECT c1, MIN(c2) FROM t1 GROUP BY c1; SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2; SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2; SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2; SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;
-- 非 MIN/MAX 聚合 SELECT c1, SUM(c2) FROM t1 GROUP BY c1; -- GROUP BY 不從索引最左開始 SELECT c1, c2 FROM t1 GROUP BY c2, c3; -- 引用索引中間列無常量 SELECT c1, c3 FROM t1 GROUP BY c1, c2; -- 修正:WHERE c3 = 'const'
定義:掃描 WHERE 範圍內所有索引鍵再分組。
觸發條件:GROUP BY 列非連續前綴,但 WHERE 提供常量填補間隙形成完整前綴。
EXPLAIN 識別:Using where; Using index(無 for group-by)
-- group_id 常量填補前綴 SELECT MAX(gmt_create) FROM group_message WHERE group_id = 2 GROUP BY user_id; -- WHERE 提供索引第一列常量 SELECT group_id, gmt_create FROM group_message WHERE user_id = 1 GROUP BY group_id, gmt_create; SELECT group_id, gmt_create FROM group_message WHERE group_id = 1 GROUP BY user_id, gmt_create;
CREATE TABLE group_message ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, group_id INT UNSIGNED, user_id INT UNSIGNED, gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP, abc INT DEFAULT 0, KEY group_id_user_id_gmt_create (group_id, user_id, gmt_create) ) ENGINE=MyISAM; -- 26 行測試數據(略)
sort_buffer_size 充足、避免大結果集、防 tmp_table_size 溢出落盤參考資料:MySQL 官方手冊「How MySQL Optimizes GROUP BY」、鬆散索引掃描文檔、本系列前文。