包含列的索引:SQL Server索引的階梯級別5

SQL Server索引的階梯」的一部分 索引是數據庫設計的基礎,並告訴開發人員使用數據庫關於設計者的意圖。不幸的是,當性能問題出現時,索引每每被添加爲過後考慮。這裏最後是一個簡單的系列文章,應該使他們快速地使任何數據庫專業人員「快速」 前面的級別引入了聚簇和非聚簇索引,突出瞭如下各方面: •表中每一行的索引老是有一個條目(咱們注意到,這個規則的一個例外將在後面的級別中進行討論)。這些條目始終處於索引鍵序列中。 •在聚簇索引中,索引條目是表的實際行。 •在非彙集索引中,條目與數據行分開;由索引鍵列和書籤值組成,以將索引鍵列映射到表的實際行。 前面句子的後半部分是正確的,但不完整。在這個級別中,咱們檢查選項以將其餘列添加到非彙集索引(稱爲包含列)。在檢查書籤操做的級別6中,咱們將看到SQL Server可能會單方面向您的索引添加一些列。 包括列 在非彙集索引中但不屬於索引鍵的列稱爲包含列。這些列不是鍵的一部分,所以不影響索引中條目的順序。並且,正如咱們將會看到的那樣,它們比鍵列形成的開銷更少。 建立非彙集索引時,咱們指定了與鍵列分開的包含列;如清單5.1所示。數據庫

CREATE NONCLUSTERED INDEX FK_ProductID_ ModifiedDate數據庫設計

       ON Sales.SalesOrderDetail (ProductID, ModifiedDate)性能

       INCLUDE (OrderQty, UnitPrice, LineTotal)測試

清單5.1:建立包含列的非彙集索引 在本例中,ProductID和ModifiedDate是索引鍵列,OrderQty,UnitPrice和LineTotal是包含的列。 若是咱們沒有在上面的SQL語句中指定INCLUDE子句,那麼結果索引看起來應該是這樣的:優化

ProductID   ModifiedDate   Bookmarkspa

Page n:設計

707         2004/07/25        =>   707         2004/07/26        =>   707         2004/07/26        =>   707         2004/07/26        =>   707         2004/07/27        =>   707         2004/07/27        =>   707         2004/07/27        =>   707         2004/07/28        =>   707         2004/07/28        =>   707         2004/07/28        =>   707         2004/07/28        =>   707         2004/07/28        =>   707         2004/07/28        =>  排序

Page n+1:索引

707         2004/07/29        =>   707         2004/07/31        =>   707         2004/07/31        =>   707         2004/07/31        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>   708         2001/07/01        =>  內存

可是,告訴SQL Server包含OrderQty,UnitPrice和LineTotal列時,索引以下所示:

:- Search Key Columns -: :---  Included Columns  ---:  : Bookmark :

ProductID  ModifiedDate  OrderQty  UnitPrice  LineTotal

Page n-1:

707         2004/07/29       1         34.99      34.99      =>  707         2004/07/31       1         34.99      34.99       =>  707         2004/07/31       3         34.99      104.97     =>  707         2004/07/31       1        34.99      34.99       =>  708         2001/07/01       5         20.19       100.95     => 

Page n:

708         2001/07/01       1         20.19       20.19      =>   708         2001/07/01      1          20.19       20.19     =>   708        2001/07/01       2          20.19      40.38      =>   708        2001/07/01       1           20.19       20.19     =>   708        2001/07/01       2          20.19       40.38     => 

708        2001/12/01       7          20.19       141.33     =>   708       2001/12/01       1           20.19       20.19     =>   708       2002/01/01       1           20.19       20.19    =>   708       2002/01/01        1           20.19       20.19    =>   708       2002/01/01         1          20.19        20.19    =>   

Page n+1:

708       2002/01/01        2       20.19       40.38       =>   708       2002/01/01       5       20.19       100.95       =>   708      2002/02/01        1       20.19       20.19        =>   708      2002/02/01        1       20.19       20.19      =>   708     2002/02/01        2       20.19       40.38       =>  

