1. 程式人生 > >《Pro SQL Server Internals》——CHAPTER2 節 (P36-P45) 翻譯

《Pro SQL Server Internals》——CHAPTER2 節 (P36-P45) 翻譯

聚集索引

         聚集索引指示表中資料的物理順序,該順序是根據聚集索引鍵排序的。表只能定義一個聚集索引

         讓我們假設您希望在堆表上使用資料建立叢集索引。作為第一步,如圖2-5所示,SQL Server建立資料的另一個副本,然後根據叢集鍵的值進行排序。資料頁在雙鏈表中連結,其中每個頁面都包含指向鏈中的下一個和上一個頁面的指標。這個列表稱為索引的葉級,它包含實際的表資料。

                                                                            圖2 - 5:聚集索引結構:葉級


 

 注意,頁面上的排序順序由插槽陣列控制。頁面上的實際資料沒有排序。


      當葉子層包含多個頁面時,SQL Server開始構建索引的中間層,如圖2-6所示。

                                                                                        圖2 - 6:聚類索引結構:中間層和葉層

         中間層為每個葉級頁面儲存一行。它儲存了兩條資訊:實體地址和它引用的頁面索引鍵的最小值。唯一的例外是第一頁的第一行,其中SQL Server儲存NULL,而不是最小索引鍵值。通過這種優化,當您插入表中鍵值最低的行時,SQL Server不需要更新非葉級別的行。

         中間層上的頁面也連結到雙鏈表。SQL Server添加了越來越多的中間級別,直到有一個級別只包含單個頁面。這個級別稱為根級別,它成為索引的入口點,如圖2-7所示。

                                                                                               圖2 - 7:聚集索引結構:根級別

        正如您所看到的,索引總是具有一個葉級、一個根級和零個或多箇中間級。唯一的例外是索引資料適合於單個頁面。在這種情況下,SQL Server不會建立單獨的根級別頁面,而索引只包含單個葉級別頁面。

       索引中的級別數量主要取決於行和索引鍵大小。例如,4位元組整數列上的索引在中間和根級別上需要每一行13位元組。這13個位元組包括一個2位元組的槽陣列條目、一個4位元組的索引鍵值、一個6位元組的頁面指標和一個1位元組的行開銷,這就足夠了,因為索引鍵不包含可變長度和空列。

     因此,每行可以容納8060位元組/ 13位元組=每頁620行。這意味著,使用一箇中間層,可以儲存最多620 * 620 = 384,400頁的資訊。如果資料行大小為200位元組,那麼每個葉級頁面可以儲存40行,索引中最多可以儲存15,376,000行,其中只有三個級別。向索引中新增另一箇中間級別基本上可以覆蓋所有內容可能的整數值。


 

注意,在現實生活中,索引碎片化會減少這些數字。我們將在第6章討論索引碎片。


 

       SQL Server可以通過三種不同的方式從索引讀取資料。第一個是有序掃描。我們假設希望從dbo執行SELECT名稱。客戶通過CustomerId查詢下單。索引頁級別上的資料已經根據CustomerId列值排序。因此,SQL Server可以從第一個頁面掃描到最後一個頁面的索引葉級別,並按照儲存它們的順序返回行。

       SQL Server從索引的根頁面開始,從根頁面讀取第一行。該行引用中間頁,其中包含來自表的最小鍵值。SQL Server讀取該頁面並重復該過程,直到找到葉子級別上的第一個頁面。然後,SQL Server開始逐個讀取行,遍歷頁面的連結串列,直到讀取了所有行。圖2-8說明了這個過程。

                                                                                                  圖2 - 8:命令索引掃描

        前面查詢的執行計劃顯示了叢集索引掃描操作符,其有序屬性設定為true,如圖2-9所示。

                                                                       圖2 - 9:排序索引掃描執行計劃

        值得一提的是,order by子句不需要觸發有序掃描。有序掃描意味著SQL Server根據索引鍵的順序讀取資料。

        SQL Server可以在前進和後退兩個方向上導航索引。然而,有一個重要的方面,你必須記住:SQL Server在向後索引掃描期間不使用並行性。


 

