聚簇索引:SQL Server索引級別3

本文是「Stairway系列:SQL Server索引的階梯」的一部分數據庫

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

這個階段的前面的層次提供了通常索引和非彙集索引的概述。它如下面關於SQL Server索引的關鍵概念結束。當請求到達您的數據庫時,不管是SELECT語句仍是INSERT,UPDATE或DELETE語句,SQL Server都只有三種可能的方式來訪問語句中引用的表的數據:數據庫設計

只訪問非彙集索引並避免訪問表。這隻能在索引包含查詢請求的這個表的全部數據時纔有可能性能

使用搜索鍵訪問索引,而後使用選定的書籤訪問表中的單個行。ui

忽略索引並在表中搜索請求的行。spa

這個級別的重點是上面列表中的第三個選項。搜索桌子。這反過來又會引導咱們討論彙集索引。在第二級提到但沒有涉及的主題。設計

咱們將在此級別使用的主要AdventureWorks數據庫表是SalesOrderDetail表。在121,317行,它足以說明在表上有彙集索引的一些好處。並且,有兩個外鍵,足以說明一些關於聚簇索引的設計決策。對象

示例數據庫blog

儘管咱們已經討論過一級的樣本數據庫,可是這個時候仍是要重複的。 在整個這個階段,咱們將用例子來講明概念。 這些示例基於Microsoft AdventureWorks示例數據庫。 咱們專一於銷售訂單。 五張表將給咱們交易和非交易數據的良好組合; Customer,SalesPerson,Product,SalesOrderHeader和SalesOrderDetail。 爲了保持重點,咱們使用列的一個子集。 因爲AdventureWorks規範化很好,銷售人員信息被分解爲三個表:SalesPerson,Employee和Contact。排序

在整個階梯中,咱們使用如下兩個術語來交換訂單上的單行:「訂單項」和「訂單明細」。 前者是更常見的業務術語; 後者出如今AdventureWorks表的名字內。

圖1顯示了一整套表格及其之間的關係。

圖1:這個Stairway的例子中使用的表

注意:

在這個樓梯級別顯示的全部TSQL代碼能夠與文章一塊兒下載。

彙集索引

咱們首先提出如下問題:若是不使用非彙集索引,須要多少工做才能在表中找到一行?在表中搜索請求的行意味着掃描無序表中的每一行嗎?或者,SQL Server能夠永久性地對錶中的行進行排序,以便經過搜索關鍵字快速訪問它們,就像經過搜索關鍵字快速訪問非彙集索引的條目同樣?答案取決於您是否指示SQL Server在表上建立聚簇索引。

與非聚簇索引是一個獨立的對象並佔用他們本身的空間不一樣,聚簇索引和表是同樣的。經過建立彙集索引,能夠指示SQL Server將表中的行排序爲索引鍵序列,並在未來的數據修改期間維護該序列。即將到來的級別將查看生成的內部數據結構來完成此操做。但如今,把聚簇索引看做是一個有序表。給定一個行的索引鍵值,SQL Server能夠快速訪問該行;並能夠從該行按順序進行。

爲了演示目的,咱們建立了示例表SalesOrderDetail的兩個副本;一個沒有索引,一個有彙集索引。關於索引的關鍵字段,咱們作出與AdventureWorks數據庫的設計者作出相同的選擇:SalesOrderID / SalesOrderDetailID。清單1中的代碼建立了SalesOrderDetail表的副本。咱們能夠隨時從新運行這個代碼,咱們但願從一個「乾淨的石板」開始。

