聚集索引扫描与非聚集索引查找之间最好的是什么

What is best among clustered index scan vs non-clustered index seek

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

查询:

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

首先,没有'best'运算符。有时读取更多数据比读取一些数据并处理它们以获得我们的结果更有效。 'Best' 因为几乎一切都是相对的。

让我们尝试了解评论中发生的事情...

查询

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

其中说:

I want the first and the last ID (min/max) of any record where the date is less than a constant date.

集群

没有 index/index 提示的第一个查询做了 SQL 服务器认为比读取任何索引更便宜的操作,即使它需要更多的 IO(磁盘使用)。这是因为在验证 table 中的记录时找到最小值和最大值比选择 table 的一半更便宜,然后 reordering/aggregating 他们找到完全相同的信息。

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

以日期为键的索引

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

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

以ID为key的索引

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

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

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

第二个索引如何工作(通过查询近似)

您可以通过

获得最小的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

如果您将它们作为子查询交叉连接,您几乎可以了解执行计划所描述的内容。

备注

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