深刻非彙集索引:SQL Server索引進階 Level 2

做者David Durant,2017/10/18(首次發佈於:2014/11/26)sql

關於系列

本文屬於進階系列:Stairway to SQL Server Indexes數據庫

索引是數據庫設計的基礎,並告訴開發人員使用數據庫關於設計者的意圖。 不幸的是,當性能問題出現時,索引每每被添加爲過後考慮。 這裏最後是一個簡單的系列文章,應該使他們快速地使任何數據庫專業人員「快速」數據庫設計

SQL Server索引階段1中的級別1一般引入了SQL Server索引,特別引入了非聚簇索引。做爲咱們的第一個案例研究,咱們演示了從表中檢索單個行時索引的潛在好處。在這個層面上,咱們繼續調查非集羣指標。在超出從表中檢索單個行的狀況下,檢查他們對良好查詢性能的貢獻。sqlserver

就像大多數這些層面的狀況同樣,咱們引入少許的理論,檢查一些索引內部的內容來幫助解釋理論,而後執行一些查詢。這些查詢是在沒有索引的狀況下執行的,而且打開了性能報告統計信息,以便查看索引的影響。性能

咱們將使用咱們在Level 1中使用的AdventureWorks數據庫中的表的子集,集中在整個級別的Contact表。咱們將只使用一個索引,即咱們在1級中使用的FullName索引來講明咱們的觀點。爲了確保咱們控制Contact表上的索引,咱們將在dbo模式中建立表的兩個副本,並僅在其中一個上建立FullName索引。這將給咱們咱們的受控環境:表的兩個副本:一個具備單個非彙集索引,另外一個沒有任何索引。測試

注意:
在這個樓梯級別顯示的全部TSQL代碼能夠在文章底部下載。設計

清單1中的代碼建立了Person.Contact表的副本,咱們能夠在咱們但願以「clean slate」開始的任什麼時候候從新運行這個批處理。指針

IF EXISTS (

    SELECT * 

        FROM sys.tables 

        WHERE OBJECT_ID = OBJECT_ID('dbo.Contacts_index'))
DROP TABLE dbo.Contacts_index;
GO
IF EXISTS (

    SELECT * 

        FROM sys.tables 

        WHERE OBJECT_ID = OBJECT_ID('dbo.Contacts_noindex'))

    DROP TABLE dbo.Contacts_noindex;
GO
SELECT * INTO dbo.Contacts_index 

    FROM Person.Contact;
SELECT * INTO dbo.Contacts_noindex 

    FROM Person.Contact;

清單2.1:製做Person.Contact表的副本code

顯示在這裏的一個聯繫人表格片斷:orm

ContactID   FirstName  MiddleName LastName  EmailAddress
                                    .
                                    .
1288        Laura      F          Norman    laura1@adventure-works.com
651         Michael               Patten    michael20@adventure-works.com
1652        Isabella   R          James     isabella6@adventure-works.com
1015        David      R          Campbell  david8@adventure-works.com
1379        Balagane              Swaminath balaganesan0@adventure-works.c
742         Steve                 Schmidt   steve3@adventure-works.com
1743        Shannon    C          Guo       shannon16@adventure-works.com
1106        John       Y          Chen      john2@adventure-works.com
1470        Blaine                Dockter   blaine1@adventure-works.com
833         Clarence   R.         Tatman    clarence0@adventure-works.com
1834        Heather    M          Wu        heather6@adventure-works.com
1197        Denise     H          Smith     denise0@adventure-works.com
560         Jennifer   J.         Maxham    jennifer1@adventure-works.com
1561        Ido                   Ben-Sacha ido1@adventure-works.com
924         Becky      R.         Waters    becky0@adventure-works.com

非彙集索引條目

如下語句在Contacts_index表上建立咱們的FullName非聚簇索引。

CREATE INDEX FullName

            ON Contacts_index

    ( LastName, FirstName );

清單2.2 - 建立一個非彙集索引

請記住,非聚簇索引按順序存儲索引鍵,以及用於訪問表中實際數據的書籤。 您能夠將書籤看做一種指針。 將來的層次將更詳細地描述書籤,其形式和使用。

這裏顯示FullName索引的片斷,包括姓氏和名字做爲鍵列,加上書籤:

:---      Search Key Columns   :         Bookmark

                                     .

Russell          Zachary                  =>  
Ruth             Andy                     =>  
Ruth             Andy                     =>  
Ryan             David                    =>  
Ryan             Justin                   =>  
Sabella          Deanna                   =>  
Sackstede        Lane                     =>  
Sackstede        Lane                     =>  
Saddow           Peter                    =>  
Sai              Cindy                    =>  
Sai              Kaitlin                  =>  
Sai              Manuel                   =>  
Salah            Tamer                    =>  
Salanki          Ajay                     =>  
Salavaria        Sharon                   =>

每一個條目都包含索引鍵列和書籤值。 另外,SQL Server非聚簇索引條目具備一些僅供內部使用的頭信息,可能包含一些可選的數據值。 這兩個都將在後面的層面進行討論。 在這個時候,對非基本指標的基本理解也不重要。

