Table Scan, Index Scan, Index Seekhtml
SQL SERVER – Index Seek vs. Index Scan – Diffefence and Usage – A Simple Notesql
Index Seek和Index Scan的區別以及適用狀況oracle
在oracle中有表訪問方式的說法,訪問表中的數據主要經過三種方式進行訪問:ide
在sqlserver中也有相似的內容,這裏就要將的是table scan,index scan以及index seek.sqlserver
(ps:如下2.1-2.6於2012-9-4補充)性能
在介紹完clustered index scan,table scan和index scan之後,咱們將經過實驗來表述會在什麼狀況下使用這些表掃描方式。咱們將使用AdventureWorks2008R2這個sample database進行實驗,首先準備實驗數據,TSQL以下所示:測試
--準備測試數據-------------------------------------------------- use adventureworks2008R2 go --若是表已存在,刪除 drop table dbo.SalesOrderHeader_test go drop table dbo.SalesOrderDetail_test go --建立表 select * into dbo.SalesOrderHeader_test from Sales.SalesOrderHeader go select * into dbo.SalesOrderDetail_test from Sales.SalesOrderDetail go --建立索引 create clustered index SalesOrderHeader_test_CL on dbo.SalesOrderHeader_test (SalesOrderID) go create index SalesOrderDetail_test_NCL on dbo.SalesOrderDetail_test (SalesOrderID) go --select * from dbo.SalesOrderDetail_test --select * from dbo.SalesOrderHeader_test declare @i int set @i = 1 while @i<=9 begin insert into dbo.SalesOrderHeader_test (RevisionNumber, OrderDate, DueDate, ShipDate,Status, OnlineOrderFlag, SalesOrderNumber,PurchaseOrderNumber, AccountNumber, CustomerID, SalesPersonID, TerritoryID, BillToAddressID, ShipToAddressID, ShipMethodID, CreditCardID, CreditCardApprovalCode, CurrencyRateID, SubTotal,TaxAmt, Freight,TotalDue, Comment,rowguid,ModifiedDate) select RevisionNumber, OrderDate, DueDate, ShipDate,Status, OnlineOrderFlag, SalesOrderNumber,PurchaseOrderNumber, AccountNumber, CustomerID,SalesPersonID, TerritoryID, BillToAddressID, ShipToAddressID, ShipMethodID, CreditCardID, CreditCardApprovalCode, CurrencyRateID, SubTotal,TaxAmt, Freight,TotalDue, Comment,rowguid,ModifiedDate from dbo.SalesOrderHeader_test where SalesOrderID = 75123 insert into dbo.SalesOrderDetail_test (SalesOrderID, CarrierTrackingNumber, OrderQty, ProductID, SpecialOfferID,UnitPrice,UnitPriceDiscount,LineTotal, rowguid,ModifiedDate) select 75123+@i, CarrierTrackingNumber, OrderQty, ProductID, SpecialOfferID,UnitPrice,UnitPriceDiscount,LineTotal, rowguid, getdate() from Sales.SalesOrderDetail set @i = @i +1 end go --數據準備完畢--------------------------------
sql server中表分爲兩種,一種是有彙集索引的彙集表,另一種是沒有彙集索引的對錶。在彙集表中數據按照彙集索引有序存放,而對錶則是無序存放在hash中的。以dbo.SalesOrderDetail_test爲例,它的上面沒有彙集索引,只有一個在SalesOrderID上的非彙集索引。因此表格的每一行記錄,不會按照任何順序,而是隨意地存放在Hash裏。此時咱們找全部單價大於200的銷售詳細記錄,要運行以下語句:優化
因爲表格在UnitPrice上沒有索引,因此SQL Server不得不對這個表格從頭至尾掃描一遍,把全部UnitPrice的值大於200的記錄一個一個挑出來,其過程以下圖所示。ui
從執行計劃裏能夠清楚地看出來SQL Server這裏作了一個表掃描,以下圖所示:
咱們在SalesOrderID上建立了非彙集索引,加入查詢條件是SalesOrderID,而且只SalesOrderID這一列的話,那麼會以什麼查詢方式執行呢?首先咱們查詢SalesOrderID<43664的記錄,執行以下TSQL語句:
select SalesOrderID from SalesOrderDetail_test where SalesOrderID< 43664
其執行計劃以下圖所示,咱們發現執行的是index seek
假如咱們要查詢全部SalesOrderID記錄而且不加where條件,
select SalesOrderID from SalesOrderDetail_test
那麼查詢計劃以下圖所示,咱們發現執行的是index scan。
那麼假如咱們要求查詢全部SalesOrderID<80000的記錄呢,是按照什麼方式查詢的。在執行查詢以前晴空執行計劃緩存
DBCC DROPCLEANBUFFERS--清空執行計劃緩存 DBCC FREEPROCCACHE--清空數據緩存 select SalesOrderID from SalesOrderDetail_test where SalesOrderID< 80000
其查詢計劃以下圖所示,咱們發現使用的是index seek
若是這個表格上有彙集索引,事情會怎樣呢?仍是以剛纔那張表作例子,先給它在值是惟一的字段SalesOrderDetailID上創建一個彙集索引。這樣全部的數據都會按照彙集索引的順序存儲。
惋惜的是,查詢條件UnitPrice上沒有索引,因此SQL Server仍是要把全部記錄都掃描一遍。和剛纔有區別的是,執行計劃裏的表掃描變成了彙集索引掃描(clustered index scan)。以下圖所示:
由於在有彙集索引的表格上,數據是直接存放在索引的最底層的,因此要掃描整個表格裏的數據,就要把整個彙集索引掃描一遍。在這裏,彙集索引掃描就至關於一個表掃描。所要用的時間和資源與表掃描沒有什麼差異。並非說這裏有了「Index」這個字樣,就說明執行計劃比表掃描的有多大進步。固然反過來說,若是看到「Table Scan」的字樣,就說明這個表格上沒有彙集索引。
如今在UnitPrice上面建一個非彙集索引,看看狀況會有什麼變化。
--在UnitPrice上建立非彙集索引 create index SalesOrderDetail_test_NCL_Price on dbo.SalesOrderDetail_test (UnitPrice) go
在非彙集索引裏,會爲每條記錄存儲一份非彙集索引索引鍵的值和一份彙集索引索引鍵的值(在沒有彙集索引的表格裏,是RID值)。因此在這裏,每條記錄都會有一份UnitPrice和SalesOrderDetailID記錄,按照UnitPrice的順序存放。
再跑剛纔那個查詢,
select SalesOrderDetailID, UnitPrice from dbo.SalesOrderDetail_test where UnitPrice > 200
你會看到此次SQL Server不用掃描整個表了,以下圖所示。此次查詢將根據索引直接找到UnitPrice > 200的記錄。
根據新建的索引,它直接找到了符合記錄的值,查詢計劃以下圖所示。咱們能夠看到是直接在nonclustered index上進行index seek操做。
可是光用創建在UnitPrice上的索引不能告訴咱們其餘字段的值。若是在剛纔那個查詢裏再增長几個字段返回,以下TSQL查詢:
SQL Server就要先在非彙集索引上找到全部UnitPrice大於200的記錄,而後再根據SalesOrderDetailID的值找到存儲在彙集索引上的詳細數據。這個過程能夠稱爲「Bookmark Lookup」,以下圖所示。
在SQL Server 2005之後,Bookmark Lookup的動做用一個嵌套循環來完成。因此在執行計劃裏,能夠看到SQL Server先seek了非彙集索引SalesOrderDetail_test_NCL_Price,而後用Clustered Index Seek把須要的行找出來。這裏的嵌套循環其實就是Bookmark Lookup,以下圖所示:
上述Key Lookup就是Bookmark Lookup中的一種,這是由於咱們的表中建有彙集索引,若是咱們沒有彙集索引,那麼這裏就是RID Lookup,以下圖所示:
上述key lookup其所消耗的時間以下所示:
SQL Server Execution Times: CPU time = 2995 ms, elapsed time = 10694 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
在上述查詢中,之因此要使用with (index (SalesOrderDetail_test_NCL_Price))這個語句,是爲了強制其使用SalesOrderDetail_test_NCL_Price這個非彙集索引,經過非彙集索引找到了彙集索引鍵值之後再去彙集索引中查詢。若是不使用的話,sql server有可能會使用clustered index scan,也可能使用bookmark lookup,這取決於查詢返回的數據量。
(1)好比仍是查詢UnitPrice > 200的結果:
select SalesOrderID,SalesOrderDetailID,UnitPrice from dbo.SalesOrderDetail_test where UnitPrice > 200
其查詢計劃以下,咱們能夠發現使用的是clustered index scan,返回的記錄數有481590條,很是大。
更重要的是其cpu time,以下所示:
SQL Server Execution Times: CPU time = 515 ms, elapsed time = 10063 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
咱們發現cpu time只有515ms,比咱們以前看到的2995ms要小。這就代表:index seek 並不必定就比index scan要好。sql server會根據統計信息選擇更有的方式執行操做。
(2)假如查詢UnitPrice <2的結果:
select SalesOrderID,SalesOrderDetailID,UnitPrice from dbo.SalesOrderDetail_test where UnitPrice < 2
咱們發現查詢計劃就再也不使用cluster index scan了,而是使用了index seek+clustered index seek,以下圖所示,返回記錄數只有1630條。相對來講記錄數目比較小,因此不須要clustered index scan。
總結一下,在SQL Server里根據數據找尋目標的不一樣和方法不一樣,有下面幾種狀況。
結 構 |
Scan |
Seek |
堆(沒有彙集索引的表格數據頁) |
Table Scan |
無 |
彙集索引 |
Clustered Index Scan |
Clustered Index Seek |
非彙集索引 |
Index Scan |
Index Seek |
若是在執行計劃裏看到這些動做,就應該可以知道SQL Server正在對哪一種對象在作什麼樣的操做。table scan(表掃描)代表正在處理的表格沒有彙集索引,SQL Server正在掃描整張表。clustered index scan(彙集索引掃描)代表SQL Server正在掃描一張有彙集索引的表格,可是也是整表掃描。Index Scan代表SQL Server正在掃描一個非彙集索引。因爲非彙集索引上通常只會有一小部分字段,因此這裏雖然也是掃描,可是代價會比整表掃描要小不少。Clustered Index Seek和Index Seek說明SQL Server正在利用索引結果檢索目標數據。若是結果集只佔表格總數據量的一小部分,Seek會比Scan便宜不少,索引就起到了提升性能的做用。若是查詢結果集不少,那麼可能會更傾向使用table scan。
Index Seek就是SQL在查詢的時候利用創建的索引進行掃描,先掃描索引節點,即遍歷索引樹。在查找到索引的葉子節點後,若是是聚簇索引就直接取葉子節點值的值,若是是非聚簇索引,則根據葉子節點中的rowid去查找相應的行(彙集索引的葉子節點是數據頁,而非彙集索引的葉子節點是指向數據頁的索引頁,也就是數據頁的rowid,這是在表沒有彙集索引的狀況下發生的;若是表自己含有彙集索引,那麼非彙集索引的葉子結點中保存的是非彙集索引鍵值和彙集索引鍵值,在獲得彙集索引鍵值之後會再去彙集索引中查找。)。而對於Index Scan是從頭到位遍歷整個索引頁中的全部行,從頭至尾,所以在數據量很大時效率並非很高,在彙集索引的狀況下,clustered index scan就是table scan。
SQL有一個查詢優化分析器 Query Optimizer,其在執行查詢以前首先會進行分析,當查詢中有能夠利用的索引時,那麼就優先分析使用Index Seek進行查詢的效率,假如得出使用Index Seek的查詢效率並很差,那麼就使用Index Scan進行查詢。那到底是在什麼狀況下會形成Index Seek效率比Index Scan還低呢?能夠分一下集中狀況:
1.在要查詢的表中數據並非不少的狀況下,使用Index Seek效率不必定高,由於使用Index seek還要先從索引樹開始,而後再利用葉子節點去查找相應的行。在行數比較少的狀況下,尚未直接進行Index scan快。所以,表中存儲的數據不能太少。
2.在返回的數據量很大的狀況下,好比返回的數據量佔總數據量的50%或者超過50%,使用Index Seek效率不必定好,在返回的數據量佔10%-15%時,利用Index Seek能得到最佳的性能。所以假如要使用index seek,返回的數據量既不能太多,也不能太少。
3.在創建索引的列的取值不少是一致的狀況下,創建索引不必定能得到很好的效率。好比不建議在「性別」列上創建索引。其實理由很簡單,當創建索引的列取值的變化少的狀況下,創建的索引二叉樹應該是矮胖型的,樹層次不高,不少行的信息都包含在葉子上,這樣的查詢顯然是不能很好的利用到索引
MSDN原話:不要老是將索引的使用等同於良好的性能,或者將良好的性能等同於索引的高效使用。若是隻要使用索引就能得到最佳性能,那查詢優化器的工做就簡單了。但事實上,不正確的索引選擇並不能得到最佳性能。所以,查詢優化器的任務是隻在索引或索引組合能提升性能時才選擇它,而在索引檢索有礙性能時則避免使用它。
The I/O from an instance of SQL Server is divided into logical and physical I/O. A logical read occurs every time the database engine requests a page from the buffer cache. If the page is not currently in the buffer cache, a physical read is then performed to read the page into the buffer cache. If the page is currently in the cache, no physical read is generated; the buffer cache simply uses the page already in memory.
在sqlserver中I/O能夠分爲邏輯IO和物理IO,從緩存(buffer cache)中讀取一個頁(page)是邏輯讀,若是數據頁不在當前的緩存中,那麼必須從磁盤上讀取數據頁到緩存中,這樣算是物理讀。
轉:http://www.cnblogs.com/xwdreamer/archive/2012/07/06/2579504.html