IF EXISTS (SELECT * FROM sys.tables 

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

DROP TABLE dbo.SalesOrderDetail_index;

GO

IF EXISTS (SELECT * FROM sys.tables 

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

DROP TABLE dbo.SalesOrderDetail_noindex;

GO

 

 

SELECT * INTO dbo.SalesOrderDetail_index FROM Sales.SalesOrderDetail;

SELECT * INTO dbo.SalesOrderDetail_noindex FROM Sales.SalesOrderDetail;

GO

 

 

CREATE CLUSTERED INDEX IX_SalesOrderDetail

ON dbo.SalesOrderDetail_index (SalesOrderID, SalesOrderDetailID)

GO

清單1:建立SalesOrderDetail表的副本

所以,在建立彙集索引以前,假設SalesOrderDetail表以下所示:

SalesOrderID SalesOrderDetailID ProductID   OrderQty UnitPrice
69389    102201       864      3     38.10
56658    59519       711      1     34.99
59044    70000       956      2     1430.442
48299    22652       853      4     44.994
50218    31427       854      8     44.994
53713    50716        711      1     34.99
50299    32777       739      1     744.2727
45321    6303        775      6     2024.994
72644    115325       873      1     2.29
48306    22705       824      4     141.615
69134    101554       876      1     120.00
48361    23556        760      3     469.794
53605    50098       888      1     602.346
48317     22901        722      1     183.9382
66430    93291        872      1      8.99
65281    90265       889      2     602.346
52248    43812       871      1     9.99
47978    20189       794      2     1308.9375

在建立如上所示的彙集索引以後,生成的表/彙集索引將以下所示:

SalesOrderID SalesOrderDetailID ProductID   OrderQty UnitPrice
43668     106        722      3    178.58
43668     107        708      1    20.19
43668     108        733      3     356.90
43668     109        763      3    419.46
43669     110        747      1    714.70
43670     111        710      1    5.70
43670     112        709      2    5.70
43670     113        773      2    2,039.99
43670     114        776      1    2,024.99
43671     115        753       1    2,146.96
43671     116        714      2    28.84
43671     117        756      1    874.79
43671     118        768      2    419.46
43671     119        732      2    356.90
43671     120        763      2    419.46
43671     121        755      2    874.79
43671     122        764      2    419.46
43671     123        716      1    28.84
43671     124        711      1    20.19
43671      125        708          1     20.19
43672     126        709      6     5.70
43672     127        776      2    2,024.99
43672     128        774      1    2,039.99
43673     129        754      1    874.79
43673     130        715       3    28.84
43673     131        729      1    183.94

當您查看上面顯示的示例數據時,您可能會注意到每一個SalesOrderDetailID值都是惟一的。不要混淆; SalesOrderDetailID不是表的主鍵。 SalesOrderID / SalesOrderDetailID的組合是表的主鍵;以及聚簇索引的索引鍵。

瞭解羣集索引的基礎知識

聚簇索引鍵能夠由您選擇的任何列組成;它沒必要以主鍵爲基礎。在咱們的例子中,最重要的是最左邊的一列是一個外鍵,即SalesOrderID值。所以,銷售訂單的全部行項目都會在SalesOrderDetail表中連續出現。

請記住如下有關SQL Server聚簇索引的附加要點:

因爲聚簇索引的條目是表的行,聚簇索引條目中沒有書籤值。當SQL Server已經在一行時,它不須要一條信息告訴它在哪裏找到那一行。

聚簇索引始終覆蓋查詢。因爲索引和表是同樣的,表的每一列都在索引中。

在表上建立聚簇索引不會影響在該表上建立非聚簇索引的選項。

選擇彙集索引鍵列

每一個表最多能夠有一個聚簇索引。表格的行只能是一個序列。你須要決定什麼樣的順序,若是有的話,對每一個表最好;並在可能的狀況下在表格填充數據以前建立彙集索引。在作出這個決定時,要記住排序不只意味着排序,並且意味着分組;如按銷售訂單對訂單項進行分組。

這就是爲何AdventureWorks數據庫的設計者選擇SalesOrderID內的SalesOrderDetailID做爲SalesOrderDetail表的順序的緣由;這是訂單項的天然順序。

例如,若是用戶請求訂單的訂單項,則一般會請求該訂單的全部訂單項。一個典型的銷售訂單表單告訴咱們,訂單的印刷版本老是包含全部的行項目。銷售訂單業務的性質是按銷售訂單對行項目進行分組。倉庫偶爾會要求按產品而不是銷售訂單查看訂單項,但大部分的要求;如銷售人員或客戶,打印發票的程序或計算每一個訂單總價值的查詢;將須要全部銷售訂單的全部行項目。

然而,用戶需求自己並不能決定什麼是最好的彙集索引。本系列的將來級別將覆蓋指標的內部;由於索引的某些內部方面也會影響你對聚簇索引列的選擇。

若是表中沒有彙集索引,則該表稱爲堆。每一個表都是一個堆或一個彙集索引。因此,雖然咱們常常說每個指標都屬於聚類或非聚類兩種類型之一,一樣重要的是要注意,每張桌子都屬於兩種類型之一;它是一個彙集索引或它是一堆。開發人員常常說,一個表「有」或「沒有」彙集索引,但更有意義的說,表「是」或「不是」彙集索引。

SQL Server在查找行(不包括使用非聚簇索引)時搜索堆只有一種方法,這是從表的第一行開始,而後繼續執行表,直到讀取全部行。沒有序列,沒有搜索鍵,也沒法快速導航到特定的行。

比較聚簇索引和堆

爲了評估聚簇索引與堆的性能,清單1建立了SalesOrderDetailtable的兩個副本。一個副本是堆版本,另外一個是建立原始表(SalesOrderID,SalesOrderDetailID)上的同一個彙集索引。這兩個表都沒有任何非彙集索引。

咱們將對每一個版本的表執行相同的三個查詢;一個檢索單個行,一個檢索單個訂單的全部行,一個檢索單個產品的全部行。咱們在下面的表格中給出了SQL和每一個執行的結果。

咱們的第一個查詢檢索單個行,執行細節顯示在表1中。

SQL

SELECT *
FROM SalesOrderDetail
WHERE SalesOrderID = 43671
AND SalesOrderDetailID = 120

Heap

(1 row(s) affected)
Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495.

Clustered Index

(1 row(s) affected)
Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 3.

Impact of having the Clustered Index

IO reduced from 1495 reads to 3 reads.

Comments

No surprise.  Table scanning 121,317 rows to find just one is not very efficient.

表1:檢索單行

咱們的第二個查詢檢索單個銷售訂單的全部行,您能夠在表2中看到執行的詳細信息。

SQL

SELECT *
FROM SalesOrderDetail
WHERE SalesOrderID = 43671

Heap

(11 row(s) affected)
Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495.

Clustered Index

(11 row(s) affected)
Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 3.

Impact of having the Clustered Index

IO reduced from 1495 reads to 3 reads.

Comments

Same statistics as the previous query.  The heap still required a table scan, while the clustered index grouped the 11 detail rows of the requested order sufficiently close together so that the IO required to retrieve 11 rows was the same as the IO required to retrieve one row.  An upcoming Level will explain in detail why no additional reads were required to retrieve the additional 10 rows.  

表2:檢索單個SalesOrder的全部行

咱們的第三個查詢檢索單個產品的全部行,執行結果如表3所示。

SQL

SELECT *
FROM SalesOrderDetail
WHERE ProductID = 755

Heap

(228 row(s) affected)
Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495.

Clustered Index

(228 row(s) affected)
Table 'SalesOrderDetail_index'. Scan count 1, logical reads 1513.

Impact of having the Clustered Index

IO slightly greater for the clustered index version; 1513 reads versus 1495 reads.

Comments

Without a nonclustered index on the ProductID column to help find the rows for a single Product, both versions had to be scanned.  Because of the overhead of having a clustered index, the clustered index version is the slightly larger table; therefore scanning it required a few more reads than scanning the heap.

表3:檢索單個產品的全部行

前兩個查詢大大受益於聚簇索引的存在;第三個是大體相等的。有時彙集索引是有害的嗎?答案是確定的,主要與插入,更新和刪除行有關。像在這些早期階段遇到的索引的不少其餘方面同樣,這也是一個更高級別更詳細的主題。

通常來講,檢索效益大於維護損害;使聚簇索引更適合堆。若是您要在Azure數據庫中建立表,則別無選擇。每一個表都必須是聚簇索引。

結論

彙集索引是一個有序表,其順序由您在建立索引時指定,並由SQL Server維護。根據其關鍵值,該表中的任何行均可以快速訪問。在索引鍵序列中,任何一組行均可以經過鍵的範圍快速訪問。

每一個表只能有一個聚簇索引。哪些列應該是聚簇索引鍵列的決定是您將爲任何表格作出的最重要的索引決定。

在咱們的四級中,咱們將重點從邏輯轉向物理,介紹頁面和範圍,並檢查索引的物理結構。

可下載的代碼

相關文章
相關標籤/搜索