今天我們來探討下聚集索引的設計要求。在選擇聚集索引鍵時,有好幾點需要考慮的。選擇聚集索引鍵沒有硬性規定。多實踐,多從網上找問題的答案都會幫你找到正確的索引鍵。
唯一性(Uniqueness)SQL Server允許你在非唯一列創建聚集索引,但是唯一性是任何索引最理想的屬性,尤其對于聚集索引。即使SQL Server允許在非唯一列創建聚集索引,在內部,SQL Server會為所有聚集索引鍵的重復值增加4 bytes的值,這個4 bytes 變長列就是所謂的uniquifiers。在這個情況下,SQL Server在聚集索引上定義的非唯一列和內部生成的uniquifiers列的組合當作聚集鍵。這個值在每個聚集索引鍵都會保存。例如在聚集表上定義的非聚集索引的葉子層。
我們來看一個例子,創建SalesOrderDetail表的副本,并在PRoductid列(包含重復值)上定義一個聚集索引。
1 Use IndexDB2 GO3 SELECT * INTO dbo.SalesOrderDetailDupCI FROM AdventureWorks2008r2.Sales.SalesOrderDetail4 GO5 CREATE CLUSTERED INDEX ix_SalesOrderDetailDupCI ON dbo.SalesOrderDetailDupCI(ProductId)
我們通過DBCC INC命令看看它的分配頁,并找出它的根頁:
1 DBCC IND('IndexDB','SalesOrderDetailDupCI',1)2 3 TRUNCATE TABLE dbo.sp_table_pages4 INSERT INTO sp_table_pages EXEC('DBCC IND(IndexDB,SalesOrderDetailDupCI,1)')5 GO6 7 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC --根節點/索引頁
可以看到5650頁是根頁(indexlevel列值為最大值2),我們用DBCC PAGE命令看下根頁的內容。
1 DBCC TRACEON(3604)2 DBCC PAGE(IndexDB,1,5650,3)
再用DBCC PAGE看看中間頁5648的內容:
1 DBCC TRACEON(3604)2 DBCC PAGE(IndexDB,1,5648,3)
可以看到,我們的中間級的索引頁額外增加了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)把聚集索引定義在自增長列(連續的)是個最佳做法。因為這個原因我們經??吹骄奂饕x在標識列(identity column)。聚集索引定義在非連續列會帶來碎片。一個非連續的聚集索引列會強制SQL Server把記錄插在中間(in between)用來保持數據的邏輯順序。這會導致頁分裂,也是造成外部和內部碎片的原因。
總結我們已經討論聚集索引設計的屬性要求,還有它們背后的原因。在我們決定聚集索引鍵時,上述討論的幾點通常是最佳做法。除此之外,數據訪問模式(data access pattern)也會影響我們聚集索引鍵的選擇。
在我們沒有完全理解數據訪問模式前,我們需要用不同的方法測試下性能先。
新聞熱點
疑難解答