在《數據庫原理》里面,對聚簇索引的解釋是:聚簇索引的順序就是數據的物理存儲順序,而對非聚簇索引的解釋是:索引順序與數據物理排列順序無(wú)關(guān)。正式因為如此,所以一個(gè)表最多只能有一個(gè)聚簇索引。
不過(guò)這個(gè)定義太抽象了。在SQL Server中,索引是通過(guò)二叉樹(shù)的數據結構來(lái)描述的,我們可以這么理解聚簇索引:索引的葉節點(diǎn)就是數據節點(diǎn)。而非聚簇索引的葉節點(diǎn)仍然是索引節點(diǎn),只不過(guò)有一個(gè)指針指向對應的數據塊。如下圖:
非聚簇索引
聚簇索引
聚簇索引與非聚簇索引的本質(zhì)區別到底是什么?什么時(shí)候用聚簇索引,什么時(shí)候用非聚簇索引?
這是一個(gè)很復雜的問(wèn)題,很難用三言?xún)烧Z(yǔ)說(shuō)清楚。我在這里從SQL Server索引優(yōu)化查詢(xún)的角度簡(jiǎn)單談?wù)?如果對這方面感興趣的話(huà),可以讀一讀微軟出版的《Microsoft SQL Server 2000數據庫編程》第3單元的數據結構引論以及第6、13、14單元)。
一、索引塊與數據塊的區別
大家都知道,索引可以提高檢索效率,因為它的二叉樹(shù)結構以及占用空間小,所以訪(fǎng)問(wèn)速度塊。讓我們來(lái)算一道數學(xué)題:如果表中的一條記錄在磁盤(pán)上占用1000字節的話(huà),我們對其中10字節的一個(gè)字段建立索引,那么該記錄對應的索引塊的大小只有10字節。我們知道,SQL Server的最小空間分配單元是“頁(yè)(Page)”,一個(gè)頁(yè)在磁盤(pán)上占用8K空間,那么這一個(gè)頁(yè)可以存儲上述記錄8條,但可以存儲索引800條?,F在我們要從一個(gè)有8000條記錄的表中檢索符合某個(gè)條件的記錄,如果沒(méi)有索引的話(huà),我們可能需要遍歷8000條×1000字節/8K字節=1000個(gè)頁(yè)面才能夠找到結果。如果在檢索字段上有上述索引的話(huà),那么我們可以在8000條×10字節/8K字節=10個(gè)頁(yè)面中就檢索到滿(mǎn)足條件的索引塊,然后根據索引塊上的指針逐一找到結果數據塊,這樣IO訪(fǎng)問(wèn)量要少的多。
二、索引優(yōu)化技術(shù)
是不是有索引就一定檢索的快呢?答案是否。有些時(shí)候用索引還不如不用索引快。比如說(shuō)我們要檢索上述表中的所有記錄,如果不用索引,需要訪(fǎng)問(wèn)8000條×1000字節/8K字節=1000個(gè)頁(yè)面,如果使用索引的話(huà),首先檢索索引,訪(fǎng)問(wèn)8000條×10字節/8K字節=10個(gè)頁(yè)面得到索引檢索結果,再根據索引檢索結果去對應數據頁(yè)面,由于是檢索所有數據,所以需要再訪(fǎng)問(wèn)8000條×1000字節/8K字節=1000個(gè)頁(yè)面將全部數據讀取出來(lái),一共訪(fǎng)問(wèn)了1010個(gè)頁(yè)面,這顯然不如不用索引快。
SQL Server內部有一套完整的數據檢索優(yōu)化技術(shù),在上述情況下,SQL Server的查詢(xún)計劃(Search Plan)會(huì )自動(dòng)使用表掃描的方式檢索數據而不會(huì )使用任何索引。那么SQL Server是怎么知道什么時(shí)候用索引,什么時(shí)候不用索引的呢?SQL Server除了日常維護數據信息外,還維護著(zhù)數據統計信息,下圖是數據庫屬性頁(yè)面的一個(gè)截圖:
從圖中我們可以看到,SQL Server自動(dòng)維護統計信息,這些統計信息包括數據密度信息以及數據分布信息,這些信息幫助SQL Server決定如何制定查詢(xún)計劃以及查詢(xún)是是否使用索引以及使用什么樣的索引(這里就不再解釋它們到底如何幫助SQL Server建立查詢(xún)計劃的了)。我們還是來(lái)做個(gè)實(shí)驗。建立一張表:tabTest(ID, unqValue,intValue),其中ID是整形自動(dòng)編號主索引,unqValue是uniqueidentifier類(lèi)型,在上面建立普通索引,intValue 是整形,不建立索引。之所以?huà)焐弦粋€(gè)沒(méi)有索引的intValue字段,就是防止SQL Server使用索引覆蓋查詢(xún)優(yōu)化技術(shù),這樣實(shí)驗就起不到作用了。向表中錄入10000條隨機記錄,代碼如下:

































然后我們執行兩個(gè)查詢(xún)并查看執行計劃,如圖:(在查詢(xún)分析器的查詢(xún)菜單中可以打開(kāi)查詢(xún)計劃,同時(shí)圖上第一個(gè)查詢(xún)的GUID是我從數據庫中找的,大家做實(shí)驗的時(shí)候可以根據自己數據庫中的值來(lái)定):

