索引搜索与索引扫描


64

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

索引搜索和索引扫描之间有什么区别?

哪个表现更好?

SQL如何选择一个?

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


6
您对use-the-index-luke有很好的参考。
玛丽安

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

@AaronBertrand,但是如果到达范围的开头并读取它,则基本上意味着您仍然需要数据。此外,它会寻找范围的终点。
乔治·波列沃

Answers:


76

简短版:寻求更好

较短的版本:寻道通常要好得多,但是很多寻道(例如,由于不良的查询设计和令人讨厌的相关子查询所致,或者因为您在游标操作或其他循环中进行了许多查询)可能会比扫描,尤其是在查询最终可能会从受影响的表中的大多数行返回数据的情况下。

它有助于覆盖整个家庭的数据查找操作,以充分了解性能影响。

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

具有行查找的索引扫描:没有找到可直接用于查找的索引,但是存在包含正确列的索引,因此可以使用索引扫描。例如,如果您有一个包含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],则选择仔细放置它以获得最大的收益。

还要注意,聚集索引是因为表的“聚集键”并包含在表的每个非聚集索引中,因此,宽聚集索引通常不是一个好主意。

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

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

补充说明:扫描不完整:

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

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


6

通常,搜索效果良好,扫描效果较差。

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

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

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

这里有些有趣的书可供阅读-这两本书都来自Red-Gate书店,网址http://www.red-gate.com/community/books/

  • SQL Server执行计划,作者Grant Fritchey
  • 在查询优化器内部,作者Benjamin Nevarez
  • Holger Schmeling撰写的SQL Server统计信息

7
对于同一计划,单个表扫描是好的,一百万次搜索是不好的。因此,您的第一个陈述并不完全正确。
玛丽安

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

5

如果你想挖的主题,一个非常有用的书(至少对我来说)是由格兰特Fritchey SQL Server的执行计划,可以免费从展鹏这里

如果您有以下查询

SELECT *
FROM myTable

SQL Server可能会使用索引扫描,因为它需要遍历所有行以显示所需的结果。

反之,

SELECT *
FROM myTable
WHERE myID = 1

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


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

我认为这个答案并不能真正涵盖所提出的问题。
Zero3 '17

5

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

但是举个例子,假设您的代码正在查询给定过滤器上的A列和B列,但是您还想返回C列和E列的值,您可能想使用INCLUDE在A列和B列上创建索引选项包含C和E列。这样,单个索引查找将返回您需要的所有内容,因为无需执行查找即可检索同一行中的其他值(C和E)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.