转:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek

0.参考文献

Table Scan, Index Scan, Index Seekhtml

SQL SERVER – Index Seek vs. Index Scan – Diffefence and Usage – A Simple Notesql

oracle表访问方式缓存

Index Seek和Index Scan的区别以及适用状况oracle

1.oracle中的表访问方式

在oracle中有表访问方式的说法,访问表中的数据主要经过三种方式进行访问:ide

  1. 全表扫描(full table scan),直接访问数据页,查找知足条件的数据
  2. 经过rowid扫描(table access by rowid),若是知道数据的rowid,那么直接经过rowid进行查找
  3. 索引扫描(index scan),若是一个表建立了索引,那么能够经过索引来找出咱们想要的数据在表中的存放位置,也就是rowid,经过返回rowid而后用rowid来进行访问具体数据。
  4. 而索引扫描中又可分为索引全扫描(index full scan)、索引范围扫描(index range scan)和索引惟一扫描(index unique scan)等。

2.sql server中clustered index scan,table scan,index scan

在sqlserver中也有相似的内容,这里就要将的是table scan,index scan以及index seek.sqlserver

  1. table scan is where the table is processed row by row from beginning to end.
  2. An index scan is where the index is processed row by row from beginning to end.
  3. If the index is a clustered index then an index scan is really a table scan.
  4. 总结:在sql server中,对表中数据从头至尾一行一行的进行出来就是表扫描。这里的处理咱们能够理解为sql中where子句的条件判断。咱们须要遍历表中的每一行,判断是否知足where条件。最简单的table scan是select * from table。
  5. 索引扫描就是对索引中的每一个节点从头至尾的访问。假设咱们的索引是B树结构的,那么index scan就是访问B树中的每个节点。
  6. 假如索引是汇集索引,那么B树索引的叶子节点保存的是数据页中的实际数据。假如索引是非汇集索引,那么B树叶子节点保存的是指向数据页的指针。

(ps:如下2.1-2.6于2012-9-4补充)性能

2.1实验数据准备

在介绍完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
--数据准备完毕--------------------------------
复制代码

2.2实验数听说明:

  1. dbo.SalesOrderHeader_test里存放的是每一张订单的头信息,包括订单建立日期、客户编号、合同编号、销售员编号等,每一个订单都有一个单独的订单号。在订单号这个字段上,有一个汇集索引
  2. dbo.SalesOrderDetail_test里存放的是订单的详细内容。一张订单能够销售多个产品给同一个客户,因此dbo.SalesOrderHeader_test和dbo.SalesOrderDetail_test是一对多的关系。每条详细内容包括它所属的订单编号,它本身在表格里的惟一编号(SalesOrderDetailID)、产品编号、单价,以及销售数量等。在这里,先只在SalesOrderID上创建一个非汇集索引。create index默认建立的就是非汇集索引。
  3. 按照AdventureWorks里原先的数据,dbo.SalesOrderHeader_test里有3万多条订单信息,dbo.SalesOrderDetail里有12万多条订单详细记录,基本上一条订单有3~5条详细记录。这是一个正常的分布。为了使数据分布不均匀,咱们再在dbo.SalesOrderHeader_test里加入9条订单记录,它们的编号是从75124到75132。这是9张特殊的订单,每张有12万多条详细记录。也就是说,dbo.SalesOrderDetail_test里会有90%的数据属于这9张订单。主要是使用“select 75123+@i...”来搜索出Sales.SalesOrderDetail中的全部记录插入到dbo.SalesOrderDetail。一共执行9次。

 2.3 table scan

sql server中表分为两种,一种是有汇集索引的汇集表,另一种是没有汇集索引的对表。在汇集表中数据按照汇集索引有序存放,而对表则是无序存放在hash中的。以dbo.SalesOrderDetail_test为例,它的上面没有汇集索引,只有一个在SalesOrderID上的非汇集索引。因此表格的每一行记录,不会按照任何顺序,而是随意地存放在Hash。此时咱们找全部单价大于200的销售详细记录,要运行以下语句:优化

View Code

因为表格在UnitPrice上没有索引,因此SQL Server不得不对这个表格从头至尾扫描一遍,把全部UnitPrice的值大于200的记录一个一个挑出来,其过程以下图所示ui

从执行计划里能够清楚地看出来SQL Server这里作了一个表扫描,以下图所示:

2.4 index scan 和 index seek

咱们在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

2.5 clustered index scan

若是这个表格上有汇集索引,事情会怎样呢?仍是以刚才那张表作例子,先给它在值是惟一的字段SalesOrderDetailID上创建一个汇集索引。这样全部的数据都会按照汇集索引的顺序存储。

View Code

惋惜的是,查询条件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查询:

View Code

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。

 2.6总结

总结一下,在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。

3.Index Scan, Index Seek的比较

        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原话:不要老是将索引的使用等同于良好的性能,或者将良好的性能等同于索引的高效使用。若是只要使用索引就能得到最佳性能,那查询优化器的工做就简单了。但事实上,不正确的索引选择并不能得到最佳性能。所以,查询优化器的任务是只在索引或索引组合能提升性能时才选择它,而在索引检索有碍性能时则避免使用它。

4.Sql server中的I/O

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

相关文章
相关标签/搜索