在查看运行缓慢的查询的执行计划时,我注意到一些节点是索引查找,而有些节点是索引扫描。

索引查找和索引扫描有什么区别?

哪个性能更好?

SQL如何选择另一个?

我意识到这是3个问题,但我想回答第一个问题将解释其他内容。

评论

您对use-the-index-luke有很好的参考。

并非所有扫描都不好-有时这是满足查询要求的最有效方法。另请注意,并非所有搜索都是搜索-通常它们实际上是范围扫描,并且搜索仅指示其如何到达范围的起点。

@AaronBertrand,但是如果它到达范围的开头并读取它,则基本上意味着您仍然需要数据。此外,它还会搜索范围的结尾。

#1 楼

简短版本:寻道要好得多

更少简短版本:寻道通常要好得多,但是寻道很多(例如,不良的查询设计和令人讨厌的相关子查询,或者是由于游标操作或其他循环中的许多查询)可能比扫描更糟糕,尤其是如果您的查询最终可能从受影响的表中的大多数行返回数据时。

它有助于覆盖整个表

表扫描:由于没有任何与您的查询相关的索引,计划者被迫使用表扫描,这意味着要查看每一行。这可能导致从磁盘读取与表数据有关的每个页面,这通常是最坏的情况。请注意,对于某些查询,即使存在有用的索引,它也会使用表扫描-这通常是因为表中的数据太小,以至于遍历索引比较麻烦(如果是这种情况,您会期望计划随着数据的增长而变化,并假设索引的选择性度量良好。)

使用行查找的索引扫描:没有找到可直接用于查找的索引,但包含右边的列可以使用索引扫描。例如,如果您有一个包含20列的大型表,并且在column1,col2,col3上具有索引,则发出SELECT col4 FROM exampletable WHERE col2=616,在这种情况下,扫描索引以查询col2优于扫描整个表。一旦找到匹配的行,则需要将数据页读取到拾取col4以进行输出(或进一步联接),这就是在查询计划中看到“书签查找”阶段时所要执行的操作。

不进行行查找的索引扫描:如果上面的示例是SELECT col1, col2, col3 FROM exampletable WHERE col2=616,则不需要额外的努力来读取数据页:一旦找到与col2=616相匹配的索引行,所有请求的数据就会知道。这就是为什么有时您会看到列永远不会被搜索,但是可能会被要求输出的列添加到索引末尾的原因-它可以节省行查找。仅出于这个原因(仅出于这个原因)将列添加到索引时,请使用INCLUDE子句将其添加到引擎,以告知引擎不需要针对这些列基于查询优化索引布局(这可以加快对这些列的更新)。索引扫描也可能来自没有过滤子句的查询:SELECT col2 FROM exampletable将扫描此示例索引而不是表页。

