Articles (文章表)ArticleID (PK, IDENTITY, Clustered Index) + ATitle (NVARCHAR(100)) + AContent (VARCHAR(2000)) + CreateDate (DATETIME)DBCC FREEPROCCACHE; -- 清除計畫快取
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO
-- 測試語句置於此
GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
觀測指標:CPU 時間、經過時間、邏輯讀取。
| 查詢語句 | CPU 時間 (ms) | 經過時間 (ms) | 邏輯讀取 | 掃描模式 |
|---|---|---|---|---|
SELECT COUNT(*) FROM Articles | 1,125 | 1,140 | 120,368 | Clustered Index Scan |
SELECT COUNT(ArticleID) FROM Articles | 1,093 | 1,094 | 120,368 | Clustered Index Scan |
SELECT COUNT(ATitle) FROM Articles | 2,266 | 2,267 | ~240,000+ | Clustered Index Scan + 解壓縮 NVARCHAR |
SELECT COUNT(AContent) FROM Articles | 2,296 | 2,303 | ~400,000+ | Clustered Index Scan + 解壓縮 VARCHAR(MAX) 離列儲存 |
COUNT(*) 語意為「計算列數」,優化器直接掃描最窄索引 (通常是叢集索引或最小非叢集索引) 的葉層頁面計數,不檢查 NULL。COUNT(PK) 因 PK 為 NOT NULL,語意等同 COUNT(*),優化器同樣走最窄索引掃描。NVARCHAR(100) 需解壓縮 Unicode;VARCHAR(2000) 可能觸發離列儲存 (LOB) 讀取,邏輯讀取量暴增。-- 瞬間完成,無 I/O 掃描
SELECT SUM(p.rows)
FROM sys.partitions p
WHERE p.object_id = OBJECT_ID('Articles') AND p.index_id IN (0,1); -- 0=堆疊, 1=叢集索引
結果:CPU < 1ms,邏輯讀取 ≈ 0。
原理:sys.partitions.rows 由儲存引擎維護的中繼資料,不保證 100% 即時精確 (未提交交易、延遲更新),但誤差極小,適合「近似總筆數」報表、分頁總頁數估算。
若頻繁 COUNT(ATitle) 且該欄位常用於查詢:
CREATE NONCLUSTERED INDEX IX_Articles_ATitle_Cover ON Articles(ATitle) INCLUDE (ArticleID);
-- 或僅建立欄位索引 (若不需覆蓋其他查詢)
CREATE NONCLUSTERED INDEX IX_Articles_ATitle ON Articles(ATitle);
此後 COUNT(ATitle) 可掃描此窄索引 而非叢集索引,I/O 大幅降低。
COUNT(*) (語意最清晰) 或系統表 sys.partitions (極速、近似值)。COUNT(NotNullColumn),確保該欄有索引或建立覆蓋索引。COUNT(大型文字/LOB欄位),必要時改用觸發器維護計數表。COUNT(*) 結果 (如 Redis),避免每頁查詢重複掃描。千萬筆實測證實:COUNT(*) = COUNT(PK) >> COUNT(定長數值) > COUNT(變長文字) > COUNT(LOB)。選對寫法、配合索引覆蓋、善用系統中繼資料,是大數據量下 COUNT 效能優化的鐵三角。