阅读查询计划:通往SQL Server索引级别9的阶梯


原文连接:   
http://www.sqlservercentral.com/articles/Stairway+Series/72441/

sql

阅读查询计划:通往SQL Server索引级别9的阶梯数据库

大卫•杜兰特2011/10/05服务器

该系列数据库设计

本文是楼梯系列的一部分:SQL Server索引的阶梯函数

索引是数据库设计的基础,并告诉开发人员使用数据库很是了解设计器的意图。不幸的是,当性能问题出现时,索引经常被添加到过后。这里最后是一个简单的系列文章,它应该能让任何数据库专业人员快速“跟上”他们的步伐工具

在整个楼梯中,咱们常常声明某个查询以某种方式执行;咱们引用生成的查询计划来支持咱们的语句。管理工做室的估计和实际查询计划的显示能够帮助您肯定索引的优势或不足。所以,这个级别的目的是让您充分了解查询计划,您能够:sqlserver

当您阅读这段楼梯时,验证咱们的断言。性能

肯定您的索引是否有利于您的查询。优化

有许多关于阅读查询计划的文章,包括MSDN库中的一些文章。咱们的目的不是扩大或取代它们。事实上,咱们将在这个层面上为他们中的许多人提供连接/参考。一个很好的起点是图形显示执行计划(http://msdn.microsoft.com/en-us/library/ms178071.aspx)。其余有用的资源包括Grant Fritchey的书,SQL Server执行计划(免费提供电子书格式),还有Fabiano Amorim的一系列关于查询计划输出中发现的操做符的简单讨论文章(http://www.simple-talk.com/author/fabiano - amorim/)。设计

图形查询计划

查询计划是SQL服务器执行查询的指令集。SQL Server Management Studio将以文本、图形或XML格式为您显示查询计划。例如,考虑如下简单查询:

SELECT LastName, FirstName, MiddleName, Title 
FROM Person.Contact
WHERE Suffix = 'Jr.'
ORDER BY Title

 

 

图1 -图形格式的实际查询计划

或者,它能够被视为文本:

|——排序(按:[AdventureWorks]。[人] (联系) [标题]ASC))

|——汇集索引

扫描(对象:([AdventureWorks],[人] [联系] [PK_Contact_ContactID]),

地点:([AdventureWorks],[人] (接触) (后缀)= N 'Jr。'))

或者做为一个XML文档,开始这样:

 

查询计划的显示可被要求以下:

要请求图形化查询计划,请使用Management Studio的SQL Editor工具栏,该工具栏有“显示估计执行计划”和“包含实际执行计划”按钮。“显示估计执行计划”选项将当即显示所选的TSQL代码的查询计划图,而不执行查询。“包含实际执行计划”按钮是一个开关,一旦你选择了这个选项,你执行的每个查询批量都会在一个新标签中显示你的查询计划图,以及结果和消息。这个选项如图1所示。

要请求文本查询计划,请使用SET SHOWPLAN_TEXT语句。打开文本版本将会关闭图形化版本,不会执行任何查询。

阅读图形查询计划

图形化查询计划一般从右到左读取;最右边的图标表明数据收集流中的第一步。这一般是访问堆或索引。您将看不到这里使用的单词表;相反,您将看到集群索引扫描或堆扫描。这是第一个查看哪些索引(若是有的话)正在使用的地方。

图形化查询计划中的每一个图标表示一个操做。附加信息的可能的图标,看到图形在http://msdn.microsoft.com/en-us/library/ms175913.aspx上执行计划图标

链接操做的箭头表示行,从一个操做流到下一个操做。

将鼠标放在图标或箭头上,将会显示更多信息。

不要将操做视为步骤,由于这意味着必须在下一次操做开始以前完成一个操做。这并不必定是真的。例如,当一个WHERE子句被评估时,也就是说,当执行一个筛选器操做时,会一次对行进行评估;并非全部的。行能够移动到下一个操做,而后在随后一行到达过滤器操做。另外一方面,排序操做必须在第一行移动到下一个操做以前完整地完成。

使用一些额外的信息

图形化查询计划显示了两个可能有用的信息,这些信息不是计划自己的一部分;建议指标及每次操做的相对成本。

在上面的例子中,建议的索引,以绿色表示,并被空间需求截断,建议在联系人表的后缀列上有一个非汇集索引;包含标题、FirstName、MiddleName和LastName列。

这个计划的每一个操做的相对成本告诉咱们排序操做占总成本的5%,而表扫描是95%的工做。所以,若是咱们想要提升这个查询的性能,咱们应该处理表扫描,而不是排序;这就是为何要提出一个指数。若是咱们建立推荐索引,以下所示:

CREATE NONCLUSTERED INDEX IX_Suffix ON Person.Contact
(
Suffix
)
INCLUDE ( Title, FirstName, MiddleName, LastName )

而后从新运行查询,咱们的读数从569降低到3;下面显示的新查询计划说明了缘由。

 

新的非汇集索引,其索引键的后缀,有“WHERE后缀= 'Jr .”。”条目汇集在一块儿;所以,IO的还原须要检索数据。结果是,排序操做,与以前的计划相同的排序操做,如今占查询总成本的75%,而不是它所花费的5%。所以,最初的计划须要75 / 5 = 15倍的工做量,以收集与当前计划相同的信息。

因为咱们的WHERE子句只包含一个等式运算符,因此咱们能够经过将标题列移动到索引键来提升索引值,例如:

