叢集索引決定資料表的物理儲存順序,每個資料表僅能擁有一個。許多開發人員誤以為「主鍵 = 叢集索引」,導致設計失誤。本文釐清三大核心原則:主鍵與叢集索引可分離、叢集索引鍵值單調遞增避免頁分裂、鍵值寬度越窄越好,並提供實務建議與反模式對照。
預設行為:建立主鍵時,若資料表無叢集索引,SQL Server 自動在主鍵上建立唯一叢集索引。
分離場景:
-- 先在非主鍵欄位建立叢集索引
CREATE CLUSTERED INDEX IX_Orders_OrderDate ON Orders (OrderDate);
-- 再指定其他欄位為主鍵
ALTER TABLE Orders ADD CONSTRAINT PK_Orders PRIMARY KEY NONCLUSTERED (OrderID);
結果:OrderDate 為叢集索引,OrderID 為唯一非叢集索引。
決策依據:主鍵用於實體識別與外鍵參考 (適合窄、不變、隨機);叢集索引用於範圍查詢、排序、物理排序 (適合單調遞增、常用於 WHERE/ORDER BY/JOIN)。兩者職責不同,應依查詢模式獨立選擇。
IDENTITY、DATETIME、SEQUENCE),新列永遠附加在尾頁 → 零頁分裂、最佳填充因子、最小碎片、非叢集索引維護成本最低。| 欄位類型 | 適用性 | 範例 |
|---|---|---|
BIGINT IDENTITY / INT IDENTITY | ★★★ 最佳 | 交易流水號、記錄序號 |
DATETIME / DATETIME2 | ★★★ 極佳 | 訂單時間、日誌時間、感測器讀數時間 |
UNIQUEIDENTIFIER (NEWSEQUENTIALID) | ★★☆ 可接受 | 分散式系統需全域唯一時,需搭配 NEWSEQUENTIALID() 而非 NEWID() |
| 字串、GUID (NEWID)、多欄位組合 | ☆☆☆ 反模式 | 隨機分布,嚴重頁分裂 |
機制:非叢集索引葉層儲存「索引鍵值 + 叢集索引鍵值 (作為 Row Locator)」。叢集鍵越寬,每個非叢集索引列越寬 → 索引頁容納列數減少 → 樹高增加、IO 增加、記憶體壓力增大。
SMALLINT (2 bytes)INT (4 bytes)BIGINT (8 bytes)DATETIME (8 bytes) / DATETIME2(0-7) (6-8 bytes)UNIQUEIDENTIFIER (16 bytes)避免:CHAR/VARCHAR/NVARCHAR、DECIMAL(19,4)、多欄位組合鍵 (除非極窄)。
| 場景 | 主鍵 | 叢集索引 | 說明 |
|---|---|---|---|
| 高頻 INSERT、範圍查詢按時間 | BIGINT IDENTITY (非叢集) | DATETIME2 (單調遞增) | 分離主鍵與叢集,時間欄位物理排序最優 |
| 單點查詢為主、極少範圍掃描 | BIGINT IDENTITY (叢集) | 同主鍵 | 主鍵即叢集,簡單有效 |
| 分散式系統、需全域唯一 | UNIQUEIDENTIFIER (非叢集) | BIGINT IDENTITY 或 DATETIME2 | GUID 做主鍵,叢集用窄遞增鍵 |
| 唯讀/極少寫入、大量掃描 | 視情況 | 常用篩選/排序欄位 | 讀取優化優先 |
-- 查看資料表索引屬性
SELECT
t.name AS TableName,
i.name AS IndexName,
i.type_desc AS IndexType,
i.is_primary_key,
i.is_unique,
i.fill_factor,
STUFF((SELECT ', ' + c.name
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
ORDER BY ic.key_ordinal
FOR XML PATH('')), 1, 2, '') AS KeyColumns
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
WHERE t.name = 'YourTable'
ORDER BY i.type_desc, i.name;
-- 檢查碎片率
SELECT
OBJECT_NAME(ips.object_id) AS TableName,
i.name AS IndexName,
ips.index_type_desc,
ips.avg_fragmentation_in_percent,
ips.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
WHERE ips.avg_fragmentation_in_percent > 10
ORDER BY ips.avg_fragmentation_in_percent DESC;
IDENTITY、DATETIME、NEWSEQUENTIALID 為首選,杜絕隨機 GUID/字串。INT/BIGINT/DATETIME 為黃金標準,直接決定所有非叢集索引大小與效能。遵循三大原則,即可為 SQL Server 資料表奠定穩健的物理儲存基礎,大幅降低碎片、鎖爭用、IO 壓力,提升查詢與寫入效能。