InnoDB 採用索引組織表(IOT),資料按主鍵順序存儲。當執行 SELECT COUNT(DISTINCT a) FROM t 且 a 有索引時,傳統做法是全索引掃描,效率低下。MySQL 5.0+ 引入 Loose Index Scan(鬆散索引掃描),可跳過重複索引值,大幅提升 GROUP BY、DISTINCT、MIN/MAX 查詢效能。
執行計劃 Extra 顯示 Using index for group-by 即表示生效。需滿足:
GROUP BY 使用索引最左前綴列MIN()/MAX() 聚合函數WHERE 條件僅包含 = constant 操作;未出現在 GROUP BY 的索引列必須為常數條件INDEX(c1(10)))CREATE TABLE log_table ( id INT NOT NULL PRIMARY KEY, log_machine VARCHAR(20) NOT NULL, log_time DATETIME NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE INDEX ix_log_machine_time ON log_table (log_machine, log_time);
SELECT MAX(log_time) FROM log_table;
SELECT MAX(log_time) FROM log_table WHERE log_machine IN ('Machine 1');
Extra: Select tables optimized away — 僅需一次 Index Seek,優化器識別出 MIN/MAX 位於索引最左/右端。
SELECT MAX(log_time) FROM log_table
WHERE log_machine IN ('Machine 1','Machine 2','Machine 3','Machine 4');
Type: range, Extra: Using where; Using index — 掃描所有滿足條件的索引記錄再聚合,rows=82636。
SELECT MAX(log_time) FROM log_table
WHERE log_machine IN ('Machine 1','Machine 2','Machine 3','Machine 4')
GROUP BY log_machine ORDER BY 1 DESC LIMIT 1;
Extra: Using index for group-by — 對每個 log_machine 執行一次 Loose Index Scan,rows=16,效能提升 5000 倍!
等價於:
SELECT MAX(log_time) FROM log_table WHERE log_machine IN ('Machine 1')
UNION ALL
SELECT MAX(log_time) FROM log_table WHERE log_machine IN ('Machine 2')
...
假設索引 idx(c1,c2,c3):
| SQL | 是否生效 | 說明 |
|---|---|---|
SELECT c1, c2 FROM t1 GROUP BY c1, c2 | ✅ | 最左前綴匹配 |
SELECT DISTINCT c1, c2 FROM t1 | ✅ | 等價 GROUP BY |
SELECT c1, MIN(c2) FROM t1 GROUP BY c1 | ✅ | MIN/MAX 聚合 |
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 | ✅ | WHERE 常數條件滿足 |
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2 | ✅ | |
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2 | ❌ | WHERE 非最左列 |
SELECT c1, c3 FROM t1 GROUP BY c1, c2 | ❌ | SELECT 列不在 GROUP BY |
SELECT c1, c3 FROM t1 WHERE c3 = const GROUP BY c1, c2 | ✅ | WHERE 條件補足 |
當 Loose Index Scan 不滿足時,MySQL 可能退化為 Tight Index Scan:
-- 典型 Tight Index Scan 場景 SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3; SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
ICP 將部分 WHERE 條件下推到存儲引擎層過濾,減少回表次數,彌補 Loose Index Scan 覆蓋面不足的問題。
KEY(age, zip) -- 5.5 僅用 age 列 (key_len=4) SELECT name FROM people WHERE age BETWEEN 18 AND 20 AND zip IN (12345,12346,12347); -- 1分56秒 -- 改寫 UNION ALL SELECT ... WHERE age=18 AND zip IN (...) UNION ALL ... -- 5.6 ICP 生效 (key_len=8) SELECT name FROM people WHERE age=18 AND zip IN (...); -- 0.06秒
ICP 讓複合索引的非引導列也能在索引層面過濾,大幅降低 Server 層處理行數。
<, >, BETWEEN, IN),改用 = 或改寫為 UNION ALLEXPLAIN 觀察 Extra 欄位:Using index for group-by = Loose Index Scan,Using where; Using index = Tight/Range Scan