提示:可以通過檢查執行計劃中的索引掃描或索引查詢操作符屬性來檢查掃描方向。但是請記住,Management Studio不會在執行計劃的圖形表示形式中顯示這些屬性。您需要開啟Properties視窗,通過在執行計劃中選擇操作符並選擇View/Properties視窗選單項或按F4鍵來檢視它。


 

        SQL Server的企業版有一個稱為旋轉木馬掃描的優化特性,它允許多個任務共享相同的索引掃描。假設會話S1掃描索引。在掃描過程中的某個時刻,另一個會話S2執行一個查詢,該查詢需要掃描相同的索引。通過旋轉木馬掃描,S2在當前掃描位置加入S1。SQL Server只讀取每個頁面一次,將行傳遞給兩個會話。

        當S1掃描到達索引的末尾時,S2從索引的開頭開始掃描資料,直到S2掃描開始的那一點。旋轉木馬掃描是你不能依賴的另一個例子,關於索引鍵的順序以及為什麼應該總是指定order BY子句(當它重要時)。

       排序掃描之後的下一個訪問方法稱為分配順序掃描。SQL Server通過IAM頁面訪問表資料,這與它通過堆表訪問表資料的方式類似。從dbo中選擇的名稱。具有(NOLOCK)查詢的客戶和圖2-10演示了這種方法。圖2-11顯示了查詢執行計劃。

                                                                                                    圖2 - 10:分配順序掃描

                                                                             圖2 - 11:分配順序掃描執行計劃

        不幸的是,SQL Server在使用分配順序掃描時不容易檢測到。即使執行計劃中的有序屬性顯示為false,這表明SQL Server並不關心是否按照索引鍵的順序讀取行,也不關心是否使用了分配順序掃描。

       分配順序掃描可以更快地掃描大型表,儘管它有較高的啟動成本。當表很小時,SQL Server不使用這種訪問方法。另一個重要的考慮因素是資料一致性。SQL Server在具有叢集索引的表中不使用轉發指標,分配順序掃描可能產生不一致的結果。由於頁面分割導致的資料移動,可以多次跳過或讀取行。因此,SQL Server通常避免使用分配順序掃描,除非它讀取未提交或可序列化事務隔離級別中的資料。


注意,我們將在第6章“索引碎片”中討論頁面分割和碎片,並在第3部分“鎖定、阻塞和併發”中討論鎖定和資料一致性。


 

        最後一種索引訪問方法稱為索引尋道。從dbo中選擇的名稱。其中CustomerId在4到7之間的查詢和圖2-12說明了操作。

                                                                                   圖2 - 12:指數尋求

          為了從表中讀取行範圍,SQL Server需要從範圍中找到鍵值最小的行,即4。SQL Server從根頁面開始,其中第二行引用鍵值最小為350的頁面。它大於我們正在尋找的鍵值(4),SQL Server讀取根頁面上第一行引用的中間層資料頁(1:170)。

        類似地,中間頁面將SQL Server引導到第一個葉級頁面(1:176)。SQL Server讀取該頁面,然後,它讀取自定義id等於4和5的行,最後,它從第二頁讀取剩下的兩行。

        執行計劃如圖2-13所示。

                                                                                  圖2 - 13:索引查詢執行計劃

          正如您所猜測的,索引查詢比索引掃描更有效,因為SQL Server只處理行和資料頁的子集,而不是掃描整個表。

          從技術上講,有兩種索引查詢操作。第一個稱為單例查詢,有時也稱為點查詢,SQL Server在其中查詢並返回一行。您可以以CustomerId = 2謂詞為例。另一種索引尋道操作稱為範圍掃描,它要求SQL Server找到鍵的最低或最高值,並掃描(向前或向後)這組行,直到掃描範圍結束。CustomerId在4到7之間的謂詞將導致範圍掃描。這兩種情況都顯示為執行計劃中的索引查詢操作。

         正如您所猜測的,範圍掃描完全有可能強制SQL Server處理來自索引的大量甚至所有資料頁。例如,如果將查詢更改為使用WHERE CustomerId >謂詞,SQL Server將讀取所有行/頁,即使在執行計劃中顯示了索引查詢操作符。您必須記住這種行為,並在查詢效能調優期間始終分析範圍掃描的效率。

        關係資料庫中有一個概念叫做可SARGable謂詞,它代表的是S earch Arg able,如果SQL Server可以使用索引查詢操作,如果存在索引,那麼謂詞就是可SARGable。簡而言之,當SQL Server能夠隔離要處理的單個值或索引鍵值範圍時,謂詞是可SARGable的,從而限制了謂詞計算期間的搜尋。顯然,使用可SARGable謂詞編寫查詢並在任何可能的情況下使用索引搜尋是有益的。

     SARGable謂詞包括以下操作符:=、>、>=、<、<=、IN、BETWEEN和LIKE(在字首匹配的情況下)。非sargable操作符包括NOT、<>、LIKE(在非字首匹配的情況下)和NOT in。

     使謂詞不可sargable的另一種情況是對錶列使用函式或數學計算。SQL Server必須為它處理的每一行呼叫函式或執行計算。幸運的是,在某些情況下,您可以重構查詢,使這些謂詞成為可SARGable。表2-1顯示了一些例子。

