天天看點

索引深入淺出(6/10):選擇正确并合适的聚集索引鍵

今天我們來探讨下聚集索引的設計要求。在選擇聚集索引鍵時,有好幾點需要考慮的。選擇聚集索引鍵沒有硬性規定。多實踐,多從網上找問題的答案都會幫你找到正确的索引鍵。

唯一性(Uniqueness)

SQL Server允許你在非唯一列建立聚集索引,但是唯一性是任何索引最理想的屬性,尤其對于聚集索引。即使SQL Server允許在非唯一列建立聚集索引,在内部,SQL Server會為所有聚集索引鍵的重複值增加4 bytes的值,這個4 bytes 變長列就是所謂的uniquifiers。在這個情況下,SQL Server在聚集索引上定義的非唯一列和内部生成的uniquifiers列的組合當作聚集鍵。這個值在每個聚集索引鍵都會儲存。例如在聚集表上定義的非聚集索引的葉子層。

我們來看一個例子,建立SalesOrderDetail表的副本,并在productid 列(包含重複值)上定義一個聚集索引。

1 Use IndexDB
2 GO
3 SELECT * INTO dbo.SalesOrderDetailDupCI FROM AdventureWorks2008r2.Sales.SalesOrderDetail
4 GO
5 CREATE CLUSTERED INDEX ix_SalesOrderDetailDupCI ON dbo.SalesOrderDetailDupCI(ProductId)      

我們通過DBCC INC指令看看它的配置設定頁,并找出它的根頁: 

1 DBCC IND('IndexDB','SalesOrderDetailDupCI',1)
2 
3 TRUNCATE TABLE dbo.sp_table_pages
4 INSERT INTO sp_table_pages EXEC('DBCC IND(IndexDB,SalesOrderDetailDupCI,1)')
5 GO
6 
7 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC --根節點/索引頁      
索引深入淺出(6/10):選擇正确并合适的聚集索引鍵

可以看到5650頁是根頁(indexlevel列值為最大值2),我們用DBCC PAGE指令看下根頁的内容。

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,5650,3)      
索引深入淺出(6/10):選擇正确并合适的聚集索引鍵

再用DBCC PAGE看看中間頁5648的内容:

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,5648,3)      
索引深入淺出(6/10):選擇正确并合适的聚集索引鍵

可以看到,我們的中間級的索引頁額外增加了UNIQUIFIER列,用來保證聚集索引鍵productid的唯一性。當聚集索引建立在非唯一列時,SQL Server會為重複出現的聚集鍵增加4 bytes的随機值,不重複的鍵不增加(第一條記錄productid為NULL,是唯一不重複的,故UNIQUIFIER值也是NULL)。是以定義在非唯一列的聚集索引會額外生成UNIQUIFIER值,也就增加了聚集鍵的長度。productid列為int,長度為4 bytes,加上4 bytes的UNIQUIFIER,我們聚集鍵的長度也就變成了8 bytes。這個組合會複制到所有非聚集索引的葉子節點。當在非唯一列的聚集索引上建立非聚集索引時,這個問題會加劇,聚集索引值同樣要儲存到非葉子層的頁裡去。(下篇文章我們會讨論在非唯一列的聚集索引上建立非聚集索引的問題)。

如果一個表沒有一個唯一鍵去定義聚集索引,可以考慮再加幾個小列讓它變成唯一。這樣會避免UNIQUIFIER的出現,減少書簽查找操作,因為非聚集索引的非頁層有更多的列(這額外增加的列是為了保持聚集鍵的唯一性)。

靜态的(Static)

另外一個聚集索引鍵的屬性是靜态的。當我們在非靜态列定義聚集索引時,會讓UPDATE語句更加耗費資源,為了保證記錄是按聚集索引的邏輯順序儲存的,它需要把記錄移到不同的頁,同樣非聚集索引的葉子層也要更新。

即使在小表的非靜态列上定義聚集索引,且又定義一個非聚集索引在它上面。任何在在聚集索引鍵上個更改都要改動2個頁。一個資料頁,還有一個非聚集索引的葉子層頁。

聚集索引鍵大小(Size of the clustered index key)

聚集索引鍵的大小指的是儲存聚集索引鍵需要的位元組數。當聚集索引鍵大小增加是,需要更多的IO操作來擷取資料。這個發生是因為如果聚集索引更寬的話,索引頁就隻能儲存更少的索引行。這就增加了中間層的頁樹,還有索引的深度(B樹結構的層數)。例如,把聚集索引定義在整形列的話,一個包含數百萬記錄的表可能隻需要3層的B樹結構。如果把聚集索引定義在更寬的列(包含uniqueidentifier列需要16 bytes),那麼索引的深度會增加到4(索引的層數)。任何聚集索引查找需要4個IO操作,原先隻要3個IO。

這個問題也會傳遞到非聚集索引,因為聚集索引鍵也儲存在所有非聚集索引的葉子層,作為指針指向聚集索引。如果非聚集索引定義在非唯一列,聚集鍵需要儲存在非聚集索引的非頁層頁。同樣也會帶來更多的中間層頁,并增加非聚集索引的深度。這也就增加非聚集索引查找/掃描的IO操作。因為聚集索引的深度增加到4,每個書簽查找操作也會需要4個IO操作。

連續性(Sequential)

把聚集索引定義在自增長列(連續的)是個最佳做法。因為這個原因我們經常看到聚集索引定義在辨別列(identity column)。聚集索引定義在非連續列會帶來碎片。一個非連續的聚集索引列會強制SQL Server把記錄插在中間(in between)用來保持資料的邏輯順序。這會導緻頁分裂,也是造成外部和内部碎片的原因。

總結

我們已經讨論聚集索引設計的屬性要求,還有它們背後的原因。在我們決定聚集索引鍵時,上述讨論的幾點通常是最佳做法。除此之外,資料通路模式(data access pattern)也會影響我們聚集索引鍵的選擇。

在我們沒有完全了解資料通路模式前,我們需要用不同的方法測試下性能先。

參考文章:

http://www.sqlservercentral.com/blogs/practicalsqldba/2013/03/21/sql-server-part-6-design-consideration-of-clustered-index/

注:此文章為

WoodyTu

學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!

若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!

繼續閱讀