分頁查詢總行數時,常用 COUNT(*) 或 SQL_CALC_FOUND_ROWS。哪個更快?本文通過 100 萬行實測,揭示覆蓋索引對兩者性能的決定性影響。
-- 表結構 A:覆蓋索引 (bb, aa) CREATE TABLE ben ( aa INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, bb INT UNSIGNED NOT NULL, cc VARCHAR(100) NOT NULL, KEY bar (bb, aa) -- 覆蓋索引:查詢 bb, aa 只需掃描索引 ) ENGINE=MyISAM; -- 表結構 B:非覆蓋索引 (bb) CREATE TABLE ben ( aa INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, bb INT UNSIGNED NOT NULL, cc VARCHAR(100) NOT NULL, KEY bar (bb) -- 單列索引,查詢 aa 需回表 ) ENGINE=MyISAM; -- 插入 100 萬行,bb 取值 0/1 均勻分布
-- 方案 1:COUNT(*) + 分頁查詢 SELECT COUNT(*) FROM ben WHERE bb=1; SELECT aa FROM ben WHERE bb=1 LIMIT 100, 10; -- 方案 2:SQL_CALC_FOUND_ROWS + FOUND_ROWS() SELECT SQL_CALC_FOUND_ROWS aa FROM ben WHERE bb=1 LIMIT 100, 10; SELECT FOUND_ROWS();
| 索引類型 | COUNT(*) 方案 (秒) | SQL_CALC_FOUND_ROWS 方案 (秒) | 結論 |
|---|---|---|---|
| 覆蓋索引 | 0.0182 | 0.0175 | SQL_CALC_FOUND_ROWS 略快 |
| 非覆蓋索引 | 0.0177 | 0.2076 | COUNT(*) 完勝(快 10 倍) |
SQL_CALC_FOUND_ROWS 只需一次全索引掃描:掃描覆蓋索引 (bb, aa) 同時完成「計數」與「取前 110 行主鍵」;COUNT(*) 需單獨掃描一次索引計數,分頁查詢再掃一次,共兩次掃描。SQL_CALC_FOUND_ROWS 必須回表獲取 aa(主鍵),100 萬行回表開銷巨大;COUNT(*) 在 MyISAM 中極快(直接讀取表元數據行數),InnoDB 需掃描聚簇索引但仍遠快於回表。| 特性 | MyISAM | InnoDB |
|---|---|---|
| COUNT(*) 無 WHERE | O(1) 讀元數據 | O(N) 掃描聚簇索引 |
| 事務支持 | 無 | ACID |
| 鎖粒度 | 表鎖 | 行鎖 |
| 崩壞恢復 | 慢,易損壞 | Redo Log 快速恢復 |
作者觀點:讀多寫少、需頻繁 COUNT(*)、依賴文件級備份恢復的場景,MyISAM 仍有優勢;核心交易、需事務/行鎖/高可用場景,InnoDB 不二之選。
EXPLAIN 出現 Using index;SQL_CALC_FOUND_ROWS(單次掃描);COUNT(*) 分離計數,或改用游標分頁避免總數查詢;COUNT(*),考慮維護計數表或 Redis 緩存總數。