Answers:
简短版:寻求更好
较短的版本:寻道通常要好得多,但是很多寻道(例如,由于不良的查询设计和令人讨厌的相关子查询所致,或者因为您在游标操作或其他循环中进行了许多查询)可能会比扫描,尤其是在查询最终可能会从受影响的表中的大多数行返回数据的情况下。
它有助于覆盖整个家庭的数据查找操作,以充分了解性能影响。
表格扫描:在没有与您的查询相关的索引的情况下,计划人员被迫使用表格扫描,这意味着要查看每一行。这可能导致从磁盘读取与表数据有关的每个页面,这通常是最坏的情况。请注意,对于某些查询,即使存在有用的索引,它也会使用表扫描-这通常是因为表中的数据太小,以至于遍历索引比较麻烦(如果是这种情况,您会期望假设索引的选择性很好,则计划随着数据的增长而变化。
具有行查找的索引扫描:没有找到可直接用于查找的索引,但是存在包含正确列的索引,因此可以使用索引扫描。例如,如果您有一个包含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
),则会看到它只读取了很小的数字页(也许只有一页)。
如果WHERE
or JOIN ... ON
子句的谓词可以与作为数据源的扫描同时运行,则可能会发生同样的情况。查询计划程序/运行程序有时可能非常聪明,可以将谓词推回数据源,从而以这种方式尽早终止扫描(有时您可能很聪明,可以重新排列查询以帮助这样做!)。尽管数据按照标准查询计划显示中的箭头从右到左流动,但是逻辑从左到右运行,并且每个步骤(从右到左)不一定要在下一步开始之前完成。在上面的简单示例中,如果您将查询计划中的每个块视为一个代理,则该代理会向该SELECT
代理询问TOP
一行,而该行又会向该代理询问TABLE SCAN
代理请求一个,然后SELECT
代理请求另一个,但是TOP
代理知道不需要,甚至不问表读取器,SELECT
代理得到“不再相关”的响应,并且知道所有工作都已完成。当然,许多操作阻碍了这种优化,因此在更复杂的示例中,表/索引扫描确实确实读取了每一行,但请注意不要得出任何扫描都必须是昂贵操作的结论。
通常,搜索效果良好,扫描效果较差。
搜索是查询能够有效利用索引并用来查找所需行的地方。
扫描是查询在整个索引中寻找所需内容的地方。
SQL如何选择?在查询优化器的内部,根据您的查询和可用索引以及与这些索引关联的统计信息来做出决定。
这里有些有趣的书可供阅读-这两本书都来自Red-Gate书店,网址为http://www.red-gate.com/community/books/
如果你想挖的主题,一个非常有用的书(至少对我来说)是由格兰特Fritchey SQL Server的执行计划,可以免费从展鹏这里。
如果您有以下查询
SELECT *
FROM myTable
SQL Server可能会使用索引扫描,因为它需要遍历所有行以显示所需的结果。
反之,
SELECT *
FROM myTable
WHERE myID = 1
肯定会导致寻求索引。SQL Server将使用myID索引的B树结构,检索适当的行将更快。