查询优化器内核剖析第七篇:执行引擎之数据访问操作---Scan
查询优化器内核剖析第七篇:执行引擎之数据访问操作---Scan
从本篇了开始的接下来的几篇文章将会介绍与执行引擎相关的知识。
系列文章索引:
执行引擎就是由大量的物理操作组成的(而这些物理操作又会去调用存储引起的相关方法),这些操作被查询处理器用来高效的执行我们的查询。
这里不要将查询处理器与查询优化器搞混淆,它们不是同一个东西。为了使得大家对相关的概念有一个清楚的认识,请看到下面这一个图:
从图中,就可以一目了然的直到查询处理器与查询优化器之间的关系:
- 数据库基本上有两大部分组成:关系引擎,存储引擎。(建议朋友们去仔细的阅读这几篇文章:SQL 内部机制之:一个查询的生命周期)
- 我们这里所说的“查询处理器”就是图中的关系引擎,而查询优化器只是其中的一个部分。
我们最近将要介绍的“执行引擎”,就是上图中的“Query Executor”。在介绍执行引擎的过程中,我这里主要会着重的介绍几类在我们查询中常见的一些操作:数据访问,聚合,Join,还有并行操作。当然,在执行引擎中,还有更多的操作,如果大家感兴趣,可以去参看SQL Server的联机丛书。
我们首先将会介绍给数据访问的几个操作:scan,seek,还有lookup。
相信大家对这些操作应该有所了解的,我这里稍微的提及一下。
Scan:这个操作会读取整个数据结构,这个数据结构可以是一个堆表,聚集索引,非聚集索引。
Seek:这个操作不会读取整个数据结构,而是直接通过索引定位到要读取的那一行。所以Seek操作只能发生在聚集索引与非聚集索引上。
堆表:就是没有建立聚集索引的表,表中的数据没有按照顺序进行存储。一旦一个表建立了聚集索引,那么表中的数据就会按照聚集索引排序存储。
另外,非聚集索引可以建立在堆表上,也可以建立在还有聚集索引的表上。
我们通过下面的一个表做一下总结(看看不同的数据结构可以支持何种物理操作),然后迅速进入Scan操作的详细讲解。
数据结构 |
Scan |
Seek |
堆表 |
Table Scan |
|
聚集索引 |
Clustered Index Scan |
Clustered Index Seek |
非聚集索引 |
Index Scan |
Index Seek |
还是和之前一样,我们从一个例子入手,这里依然使用示例数据库AdventureWorks。我们首先来看到一个Table Scan的操作(也就是整表扫描) ,看到如下的查询:
查看实际的执行计划,如下图:
通过查看DatabaseLog表的定义,我们发现这个表是一个堆表,即,这个表没有聚集索引,如下所示:
下面,我们再看一个Clustered Index Scan(聚集索引扫描)的例子,看到如下查询:
执行计划如下如所示:
通过查看Address表的定义,发现这个表确实还有聚集索引:
可以上面的例子可以知道:
- 虽然同是进行了 Scan操作,但是因为表的一些特性不同,而最后选择的具体的物理操作Scan也不一样。
- 不管是Table Scan还是Clustered Index Scan,它们都是对相应的数据结构进行了全部的扫描,不同的是:前者发生在堆表上,后者发生在含有聚集索引的表上。
下面,给大家看一个比较有意思的查询:
执行计划如下:
图中显示的是Index Scan,也就说,这个查询没有去扫描表的所有数据页,而是去扫描索引,这个成本小得多。之所以进行了这个操作,是因为在这个表上存在一个非聚集索引,定义如下:
这个非聚集索引包含了我们查询中的两个字段:[City],[StateProvinceID]。又因为AddressID是这个表的聚集索引,对于一个有聚集索引的表而言,它的所有的非聚集索引都会包含一个对聚集索引的引用,这样是为了加快数据检索的速度(大家可以想想为什么?)。所以,我们上面查询中的三个操作都可以在索引页中找到,就没有必要去扫描底层的表了。