请注意:在介绍复杂的执行计划时,碰到的计划可能与先前书本中描述的计划存在较大差异时,这极可能是由于使用的SQL Server版本或SP补丁包不同。对于示例中演示的代码若出现一些不同之处请不要过于惊讶。
示例数据库:
采用SQL2005示例数据库-AdventureWorks,可以从以下地址获得:
http://www.codeplex.com/MSFTDBProdSamples
若您使用了除上述提供的存储过程和脚本外,请记住加密的存储过程并不会显示在执行计划中。
由于AdventureWorks数据库的版本差异,其统计信息、索引信息及数据的结构也可能不同,因此一定要注意若运行代码示例可能会看到产生的执行计划等信息也会发生一定的变化。
对于简单的执行计划而言是比较易读的,随着后续介绍的更复杂的查询或执行计划,本文也将以最简单的形式通过图形执行计划或XML格式来描述。好了,准备好了,继续!
在查看查询的执行计划之前需要一定的权限,倘若不是sysadmin,dbcreator或db_owner角色成员,则需要授予ShowPlan的权限,此外,对于查询引用的数据库访问权限也要授予用户相应的权限,运行以下语句:
GRANT SHOWPLAN TO[user_name]
User_name表示授权用户具有数据库查看执行计划的权限。
在介绍估计执行计划和实际执行计划前,首先以最简单的查询作为开始,打开【SQL Server Management Studio】,在新建的查询窗口中键入以下查询:
SELECT * FROM dbo.DatabaseLog;
通过点击工具条上的“显示估计的执行计划”来启用,或者使用快捷键“Ctrl+L”
图1:为估计执行计划
与估计划执行计划不同,实际执行计划并不显示优化器的计算,与之相反的是它显示了查询执行时的信息。这两个计划有时是相同,但是有时候也是不同的,这取决于执行计划的更改。
产生实际的执行计划有以下几种方法:
n 点击工具栏中叫作“包含实际执行计划”
n 右击查询窗口,选择“包含实际执行计划”
n 使用快捷键Ctrl+M
下图为该语句的实际执行计划:
图2:实际执行计划
从输出的计划可以看出实际的计划与估计计划是相同的。
下面来对其进行解释:
图1与图2中所看到的图标是执行计划中表示不同运算的约78种运算符的前两个。最左边的是SELECT图标,它也是你见到的最多也通常容易勿略的一个,它是关系引擎格式化结果的最后一步,在最右边的是TABLE SCAN图标,这也是最简单的一个也是导致性能问题的操作。
Table Scan,当强迫存储引擎来逐行遍历整个表时,使用了表扫描,由于未使用WHERE子句,也未使用覆盖索引(覆盖查询中引用的所有列),因此随着表中行不断增加,此操作的开销将是非常巨大的。
阅读图形扫计划通常是采取“自右至左、自顶至下”的步骤,你或许注意到在两个图标之间有一个箭头,它代表运算符之间数据的流向,正如图示所表达的。在上面的例子中,仅有一个表扫描的操作符,其中箭头的粗细代表流经的数据多与少,粗代表更多的行,也就是诊断性能问题的一个可视因素。你可以将鼠标放在其箭头上来显示其表示的详细信息。
每一图标下显示了操作所占的百分比,此数只是一个相对开销值,由优化器生成,用于估计该操作的执行时间。在上面的例子中,所有开销都是关于表扫描的,而开销也可能是以0%或100%的形式表示,记住:这只是一个阀值,并不代表实际的数据,既使0%操作也代表有一小部分的开销。
图标上方显示了查询串的信息和相对于批的开销。好比每一个查询运行多个步骤,每一个步骤均有一个相对于该查询的开销值,也可以在一个批内运行多个查询,产生其执行计划,这将从整体上显示部分执行计划的开销。
工具提示:每一个图标或与之对应的箭头均有一个弹出的窗口—工具提示,即鼠标放置该图标上所显示的信息。
下图是关于SELECT操作的信息:
右图3:SELECT运算
以下是优化器产生的统计信息:
l Cached plan size
标识此计划占用存储过程缓存的多少空间
l Estimated Operator Cost
估计操作符的开销
l Estimated Subtree Cost
累计步骤的开销
l Estimated number of rows
估计的行数
以下是表扫描中的信息:
右图4:表扫描
每一种运算符都有一组不同的数据集,右图4中的运算符所显示的内容与右图3截然不同,因此得到不同的数据集。首先是列出的物理运算符和逻辑运算符,逻辑运算符是查询执行时由优化器计算出的结果,而物理运算符则是实际产生的结果。逻辑和物理运算符通常是相同的,不过也不总是相同的(后续会将介绍)。
接着是I/O,CPU、运算符和子树的估计开销。子树是目前我们看到的执行树的简化部分,所有估计信息都是基于表中列和索引中可用的统计信息。I/O开销和CPU开销并不是实际的运算,它们只是查询优化器在计算过程中赋予的开销值,这些值对于判断I/O性能或CPU性能是否存在问题有积极作用。
或许你也注意到了(上面的例子中),运算符的开销(operator)和子树的开销是相同的,由于本例仅有表扫描一个运算符。对于包含更多运算符的复杂树而言,你将会看到其值是每一个运算符开销的总和。
另外,对于估计的行数,此值显示了每一个运算处理的结果。当分析复杂的执行计划时,可以看到不同运算符执行完所产生的行信息,通过了解这些行信息有助于你理解查询内部的执行机制。
更重要的一点是,当诊断性能问题时,对于Ordered显示的布尔值,此值则告诉你该运算符当前处理的数据集是否是有序的。例如:在一个SELECT语句中出现的ORDER BY子句,很可能需要数据以某种顺序输出。通过Ordered状态值可以得到获取数据可能需要执行的额外操作信息。
最后是节点ID(Node ID),此值以有序的数字编排。当查看子树开销累计运算时,行数的改变等信息,利用这些信息可以找出哪个步骤占用了大量的CPU资源等信息。
运算符属性
“工具提示”中显示的信息仅是一部分,您可以右击“图形执行计划”来选择“属性”来获取该运算的详细信息,如下图5 所示:
右图5:运算符属性
上面显示的多数信息您应该不会陌生,但有些部分是新产生的,从上面开始,“Defined Values”显示了该运算添加处理的信息,这些信息可能是基本查询的一部分(本例为选择的那些列),也可能是查询处理中内部运算而创建的,例如用于判断参照完整性或聚合函数统计占位符。
在“Defined Values”属性中,我们可以获取该运算的描述信息,接着是一些熟悉的的“估计开销”数据,紧接着将看到如下信息:
l Estimated Rebinds和Rewinds:这两个值描述计划中Init()运算被调用执行的次数
l Forced Index:当采用查询提示来设置查询使用指定的索引时该值为True。SQL Server提供这样一种查询提示的功能可以控制查询执行的方式,有关“查询提示(Query Hints)”的详细信息稍后会有介绍。
l NoExpandHint:与Forced Index概念一样,但仅适用于“索引视图”
通过展开Object属性,你可以看到该对象的详细信息,“Output List”属性提供了每一个输出列的详细信息,您也可以找出这个运算符是否参与了并行运算(当一个运算符使用了多个CPU时)。