統計表行數是最常見的查詢,但 COUNT(*)、COUNT(1)、COUNT(col) 效能是否有差異?本文揭示真相並給出最佳實踐。
| 寫法 | 語意 | NULL 處理 |
|---|---|---|
COUNT(*) | 統計**行數** | 包含 NULL 行 |
COUNT(1) | 統計常數 1 的非 NULL 個數(等同 COUNT(*)) | 包含 NULL 行 |
COUNT(col) | 統計該欄位**非 NULL 值**個數 | **忽略 NULL** |
-- MySQL 8.0.13+ 優化器將 COUNT(*) 和 COUNT(1) 視為完全等價
EXPLAIN SELECT COUNT(*) FROM `large_table`;
EXPLAIN SELECT COUNT(1) FROM `large_table`;
-- 兩者執行計劃相同:type=index, key=PRIMARY (或最小二級索引), rows=N
-- COUNT(col) 若該欄位有索引,同樣走索引掃描
-- 若無索引,全表掃描,效能同 COUNT(*)
結論:
COUNT(*)、COUNT(1) **完全等價**,優化器會選擇最小索引掃描(通常是主鍵或最小二級索引),不讀取資料行。COUNT(col) 語意不同(排除 NULL),**不可隨意替換**。WHERE 條件時,MyISAM 直接返回儲存的行數(O(1)),InnoDB 需掃描索引(O(N))。| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
近似值:SHOW TABLE STATUS / information_schema.TABLES | 不需精確、監控大屏 | 極快 (O(1)) | 非即時、有誤差(InnoDB 統計資訊更新延遲) |
| 觸發器維護計數表 | 需精確、高頻查詢 | 查詢 O(1)、精確 | 寫入開銷、鎖競爭、複雜度高 |
| Redis 快取行數 | 高併發讀、允許秒級延遲 | 極快、卸載 DB | 一致性維護、冷啟動預熱 |
| 定時任務統計存表 | 報表、分析 | 不影響線上 | 非即時 |
COUNT(*)**,語意清晰、優化器最優。WHERE 條件時,確保條件欄位有索引,避免全表掃描。SELECT COUNT(*) FROM t1 JOIN t2 ... 統計關聯結果,改用應用層分頁計數或觸發器維護。information_schema.TABLES.TABLE_ROWS 近似值,標註「約」。mysql> EXPLAIN SELECT COUNT(*) FROM `emp`;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | index | NULL | PRIMARY | 4 | NULL | 15 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
mysql> EXPLAIN SELECT COUNT(1) FROM `emp`;
-- 相同輸出