從圖中可以看出,在第一個(gè)查詢(xún)中,SQL Server使用了IX_tabTest_unqValue索引,根據箭頭方向,計算機先在索引范圍內找,找到后,使用Bookmark Lookup將索引節點(diǎn)映射到數據節點(diǎn)上,最后給出SELECT結果。在第二個(gè)查詢(xún)中,系統直接遍歷表給出結果,不過(guò)它使用了聚簇索引,為什么呢?不要忘了,聚簇索引的頁(yè)節點(diǎn)就是數據節點(diǎn)!這樣使用聚簇索引會(huì )更快一些(不受數據刪除、更新留下的存儲空洞的影響,直接遍歷數據是要跳過(guò)這些空洞的)。
下面,我們在SQL Server中將ID字段的聚簇索引更改為非聚簇索引,然后再執行select * from tabTest,這回我們看到的執行計劃變成了:

SQL Server沒(méi)有使用任何索引,而是直接執行了Table Scan,因為只有這樣,檢索效率才是最高的。
三、聚簇索引與非聚簇索引的本質(zhì)區別
現在可以討論聚簇索引與非聚簇索引的本質(zhì)區別了。正如本文最前面的兩個(gè)圖所示,聚簇索引的葉節點(diǎn)就是數據節點(diǎn),而非聚簇索引的頁(yè)節點(diǎn)仍然是索引檢點(diǎn),并保留一個(gè)鏈接指向對應數據塊。
還是通過(guò)一道數學(xué)題來(lái)看看它們的區別吧:假設有一8000條記錄的表,表中每條記錄在磁盤(pán)上占用1000字節,如果在一個(gè)10字節長(cháng)的字段上建立非聚簇索引主鍵,需要二叉樹(shù)節點(diǎn)16000個(gè)(這16000個(gè)節點(diǎn)中有8000個(gè)葉節點(diǎn),每個(gè)頁(yè)節點(diǎn)都指向一個(gè)數據記錄),這樣數據將占用8000條×1000字節/8K字節=1000個(gè)頁(yè)面;索引將占用16000個(gè)節點(diǎn)×10字節/8K字節=20個(gè)頁(yè)面,共計1020個(gè)頁(yè)面。
同樣一張表,如果我們在對應字段上建立聚簇索引主鍵,由于聚簇索引的頁(yè)節點(diǎn)就是數據節點(diǎn),所以索引節點(diǎn)僅有8000個(gè),占用10個(gè)頁(yè)面,數據仍然占有1000個(gè)頁(yè)面。
下面我們看看在執行插入操作時(shí),非聚簇索引的主鍵為什么比聚簇索引主鍵要快。主鍵約束要求主鍵不能出現重復,那么SQL Server是怎么知道不出現重復的呢?唯一的方法就是檢索。對于非聚簇索引,只需要檢索20個(gè)頁(yè)面中的16000個(gè)節點(diǎn)就知道是否有重復,因為所有主鍵鍵值在這16000個(gè)索引節點(diǎn)中都包含了。但對于聚簇索引,索引節點(diǎn)僅僅包含了8000個(gè)中間節點(diǎn),至于會(huì )不會(huì )出現重復必須檢索另外1000個(gè)頁(yè)數據節點(diǎn)才知道,那么相當于檢索10+1000=1010個(gè)頁(yè)面才知道是否有重復。所以聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢很多。
讓我們再來(lái)看看數據檢索的效率,如果對上述兩表進(jìn)行檢索,在使用索引的情況下(有些時(shí)候SQL Server執行計劃會(huì )選擇不使用索引,不過(guò)我們這里姑且假設一定使用索引),對于聚簇索引檢索,我們可能會(huì )訪(fǎng)問(wèn)10個(gè)索引頁(yè)面外加1000個(gè)數據頁(yè)面得到結果(實(shí)際情況要比這個(gè)好),而對于非聚簇索引,系統會(huì )從20個(gè)頁(yè)面中找到符合條件的節點(diǎn),再映射到1000個(gè)數據頁(yè)面上(這也是最糟糕的情況),比較一下,一個(gè)訪(fǎng)問(wèn)了1010個(gè)頁(yè)面而另一個(gè)訪(fǎng)問(wèn)了1020個(gè)頁(yè)面,可見(jiàn)檢索效率差異并不是很大。所以不管非聚簇索引也好還是聚簇索引也好,都適合排序,聚簇索引僅僅比非聚簇索引快一點(diǎn)。
結語(yǔ)
好了,寫(xiě)了半天,手都累了。關(guān)于聚簇索引與非聚簇索引效率問(wèn)題的實(shí)驗就不做了,感興趣的話(huà)可以自己使用查詢(xún)分析器對查詢(xún)計劃進(jìn)行分析。SQL Server是一個(gè)很復雜的系統,尤其是索引以及查詢(xún)優(yōu)化技術(shù),Oracle就更復雜了。了解索引以及查詢(xún)背后的事情不是什么壞事,它可以幫助我們更為深刻的了解我們的系統。

