前言:
执行计划,简单而言,就是SQLServer查询优化器尝试通过计算最有效的方法来实现你提交的T-SQL查询请求的结果。
执行计划可以告诉你SQLServer将会如何执行一个查询或者它是如何执行一个查询。因此,它们是处理低性能查询的主要手段。你可以通过执行计划去定位引起问题的SQL代码,而不是通过猜测为什么一个给定的查询会引起数以千计的扫描,使得你的I/O飞涨。举个例子,你的查询可能过去需要读取整个表的数据,但是通过移除WHERE子句中的函数,可以直接查找你所需要的那些行。执行计划就是告诉你这些甚至更多。
这章的目标是教你获取图形化、文本化或者XML格式的实际与预估执行计划,并理解如何查看这些计划的基础知识。为了达到这个目的,我们将覆盖以下主题:
l 查询优化器的简单背景介绍——执行计划是优化器操作的结果,所以了解至少一点点关于优化器是什么并如何执行的是非常有用的。
l 实际和预估执行计划——它们是什么并且有什么区别。
l 获取和解释各种格式的执行计划之间的不同——包括图形化、文本化和XML执行计划。
l 直接从cache中获取执行计划——通过DynamicManagement Objects(DMOs)访问计划缓存。
l 自动化执行计划获取——使用SQLServer跟踪事件。
当查询提交时发生了什么?
当你提交一个查询到SQLServer时,在服务器上会伴随有一系列针对这个查询的处理操作。这些处理的目的是为了管理系统以便能SELECT,INSERT,UPDATE或者DELETE对应的数据。
这些处理在每次提交查询到系统中时都会被激活。虽然有很多不同的操作同时发生在SQLServer中,但是我们将关注与查询相关的处理。这部分的处理大致分为两个阶段:
1. 发生在关系引擎中的处理。
2. 发生在存储引起中的处理。
在关系引擎中,查询被解析然后由查询优化器处理,最终产生执行计划。产生的执行计划以二进制格式发给存储引擎,存储引擎以这个计划作为基础查询或者修改底层数据。存储引擎是处理譬如锁定、索引维护和事务等操作的地方。我们将主要关注从关系引擎中产生的执行计划。
查询解析:
当我们提交一个T-SQL查询到SQLServer系统时,它进入的第一个地方是关系引擎(一个T-SQL可以是来自于命令行或者从存储过程中发起的数据请求的ad hoc,即席查询。)。当T-SQL到达之后,将传递给一个处理操作,以检查T-SQL是否写的正确,也就是是否有正确的格式。这个处理操作就是查询解析(query parsing)。如果一个查询在解析过程中失败,比如,你输入了SELETC(T和C的顺序错了)而不是SELECT ,那么解析操作将停止,然后SQLServer返回一个错误到查询源。解析处理的结果就是生成一个解析树,或者叫查询树甚至叫顺序树。查询树显示了执行该查询所必须的逻辑步骤。
如果T-SQL字符串不是DML语句而是DDL语句,将不会被优化,举个例子,对于SQLServer系统来说,有且仅有一个“正确的方式”去创建一个表。因此,对于这类语句,没有改进性能的机会。
Algebrizer:
如果T-SQL是DML语句且已经通过解析,那么解析树会被传输到一个名为algebrizer的处理部件中。Algebrizer标识单独列上正在访问的对象的数据类型。它同时也决定查询中的聚合运算的位置,这部分的处理叫做聚合绑定(aggregate binding)。Algebrizer非常重要,因为查询中可能存在别名或者同义词,甚至在数据库中不存在的名字,这些必须经过处理,否则查询将引用不存在于数据库中的对象。当对象不存在于这个数据库时,SQLServer会从这步中返回一个错误,表明对象名无效。比如,algebrizer可以快速地在AdventureWorks2008R2中找到Person.Person表。但是当对于Product.Person表,因为不存在,所以会引起一个错误,并且使得整个优化过程停止。
Algebrizer 输出一个二进制数据称为查询处理树,并传输给查询优化器。查询处理树包含了一个hash(描述查询的一个已经编码的值)。优化器使用这个hash去确认是否已经生成了一个计划并存储在计划缓存中。当发现已经有计划存在,优化过程将在这里结束并使用这个计划。这样可以降低优化器生成一个新计划的整体开销。
查询优化:
优化器本质是一块模型,用于模拟数据库关系引擎如何工作的软件。其中被优化器使用的数据中最重要的部分是统计信息,是由SQLServer针对索引和列产生并维护的,明确用于优化的部分。通过查询处理树和关于数据的统计信息,优化器应用模型以便算出它认为是执行查询的最佳方式,也就是说,它产生一个执行计划。
换句话说,优化器指出了如何最好地实现你所提交的查询请求的方式。它决定如果可以通过索引访问数据,会使用什么类型的关联比较合适。这个决定是通过优化器针对给定的执行计划,计算出它的开销情况,包括需要的CPU处理和I/O。因此,它是一个基于成本的计划。
优化器会产生并评估很多计划,除非已经缓存了计划,通常来说,会选择开销最低的计划,也就是它认为可以尽快且使用更少资源地执行查询的计划。对于执行开销的计算是其中最重要的计算,优化器会使用一个高CPU开销的处理过程来计算是否能更快地返回结果。有时候,如果优化器觉得评估很多计划的时间比执行一个次优计划更长,那么优化器会勉强同意使用一个次优执行计划。优化器并不是去找最好的执行计划,而是找到最少可能的迭代次数的最低开销的计划,也就是说找到在处理过程中最短时间的计划。
如果提交已给非常简单的查询,例如一个没有聚合运算或计算的单表查询,相对于花费时间去计算绝对的最优执行计划,优化器会选择使用一个无关紧要的执行计划来应对这种类型的查询。比如,下面列出的例子将创建一个无关紧要的执行计划。
USE AdventureWorks2008R2 GO SELECT d.name FROM HumanResources.Department AS D WHERE d.DepartmentID=42
当加上一个以上的表,且用JOIN关联时,会使得计划不再无关紧要。如果查询并不是无关紧要,优化器将使用基于开销的计算来选择一个计划。为了实现这种计算,会依赖于SQLServer所维护的统计信息。
统计信息是收集自在数据库内列和索引的,用于描述数据的分布、唯一性或者选择性。我们并不希望优化器在每次对查询生成执行计划时,读取所有涉及表的所有数据,所以优化器会依赖于统计信息,统计信息是提供用于优化器描述整个数据集合中的数学结构上的取样数据。优化器对统计信息的信赖度意味着这些信息必须尽可能地准确否则优化器可能选择次优甚至不正确的执行计划。
组成统计信息的数据以直方图的形式展现,是一个针对特定值的出现次数的一个统计表格,这些数据来自于表中平均取样的200个值。这些数据被称为描述数据的数据,为优化器计算提供必要的信息。
如果统计信息在相关的列或者索引上存在,那么优化器可以用它们来作为计算依据。优化器会检查统计信息,以便决定索引是否提供了足够程度的选择性用于协助当前查询。选择性是指贯穿在整个数据集中的唯一数据的程度。要求的索引的选择性级别是非常高的,通常在实例上以x%来表示。
统计信息默认情况下会在所有索引或者出现在谓词中(where子句的部分或者join on子句部分)的列上自动创建和维护。表变量永远不会创建有统计信息,所以优化器总假定它们只包含一行,而不在乎他们的实际大小。
临时表会产生与永久表同类型的统计信息,并且供优化器使用。
优化器通过把这些统计信息及查询处理树,启发式地决定最优执行计划。意味着这个工作将通过一系列的计划,测试不同的访问数据的方法,尝试不同类型的join,重排join的顺序,尝试不同的索引等等,直到找到它认为开销最低的执行计划为止。在这系列计算过程中,优化器会对计划内每个步骤赋予一个数值,表示预估的CPU数量和磁盘IO时间的组合值。这个值就是这个步骤的预估开销。这些值的总和就是执行计划本身的预估开销总和。
需要重点留意的是预估开销仅仅是预估,给定一个无穷大的时间,实时更新的统计信息,优化器可以找到执行查询的最优计划,但是,在时间允许的情况下它会尝试计算最优执行计划,并受可用统计信息的品质限制。因此,这个预估值在衡量的时候非常有用,但是并不能精确地反映实际情况。
一旦优化器获得一个执行计划,就会把预估计划创建出来并存放在一个叫做plan cache的内存块中。在前面提到过,如果优化器发现一个计划和缓存中的计划相匹配,这个处理过程将短路。
查询执行:
一旦优化器产生了一个执行计划,或者在缓存中找到,这个操作将切换到存储引擎,存储引擎是根据执行计划运行查询的地方。
在这里我们不打算深入说明,但是要注意产生的执行计划可能在执行运行过程中改变,比如:
l SQLServer 发现执行计划超过了使用并行执行的阈值,详细内容将在第三章中说明。
l 用于产生执行计划的统计信息过时,或者在原始执行计划创建后发生改变。
l 在查询中的过程或对象,如数据插入了一个临时表,引起了执行计划的重编译。
上面所述的任何一个方面都会引起预估执行计划的变更。
SQLServer在关系引起更改格式一边匹配提交的T-SQL语句请求后,返回查询的结果。