为什么SQL Server不使用计算列上的索引?

Chr*_*eng 16 sql-server performance query-optimization database-performance sql-server-2014

在SQL Server 2014 DB中给出以下内容:

create table t 
(
    c1 int primary key,
    c2 datetime2(7),
    c3 nvarchar(20),
    c4 as cast(dbo.toTimeZone(c2, c3, 'UTC') as date) persisted
);

create index i on t (c4);

declare @i int = 0;

while @i < 10000 
begin
    insert into t (c1, c2, c3) values
        (@i, dateadd(day, @i, '1970-01-02 03:04:05:6'), 'Asia/Manila');
    set @i = @i + 1;
end;
Run Code Online (Sandbox Code Playgroud)

toTimeZone是一个CLR UDF,它将datetime2时区转换为datetime2另一个时区.

当我运行以下查询时:

select c1 
from t 
where c4 >= '1970-01-02'
    and c4 <= '1970-03-04';
Run Code Online (Sandbox Code Playgroud)

SQL Server后面的执行计划表明i未使用.

相反,在最终使用查询谓词的过滤器之前,先扫描PK上的隐式索引,然后进行几次标量计算.我期待的执行计划是扫描i.

使用此ZIP文件中的SSDT项目尝试并复制问题.它包括CLR UDF的模拟定义.还包括我得到的执行计划.

Mar*_*ith 19

我可以用你的连接工程,重现该问题(这可能是相同的问题,因为这里有连接的项目在这里)

计算列首先展开到基础表达式,然后可能会或可能不会在以后匹配回计算列.

计划中的过滤器显示它已扩展到

CONVERT(date,[computed-column-index-problem].[dbo].[toTimeZone](CONVERT_IMPLICIT(datetime,[computed-column-index-problem].[dbo].[t].[c2],0),CONVERT_IMPLICIT(nvarchar(max),[computed-column-index-problem].[dbo].[t].[c3],0),CONVERT_IMPLICIT(nvarchar(max),'UTC',0)),0)>=CONVERT_IMPLICIT(date,[@1],0) 
AND 
CONVERT(date,[computed-column-index-problem].[dbo].[toTimeZone](CONVERT_IMPLICIT(datetime,[computed-column-index-problem].[dbo].[t].[c2],0),CONVERT_IMPLICIT(nvarchar(max),[computed-column-index-problem].[dbo].[t].[c3],0),CONVERT_IMPLICIT(nvarchar(max),'UTC',0)),0)<=CONVERT_IMPLICIT(date,[@2],0)
Run Code Online (Sandbox Code Playgroud)

这些隐式演员nvarchar(max)似乎正在造成伤害.一个不需要CLR的简单repro就是

DROP TABLE IF EXISTS t 
DROP FUNCTION IF EXISTS [dbo].[toTimeZone]

GO

CREATE FUNCTION [dbo].[toTimeZone] (@newTimeZone [NVARCHAR](max))
RETURNS DATE
WITH schemabinding
AS
  BEGIN
      RETURN DATEFROMPARTS(1970, 01, 02)
  END

GO

CREATE TABLE t
  (
     c1 INT IDENTITY PRIMARY KEY,
     c4 AS dbo.toTimeZone(N'UTC') persisted
  );

CREATE INDEX i
  ON t (c4);

INSERT INTO t
DEFAULT VALUES

SELECT c1
FROM   t WITH (forceseek)
WHERE  c4 >= '1970-01-02'
       AND c4 <= '1970-03-04'; 
Run Code Online (Sandbox Code Playgroud)

消息8622,级别16,状态1,行27查询处理器由于此查询中定义的提示而无法生成查询计划.重新提交查询而不指定任何提示,也不使用SET FORCEPLAN.

如果我将函数定义更改为

public static DateTime toTimeZone(DateTime dateTime,
    [SqlFacet(IsFixedLength=false, IsNullable=true, MaxSize=50)]
    string originalTimeZone,
    [SqlFacet(IsFixedLength=false, IsNullable=true, MaxSize=50)]
    string newTimeZone)
{
    return dateTime.AddHours(-8);
}
Run Code Online (Sandbox Code Playgroud)

所以字符串参数变成了nvarchar(50).然后它能够​​匹配并寻求

在此输入图像描述

具体来说,它是传递UTC需要它的文字的第二个参数.如果注释仅应用于第一个参数,则即使使用with (forceseek)提示,计划也不会生成搜索.如果注释仅应用于第二个参数,那么它可以产生搜索 - 尽管计划显示警告.

在此输入图像描述

  • 哇.您设法不仅重现了问题,而且找到了解决方案的底部. (3认同)