您的位置:首页 > 其它

查询优化器内核剖析第九篇:执行引擎之数据访问操作---Bookmark Lookup

2013-02-28 17:35 232 查看
这里有一个问题:非聚集索引在快速查找数据的时候非常有用,如果查询的某些字段不在索引中,此时会发生什么?或者换句话说,如果非聚集索引中没有包含查询中所有的字段,此时如何处理?

此时,查询优化器就决定哪一种方式比较高效:
“结合非聚集索引来快速的查询底层的表”还是“直接去底层的表扫描数据”。
为了说明问题,我们通过一个例子来说明。看到下面的查询:






20120817092443.png(14.89
K)

8/17/2012 9:28:41 AM

其中,AddressID和StateProvinceID包含在非聚集索引中,如果我们此时要查询另外两个不在索引中的字段:City和ModifiedDate,此时查询计划如下所示:






20120817092522.png(19.08
K)

8/17/2012 9:28:41 AM

在上面的例子中,查询优化器选择使用IX_Address_StateProvinceID这个非聚集索引快速的去底层的数据表中获取额外的数据列。另外索引没有包含查询中的所有数据,而要去底层的数据表中获取数据,这个操作就被称之为“Bookmark
lookup(书签查找)”。

我们下面来看另外一个查询,如下:






20120817092546.png(16.52
K)

8/17/2012 9:28:41 AM

此时,执行计划如下:






20120817092612.png(16.55
K)

8/17/2012 9:28:41 AM

这个时候,我们就纳闷了:查询优化器选择了Clustered Index Scan,为什么没有使用Bookmark
Lookup。我们两次执行的查询基本上是同一个,只是StateProvinceID的参数值不一样。其实在这里,查询优化器会根据提供的参数值去估算谓词的基数,从而选择更加高效的执行计划。其实这里也说明了一个问题:对于同一个查询,不同的参数值,产生的执行计划不一样,换句话说,通过参数值A产生的执行计划,可能对于参数B不是最优的,这就是“参数嗅探”的问题,我们后面会讲述的。

此时,查询优化器根据通过使用统计数据,为StateProvinceID值20进行估算,发现返回的数据有很多,并且通过成本的比较,还发现使用Table
Scan的效率比进行多次的Bookmark lookup更好。

这时,大家可能有一个疑问了:查询优化器决定把Bookmark
Lookup
操作转为Scan操作的一个临界点是什么

Ok,因为一个Bookmark Lookup操作需要进行随机的I/O,此时这个操作还是有点昂贵的,因为,如果某个查询中,这样的操作很多的话,查询优化器认为还不如进行Scan(Clustered
Index Scan或者Table Scan)。对于前面的例子,当StateProvinceID值为32的时候,查询优化器通过估算发现,结果只是返回一条数据,此时进行Bookmark
Lookup是可以的;但是当值为20的时候,返回300多条数据,此时的成本还不如整个扫描来的快。

朋友们可能又要问了:我们可以测试从返回的数据条1到300,来看看,到底是数据返回多少条的时候发生了两个操作的转换,来找到临界点

其实,这两个操作的转换不是看数据到底返回了多少条,而是基于成本进行估算的。其实,我们完全可以查看IX_Address_StateProvinceID的统计数据来分析(我们后文会详细讲述统计数据的分析)。其实,通过我不断的测试发现:当数据条数返回为62条的时候,就发生了两个操作的转换。下面,我们就来做个实验:将StateProvinceID的值分别给163和71来测试一下。

当值为163的时候,执行计划如下:






20120817092639.png(27.03
K)

8/17/2012 9:28:41 AM

当值为71的时候,执行计划如下:






20120817092714.png(30.65
K)

8/17/2012 9:28:41 AM

通过查看执行计划,我们就看到:当查询优化器估算如果返回结果为62条数据的时候,使用了Bookmark操作。当估算的结果为106条的时候,就选用了Clustered
Index Scan操作(因为不存在一个参数值的返回的结果在62与106之间,也就说不存在一个StateProvinceID的值,返回的结果数据条数落在62至106区间之间)。

最后,我们稍微的看一下堆表。因为非聚集索引可以在堆表上面建立,所以,我们可以在堆表上面执行Bookmark操作。下面我们来看看一个查询:






20120817092754.png(11.13
K)

8/17/2012 9:28:41 AM

然后,我们建立一个索引:






20120817092821.png(10.84
K)

8/17/2012 9:28:41 AM

执行计划如下:






20120817092846.png(20.75
K)

8/17/2012 9:28:41 AM

从图中看出,此时Bookmark Lookup的物理实现选择了RID
Lookup操作,而不是选择了Key Lookup物理操作,原因很简单,因为堆表上面没有聚集索引,所以必须通过RID去查询底层数据表去获取额外的数据列。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