理解图形化执行计划 -- 第3部分:分析执行计划

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介:

英文原文:

http://www.sqlservercentral.com/articles/Execution+Plans/105810/


对于SQL Server数据库管理员和开发来说,能够理解和分析执行计划是一项非常重要且有益的技能。执行计划将查询的预估花销、索引使用和执行的操作文档化输出。所有的信息对于试着加速一个慢查询来说都是极其重要的。


这篇文章是关于图形化执行计划的三部分系列文章之一。第1部分解释了执行计划是什么,并讨论了预估和实际执行计划的不同。第2部分显示了如何创建预估和实际执行计划。最后,第3部分深入一个简单的图形化执行计划,并讨论了一些最普遍的查询中的操作。


阅读执行计划


让我们看下如何从执行情况和执行计划中获得信息。


基础


图1从[AdventureWorks2012].[Person].[Address]表上的一个简单查询显示预估的执行计划。如你所见,它由表明操作和关联不同操作的箭头的图标组成。箭头都指向左边,表明执行计划从右边开始并向左运行。按时间顺序阅读执行计划,从最右边的操作开始并向左运行。箭头的相对层次也表明了多少数据正被从一个操作传递到另一个。


clip_image002

图1 一个简单的预估执行计划


在图2中,顶部操作的箭头比它下面的两个操作更瘦。顶部操作因此也输出更少的行。


clip_image004

图2 一个稍微复杂的执行计划


在创建了执行计划后,就有地方可以看到查询的总消耗。总消耗位于最后的操作。这就是最左边图标的上层,在这里,就是Select操作。将鼠标放在Select图标的上面会给你一个关于操作信息的提示框(图3)。


我们对Estimated Subtree Cost感兴趣。总的消耗是一个查询是否执行很快的更好判断的相对值。它由I/O消耗和CPU消耗组成。Operator Cost是I/O Cost和CPU Cost之和。在这里我们的查询很好的低于1。它有可能执行相对较快。另一方面,一个查询总消耗上千可能需要相当长时间完成。一些因素影响着预估消耗。不仅是相关表和视图的行和索引的数量,而且环境因素例如CPU数量和磁盘都被用于计算消耗。


clip_image006

图3 查看执行计划总消耗


在检查了总消耗并得到一个运行时间的相对感觉后,接下来,从右到左快速查看执行的操作。在图1,首先,有一个聚集索引扫描。一个扫描表名查询不是非常可选择性(看Frequent Plan Operations下的Index/Table Scan查看更多细节)。在这个特定情况下,它是一个没有where从句的产物。我们也有TOP操作,从查询返回前1000行。最后,有一个在select语句中返回指定列的select操作。


一旦你有一个查询正在做什么的整体感受,我们将专注在最高消耗的操作上。在每个图标的最下面对每个操作执行计划列出了总消耗的百分比。从图1到图2,我们看到聚集索引扫描占比总消耗的99%。我们想专注在这个操作上。


我们看下这个图标的提示框(看图4),你会注意到有四个消耗列出。对于每个操作消耗分为CPU和IO消耗。Operator Cost是CPU和I/O Cost之和。Sub Tree Cost是当前的Operator Cost加上在它之前的操作的整个操作消耗。如果你从最右边的操作开始并跟随箭头向左,你会看到对每个完成的操作Sub Tree Cost累加。如我们之前查看到的,在左边最上层的操作的Sub Tree Cost包含了整个查询的预估的消耗。


clip_image007

图4 执行计划消耗


常见的执行计划操作


下面是一些出现在查询计划里的最常用的操作。


Index/Table Seek -- 当执行seek,SQL Server可以在索引里有效查找特定值或值的一个可选择范围。使用图书馆类比,你使用图书馆的电脑查找一本书的位置,并获得这本书的位置。


Index/Table Scan -- 在scan操作中,SQL Server通读整个表或索引。再次使用图书馆类比,相当于查找图书馆中的每一本知道找到你想要的书。试着在大型的大学图书馆这么做!Scan表明了搜索标准没有达到足够的使用seek操作的选择性。如果在列上没有索引被查找或者返回的值的数量在索引中占百分比很大(低选择性),查看所有的值会更有效。如果你感觉有指定一个WHERE从句或JOIN足够保证使用索引,确保列上被索引。