如今,咱們只須要知道鍵值就能使SQL Server找到合適的索引條目; 而且該條目的書籤值使SQL Server可以訪問表中相應的數據行。

索引條目的優勢是在順序

索引的條目按索引鍵值進行排序,因此SQL Server能夠在任一方向上快速遍歷條目。 順序條目的掃描能夠從索引的開始,索引的結尾或索引內的任何條目開始。

所以,若是一個請求要求全部以姓氏字母「S」開頭的聯繫人(WHERE LastName LIKE'S%'),SQL Server能夠快速導航到第一個「S」項(「Sabella,Deanna」), 而後遍歷索引,使用書籤訪問行,直到到達第一個「T」條目; 在這一點上它知道它已經檢索了全部的「S」條目。

若是全部選定的列都在索引中,上面的請求會更快地執行。 所以,若是咱們發出:

SELECT FirstName, LastName 

    FROM Contact 

    WHERE LastName LIKE 'S%';

SQL Server能夠快速導航到第一個「S」條目,而後遍歷索引條目,忽略書籤並直接從索引條目檢索數據值,直到達到第一個「T」條目。在關係數據庫術語中,索引已經「覆蓋」了查詢。

從序列數據中受益的任何SQL操做符均可以從索引中受益。這包括ORDER BY,GROUP BY,DISTINCT,UNION(不是UNION ALL)和JOIN ... ON。

例如,若是一個請求經過姓氏詢問聯繫人的數量,SQL Server能夠從第一個條目開始計數,而後沿索引繼續。每次更改姓氏的值時,SQL Server都會輸出當前計數並開始新的計數。與以前的請求同樣,這是一個覆蓋查詢; SQL Server只訪問索引,徹底忽略表。

請注意按鍵列從左到右的順序的重要性。若是一個請求詢問全部姓「Ashton」的人,咱們的索引是很是有用的,可是若是這個請求是針對全部名字是「Ashton」的人,那麼這個索引幾乎沒有任何幫助。

測試一些樣本查詢

若是要執行後續的測試查詢,請確保運行腳本以建立新的聯繫人表的兩個版本:dbo.Contacts_index和dbo.Contacts_noindex; 並運行該腳本以在dbo.Contacts_index上建立LastName,FirstName索引。

爲了驗證上一節中的斷言,咱們打開了在1級中使用的相同性能統計信息,並運行一些查詢; 有和沒有索引。

SET STATISTICS io ON

SET STATISTICS time ON

因爲AdventureWorks數據庫中的Contacts表中只有19972行,因此很難得到有意義的統計時間值。 咱們大多數的查詢會顯示一個CPU時間值爲0,因此咱們不顯示統計時間的輸出; 只從統計數據IO中反映出可能須要讀取的頁數。 這些值將容許咱們在相對意義上比較查詢,以肯定哪些查詢具備哪些索引比其餘索引執行得更好。 若是您想要更大的表進行更加實際的計時測試,則可使用本文提供的構建百萬行版本的Contact表的腳本。 接下來的全部討論都假設你使用的是標準的19972行表。

測試涵蓋的查詢

咱們的第一個查詢是一個將被索引覆蓋的查詢; 一個爲全部姓氏以「S」開頭的聯繫人檢索一組有限的列。 查詢執行信息如表2.1所示。

SQL SELECT FirstName, LastName FROM dbo.Contacts WHERE LastName LIKE 'S%'
沒有索引 (2130 row(s) affected) Table 'Contacts_noindex'. Scan count 1, logical reads 568.
有索引 (2130 row(s) affected) Table 'Contacts_index'. Scan count 1, logical reads 14.
索引衝突 IO reduced from 568 reads to 14 reads.
評論 涵蓋查詢的索引是一件好事。 若是沒有索引,則會掃描整個表以查找行。 「2130行」統計代表,「S」是姓氏的流行首字母,在全部聯繫人中佔百分之十。

表2.1:運行覆蓋查詢時的執行結果

測試一個不包含的查詢

接下來,咱們修改咱們的查詢以請求與以前相同的行,但包括不在索引中的列。 查詢執行信息見表2.2。

SQL SELECT * FROM dbo.Contacts WHERE LastName LIKE 'S%'
沒有索引 與之前的查詢相同。 (由於它是一個表掃描)。
有索引 (2130 row(s) affected) Table 'Contact_index'. Scan count 1, logical reads 568.
索引衝突 沒有衝突
評論 查詢執行期間從未使用索引!SQL Server決定從一個索引條目跳轉到表中對應的行2130次(每行一次)比掃描一百萬行的整個表來查找它所須要的2130行更多的工做。

表2.2:運行非覆蓋查詢時的執行結果

測試一個不包含但更有選擇性的查詢

這一次,咱們使咱們的查詢更具選擇性; 也就是說,咱們縮小了被請求的行數。 這增長了索引對該查詢有利的可能性。 查詢執行信息如表2.3所示。

