非叢集索引 vs 叢集索引核心差異
| 特性 | 叢集索引 | 非叢集索引 |
| 資料排序 | 依叢集鍵值邏輯排序儲存 | 資料不依非叢集鍵排序 |
| 葉層結構 | 資料頁 | 索引頁 (含鍵值+行定位器) |
| 每張表數量 | 最多 1 個 | 最多 999 個 (2008) |
行定位器
非叢集索引葉層每列包含:非叢集鍵值 + 行定位器
| 基礎表結構 | 行定位器類型 | 內容 |
| 堆疊表 | RID (Row ID) | 檔案號:頁碼:列號 (4+2+2=8 bytes) |
| 叢集索引表 | 叢集鍵值 | 叢集索引鍵值 (若不唯一,內部加 4 bytes 唯一化值) |
堆疊表上的非叢集索引實測
-- 建立測試堆疊表
CREATE TABLE testHeapIndex (
name CHAR(200),
type1 CHAR(900),
type2 CHAR(900)
);
-- 建立唯一與非唯一非叢集索引
CREATE UNIQUE INDEX idx_testHeapIndex1 ON testHeapIndex(type1);
CREATE INDEX idx_testHeapIndex2 ON testHeapIndex(type2);
-- 插入測試資料
INSERT INTO testHeapIndex VALUES('A','A1','A2');
-- ... 共 10 筆
查詢索引頁面資訊
-- 取得物件與索引對應
SELECT A.NAME TABLE_NAME, B.NAME INDEX_NAME, B.INDEX_ID
FROM SYS.OBJECTS A, SYS.INDEXES B
WHERE A.OBJECT_ID=B.OBJECT_ID AND A.NAME='testHeapIndex';
-- 使用 DBCC IND 取得頁面分配
TRUNCATE TABLE tablepage;
INSERT INTO tablepage EXEC ('DBCC IND(testdb,testHeapIndex,0)'); -- 堆疊
INSERT INTO tablepage EXEC ('DBCC IND(testdb,testHeapIndex,2)'); -- 索引 2
INSERT INTO tablepage EXEC ('DBCC IND(testdb,testHeapIndex,3)'); -- 索引 3
-- 查詢頁面類型與層級
SELECT b.name table_name,
CASE c.type WHEN 0 THEN '堆疊' WHEN 1 THEN '叢集' WHEN 2 THEN '非叢集' END index_type,
c.name index_name, PagePID, IAMPID, IndexLevel, NextPagePID, PrevPagePID
FROM tablepage a, sys.objects b, sys.indexes c
WHERE A.ObjectID=b.object_id AND A.ObjectID=c.object_id AND a.IndexID=c.index_id
根頁面查詢
SELECT c.name, a.type_desc, d.name, total_pages, used_pages, data_pages,
testdb.dbo.f_get_page(first_page) first_page_address,
testdb.dbo.f_get_page(root_page) root_address,
testdb.dbo.f_get_page(first_iam_page) IAM_address
FROM sys.system_internals_allocation_units a,
sys.partitions b,
sys.objects c,
sys.indexes d
WHERE a.container_id=b.partition_id
AND b.object_id=c.object_id
AND d.object_id=b.object_id
AND d.index_id=b.index_id
AND c.name in ('testHeapIndex')
B+ 樹結構特性
- 根節點 → 中間層 → 葉層
- 葉層透過雙向鏈表連結 (NextPagePID, PrevPagePID)
- IndexLevel 0 = 葉層,遞增向根
- sys.system_internals_allocation_units.root_page 指向根頁面
實務啟示
- 非叢集索引鍵值越窄越好 (少欄位、窄資料類型)
- 叢集鍵值會複製到所有非叢集索引葉層,故叢集鍵宜窄、唯一、不常更新
- 堆疊表 RID 查找需額外 I/O,叢集索引表透過鍵值查找通常較佳
- 覆蓋索引可避免鍵值查找,大幅提升查詢效能