Seek/Scan with Bmk谓词 -- 当在一个索引查找或扫描的提示框的Seek Predicates部分查看,你有时会看到一个谓词像Bmknnnn这里nnnn是数字。这表明SQL Server会创建一个书签用于Index或RID Lookup。当书签出现,index seek/scan是一个两步过程的部分,优化器会在使用书签创建了一个数据集后,执行聚集索引查找(或表查找,如果没有聚集索引)。查看关于Key/RID Lookup部分的更多信息。


Joins

SQL Server使用了三种join操作:

  1. Hash Match/Join -- Hash Match或者Join可以用于Join(Hash Join)和Group by的(Hash Match)。在这个操作中,查询优化器从被关联的两个表(如图形化执行计划中所见)的上层表构建一个哈希表。这被称为构造表。低层表(被称为探测表)的每行然后搜索构造表匹配数据。在一个Group By的情况下,之前操作的结果被用于构造表和探测表。这类Join效率的关键是构造表的大小和服务器的可用内存数量。如果足够小,优化器将在内存中创建构造表。如果可能,Hash Match将会相当快。另一方面,如果构造表相当大,处理过程编程一个嵌套循环并且非常慢。当这发生的时候,Hash Match的消耗是执行计划的重要的百分比,你应该使得查询更具选择性或者考虑增加一个索引。Hash Join和Match会在执行前等待有足够的内存。

  2. Merge Join -- 当上层表(如图形化执行计划中所示)在Join中很大,Merge Join将会是最快的Join类型。这个Join的消耗与两个表的行数总和相关(#上层表的行数 + #下层表的行数)。Merge Join的效率的关键是关联的两个表必须在关联列上已排序。如果表还没有排序,优化器将会首先排序表。在这里,你会在Merge Join操作器之前直接看到一个排序操作器。排序操作是非常消耗性能的,因此如果一个执行计划在Merge Join之前显示了一个有高消耗排序操作器,你可能想在关联列添加索引。

  3. Nested Loop -- 嵌套循环关联的消耗和两个表行数(#上层表的行数 * #下层表的行数)的乘积有关。虽然这个Join没有Merge Join有效率,当表没有排序时它的整体消耗比Merge Join低,当上层表很大时它的整体消耗比比Hash Join低。再则,如果操作的消耗在执行计划中占很高百分比,那么让你的查询更具选择性或者添加一个索引。


Key/RID Lookup -- 当在一个非聚集索引上执行seek或scan时发生lookup,并且所有的数据不包含在索引中。当这种情况发生时,如果存在聚集索引,在聚集索引上发生Key Lookup,否则如果没有聚集索引,执行行标识符(RID)Lookup。Lookup是昂贵的操作,如果可能应该避免。为了消除Lookup,将Select语句中的列添加包含在非聚集索引中的include从句中,如果可能。要是你有很多列在Select语句中,不可能将所有都包含进去,又怎样呢?接下来最应该做的事是使得非聚集索引比当前正使用的索引更具选择性。那就是说,如果你不能移除Lookup,试着减少传递给Lookup的行数。


Compute Scalar -- 该操作执行计算处理一个单一值。通常是标量函数、算术计算或字符串联接的结果。警告,优化器不能预估标量或多语句表值函数的执行计划。当这些函数之一出现了性能低下,尝试将它转换为一个内联表值函数。


Concatenation -- 对字符串联接不操作;而是对于数据集合的联接。最常用于在UNION ALL操作中国联接数据。


Sort -- sort操作只将非排序数据作为输入并输出一个排序集合。排序在大型数据集合上是非常消耗性能的。排序也需要在执行前等待内存足够多。有高排序消耗的执行计划应该被检查用于优化。


Parallelism -- 如果SQL Server所在的机器上有不止一个处理器,可选的在多个处理器之间拆分操作。事实上,如果有多个处理器,优化器会创建两个执行计划,一个用于并行而一个没有。SQL Server然后决定哪个最后可能消耗最短的时间。Parallelism致力于将数据分割在多个处理器间覆盖操作并合并结果。如果并行操作消耗很高,你可以在查询上使用MAXDOP查询提示[OPTION (MAXDOP 1) ]强制使用一个处理器。


对于操作的完整列表,查看TechNet文章Showplan逻辑和物理操作参考


总结


在查询优化中阅读图形化执行计划是一个非常有用的技能。该系列文章介绍了这个主题。我欢迎你对后续文章的评论。如果你也想阅读更多这个主题的内容,Grant Fritchey的书SQL Server执行计划提供了最好的最广泛的涵盖内容。


参考


.分析查询,TechNet Library -- http://technet.microsoft.com/en-us/library/ms191227(v=SQL.105).aspx


.分析慢查询的清单,TechNet Library -- http://technet.microsoft.com/en-us/library/ms177500(v=sql.105).aspx


.Fritchey,Grant(2008),SQL Server执行计划,Simple Talk出版(本文使用2008版,2013年出版了第2版)

.Showplan逻辑和物理操作参考,TechNet Library -- http://technet.microsoft.com/en-us/library/ms191158.aspx


译者补充:


浅谈SQL Server中的三种物理连接操作

https://msdn.microsoft.com/zh-cn/library/dn144699.aspx


表值函数

表值函数提供强大的结果集生成能力。它可以在查询内部表或视图允许的任何地方使用。表值函数在使用上比返回一个结果集的存储过程更灵活,因为函数的结果集可以联接到查询中的其他表。

SQL Server中有两种表值函数。内联表值函数在概念上与带参数的视图类似。多语句表值函数允许多条语句在表变量中创建结果集来返回。


1. 内联表值函数

创建内联表值函数很简单。内联表值函数的内容是一条带参数的SELECT语句。返回数据类型永远是表,不过返回表的结构由SELECT语句的结构来定义。下面是内联表值函数的一个例子,检索给定CustomerID的商品销售总量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
USE AdventureWorks2008;
GO
CREATE  FUNCTION  Sales.ufnSalesByCustomer (@CustomerID  int )
RETURNS  TABLE
AS
RETURN
(
SELECT  P.ProductID, P. Name SUM (SD.LineTotal)  AS  Total
FROM  Production.Product  AS  P
JOIN  Sales.SalesOrderDetail  AS  SD
ON  SD.ProductID = P.ProductID
JOIN  Sales.SalesOrderHeader  AS  SH
ON  SH.SalesOrderID = SD.SalesOrderID
WHERE  SH.CustomerID = @CustomerID
GROUP  BY  P.ProductID, P. Name
);
GO

注意,函数体由一条RETURN语句组成。使用这个函数的一个例子如下所示:

1
SELECT  FROM  Sales.ufnSalesByCustomer(30052);

内联表值函数功能强大,在要求参数化查询的情况下值得考虑。它们在结果集如何使用上提供更多的灵活性。


2. 多语句表值函数

多语句表值函数允许多条语句来创建表的内容。多语句表值函数可以用来替换使用多个步骤来构建结果集的存储过程。

多语句表值函数允许开发人员使用多个步骤动态地填充表,这一点与存储过程类似,不过它们可以在SELECT语句中像表那样被引用。

使用多语句表值函数时,表的结构必须在函数头定义。要为表使用一个变量名,并且所有修改数据的操作只能引用表变量。

下面的例子是一个函数,类似上一节中创建的ufnSalesByCustomer。首先创建表变量,然后使用刚才创建的标量函数来更新表变量,让它包含总的存货清单。创建函数的语句如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
USE AdventureWorks2008;
GO
CREATE  FUNCTION  Sales.ufnSalesByCustomerMS (@CustomerID  int )
RETURNS  @ table  TABLE
( ProductID  int  PRIMARY  KEY  NOT  NULL ,
ProductName nvarchar(50)  NOT  NULL ,
TotalSales  numeric (38,6)  NOT  NULL ,
TotalInventory  int  NOT  NULL  )
AS
BEGIN
INSERT  INTO  @ table
SELECT  P.ProductID, P. Name SUM (SD.LineTotal)  AS  Total, 0
FROM  Production.Product  AS  P
JOIN  Sales.SalesOrderDetail SD  ON  SD.ProductID = P.ProductID
JOIN  Sales.SalesOrderHeader SH  ON  SH.SalesOrderID = SD.SalesOrderID
WHERE  SH.CustomerID = @CustomerID
GROUP  BY  P.ProductID, P. Name ;
UPDATE  @ table
SET  TotalInventory = dbo.ufnGetTotalInventoryStock(ProductID);
RETURN ;
END ;

执行这个函数与执行前面的内联函数一样:

1
SELECT  FROM  Sales. ufnSalesByCustomerMS (30052);

-------------------------------------------------------------------------------------

表值函数和标量值函数的不同是 表值函数是返回一个Table类型 Table类型相当与一张存储在内存中的一张虚拟表.















本文转自UltraSQL51CTO博客,原文链接: http://blog.51cto.com/ultrasql/1735019,如需转载请自行联系原作者



相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情: https://www.aliyun.com/product/rds/sqlserver
相关文章
|
12月前
|
缓存 负载均衡 关系型数据库
mysql查询优化
mysql查询优化
53 0
|
SQL 关系型数据库 MySQL
十九、使用explain分析你SQL执行计划
十九、使用explain分析你SQL执行计划
68 0
|
存储 SQL 分布式计算
工作常用之Hive 调优【三】 Explain 查看执行计划及建表优化
在查询时通过 WHERE 子句中的表达式选择查询所需要的指定的分区,这样的查询效率会提高很多,所以我们需要把常常用在 WHERE 语句中的字段指定为表的分区字段。
292 0
工作常用之Hive 调优【三】 Explain 查看执行计划及建表优化
|
SQL 关系型数据库 MySQL
MySQL查询优化
学习使用
83 0
MySQL查询优化
|
SQL JSON 关系型数据库
几个必须掌握的SQL优化技巧(四):使用Trace工具分析优化器执行计划
在应用的开发过程中,由于开发初期的数据量一般都比较小,所以开发过程中一般都比较注重功能上的实现,但是当完成了一个应用或者系统之后,随着生产数据量的急剧增长,那么之前的很多sql语句的写法就会显现出一定的性能问题,对生产的影响也会越来越大,这些不恰当的sql语句就会成为整个系统性能的瓶颈,为了追求系统的极致性能,必须要对它们进行优化。
359 0
几个必须掌握的SQL优化技巧(四):使用Trace工具分析优化器执行计划
|
SQL 存储 关系型数据库
几个必须掌握的SQL优化技巧(三):Explain分析执行计划
在应用的开发过程中,由于开发初期的数据量一般都比较小,所以开发过程中一般都比较注重功能上的实现,但是当完成了一个应用或者系统之后,随着生产数据量的急剧增长,那么之前的很多sql语句的写法就会显现出一定的性能问题,对生产的影响也会越来越大,这些不恰当的sql语句就会成为整个系统性能的瓶颈,为了追求系统的极致性能,必须要对它们进行优化。
243 0
几个必须掌握的SQL优化技巧(三):Explain分析执行计划
|
SQL 存储 关系型数据库
MySQL查询优化必备
查询优化本就不是一蹴而就的,需要学会使用对应的工具、借鉴别人的经验来对SQL进行优化,并且提升自己。
117 0
MySQL查询优化必备
|
SQL 存储 算法
MySQL数据库性能优化由浅入深(表设计、慢查询、SQL索引优化、Explain分析、Show Profile分析、配置优化)
通俗地理解三个范式,对于数据库设计大有好处。在数据库设计中,为了更好地应用三个范式,就必须通俗地理解三个范式(通俗地理解是够用的理解,并不是最科学最准确的理解
303 0
MySQL数据库性能优化由浅入深(表设计、慢查询、SQL索引优化、Explain分析、Show Profile分析、配置优化)
|
存储 NoSQL JavaScript
MongDB查询性能分析——explain 结果详解
MongoDB 提供 db.collection.explain(), cursort.explain() 及 explain 命令获取查询计划及查询计划执行统计信息。 explain 结果将查询计划以阶段树的形式呈现。 每个阶段将其结果(文档或索引键)传递给父节点。 叶节点访问集合或索引。 中间节点操纵由子节点产生的文档或索引键。 根节点是MongoDB从中派生结果集的最后阶段。 阶段操作描述,例:
377 0