SQL SELECT * FROM dbo.Contacts WHERE LastName LIKE 'Ste%'
沒有索引 與之前的查詢相同。 (由於它是一個表掃描)。
有索引 (107 row(s) affected) Table 'Contact_index'. Scan count 1, logical reads 111.
索引衝突 IO reduced from 568 reads to 111 reads.
評論 SQL Server訪問107「Ste%」條目,全部這些條目都位於索引內連續。而後使用每一個條目的書籤來檢索到對應的行。行不在表格內連續排列。該索引有利於此查詢;但並不像第一個查詢,「覆蓋」查詢那樣受益;特別是在檢索每一行所需的IO數量方面。您可能預期讀取107個索引條目加107行將須要107 + 107個讀取。爲何只有111個讀取須要將在較高的水平。目前,咱們會說只有極少的讀取被用來訪問索引條目;大部分用於訪問行。因爲前一個請求2130行的查詢沒有從索引中受益,而這個請求107行的查詢確實從索引中受益 - 你也許會想知道「轉折點在哪裏?」SQL Server決策背後的計算也將在將來的層面上進行討論。

表2.3:運行更具選擇性的非覆蓋查詢時的執行結果

測試涵蓋的聚合查詢

咱們最後一個示例查詢將是一個聚合查詢; 這是一個涉及計數,合計,平均等的查詢。 在這種狀況下,這是一個查詢,告訴咱們在聯繫人表中名稱重複的程度。

結果部分看起來像這樣:

Steel         Merrill             1
Steele        Joan                1
Steele        Laura               2
Steelman      Shanay              1
Steen         Heidi               2
Stefani       Stefano             1
Steiner       Alan                1

查詢執行信息見表2.4。

SQL SELECT LastName, FirstName, COUNT(*) as 'Contacts' FROM dbo.Contacts WHERE LastName LIKE 'Ste%' GROUP BY LastName, FirstName
沒有索引 與之前的查詢相同。 (由於它是一個表掃描)。
有索引 (104 row(s) affected) Table 'Contacts_index'. Scan count 1, logical reads 4.
索引衝突 IO reduced from 568 reads to 4 reads.
評論 查詢所需的全部信息都在索引中; 而且它在計算計數的理想順序中處於索引中。 全部的「姓氏以'Ste'開始」在索引內是連續的; 並在該組內,單個名字/姓氏值的全部條目將被組合在一塊兒。不須要訪問表格; 也不須要對中間結果進行排序。 一樣,涵蓋查詢的索引是一件好事。

表2.4:運行覆蓋聚合查詢時的執行結果

測試未覆蓋的聚合查詢

若是咱們改變查詢來包含不在索引中的列,咱們能夠獲得咱們在表2.5中看到的性能結果。

SQL SELECT LastName, FirstName, MiddleName, COUNT(*) as 'Contacts' FROM dbo.Contacts WHERE LastName LIKE 'Ste%' GROUP BY LastName, FirstName, MiddleName
沒有索引 與之前的查詢相同。(由於它是一個表掃描)。
有索引 (105 row(s) affected) Table 'ContactLarge'. Scan count 1, logical reads 111.
索引衝突 IO reduced from 568 reads to 111 reads; same as the previous non-covered query
評論 處理查詢時完成的中間工做並不老是出如今統計信息中。使用內存或tempdb排序和合並數據的技術就是這樣的例子。實際上,一個指數的好處可能會比統計數據顯示的好。

表2.5:運行非覆蓋聚合查詢時的執行結果

結論

咱們如今知道非彙集索引具備如下特徵。非彙集索引:

是一組有序的條目。
基礎表的每行有一個條目。
包含一個索引鍵和一個書籤。
由您建立。
由SQL Server維護。
由SQL Server使用來儘可能減小知足客戶端請求所需的工做量。
咱們已經看到了SQL Server能夠單獨知足索引請求的例子。有些則徹底忽略了指標。還有一些是使用索引和表的組合。爲此,咱們經過更新在第一級開始時的陳述來關閉第二級。

當請求到達您的數據庫時,SQL Server只有三種可能的方式來訪問該語句所請求的數據:

只訪問非彙集索引並避免訪問表。這隻能在索引包含查詢請求的全部數據的狀況下才有可能
使用索引鍵訪問非聚簇索引,而後使用選定的書籤訪問表的各個行。
忽略非聚簇索引並掃描表中的請求行。
通常來講,第一個是理想的;第二個比第三個好。在即將到來的級別中,咱們將展現如何提升索引覆蓋廣受歡迎的查詢的可能性,以及如何肯定您的非覆蓋查詢是否具備足夠的選擇性以從您的索引中受益。可是,這將須要比咱們還沒有提出的更詳細的索引內部結構信息。
在咱們達到這一點以前,咱們須要介紹另外一種SQL Server索引;彙集索引。這是3級的主題。

下載代碼

Resources:

Level 2 - NonClustered.sql | Level2_MillionRowContactTable.sql

相關文章
相關標籤/搜索