| 操作場景 | 聚集索引 | 非聚集索引 |
|---|---|---|
| 欄位經常 GROUP BY/ORDER BY | ✓ 應 | ✓ 應 |
| 返回範圍內資料 | ✓ 應 | ✗ 不應 |
| 極少不同值(如性別) | ✗ 不應 | ✗ 不應 |
| 少數不同值(如部門) | ✓ 應 | ✗ 不應 |
| 大量不同值 | ✗ 不應 | ✓ 應 |
| 頻繁更新的欄位 | ✗ 不應 | ✓ 應 |
| 外鍵欄位 | ✓ 應 | ✓ 應 |
| 主鍵欄位 | ✓ 應 | ✓ 應 |
| 頻繁修改索引欄位值 | ✗ 不應 | ✓ 應 |
日期欄位建聚集索引 → 查 2004/1/1-10/1 只需定位首尾 → 直接讀取連續頁面。非聚集索引需:查索引每筆頁碼 → 隨機讀取頁面。
| 索引策略 | 查詢語句 | 耗時 |
|---|---|---|
| 僅主鍵聚集,無時間範圍 | SELECT * FROM tgongwen | 128,470ms (128秒) |
| 主鍵聚集 + 日期非聚集 | WHERE fariqi > DATEADD(day,-90,GETDATE()) | 53,763ms (54秒) |
| 日期建聚集索引 | WHERE fariqi > DATEADD(day,-90,GETDATE()) | 2,423ms (2秒) |
相同結果集,**聚集索引選擇正確提速 22-53 倍**。
同一欄位、同一查詢,**聚集 vs 非聚集**效能天壤之別。fariqi 5003 個不同值,完美符合聚集索引「不重不疏」原則。
測試複合聚集索引(fariqi + neibuyonghu):
| 查詢條件 | 耗時 | 說明 |
|---|---|---|
| WHERE fariqi > '2004-5-5' | 2513ms | 僅用前導欄 → 效能最佳 |
| WHERE fariqi > '2004-5-5' AND neibuyonghu='辦公室' | 2516ms | 用全複合索引 → 略慢(結果集相同) |
| WHERE neibuyonghu='辦公室' | 60,280ms | **僅用非前導欄 → 索引完全失效** |
**鐵律:複合索引前導欄必須是最頻繁查詢欄位**。
WHERE fariqi='2004-9-16' (聚集) -- 3326ms
WHERE gid<=250000 (主鍵非聚集) -- 4470ms
快近 1/4。
ORDER BY fariqi (聚集) -- 12936ms
ORDER BY gid (主鍵) -- 18843ms
快 3/10。小數據量差異更明顯。
fariqi > '2004-1-1' (100萬筆) -- 6343ms
fariqi > '2004-6-6' (50萬筆) -- 3170ms
fariqi = '2004-9-16' (25萬筆) -- 3326ms
fariqi > '2004-1-1' AND < '2004-6-6' (50萬筆) -- 3280ms
同樣 50 萬筆:精確到日 vs 精確到秒 → 查詢耗時無顯著差異(6390ms vs 6453ms)。
參考前文「SQL Server 索引結構與查詢優化(二)」第 3 節 11 大實戰法則。
參考前文「SQL Server 分頁查詢優化實戰」第 3-4 節。
CREATE PROCEDURE pagination3
@tblName VARCHAR(255), @strGetFields VARCHAR(1000) = '*',
@fldName VARCHAR(255) = '', @PageSize INT = 10, @PageIndex INT = 1,
@doCount BIT = 0, @OrderType BIT = 0, @strWhere VARCHAR(1500) = ''
AS
BEGIN
DECLARE @strSQL VARCHAR(5000), @strTmp VARCHAR(110), @strOrder VARCHAR(400)
IF @doCount != 0
BEGIN
IF @strWhere != '' SET @strSQL = "SELECT COUNT(*) AS Total FROM [" + @tblName + "] WHERE " + @strWhere
ELSE SET @strSQL = "SELECT COUNT(*) AS Total FROM [" + @tblName + "]"
END
ELSE
BEGIN
IF @OrderType != 0 BEGIN SET @strTmp = "<(SELECT MIN"; SET @strOrder = " ORDER BY [" + @fldName + "] DESC" END
ELSE BEGIN SET @strTmp = ">(SELECT MAX"; SET @strOrder = " ORDER BY [" + @fldName + "] ASC" END
IF @PageIndex = 1
BEGIN
IF @strWhere != '' SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields + " FROM [" + @tblName + "] WHERE " + @strWhere + " " + @strOrder
ELSE SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields + " FROM [" + @tblName + "] " + @strOrder
END
ELSE
BEGIN
SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields + " FROM [" + @tblName + "] WHERE [" + @fldName + "]" + @strTmp + "([" + @fldName + "]) FROM (SELECT TOP " + STR((@PageIndex-1)*@PageSize) + " [" + @fldName + "] FROM [" + @tblName + "]" + @strOrder + ") AS tblTmp)" + @strOrder
IF @strWhere != '' SET @strSQL = "SELECT TOP " + STR(@PageSize) + " " + @strGetFields + " FROM [" + @tblName + "] WHERE [" + @fldName + "]" + @strTmp + "([" + @fldName + "]) FROM (SELECT TOP " + STR((@PageIndex-1)*@PageSize) + " [" + @fldName + "] FROM [" + @tblName + "] WHERE " + @strWhere + " " + @strOrder + ") AS tblTmp) AND " + @strWhere + " " + @strOrder
END
END
EXEC(@strSQL)
END
參考文章 86506 核心結論:新增高精度時間欄位作聚集索引,同時滿足範圍查詢與分頁排序雙重需求。