转:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek
0.参考文献
Table Scan, Index Scan, Index Seek
SQL SERVER – Index Seek vs. Index Scan – Diffefence and Usage – A Simple Note
Index Seek和Index Scan的区别以及适用情况
1.oracle中的表访问方式
在oracle中有表访问方式的说法,访问表中的数据主要通过三种方式进行访问:
- 全表扫描(full table scan),直接访问数据页,查找满足条件的数据
- 通过rowid扫描(table access by rowid),如果知道数据的rowid,那么直接通过rowid进行查找
- 索引扫描(index scan),如果一个表创建了索引,那么可以通过索引来找出我们想要的数据在表中的存放位置,也就是rowid,通过返回rowid然后用rowid来进行访问具体数据。
- 而索引扫描中又可分为索引全扫描(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.
- A table scan is where the table is processed row by row from beginning to end.
- An index scan is where the index is processed row by row from beginning to end.
- If the index is a clustered index then an index scan is really a table scan.
- 总结:在sql server中,对表中数据从头到尾一行一行的进行出来就是表扫描。这里的处理我们可以理解为sql中where子句的条件判断。我们需要遍历表中的每一行,判断是否满足where条件。最简单的table scan是select * from table。
- 索引扫描就是对索引中的每个节点从头到尾的访问。假设我们的索引是B树结构的,那么index scan就是访问B树中的每一个节点。
- 假如索引是聚集索引,那么B树索引的叶子节点保存的是数据页中的实际数据。假如索引是非聚集索引,那么B树叶子节点保存的是指向数据页的指针。
(ps:以下2.1-2.6于2012-9-4补充)
2.1实验数据准备
在介绍完clustered index scan,table scan和index scan以后,我们将通过实验来表述会在什么情况下使用这些表扫描方式。我们将使用AdventureWorks2008R2这个sample database进行实验,首先准备实验数据,TSQL如下所示:
![](https://common.cnblogs.com/images/copycode.gif)
--准备测试数据--------------------------------------------------
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
--数据准备完毕--------------------------------
![](https://common.cnblogs.com/images/copycode.gif)
2.2实验数据说明:
- dbo.SalesOrderHeader_test里存放的是每一张订单的头信息,包括订单创建日期、客户编号、合同编号、销售员编号等,每个订单都有一个单独的订单号。在订单号这个字段上,有一个聚集索引。
- dbo.SalesOrderDetail_test里存放的是订单的详细内容。一张订单可以销售多个产品给同一个客户,所以dbo.SalesOrderHeader_test和dbo.SalesOrderDetail_test是一对多的关系。每条详细内容包括它所属的订单编号,它自己在表格里的唯一编号(SalesOrderDetailID)、产品编号、单价,以及销售数量等。在这里,先只在SalesOrderID上建立一个非聚集索引。create index默认创建的就是非聚集索引。
- 按照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的销售详细记录,要运行如下语句:
由于表格在UnitPrice上没有索引,所以SQL Server不得不对这个表格从头到尾扫描一遍,把所有UnitPrice的值大于200的记录一个一个挑出来,其过程如下图所示。
从执行计划里可以清楚地看出来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上建立一个聚集索引。这样所有的数据都会按照聚集索引的顺序存储。
可惜的是,查询条件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。
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
转:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek的更多相关文章
- Sql Server中的表访问方式Table Scan, Index Scan, Index Seek
1.oracle中的表访问方式 在oracle中有表访问方式的说法,访问表中的数据主要通过三种方式进行访问: 全表扫描(full table scan),直接访问数据页,查找满足条件的数据 通过row ...
- 在SQL SERVER中获取表中的第二条数据
在SQL SERVER中获取表中的第二条数据, 思路:先根据时间逆排序取出前2条数据作为一个临时表,再按顺时排序在临时表中取出第一条数据 sql语句如下: select top 1 * from(se ...
- 显示 Sql Server 中所有表或表中行的信息
在MSSQL中显示某个数据库中所有表或视图的信息: (以下语句为获取所有表信息,将绿色字"U"替换为"V"则获取所有视图信息.) SELECT sysobjec ...
- 快速查看SQL Server 中各表的数据量以及占用空间大小
快速查看SQL Server 中各表的数据量以及占用空间大小. CREATE TABLE #T (NAME nvarchar(100),ROWS char(20),reserved varchar(1 ...
- Sql Server中判断表、列不存在则创建的方法[转]
一.Sql Server中如何判断表中某列是否存在 首先跟大家分享Sql Server中判断表中某列是否存在的两个方法,方法示例如下: 比如说要判断表A中的字段C是否存在两个方法: 第一种方法 ? ...
- SQL Server中多表连接时驱动顺序对性能的影响
本文出处:http://www.cnblogs.com/wy123/p/7106861.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错 ...
- MS SQL Server中数据表、视图、函数/方法、存储过程是否存在判断及创建
前言 在操作数据库的时候经常会用到判断数据表.视图.函数/方法.存储过程是否存在,若存在,则需要删除后再重新创建.以下是MS SQL Server中的示例代码. 数据表(Table) 创建数据表的时候 ...
- (转)SQLServer_十步优化SQL Server中的数据访问 三
原文地址:http://tech.it168.com/a2009/1125/814/000000814758_all.shtml 第六步:应用高级索引 实施计算列并在这些列上创建索引 你可能曾经写过从 ...
- 多个程序对sql server中的表进行查询和插入操作导致死锁
最近在做一个项目,是要用多个程序对sql server中的相同的数据库进行操作(查询和插入),所以在开始的时候常会出现死锁问题,后来在网上进行了咨询,发现了一些解决方法,留作大家参考: 并发去操纵一张 ...
随机推荐
- Tomcat学习总结(3)——Tomcat优化详细教程
Tomcat是我们经常使用的 servlet容器之一,甚至很多线上产品都使用 Tomcat充当服务器.而且优化后的Tomcat性能提升显著,本文从以下几方面进行分析优化. 一.内存优化 默认情况下To ...
- MySQL问答整理
1.Mysql中有哪些不同的表格? MyISAM: 基于IASM代码.可以被压缩,支持全文搜索,事务不安全,而且也不支持外键.如果事务回滚将会造成不完全回滚,从而不具备原子性.所以假如忽略事务以及访问 ...
- JavaEE----登陆界面验证码实现
主要使用后端验证,调用awt API ,会简单调用即可,绘图代码已封装到LoginVerifyUtils中. 界面展示: LoginVerifyUtils全部代码 import java.awt.Co ...
- [转载] npm 一些操作
npm i module_name -S = > npm install module_name --save 写入到 dependencies 对象 npm i module_name -D ...
- 使用authentication进行身份验证,与Forms表单登陆
做到登录时,不像在用自己的逻辑去判断用户是否登陆,就上网搜查,得知还有此方法,这个方法用起来很简单实用,第一次使用,还有很多不理解的地方,记下来方便以后查阅更改. 使用这个方法当然需要了解里面的属性和 ...
- 卡片游戏(hdu4550)贪心
卡片游戏 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others) Total Submi ...
- 多线程系列(2)线程池ThreadPool
上一篇文章我们总结了多线程最基础的知识点Thread,我们知道了如何开启一个新的异步线程去做一些事情.可是当我们要开启很多线程的时候,如果仍然使用Thread我们需要去管理每一个线程的启动,挂起和终止 ...
- Mycat入门配置_读写分离配置
1.Mycat的分片 两台数据库服务器: 192.168.80.11 192.168.80.4 操作系统版本环境:centos6.5 数据库版本:5.6 mycat版本:1.4 release 数据库 ...
- CPA理论与Base理论
CPA理论: 由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的CAP原理包含如下三个元素. C:Consistency,一致性.在分布式系统中的所有数据 备份, ...
- JavaScript中何时使用===,何时使用==
建议尽量都使用===,原因如下: 一致性:使用==对一致性没有任何好处,那么为什么不避免使用呢. 简单和性能:一般来说,===是最简单的操作符,因为它不用进行类型转换.JavaScript引擎的性能参 ...