【译】索引进阶(九):阅读查询计划【下篇】
译者前言:上篇我们了解了查询计划的基本知识,本章我们继续深入探究查询计划,分析其常见的几种操作。
原文链接:传送门。
如果两个数据行处理流能够并行进行,在图形化的显示上,它们会出现在对方的上下位置。箭头的宽度代表着在各个流中有多少行数据正在被处理。
举个例子,如下的join查询,扩展了之前的查询以包含销售信息:
SELECT C.LastName, C.FirstName, C.MiddleName, C.Title , H.SalesOrderID, H.OrderDate FROM Person.Contact C JOIN Sales.SalesOrderHeader H ON H.ContactID = C.ContactID WHERE Suffix = 'Jr.' ORDER BY Title
其对应的查询计划如图3:
图3:一个关联查询的查询计划
快速的看下这个查询计划,它告诉了我们如下一些事情:
- 两张表同一时间进行扫描
- 大部分工作花费在扫描表上
- SalesOrderHeader 表查询出来的数据行比Contact 表要多
- 两张表没有聚集成一致的顺序,因此将各个SalesOrderHeader 行与它对应的Contact 行匹配起来需要额外的努力,在本例中,使用了一个哈希匹配操作(后面会讲解哈希的更多内容)。
- 用来排序所选数据行的努力可以忽略不计
甚至一个数据行处理流也会被拆为几个包含更少数据行的独立的数据流,以使用并行处理的优势。举个例子,如果我们把上面查询的WHERE子句改成WHERE Suffix is NULL,因为超过95%的Contact 表数据行都有一个NULL的Suffix值,因而更多的数据行会被返回,新的查询计划反映了这个,如图4所示:
图4:并行的查询计划
新的查询计划也提示我们,Contact 表增加的行数 已经使得匹配和排序的操作成为这个查询的关键路径,如果我们需要提高它的性能,我们必须首先攻击这两个操作。再一次,带有包含列的索引会起到帮助作用。
像其他大多数连接一样,我们的例子也是通过外键/主键关系来连接两个表的。这些表的一个Contact,是按照ContactID进行排序的,它的主键也是如此,在另一张表SaleOrderHeader,ContactID 是一个外键,既然ContactID 是一个外键,根据ContactID来请求SaleOrderHeader表的数据(像我们的例子)或许是一个通用的需求,这些查询会得益于ContactID列上的一个索引。
每当在 一个外键上建立索引的时候,总是问问自己,哪些列可以当做包含列被包含进这个索引,在我们的例子中,我们只有一个查询而不是一簇查询需要支持,因此,我们唯一的包含列应该是OrderDate,为了支持SaleOrderHeader表上一系列基于ContactID 列的查询,我们应该包含SaleOrderHeader 表更多的列在包含列中,这样,如有需要,便可以支撑其他额外的查询。
我们的CREATE INDEX语句是这样:
CREATE NONCLUSTERED INDEX IX_ContactID ON Sales.SalesOrderHeader ( ContactID ) INCLUDE ( OrderDate )
执行表SalesOrderHeader 和Contact连接的计划入图5所示:
图5:表上带有支持索引的连接查询计划
因为两个输入数据流现在都是按照连接谓词列:ContactID排序的,查询的连接部分现在可以不必分割和哈希就可以完成,因此将工作负荷的 26 + 5 + 3 = 34% 减少到4%。
排序,预排序,哈希
许多查询操作在操作可以进行之前都需要数据被分组。这些操作包括DISTINCT,UNION (它影响distinct),GROUP BY(和它的各个聚合函数)和JOIN。通常SQL SERVER会使用三种方法之一来达到分组的目的,其中第一个需要你的协助:
- 开心的发现数据已经被预排序进分组序列中
- 通过一个哈希操作来分组
- 把数据排序进分组序列
预排序
索引是你预排序数据的方式,也就是说,以最常用的序列提供给SQL SERVER,这就是为什么创建一个非聚集索引并具有包含列会有益于我们之前例子的原因。实际上,在最近的查询,如果你将鼠标放在Merge Join图标上,会出现这样一句话:“Match rows from two suitably sorted input streams, exploiting their sort order.”。这告知你这两个表使用了绝对最小的内存和处理器时间来进行连接。当鼠标移动到查询计划图标时,Suitably sorted input 是一个看到的精彩的短语,因为它验证了你的索引选择是正确的。
哈希
如果即将来的数据不是期望的顺序,SQL SERVER会使用一个哈希操作来对数据进行分组,哈希是一种比Sorting更耗费内存但更高效的一种技术,当进行 DISTINCT, UNION, and JOIN操作时,哈希比排序更有优势:各个行能够被传给下一个操作而不必等待所有的数据行都被哈希。然而当计算分组聚合时,在任何聚合值被传递给下一个操作之前,所有的数据行都必须被读取。
哈希信息所需要的内存数直接取决于分组数,因此如下哈希:
SELECT Gender, COUNT(*) FROM NewYorkCityCensus GROUP BY Gender
将会需要很少的内存,因为不管输入的数据行有多少,它将只有两个分组,男和女。在另一方面:
SELECT LastName, FirstName, COUNT(*) FROM NewYorkCityCensus GROUP BY LastName, FirstName
这个哈希会导致大量的分组,每一个都需要自己的内存空间。消耗这么多的内存使得哈希成为了一个不被期望的技术。
关于查询计划哈希的更多信息,请访问:http://msdn.microsoft.com/en-us/library/ms189582.aspx。
排序
如果数据没有被预排序(没有索引),并且SQL SERVER任务哈希不能被高效的完成,那么SQL SERVER便会对数据排序,这通常是最不令人想要的选项,因此如果一个Sort图标早早的出现在查询计划中,就检查是否可以优化你的索引,如果Sort图标出现在计划的末尾附近,很可能意味着SQL SERVER在将最终的输出结果排序为 ORDER BY 子句说起请求的顺序。并且这个顺序与用来解决查询的 JOINs, GROUP BYs, UNIONs的顺序不同,你几乎不能做什么来避免这个排序。
结论
查询计划告诉你SQL SERVER在执行查询时,打算使用或已经使用的方法学。它通过详细列出其将使用的操作来做这件事,SQL SERVER的数据行从一个操作流向下一个操作,并可以进行并行处理。
- 你可以以文本,图形或者XML查看这些信息
- 图形化的查询计划显示了各个操作相关的工作量
- 图形计划或许会建议一个可用来提高查询性能的索引
- 理解查询计划会帮助你计算和优化你的索引设计