MSSQL数据库查询优化(一)

--优化学习(一)
 
我现有一表Orders,其中包含OrderId,UserId,CreateDate,TotalMoney,OrderType五个字段,
 
目前没有主键和其余索引
 
现在我想查询出在指定某个日期的订单数量,并返回OrderId,UserId,TotalMoney三字段,具体
 
查询语句以下:
 
select
 OrderId,
 UserId,
 TotalMoney
from
 Orders
where CreateDate='2012-12-17 14:59:53.463'
 
这个时候我执行如下查询语句,观察一下执行计划:
 
 图-1

此时执行计划显示表扫描.分析表的数据存储结构,该表无主键和索引,数据存放形式
 
为堆存储,在查询的时候会读取表的每一行,在CreateDate中评估此谓词,为真则返回
 
此行数据,输出列OrderId,UserId,TotalMoney
 
在这个查询中,扫描表操做涉及到该表的全部数据行,无论该行数据是否知足条件,因此
 
扫描的开销是与表的数据量成正比的.若是在表的数据量不多的时候或者知足where后
 
谓词的数据行比例较大时,这样的执行计划是有效的,可是若是表的数据量大,且符合条
 
件的数据比例很小,那么这样的扫描显然是会涉及到不少没必要要的数据页与行,同时形成
 
不少没必要要的I/O开销
 
这时,我首先对表Orders加上一个汇集索引
 
create clustered index Orders_OrderId_idxon Orders(OrderId)
go
 
如今表的数据存放是按照B-Tree结构存储,如今执行该查询语句,查看执行计划:
 

  图-2
这个时候观察执行计划,咱们能够清晰地发现以前执行计划中出现的表扫描消失,取而代之的是
 
汇集索引扫描,那么这二者之间有什么区别?

当表不存在汇集索引的时候,是堆表.当创建汇集索引后,表是以B-Tree形式存储
 
而我在添加主键的时候,主键字段默认汇集索引,这个时候会默认建立统计信息,系统表 sys.stats
 
中也会出现.这个时候优化器会选择汇集索引扫描,而不是以前的表扫描.但从I/O开销上来讲,并无
 
带来优化.这是因为聚焦索引和数据存储在一块儿, 决定表数据的物理存储顺序, 一个表只能有一个聚
 
集索引, 其叶子结点是数据行,汇集索引扫描的是索引页,而索引页存放整个表(整个表的一个副本),
 
经过汇集索引找到一条记录的时候, 这条记录相关的列的值也能够直接取出来,而表扫描则是扫描RID,
 
也是同数据存放在一块儿的,扫描到每一行的时候,也能够直接取出该行数据的值,二者在开销上并无太
 
大的区别
 
如今我在字段CreateDate字段上建立一个非汇集索引:
 
create nonclustered index Orders_CreateDate_idxon Orders(CreateDate)
go
 
这个时候的执行计划如图
 

  图-3

从该执行计划中发现,以前的汇集索引扫描已经消失,取而代之的是费汇集索引查找和检查找两部分,而且
 
分别占到总开销的%左右
 
以下图:
 

  图-4



  图-5

那么不少人可能在这个时候误认为这已是最优的执行计划了,但其实否则。咱们分析能够看出,图-4

和图-5咱们能够看出,索引查找这一部分只输出了OrderId这一个字段,而检查找是用与输出UserId和
 
TotalMoney两个字段。形成这个执行计划出现的缘由是:
 
非汇集索引单独存储,若是查询的结果引用了非聚焦索引不包括的那些列,那么非聚焦索引还须要经过行
 
定位器去表中取该记录对应的列的数据, 这里面就有一个再次查找的问题。而这个时候咱们的查询语句
 
返回了三个字段,第一个为汇集索引所在的字段,第二个和第三个字段上没有任何索引。那么为何返回
 
的汇集索引字段没有出现二次查找的状况呢?这是因为在OrderId字段简历汇集索引后,在这个表建立的
 
其它全部的非汇集索引都已经覆盖了汇集索引字段,具体原则以下图:
 

 图-6

这个时候为了返回两个非索引覆盖字段,优化器就会经过二次查找返回这两个字段,而二次查找则根据
 
以前创建的汇集索引来实现,因而出现了键查找这一迭代器。

此时,咱们若是让索引覆盖这两个字段就能够避免出现键查找的出现。

drop index Orders_CreateDate_idx onOrders
create nonclustered index Orders_CreateDate_idxon Orders(CreateDate) include(UserId,TotalMoney)
go
 
这个时候咱们再来查看执行计划:
 

  图-7

如今以前出现的部分已经被索引查找给取代,键查找部分的开销也已经被优化掉:
 
 图-8

在这个例子中,若是咱们不建立汇集索引,那么又是会什么样呢?

我删掉以前的索引,而后从新建立一个只包含CreateDate字段的非汇集索引
 
drop index Orders_CreateDate_idx onOrders
drop index Orders_OrderId_idx onOrders
create nonclustered index Orders_CreateDate_idxon Orders(CreateDate)
go
 
接下来看执行计划:
 
 图-9

咱们比较以前的图-3能够发现,二者的区别在于如今是RID查找,以前是键查找。其实这样的
 
差异的出现是因为如今的表是堆表,二次查找须要经过行定位符去查找索引不包含的那两列,
 
而以前存在汇集索引,优化器选择了汇集索引键查找返回了那两列数据。

这个时候咱们也只需修改非汇集索引,让它包含这两个字段便可达到目的。

drop index Orders_CreateDate_idx onOrders
create nonclustered index Orders_CreateDate_idxon Orders(CreateDate) include(OrderId,UserId,TotalMoney)
go
 
执行计划如图:
 
 图-10

这里你们须要认真观察我每一个索引的建立,特别注意存在汇集索引和不存在汇集索引的时候。

不论是RID SEEK仍是CLUSTERED SEEK,通常状况下咱们均可以选择让索引包含这些返回字段的
 
方法优化他们,从而避免二次查找的出现,力求I/O开销达到最优。html

文章来源:MSSQL数据库查询优化(一)数据库

相关文章
相关标签/搜索