从具有聚集索引的列中过滤计算为 DATEADD 的列时进行索引扫描
Index scan when filtering on column calculated as DATEADD from column with clustered index
我很困惑为什么我在使用简单 DATEADD 函数(应该是确定性的)的计算列上的查询上进行聚集索引搜索,而不是其他聚集索引的列。
我能否保留非具体化的计算列(因为这是针对现有的大量遗留数据)并能够按此列进行筛选并让 sql 命中索引?我需要在计算列上进行过滤(因为我无法更改查询并且它们需要使用适当的偏移量进行查询)。
简单的示例定义:
-- table with calculated column
CREATE TABLE [dbo].[_test](
[Dt] [datetime] NOT NULL
) ON [PRIMARY]
GO
--clustered index
CREATE CLUSTERED INDEX [Idx] ON [dbo].[_test]
(
[Dt] ASC
) ON [PRIMARY]
GO
--simulation of legacy data
DECLARE @rdate DATE
DECLARE @startLoopID INT = 1
DECLARE @endLoopID INT = 1000
WHILE @startLoopID <= @endLoopID
BEGIN
SET @rdate = DATEADD(Hour, ABS(CHECKSUM(NEWID()) % (365 * 24) ), '2020-01-01');
SET @startLoopID = @startLoopID + 1;
INSERT INTO [_test] (Dt)
VALUES (@rdate);
END
--adding the calculated column with proper offset
alter table _test ADD [Dt_2] AS DATEADD(MINUTE, 300, Dt)
执行示例:
声明 @EndTime DATETIME2(7) = '2020-07-10 00:00:00.000'
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime)
--select is performing seek
select * from _test
where
Dt > @StartTime AND Dt < @EndTime
--select is performing scan
select * from _test
where
Dt_2 > @StartTime AND Dt_2 < @EndTime
查询计划:
在 Dt
列上过滤时,我得到了预期的搜索。在 Dt_2
列上过滤时 - 通过确定性函数从 Dt
计算 - 我得到索引扫描。
在具有大量数据的真实场景中,这会导致巨大的性能损失。
您需要为计算列编制索引,以便 SQL 能够执行查找。计算列仅在 select 时评估,除非它被持久化或索引。即使标记为persisted
,也仍然需要在没有索引的情况下进行扫描。表达式具有确定性和精确性这一事实意味着它 可以 被索引,但您仍然需要添加索引。
您在下面的评论表明您无法向计算列添加索引。但是在这种特殊情况下,您实际上并不需要一个来执行相同的查询,因为您的计算列正在向原始列添加一个常量偏移量。因此,可以将常量表达式移到查询本身比较操作的右侧,SQL仍然可以使用原来的聚簇索引。
也就是说,不是创建一个等于 Dt
加上某个常数的新列,而是可以从不等式的 RHS 中减去该常数。而不是:
alter table _test ADD [Dt_2] AS DATEADD(MINUTE, 300, Dt);
DECLARE @EndTime DATETIME2(7) = '2020-07-10 00:00:00.000'
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime);
select * from _test
where
Dt_2 > @StartTime AND Dt_2 < @EndTime;
您可以使用:
--subtract 300 minutes from the @endTime parameter instead of adding 300 minutes to every value of Dt
DECLARE @EndTime DATETIME2(7) = dateadd(minute, -300, '2020-07-10 00:00:00.000');
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime);
select * from _test
where
Dt > @StartTime
AND Dt < @EndTime;
我很困惑为什么我在使用简单 DATEADD 函数(应该是确定性的)的计算列上的查询上进行聚集索引搜索,而不是其他聚集索引的列。
我能否保留非具体化的计算列(因为这是针对现有的大量遗留数据)并能够按此列进行筛选并让 sql 命中索引?我需要在计算列上进行过滤(因为我无法更改查询并且它们需要使用适当的偏移量进行查询)。
简单的示例定义:
-- table with calculated column
CREATE TABLE [dbo].[_test](
[Dt] [datetime] NOT NULL
) ON [PRIMARY]
GO
--clustered index
CREATE CLUSTERED INDEX [Idx] ON [dbo].[_test]
(
[Dt] ASC
) ON [PRIMARY]
GO
--simulation of legacy data
DECLARE @rdate DATE
DECLARE @startLoopID INT = 1
DECLARE @endLoopID INT = 1000
WHILE @startLoopID <= @endLoopID
BEGIN
SET @rdate = DATEADD(Hour, ABS(CHECKSUM(NEWID()) % (365 * 24) ), '2020-01-01');
SET @startLoopID = @startLoopID + 1;
INSERT INTO [_test] (Dt)
VALUES (@rdate);
END
--adding the calculated column with proper offset
alter table _test ADD [Dt_2] AS DATEADD(MINUTE, 300, Dt)
执行示例: 声明 @EndTime DATETIME2(7) = '2020-07-10 00:00:00.000'
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime)
--select is performing seek
select * from _test
where
Dt > @StartTime AND Dt < @EndTime
--select is performing scan
select * from _test
where
Dt_2 > @StartTime AND Dt_2 < @EndTime
查询计划:
在 Dt
列上过滤时,我得到了预期的搜索。在 Dt_2
列上过滤时 - 通过确定性函数从 Dt
计算 - 我得到索引扫描。
在具有大量数据的真实场景中,这会导致巨大的性能损失。
您需要为计算列编制索引,以便 SQL 能够执行查找。计算列仅在 select 时评估,除非它被持久化或索引。即使标记为persisted
,也仍然需要在没有索引的情况下进行扫描。表达式具有确定性和精确性这一事实意味着它 可以 被索引,但您仍然需要添加索引。
您在下面的评论表明您无法向计算列添加索引。但是在这种特殊情况下,您实际上并不需要一个来执行相同的查询,因为您的计算列正在向原始列添加一个常量偏移量。因此,可以将常量表达式移到查询本身比较操作的右侧,SQL仍然可以使用原来的聚簇索引。
也就是说,不是创建一个等于 Dt
加上某个常数的新列,而是可以从不等式的 RHS 中减去该常数。而不是:
alter table _test ADD [Dt_2] AS DATEADD(MINUTE, 300, Dt);
DECLARE @EndTime DATETIME2(7) = '2020-07-10 00:00:00.000'
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime);
select * from _test
where
Dt_2 > @StartTime AND Dt_2 < @EndTime;
您可以使用:
--subtract 300 minutes from the @endTime parameter instead of adding 300 minutes to every value of Dt
DECLARE @EndTime DATETIME2(7) = dateadd(minute, -300, '2020-07-10 00:00:00.000');
DECLARE @StartTime DATETIME2(7) = DATEADD(day, -12, @EndTime);
select * from _test
where
Dt > @StartTime
AND Dt < @EndTime;