許多開發者認為 COUNT(*) 與 COUNT(col) 效果相同,實則在語義、NULL 處理、優化器路徑選擇上有本質區別。本文從定義、實測、優化器行為、最佳實踐四個維度徹底釐清。
| 函數 | 統計對象 | NULL 值處理 |
|---|---|---|
COUNT(*) | 所有行(含全 NULL 行) | 計入 |
COUNT(col) | 該欄位非 NULL 的行 | 忽略 |
COUNT(DISTINCT col) | 該欄位非 NULL 且去重後的值 | 忽略 |
-- 假設 cdb_posts 表 7.9 萬行,fid=604 SELECT COUNT(*) FROM cdb_posts WHERE fid = 604; -- 0.03 sec SELECT COUNT(tid) FROM cdb_posts WHERE fid = 604; -- 0.33 sec SELECT COUNT(pid) FROM cdb_posts WHERE fid = 604; -- 0.33 sec
結論: 有 WHERE 條件時,COUNT(*) 明顯快於 COUNT(col)。
COUNT(*) ≈ COUNT(pk)。COUNT(*):優化器可選擇覆蓋索引(最小索引)掃描,不回表。COUNT(col):若 col 無索引或非覆蓋,需回表檢查 NULL,導致額外 I/O。is_deleted BIT(1))建立索引,COUNT(*) 會自動選擇最小索引,速度提升數倍。「COUNT(1) 比 COUNT(*) 快」→ 錯誤。MySQL 內部將
COUNT(1)直接轉為COUNT(*),執行計畫完全一致。「COUNT(col) 能用索引」→ 視情況。只有當
col在索引中且滿足覆蓋條件才會用索引,否則全表掃描。
COUNT(*):語義清晰、優化器最友好、無需維護欄位列表。SELECT COUNT(*) ... WHERE col = val:大表需加組合索引覆蓋 WHERE 條件,或用緩存/統計表預計算。SELECT COUNT(col) FROM table:無索引時全表掃描+NULL 判斷,純粹負擔。EXPLAIN SELECT COUNT(*) ... 觀察 rows 估算值(誤差 ±10-20%)。INFORMATION_SCHEMA.TABLES.TABLE_ROWS 統計值(非即時)。COUNT(*) FILTER (WHERE ...) 但不改變掃描本質。理解語義、信任優化器、避開反模式,才是高效計數的正道。