表2 - 1:重構不可SARGable謂詞到可SARGable謂詞的示例

     您必須記住的另一個重要因素是型別轉換。在某些情況下,您可以使用不正確的資料型別使謂詞不可sargable。讓我們使用varchar列建立一個表,並用一些資料填充它,如清單2-6所示。

清單2 - 6:SARG謂詞和資料型別:測試表的建立

 

     叢集索引鍵列被定義為varchar,儘管它儲存整數值。現在,讓我們執行兩個選擇,如清單2-7所示,並檢視執行計劃。

清單2 - 7:SARG謂詞和資料型別:使用整型引數進行選擇

 

        如圖2-14所示,對於integer引數,SQL Server掃描叢集索引,將varchar轉換為每一行的整數。在第二種情況下,SQL Server在開始時將整型引數轉換為varchar,並使用更高效的叢集索引查詢操作。

圖2 - 14:SARG謂詞和資料型別:具有整型引數的執行計劃


 

提示:請注意連線謂詞中的列資料型別。隱式或顯式資料型別轉換會顯著降低查詢的效能。


      在unicode字串引數的情況下,您將觀察到非常類似的行為。讓我們執行清單2-8所示的查詢。圖2-15顯示了語句的執行計劃。

清單2 - 8:SARG謂詞和資料型別:使用字串引數進行選擇  

select * from dbo.Data where VarcharKey = '200';
select * from dbo.Data where VarcharKey = N'200'; -- unicode parameter

 

圖2-15:SARG謂詞和資料型別:帶有字串引數的執行計劃

         如您所見,對於varchar列,unicode字串引數是不可sargable的。這是一個比看上去大得多的問題。雖然很少以這種方式編寫查詢,如清單2-8所示,但是現在大多數應用程式開發環境都將字串視為unicode。作為一個結果,除非引數資料型別被顯式指定為varchar,否則SQL Server客戶端庫將為字串物件生成unicode (nvarchar)引數。這使得謂詞不可sargable,而且由於不必要的掃描,甚至在索引varchar列時,也會導致效能下降。


 

重要:總是在客戶端應用程式中指定引數資料型別。例如,在ADO中。淨,使用Parameters.Add(“@ParamName SqlDbType。Varchar、<大小>)。Value = stringVariable而不是Parameters.Add("@ParamName")。Value = stringVariable過載。在ORM框架中使用對映顯式地指定類中的非unicode屬性。


 

    值得一提的是,對於nvarchar unicode資料列,varchar引數是可SARGable的。