【问题标题】:What is best among clustered index scan vs non-clustered index seek聚集索引扫描与非聚集索引查找之间最好的是什么
【发布时间】:2022-01-08 10:21:34
【问题描述】:

我正在尝试找出最佳选择,我的主要要求是减少 IO。

  • 我有一个包含 500M 记录的表,下面提到的查询是在表上选择默认的聚集索引扫描。
  • 我试图创建一个覆盖非聚集索引,但它仍然选择聚集索引扫描作为默认值。所以我强迫它使用覆盖索引,我的观察是逻辑读取从 3M 下降到 1M,但 CPU 和持续时间增加了。
  • 我正在尝试了解这种行为以及这里的最佳做法。

查询:

set statistics time, io on;
select 
    min(CampaignID), 
    max(CampaignID) 
from Campaign
where datecreated < dateadd(day, -90, getutcdate())
go
CREATE NONCLUSTERED INDEX [NCIX] 
ON [dbo].[Campaign](DateCreated)
INCLUDE (Campaignid)
go
select 
    min(CampaignID), 
    max(CampaignID) 
from Campaign with (index = NCIX)
where datecreated < dateadd(day, -90, getutcdate())
set statistics time, io off;

消息:

(1 row affected)
Table 'Campaign'. Scan count 2, logical reads 3548070, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

(8 rows affected)

(1 row affected)

 SQL Server Execution Times:
   CPU time = 14546 ms,  elapsed time = 14723 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 3 ms.

(1 row affected)
Table 'Campaign'. Scan count 1, logical reads 1191017, physical reads 0, page server reads 0, read-ahead reads 19, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

(6 rows affected)

(1 row affected)

 SQL Server Execution Times:
   CPU time = 163953 ms,  elapsed time = 164163 ms.

执行计划:

Complete execution plan

【问题讨论】:

  • 没有“最佳”。在某些情况下,一种表现更好,而另一种则表现更好。在您的示例中,扫描速度更快,SQL Server 正在使用它,因为它知道(正确猜测)扫描表比使用索引搜索所需的工作更少,然后查找所有匹配的记录。我们不知道你的索引,我们不知道你的数据,很难说别的。以我的经验,最好的办法是让 SQL Server 在大多数情况下进行选择。您可以提供帮助,但我不会使用索引提示。
  • 谢谢,我一定会考虑您的意见。但是根据您的要求,我已经包含了索引定义,以便了解我的要求。
  • 对这里的逻辑读取有什么想法吗?选择的 sql 服务器正在执行 350 万次逻辑读取,而带有索引提示的 SQL 服务器正在执行 110 万次逻辑读取。
  • 索引很好,有了这个,我可以说SQL Server要么按日期顺序查询索引,很可能得到大多数/很多记录,然后需要排序(聚合) 用于获取 MIN 和 MAX 的 ID。另一种选择是遍历聚集索引上的数据并按顺序获取 ID(我假设您的聚集索引键是 ID)。当匹配您的 where 条件的记录数接近记录总数时,第二个更快。
  • 分享执行计划的好方法是上传到Paste The Plan 并将链接添加到您的问题。计划的图像并不是故事的全部。

标签: sql-server sql-execution-plan


【解决方案1】:

首先,没有“最佳”运算符。有时读取更多数据比读取一些数据并对其进行按摩以获得我们的结果更有效。 “最好”,因为几乎所有事情都是相对的。

让我们试着了解在 cmets 中发生了什么...

查询

select 
    min(CampaignID), 
    max(CampaignID) 
from Campaign
where datecreated < dateadd(day, -90, getutcdate())

上面写着:

我想要日期小于固定日期的任何记录的第一个和最后一个 ID(最小值/最大值)。

集群

没有索引/索引提示的第一个查询执行了 SQL Server 认为比读取任何索引更便宜的操作,即使它需要更多 IO(磁盘使用)。这是因为在验证表中的记录时找到最小值和最大值比选择表的一半,然后重新排序/聚合它们找到完全相同的信息要便宜。

聚集索引将所有数据存储在磁盘上,并按键列进行逻辑排序,在本例中为 CampaignID(我假设)。这意味着,找到最小和最大 ID 很容易:最小值是与条件匹配的第一个 ID -> 让我们从第一个 ID 检查每个 ID,并在找到日期所在的记录后停止(这将很可能是第一个)。最大值是从索引末尾开始匹配条件的第一条记录。

以日期为键的索引

CREATE NONCLUSTERED INDEX [NCIX] 
ON [dbo].[Campaign](DateCreated)
INCLUDE (Campaignid)

有了第一个索引(日期作为关键列),SQL Server 可以使用日期来过滤数据,没错,但它对排序没有帮助。它仍然需要检查该索引中的每条记录,并从一组可能无序的值中找出最小值和最大值。

以 ID 为键的索引

CREATE NONCLUSTERED INDEX [NCIX] 
ON [dbo].[Campaign](Campaignid)
INCLUDE (DateCreated)

对于第二个索引,其中 ID 是键列,SQL Server 可以使用与聚集键相同的技巧。唯一的区别是没有其他数据可以读取,只有 ID 和日期,比整条记录要小得多,因此它可以容纳更少的页面,并且需要更少的 IO。

即使没有索引提示,SQL Server 也很可能会选择第二个索引。

第二个索引的工作原理(查询近似)

您可以通过

获得最小的Campaignid
SELECT TOP(1)
  Campaignid
FROM
  [dbo].[Campaign]
WHERE
  datecreated < dateadd(day, -90, getutcdate())
ORDER BY
  Campaignid ASC

和非常相似的查询的最大值

SELECT TOP(1)
  Campaignid
FROM
  [dbo].[Campaign]
WHERE
  datecreated < dateadd(day, -90, getutcdate())
ORDER BY
  Campaignid DESC

如果您将它们作为子查询交叉加入,您几乎可以得到执行计划所描述的内容。

备注

在这里我要补充一点:仅针对一个查询进行优化并不总是最好的策略。您无法针对所有内容进行优化,如果此查询每天/每周/每季度运行一次,那么使用集群键的 14-15 秒运行时间很可能不会造成任何伤害。如果索引对其他查询没有帮助,我不会创建它,除非它是关键任务查询。

【讨论】:

  • 感谢您对此的详细解释。我们环境中的这个特定查询每天都会运行,我们看到的影响是针对在此期间尝试访问的其他调用。希望这有助于解决这个问题。
  • @SivaDasari 由于您的查询使用 DATE(并且不关心时间),您可以在午夜运行一次并将结果存储在某处,然后一整天重复使用这些结果。
猜你喜欢
  • 2015-01-27
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-26
相关资源
最近更新 更多