InnoDB 表上 COUNT(*) 全表掃描極慢。本文透過實測對比發現:利用 Secondary Index 掃描比 Primary Key 快 3 倍以上,深度解析 InnoDB 聚簇索引與輔助索引結構差異,給出優化建議。
-- sbtest: PK=aid(BIGINT), Secondary Index=id(INT)
-- sbtest1: PK=aid(INT), Secondary Index=id(BIGINT)
-- 皆為 100 萬筆資料,InnoDB,latin1
| 查詢方式 | sbtest (PK=BIGINT) | sbtest1 (PK=INT) | 說明 |
|---|---|---|---|
COUNT(*) 無條件 | 1.42 秒 | 1.42 秒 | 優化器選 PK 掃描 |
COUNT(*) WHERE pk >= 0 | 1.39 秒 | 1.42 秒 | Range 掃描 PK,仍為全掃描 |
COUNT(*) WHERE secondary_idx >= 0 | 0.43 秒 | 0.45 秒 | 掃描 Secondary Index,快 3 倍+ |
關鍵發現:欄位長度互換後,Secondary Index 仍較快,證明非長度因素,而是索引結構差異。
因此 COUNT(*) 只需統計行數,掃描體積最小的 Secondary Index 最划算。
InnoDB 設計者 Heikki Tuuri 解釋:
範例表中 Secondary Index 剛好是完美順序插入(極罕見)。實際上 Secondary Index 通常碎片化嚴重,導致隨機 I/O,反而比 PK 掃描慢。優化器目前無「聚類比」統計資訊,無法判斷 Secondary Index 是否接近有序。
功能請求:維護 Secondary Index 的「聚類比」統計,若接近有序則優先選擇。但該功能至今未實作。
SELECT COUNT(*) FROM sbtest WHERE id >= 0; -- id 有索引
table_stats(row_count),查詢直接讀取EXPLAIN 驗證執行計畫:確認 key 欄位為預期索引,Extra 顯示 Using indexTABLE_ROWS(近似值)或應用層快取MyISAM 維護精確行數計數器,COUNT(*) 無 WHERE 時直接回傳元資料,極快。但不支援事務、行鎖,適用場景受限。