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