IF  EXISTS (SELECT * FROM sys.indexes
WHERE OBJECT_ID = OBJECT_ID(N'Person.Contact')
 
AND name = N'IX_Suffix')
DROP INDEX IX_Suffix ON Person.Contact
CREATE NONCLUSTERED INDEX IX_Suffix ON Person.Contact
(
Suffix, Title
)
INCLUDE ( FirstName, MiddleName, LastName )

如今,须要的条目仍然汇集在索引中,在每一个集群中它们都在请求的序列中;如新的查询计划所示,如图2所示。

 

图2 -重建非汇集索引后的查询计划

如今,该计划代表再也不须要排序操做。在这一点上,咱们能够放弃咱们很是有益的覆盖指数。它将联系表恢复到咱们开始时的样子;当咱们进入下一个主题时,咱们但愿它进入状态。

查看平行流

若是两个行能够并行处理,它们将在图形显示中出如今上面和下面。箭头的相对宽度表示每一个流中处理了多少行。

例如,下面的链接扩展了之前的查询,以包含销售信息:

 

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 -一个链接的查询计划

快速浏览一下计划告诉咱们一些事情:

两张桌子同时扫描。

大部分的工做都花在了扫描表格上。

出现更多的行或SalesOrderHeader表,而不是从表中取出。

这两张表没有按相同的顺序排列;所以,将每一个SalesOrderHeader与它的联系人行匹配将须要额外的努力。在这种状况下,使用散列匹配操做。(稍后将详细介绍散列)。

排序选定行所需的工做量能够忽略不计。

即便单独的行流也能够被分割成不一样的流,每一行均可以使用并行处理。例如,若是咱们将上述查询中的WHERE子句更改成NULL。

将会返回更多的行,由于95%的联系人行有一个NULL后缀。新的查询计划反映了这一点,如图4所示。

 

 

图4 -一个并行查询计划

新计划还向咱们代表,愈来愈多的接触行致使匹配和排序操做成为该查询的关键路径。若是咱们须要改进它的性能,咱们必须首先攻击这两个操做。一样,包含列的索引也会有所帮助。

与大多数链接同样,咱们的示例经过外键/主键关系链接两个表。其中一个表,Contact,是由ContactID进行排序的,这也是它的主键。在另外一个表SaleOrderHeader中,ContactID是一个外键。由于ContactID是一个外键,因此对于ContactID访问的SaleOrderHeader数据的请求,例如咱们的join示例,多是一个常见的业务需求。这些请求将受益于ContactID的索引。

每当索引外键列时,老是要问本身,若是有的话,应该将列添加到索引中。在咱们的例子中,咱们只有一个查询,而不是支持的查询系列。所以,咱们仅包含的列将是OrderDate。为了支持针对SaleOrderHeader表的面向对象的查询系列,咱们将在须要时包含更多的SaleOrderHeader列,以支持这些额外的查询。

咱们的CREATE INDEX语句是:

CREATE NONCLUSTERED INDEX IX_ContactID ON Sales.SalesOrderHeader
(
ContactID
)
INCLUDE ( OrderDate )

 

图5 -每一个表上有一个支持索引的链接查询计划

由于如今两个输入流都由链接谓词列进行排序,ContactID;查询的链接部分能够在不分割流和不进行哈希的状况下完成;所以,减小26 + 5 + 3 = 34%的工做负载降低到工做负载的4%。

排序、预分类和散列

许多查询操做要求在执行操做以前将数据分组。这些包括不一样的、联合的(意味着不一样的)、组(以及它的各类聚合函数),并加入。一般,SQL Server将使用三种方法中的一种来实现这个分组,第一个方法须要您的帮助:

很高兴地发现数据已经被按分组顺序显示出来了。

经过执行散列操做来分组数据。

将数据排序到分组序列中。

预分类

索引是您进行数据显示的方式;也就是说,在常常须要的序列中向SQL Server提供数据。这就是为何建立非汇集索引(每一个包含包含列)的缘由,使咱们之前的例子受益。实际上,若是您将鼠标放在最近查询中的Merge Join图标上,那么这个短语匹配两个适当排序的输入流中的行,利用它们的排序顺序。就会出现。这告诉您两个表/索引的行使用了绝对最小的内存和处理器时间。适当的排序输入是一个很好的短语,当鼠标在查询计划图标上进行鼠标操做时,它能够验证您选择的索引。

哈希

若是传入的数据不是理想的序列,那么SQL Server可能会使用散列操做来分组数据。哈希是一种可使用大量内存的技术,但一般比排序更有效。在执行不一样的、联合和链接操做时,哈希比在单独的行中排序能够传递到下一个操做,而没必要等待全部传入的行被散列。然而,在计算分组集合时,必须读取全部输入行,而后才能将任何聚合值传递给下一个操做。

散列信息所需的内存数量与所需的组数直接相关。所以,hashing须要解决:

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将对数据进行排序。这一般是最不可取的选择。所以,若是在计划的早期出现了排序图标,请检查是否能够改进索引。若是Sorticon在计划的末尾出现,它可能意味着SQL Server将最终输出排序为ORDER by子句所请求的序列;这个序列与用于解析查询的链接、组BYs和union的序列不一样。一般,在这一点上,你几乎没有办法避免这种状况。

结论

查询计划向您展现了SQL Server打算使用或使用的方法来执行查询。它经过详细描述将要使用的操做、从操做到操做的行流程以及所涉及的并行性来实现。

您将此信息视为文本、图形或XML显示。

图形化计划显示了每一个操做的相对工做负载。

图形化的计划能够建议一个索引来提升查询的性能。

了解查询计划将帮助您评估和优化您的索引设计。

本文是通往SQL Server索引楼梯的楼梯的一部分

相关文章
相关标签/搜索