Gre*_*reg 66 performance index sql-server-2005 sql-server execution-plan
查看运行缓慢的查询的执行计划,我注意到有些节点是索引查找,有些是索引扫描。
索引查找和索引扫描有什么区别?
哪个表现更好?
SQL 如何选择一个?
我意识到这是 3 个问题,但我认为回答第一个会解释其他问题。
Dav*_*ett 77
简短版本:seek 好多了
不那么短的版本:seek 通常要好得多,但是大量的seek(例如,由于糟糕的查询设计和令人讨厌的相关子查询导致,或者因为您在游标操作或其他循环中进行了许多查询)可能比 a扫描,尤其是当您的查询可能最终从受影响表中的大多数行返回数据时。
它有助于覆盖整个系列的数据查找操作,以充分了解性能影响。
表扫描:由于根本没有与查询相关的索引,规划器被迫使用表扫描,这意味着查看每一行。这可能导致从磁盘读取与表数据相关的每个页面,这通常是最坏的情况。请注意,对于某些查询,即使存在有用的索引,它也会使用表扫描 - 这通常是因为表中的数据太小以至于遍历索引更麻烦(如果是这种情况,您会期望计划随着数据的增长而改变,假设索引的选择性度量是好的)。
使用行查找的索引扫描:如果没有找到可直接用于查找的索引,但存在包含正确列的索引,则可以使用索引扫描。例如,如果您有一个包含 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),您会看到它只读取了一个非常小的数字页数(可能只有一页)。
如果WHEREorJOIN ... ON子句的谓词可以与作为数据源的扫描同时运行,也会发生同样的情况。查询规划器/运行器有时可以非常聪明地将谓词推回数据源以允许以这种方式提前终止扫描(有时您可以聪明地重新排列查询以帮助它这样做!)。虽然数据按照标准查询计划显示中的箭头从右到左流动,但逻辑从左到右运行,并且每个步骤(从右到左)不一定在下一个可以开始之前运行完成。在上面的简单示例中,如果您将查询计划中的每个块视为代理,SELECT代理会向代理询问TOP一行,然后代理会询问TABLE SCAN一个代理,然后SELECT代理要求另一个,但TOP代理知道没有必要甚至不费心去问表阅读器,SELECT代理得到“不再相关”的响应,并且知道所有工作都已完成。许多操作当然会阻止这种优化,因此在更复杂的示例中,表/索引扫描确实会读取每一行,但请注意不要得出任何扫描都必须是昂贵操作的结论。
一般来说,搜索是好的,扫描是坏的。
搜索是查询能够有效使用索引的地方,并使用它来查找所需的行。
扫描是查询查看整个索引以试图找到它需要的内容的地方。
SQL如何选择?在查询优化器的内部深处,根据您的查询和可用索引以及与这些索引关联的统计信息做出决定。
这里有几本书可能会引起您的兴趣——都来自 Red-Gate 书店,网址为http://www.red-gate.com/community/books/
如果你想深入研究这个主题,一本非常有帮助的书(至少对我来说)是 Grant Friitchey 的 SQL Server Execution Plans,可在 RedGate此处免费获得。
如果您有一个查询,例如
SELECT *
FROM myTable
Run Code Online (Sandbox Code Playgroud)
SQL Server 可能会使用索引扫描,因为它需要遍历所有行以显示所需的结果。
相反,
SELECT *
FROM myTable
WHERE myID = 1
Run Code Online (Sandbox Code Playgroud)
肯定会导致索引搜索。SQL Server 将使用myID 索引的B 树结构,检索正确的行会快得多。
其他人已经很好地定义了搜索和扫描之间的差异。在这种情况下,您的查询本身和执行计划程序应该为您提供所需的信息,以查看在每个部分中将哪些值用作查询的谓词(过滤器)。通常,总是在外键上添加非聚集索引是一个很好的做法,并且根据程序代码中的用例,您可能希望研究创建额外的多列索引或包含的列索引。使用此处提供的术语,谷歌搜索将在每个示例上给出不错的结果。
但举个例子,假设您的代码在给定的过滤器上查询 A 列和 B 列,但您还想返回 C 列和 E 列的值,您可能希望使用 INCLUDE 在 A 列和 B 列上创建索引包含列 C 和 E 的选项。这样,单个索引查找将返回您需要的所有内容,因为无需进行查找即可检索同一行上的其他值(C 和 E)。
| 归档时间: |
|
| 查看次数: |
56421 次 |
| 最近记录: |