索引查找(有或没有行查找):在查找中,并非所有索引被认为。对于查询SELECT * FROM exampletable WHERE c1 BETWEEN 1234 AND 4567,查询引擎可以通过对c1上的索引进行基于树的搜索来找到要匹配的第一行,然后可以按顺序导航索引,直到到达范围的末尾(与查询c1=1234,因为即使对于=操作,也可能有很多行符合条件。这意味着只需要读取相关的索引页面(加上一些初始搜索所需的索引页面),而不是索引(或表)中的每个页面。

聚集索引:使用聚集索引时,表数据存储在该索引的叶节点中,而不是存储在单独的堆结构中。这意味着使用该索引查找行之后,无论需要什么列,都将不需要进行任何额外的行查找(除非您拥有页外数据,例如TEXT列或VARCHAR(MAX)包含长数据的列)。

由于这个原因,您只能有一个聚集索引[1],聚集索引是您的表,而不是具有单独的堆结构,因此,如果您使用一个[2],请仔细选择放置它的位置,以获取最大的收益。 br />
还请注意,由于表的“聚集键”包含在表的每个非聚集索引中,因此聚集索引是一个好主意。

[1]实际上,通过定义覆盖或包括表中每一列的非聚集索引,您可以有效地拥有多个聚集索引,但是这很可能浪费空间,因此会影响写入性能。考虑确保确实需要这样做。

[2]当我说“如果您使用聚集索引”时,请注意,通常建议在每个表上都使用一个。除所有经验法则外,还有一些例外,最常见的示例是批量插入和无序读取(可能是ETL进程的登台表),这些表是最常见的反例。

其他要点:不完整扫描:

重要的是要记住,根据查询的其余部分,表/索引扫描实际上可能不会扫描整个表-如果逻辑允许查询计划,则可能导致它扫描提前中止。最简单的示例是SELECT TOP(1) * FROM HugeTable-如果您查看查询计划,则会看到扫描仅返回了一行,并且如果您查看IO统计信息(SET STATISTICS IO ON; SELECT TOP(1) * FROM HugeTable),则会看到它只读取了页面数少(也许只有一页)。

如果WHEREJOIN ... ON子句的谓词可以与作为数据源的扫描同时运行,则可能会发生同样的情况。查询计划程序/运行程序有时可能非常聪明,可以将谓词推回数据源,从而以这种方式尽早终止扫描(有时您可能很聪明地重新安排查询以帮助这样做!)。尽管数据按照标准查询计划显示中的箭头从右到左流动,但逻辑从左到右运行,并且每个步骤(从右到左)不一定要在下一步开始之前完成。在上面的简单示例中,如果您将查询计划中的每个块视为一个代理,则SELECT代理向TOP代理询问一行,而行又向TABLE SCAN代理询问一个代理,然后SELECT代理询问另一个代理,但是TOP代理知道根本不需要问表读取器,SELECT代理会收到“不再相关”的响应,并且知道所有工作都已完成。当然,许多操作都会阻止这种优化,因此在更复杂的示例中,表/索引扫描确实确实读取了每一行,但请注意不要得出任何扫描都必须是昂贵操作的结论。

#2 楼

通常,搜索是好的,扫描是不好的。

搜索是查询能够有效利用索引并用来查找所需行的地方。

扫描是查询在整个索引中寻找所需内容的地方。

SQL如何选择?在查询优化器的内部,根据您的查询和可用的索引以及与这些索引相关的统计信息来做出决定。

有几本书可能很有趣此处-都来自Red-Gate书店,位于http://www.red-gate.com/community/books/


Grant Fritchey编写的SQL Server执行计划
Benjamin Nevarez编写的查询优化器
Holger Schmeling编写的SQL Server Statistics


评论


对于同一计划,单个表扫描是好的,一百万次搜索是不好的。因此,您的第一句话并不完全正确。

–玛丽安
13年5月20日在8:36

确实,索引查找和索引扫描各有其用途,如果没有基础表和查询的上下文,您不能说一个比另一个更好。在大多数情况下,如果表的统计信息不正确,执行计划可能会出现次优的情况,例如在索引扫描中错误地选择了索引查找,反之亦然。

– jyao
17年8月2日在20:52

#3 楼

如果您想研究这个主题,一本非常有用的书(至少对我而言)是Grant Fritchey撰写的《 SQL Server执行计划》,可在RedGate上免费获得。

如果您有诸如

SELECT *
FROM myTable
这样的查询,则SQL Server可能会使用索引扫描,因为它需要遍历所有行来显示所需的结果。相反,

SELECT *
FROM myTable
WHERE myID = 1


肯定会导致索引查找。 SQL Server将使用myID索引的B树结构,检索适当的行将更快。

评论


我不知道我是否“肯定”同意-即使索引将myID作为前导列,搜索也可能不是最佳答案(取决于很多因素,例如它是否唯一-可能是在客户表中为true,但在订单表中为customerID则不是,需要覆盖多少列但不在索引中,等等)。

–亚伦·伯特兰(Aaron Bertrand)
13年11月18日在22:29

我认为这个答案并没有真正涵盖所提出的问题。

–Zero3
17年2月23日在21:05

#4 楼

其他人已经很好地定义了搜索和扫描之间的区别。在这种情况下,您的查询本身和执行计划者应为您提供所需的信息,以查看在每个部分中哪些值用作查询的谓词(过滤器)。通常,最好始终在外键上添加非聚集索引,并且根据程序代码中的用例,您可能希望研究创建其他多列索引或包含的列索引。使用此处提供的术语,谷歌搜索将为每个示例提供不错的结果。要返回列C和列E的值,您可能希望使用包含列C和E的INCLUDE选项在列A和B上创建索引。这样一来,单个索引查找将返回您需要的所有内容,因为不需要进行查找以检索同一行上的其他值(C和E)。