檢查顯示的這個索引的內容,顯然這些行按索引鍵列排序。例如,修改日期爲2002年1月1日(以粗體突出顯示)的產品708的五行在索引中是連續的,每隔一個ProductID / ModifiedDate組合的行也是如此。 你可能會問「爲何甚至包括列?爲何不簡單地將OrderQty,UnitPrice和LineTotal添加到索引鍵?「索引中有這些列但索引鍵中沒有這些列有幾個優勢,例如: •不屬於索引鍵的列不會影響索引內條目的位置。這反過來又減小了讓他們在索引中的開銷。例如,若是行中的ProductID或ModifiedDate值被修改,那麼該行的條目必須在索引內從新定位。可是,若是行中的UnitPricevalue被修改,索引條目仍然須要更新,但不須要移動。 •在索引中查找條目所需的努力較少。 •索引的大小會略小。 •索引的數據分佈統計將更容易維護。 當咱們查看索引的內部結構以及由SQL Server維護的用於優化查詢性能的一些附加信息時,大多數這些優點在之後的級別中將更有意義。 肯定索引列是不是索引鍵的一部分,或只是包含的列,不是您將要作的最重要的索引決定。也就是說,頻繁出如今SELECT列表中但不在查詢的WHERE子句中的列最好放在索引的包含列部分。 成爲覆蓋指標在級別4中,咱們表示與AdventureWorks數據庫的設計者達成協議,決定將SalesOrderID / SalesOrderDetailID做爲SalesOrderDetail表的彙集索引。針對此表的大多數查詢都將請求按銷售訂單編號排序或分組的數據。然而,可能來自倉庫人員的一些查詢將須要產品序列中的信息。這些查詢將受益於清單5.1所示的索引。 爲了說明在索引中包含列的潛在好處,咱們將查看兩個針對SalesOrderDetailtable的查詢,每一個查詢咱們將執行三次,以下所示: •運行1:沒有非彙集索引 •運行2:使用不包含列的非聚簇索引(只有兩個關鍵列) •運行3:使用清單5.1中定義的非彙集索引 正如咱們在前面的級別所作的那樣,咱們再次使用讀取次數做爲主要度量標準,可是咱們也使用SQL Server Management Studio的「顯示實際執行計劃」選項來查看每一個執行的計劃。這會給咱們一個額外的指標:在非讀取活動上花費的工做量的百分比,例如在將相關數據讀入內存以後進行匹配。這使咱們更好地瞭解查詢的總成本。 測試第一個查詢:產品的活動總數 清單5.2中顯示的第一個查詢是按特定產品的日期提供活動總計的查詢。

SELECT  ProductID ,

        ModifiedDate ,

        SUM(OrderQty) AS 'No of Items' ,

        AVG(UnitPrice) 'Avg Price' ,

        SUM(LineTotal) 'Total Value'

FROM    Sales.SalesOrderDetail

WHERE   ProductID = 888

GROUP BY ProductID ,

        ModifiedDate ;

清單5.2:「按產品的活動總計」查詢 因爲索引能夠影響查詢的性能,但不影響結果; 對這三個不一樣的索引方案執行這個查詢老是產生下面的行集合:

ProductID  ModifiedDate  No of Rows  Avg Price Total Value

----------- ------------  ----------- ----------------------------- 888     2003-07-01   16     602.346   9637.536000 888     2003-08-01   13     602.346    7830.498000 888     2003-09-01   19     602.346   11444.574000 888     2003-10-01   2      602.346   1204.692000 888     2003-11-01   17     602.346   10239.882000 888     2003-12-01   4      602.346   2409.384000 888     2004-05-01   10     602.346   6023.460000 888     2004-06-01   2      602.346   1204.692000

這八行輸出從表中的三十九個「ProductID = 888」行聚合而成,每一個日期有一個或多個「ProductID = 888」銷售的輸出行。進行測試的基本方案是 如代碼5.3所示。 在運行任何查詢以前,請確保您運行SET STATISTICS IO ON。

