上個星期我向你介紹了堆表(heap tables)。我們說過,在SQL Server表可以是堆表(Heap Table)或者聚集表(Clustered Table)——一個在它上面有聚集索引(Clustered Index)定義的表。今天我們來談論聚集索引(Clustered Index)的更多細節,還有如何選擇正確的聚集鍵(Clustered Key)。
每次你在SQL Server創建一個主鍵約束(Primary Key constraint),這個約束(默認情況)是通過唯一聚集索引(Unique Clustered Index)來執行的。這意味着你選擇的那列(或多列,當你定義復合主鍵(composite primary key)時)必須是唯一值。作為一種副作用,你的表數據是按那列(或那些列)物理排序的。讓我們一起看下在SQL Server里聚集索引(Clustered Index)的優點和缺點。
優點
聚集表最大的優點是,數據是在你的存儲子系統里是按聚集鍵(Clustered Key)物理排序的。你可以拿傳統電話本與聚集表(Clustered Table)做比較:電話本是按姓來聚合排序的,這意味着Aschenbrenner排在Bauer之前,Bauer排在Meyer之前。因此聚集表(Clustered Table)和堆表(heap tables)完全不一樣,堆表沒有物理上的排序順序。
你可以從聚集表(Clustered Table)獲得真正的巨大受益。想象下你在便利查找一條具體的記錄,在WHERE語句里那列是你用來限制你數據的主鍵(Clustered Key)。在那個情況下,SQL Server在執行計划里會選擇聚集索引(Clustered Index Seek)查找運算符。查找運算符會非常,非常高效,因為SQL Server使用B-tree結構來找相關的數據。這個查找運算符的復雜度總是O(log N)。如果你想學習更多關於B+tree在內部是如何使用的,你可以觀看我關於這個話題的SQL Server Quickie。在過去的2010年里,我也寫了關於這個話題的很多博客帖子。
當你在電話本找名為Aschenbrenner的號碼是一樣的,你知道那個名只能在電話本的開頭部分找到,因為電話本是按這個數據(名)排序的。因此你可以避免整個電話本的掃描,而SQL Server可以避免在葉子節點聚集索引(Clustered Index)的完全掃描。
只要在你的聚集索引(Clustered Index)里沒有索引碎片(index fragmentation),當你使用掃描運算符訪問聚集索引時,你會使用循序存取(sqquential I/O)。索引碎片(index fragmentation)指的是你在葉節點里的頁,邏輯上和物理上的排列順序是不一樣的。你可以通過Index Rebuild和Index Reorganized操作來修復索引碎片(index fragmentation)。在第24周,當我們涉及數據庫維護時,我們會談到這2個操作間的區別。
是否有索引碎片取決於你選擇的聚集鍵(Clustered Key)列。只要你使用自增長值(像 INT IDENTIY,或訂單日期(OrderDate)列),記錄插在聚集索引(Clustered Index)的末端。這意味着在你索引里,碎片不會被引入。因為SQL Server只在你聚集索引(Clustered Index)末端追加數據。但在一些極少的情況下,也會產生索引碎片(index fragmentation)。因此我們現在會談到聚集索引(Clustered Index)擁有的缺點,還有聚集鍵(Clustered Key)的錯誤選擇。
缺點
數據只插在聚集索引(Clustered Index)的末端會引入被稱為最后頁插入加鎖競爭(Last Page Insert Latch Contention)的問題,因為在你的聚集索引(Clustered Index)的末端你只有一個熱區(hotspot),那里各個查詢在遍歷(traversing through)B-tree結構時互相競爭。下圖演示了這個現象。
為了克服這個問題,你可以選擇隨機聚集鍵(random Clustered Key)作為你的聚集索引(Clustered Index),那樣的話,你就可以把插入的數據散步到聚集索引(Clustered Index)里各個不同地方。但是隨機聚集鍵(random Clustered Key)會引入被稱為硬頁分裂(Hard Page Splits)的問題,因為SQL Server需要把新數據頁分配到在聚集索引(Clustered Index)葉子級別之內的一些地方。硬頁分裂(Hard Page Splits)同樣也有在事務日志(transaction log)性能上的負面影響,因為相比在你聚集索引(Clustered Index)末端記錄一個普通的INSERT(被稱為軟頁分裂(Soft Page Splits)),記錄一個硬頁分裂(Hard Page Splits)需要更多的工作。
作為一個副作用,隨機聚集鍵(random Clustered Key)會引入索引碎片(index fragementation),因為你的邏輯和物理排列順序已經不再一樣。隨機存取(random I/O) 會扼殺你在傳統的旋轉存儲的掃描操作性能,因為當讀取各個數據頁的時候,磁頭必須在硬盤的盤片上前后移動。
小結
聚集索引(Clustered Index)伸縮性(scale)很好,因為它內部采用了B-tree數據結構。當在你表進行索引查找(index seek)運算符時,SQL Server可以很高效的利用這個結構。但是選擇一個正確並合適的聚集鍵(Clustered Key)是個很耗時的工作,因為你要考慮每個情況下所有優點和缺點(什么時候用增值型(increasing value),什么時候用隨機值型(random value))。
了解聚集索引的更多細節信息,可以看下索引深入淺出:聚集索引的B樹結構。
下星期我會談論SQL Server里非聚集索引(Non-Clustered Indexes)的更多信息。你會學到什么是非聚集索引(Non-Clustered Indexes),還在聚集索引(Clustered Indexe)里定義的聚集鍵(Clustered Key),非聚集索引(Non-Clustered Indexes)與它有怎樣的依賴關系。好好享受接下來的7天,到時候我們會再次見面。