为什么此查询不使用我的非聚集索引,我该如何进行查询?


12

在跟进有关提高查询性能的这个问题之后,我想知道是否有一种方法可以使我的索引默认使用。

该查询运行大约2.5秒:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

这大约需要33毫秒:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

[ID]字段(pk)上有一个聚集索引,[DateEntered],[DeviceID]上没有聚集索引。第一个查询使用聚集索引,第二个查询使用我的非聚集索引。我的问题分为两个部分:

  • 为什么,由于两个查询在[DateEntered]字段上都有WHERE子句,服务器为什么在第一个而不是第二个上使用聚集索引?
  • 即使没有orderby,如何使该查询默认使用非聚集索引?(或者为什么我不想要那种行为?)

DateEntered是一个DateTime,在这种情况下,我使用的是日期部分,但有时我会同时查询日期和时间。
Nate 2012年

Answers:


9

在第一个查询中,将根据我先前在阈值中说明的阈值进行表扫描:是否可以提高具有数百万行的狭窄表的查询性能?

(最有可能没有该TOP 1000子句的查询将返回超过46k行。或者某些行介于35k和46k之间(灰色区域;-))

第二个查询,必须排序。由于您按需要的顺序对NC索引进行了排序,因此对于优化器来说,使用该索引会更便宜,然后对聚簇索引进行书签查找,以获取缺少的列,这与进行聚簇索引扫描和需要订购。

反转ORDER BY子句中列的顺序,因为NC INDEX没用,您将返回到聚集索引扫描。

编辑忘记了第二个问题的答案,为什么你不想要这个

使用非聚簇的非覆盖索引意味着在NC索引中查找rowID,然后必须在聚簇索引中查找缺少的列(聚簇索引包含表的所有列)。在聚集索引中查找缺失列的IO是随机IO。

关键是随机。因为对于在NC索引中找到的每一行,访问方法都必须在聚簇索引中查找新页面。这是随机的,因此非常昂贵。

现在,另一方面,优化器也可以进行聚簇索引扫描。它可以使用分配映射表来查找扫描范围,并只是开始大块读取聚簇索引。这是连续的并且便宜得多。(只要您的表没有碎片:-))缺点是,需要读取整个索引。这对您的缓冲区和潜在的大量IO都是不利的。但是顺序IO

在您的情况下,优化程序确定行数介于35k和46k之间,对于完整的聚集索引扫描而言,它的成本较低。是的,这是错误的。而且在很多情况下,狭窄的非聚集索引没有针对选择WHERE子句或大型表的问题,这会出错。(您的表更糟,因为它的表也很窄。)

现在,添加ORDER BY使得扫描整个聚簇索引然后排序结果变得更加昂贵。取而代之的是,优化器认为使用已就绪的有序NC索引并为书签查找支付随机IO损失会更便宜。

因此,您的订单依据是一种完美的“查询提示”解决方案。但是,在某个时候,一旦查询结果很大,书签查找随机IO的代价就会很大,从而变慢。我认为优化程序将在此之前将计划更改回聚簇索引扫描,但是您不确定。

在您的情况下,只要您按照enterdate的顺序对插入内容进行了排序(如聊天和上一个问题(请参阅链接)中所述),则最好在enterDate列上创建聚簇索引。


20

使用不同的语法表达查询有时可以帮助将您希望使用非聚集索引的信息传达给优化器。您应该在下面的表格中找到所需的计划:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

查询计划

将该计划与强制非聚集索引并带有提示时产生的计划进行比较:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

强制索引提示计划

计划本质上是相同的(“键查找”无非是对聚簇索引的搜索)。两种计划形式都只会对非聚集索引执行一次查找,并且最多只能对聚集索引进行1000次查找。

重要的区别在于Top运算符的位置。定位在两个搜索之间,Top阻止优化程序用对聚簇索引的逻辑等效扫描来替换两个搜索操作。优化程序通过用等效的关系操作替换逻辑计划的某些部分来工作。Top不是关系运算符,因此重写会阻止转换为聚集索引扫描。如果优化程序能够重新定位Top运算符,则由于成本估算的工作方式,它仍然会优先选择扫描而不是搜索+查找。

搜寻的成本

在非常高的水平上,优化程序的扫描和查找成本模型非常简单:它估计320次随机查找的成本与读取1350页扫描的费用相同。这可能与任何特定的现代I / O系统的硬件功能几乎没有相似之处,但它确实可以作为实际模型很好地工作。

该模型还做出了许多简化的假设,其中主要的假设是,假定每个查询都从缓存中没有数据或索引页开始。这意味着每个I / O都会产生物理I / O,尽管实际上很少出现这种情况。即使具有冷高速缓存,预取和预读也意味着所需的页面实际上很可能在查询处理器需要它们的时候就已经存在于内存中。

另一个考虑因素是,对不在内存中的行的第一个请求将导致整个页面从磁盘中获取。随后对同一页面上的行的请求很可能不会导致物理I / O。成本核算模型确实包含逻辑,以考虑到类似这样的影响,但这并不完美。

所有这些(以及更多)意味着优化器倾向于比可能更早地切换到扫描。如果发生物理操作,则随机I / O仅比“顺序” I / O“昂贵得多”-确实访问内存中的页面非常快。即使在需要物理读取的情况下,由于碎片,扫描可能根本不会导致顺序读取,并且可以并置查找,以使该模式实质上是顺序的。加上现代I / O系统(尤其是固态)不断变化的性能特征,整个过程看起来非常不稳定。

行目标

计划中高层管理人员的出现改变了成本核算方法。优化器足够聪明,可以知道使用扫描查找1000行可能不需要扫描整个聚集索引-一旦找到1000行,它就可以停止。它在Top运算符处设置了1000行的“行目标”,并使用统计信息从那里开始进行工作,以估计行源中期望需要多少行(在这种情况下为扫描)。我在这里写了有关此计算的详细信息。

此答案中的图像是使用SQL Sentry Plan Explorer创建的。

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.