IF EXISTS ( SELECT  1

            FROM    sys.indexes

            WHERE   name = 'FK_ProductID_ModifiedDate'

                    AND OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') )

    DROP INDEX Sales.SalesOrderDetail.FK_ProductID_ModifiedDate ;

GO

--RUN 1:在這裏執行清單5.2(沒有非彙集索引)

CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate

ON Sales.SalesOrderDetail (ProductID, ModifiedDate) ;

 

--RUN 2:在這裏從新執行清單5.2(不包含非彙集索引)

IF EXISTS ( SELECT  1

            FROM    sys.indexes

            WHERE   name = 'FK_ProductID_ModifiedDate'

                    AND OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') )

    DROP INDEX Sales.SalesOrderDetail.FK_ProductID_ModifiedDate ;

GO

 

CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate

ON Sales.SalesOrderDetail (ProductID, ModifiedDate)

INCLUDE (OrderQty, UnitPrice, LineTotal) ;

--RUN 3:在這裏從新執行清單5.2(包含非聚簇索引)

清單5.3:測試「按產品的活動總計」查詢 表5.1顯示了對每一個索引方案執行查詢所需的相對工做量。

Run 1:

No Nonclustered Index

Table 'SalesOrderDetail'. Scan count 1, logical reads 1238.

Non read activity:  8%.

Run 2:

Index – No Included Columns

Table 'SalesOrderDetail'. Scan count 1, logical reads 131.

Non read activity:  0%.

Run 3:

With Included Columns

Table 'SalesOrderDetail'. Scan count 1, logical reads 3.

Non read activity:  1%.

表5.1:使用不一樣的非彙集索引可運行第一次查詢三次的結果 正如你能夠從這些結果看到的: •運行1須要完整掃描SalesOrderDetail表;每一行都必須閱讀和檢查,以肯定是否應該參與結果。 •運行2使用非聚簇索引爲39個請求的行快速查找書籤,但必須從表中單獨檢索每一個行。 •運行3在非彙集索引中找到所需的全部內容,並以最有利的順序 - 產品ID中的ModifiedDate。它迅速跳到第一個要求的條目,閱讀了39個連續的條目,對每一個條目進行了總計算,讀取完成。 測試第二個查詢:基於日期的活動總數 咱們的第二個查詢與第一個查詢是相同的,除了WHERE子句的更改。此次倉庫正在根據日期而不是產品請求信息。咱們必須過濾最右邊的搜索鍵列ModifiedDate;而不是最左邊的一列ProductID。新的查詢如清單5.4所示。

SELECT  ModifiedDate ,

        ProductID ,

        SUM(OrderQty) 'No of Items' ,

        AVG(UnitPrice) 'Avg Price' ,

        SUM(LineTotal) 'Total Value'

FROM    Sales.SalesOrderDetail

WHERE   ModifiedDate = '2003-10-01'

GROUP BY ModifiedDate ,

        ProductID ;

清單5.4:「按日期的活動總計」查詢 生成的行集部分是:

ProductID   ModifiedDate    No of Items Avg Price             Total Value ----------- ------------    ----------- --------------------- ----------------                                    :                                    : 782         2003-10-01      62          1430.9937             86291.624000 783         2003-10-01      72          1427.9937             100061.564000 784         2003-10-01      52          1376.994              71603.688000 792         2003-10-01      12          1466.01               17592.120000 793         2003-10-01      46          1466.01               67436.460000 794         2003-10-01      37          1466.01               54242.370000 795         2003-10-01      22          1466.01               32252.220000                                    :                                    : (164 row(s) affected)

WHERE子句將表格過濾爲1492個符合條件的行; 其中,分組時,產生了164行的輸出。 要運行測試,請按照代碼5.3中所述的相同方案,但使用代碼清單5.4中的新查詢。 結果是表5.2顯示了對每一個索引方案執行查詢所需的相對工做量。

Run 1:

No Nonclustered Index

Table 'SalesOrderDetail'. Scan count 1, logical reads 1238.

Non read activity:  10%.

Run 2:

With Index – No Included Columns

Table 'SalesOrderDetail'. Scan count 1, logical reads 1238.

Non read activity:  10%.

Run 3:

With Included Columns

Table 'SalesOrderDetail'. Scan count 1, logical reads 761.

Non read activity:  8%.

表2:使用可用的不一樣非聚簇索引三次運行第二個查詢的結果 第一次和第二次測試都是相同的計劃。對SaleOrderDetail表的完整掃描。因爲第4級中詳細說明的緣由,WHERE子句沒有足夠的選擇性從非覆蓋索引中受益。並且,包含任何一個組的行都散佈在整個表格中。正在讀表時,每一行都必須與其組相匹配。以及消耗處理器時間和內存的操做。 第三個測試發現了它在非彙集索引中須要的一切;但與前面的查詢不一樣,它沒有找到索引內連續的行。構成每一個單獨組的行在索引內是連續的;可是這些羣體自己分散在指數的長度上。所以,SQL Server掃描索引。 掃描索引而不是表格有兩個好處: •索引小於表,須要更少的讀取。 •行已經分組,須要較少的非閱讀活動。 結論 包含的列使非彙集索引可以覆蓋各類查詢的索引,從而提升這些查詢的性能;有時至關戲劇性。包含的列增長了索引的大小,但在開銷方面增長了不多的內容。任什麼時候候你建立一個非彙集索引,特別是在一個外鍵列上,問本身 - 「我應該在這個索引中包含哪些額外的列?」

相關文章
相關標籤/搜索