为什么 SQL 服务器不使用计算列上的索引?
Why doesn't SQL Server use the index on the computed column?
在 SQL Server 2014 数据库中给出以下内容:
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;
toTimeZone
是一个 CLR UDF,它将一个时区的 datetime2
转换为另一个时区的 datetime2
。
当我运行以下查询时:
select c1
from t
where c4 >= '1970-01-02'
and c4 <= '1970-03-04';
SQL 服务器后跟的执行计划表明 i
没有被使用。
取而代之的是对 PK 上的隐式索引进行扫描,然后进行一些标量计算,最后使用查询的谓词进行过滤。我期待的执行计划是对 i
.
的扫描
使用 this ZIP file 中的 SSDT 项目尝试重现问题。它包括 CLR UDF 的模拟定义。还包括我得到的执行计划。
我能够使用您的附加项目重现该问题(这可能与 here with connect item here 的问题相同)
计算列首先扩展为基础表达式,然后可能匹配也可能不匹配回计算列。
您计划中的过滤器显示它已扩展到
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)
这些对 nvarchar(max)
的隐式转换似乎造成了损害。不需要 CLR 的简单重现是
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';
Msg 8622, Level 16, State 1, Line 27 Query processor could not produce
a query plan because of the hints defined in this query. Resubmit the
query without specifying any hints and without using 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);
}
所以字符串参数变成了nvarchar(50)
。然后它能够匹配并给出一个seek
具体来说,是第二个参数传递的文字 UTC
需要这个。如果注释仅应用于第一个参数,那么即使有 with (forceseek)
提示,计划也不会产生查找。如果注释仅应用于第二个参数,则它可以产生搜索 - 尽管计划显示警告。
在 SQL Server 2014 数据库中给出以下内容:
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;
toTimeZone
是一个 CLR UDF,它将一个时区的 datetime2
转换为另一个时区的 datetime2
。
当我运行以下查询时:
select c1
from t
where c4 >= '1970-01-02'
and c4 <= '1970-03-04';
SQL 服务器后跟的执行计划表明 i
没有被使用。
取而代之的是对 PK 上的隐式索引进行扫描,然后进行一些标量计算,最后使用查询的谓词进行过滤。我期待的执行计划是对 i
.
使用 this ZIP file 中的 SSDT 项目尝试重现问题。它包括 CLR UDF 的模拟定义。还包括我得到的执行计划。
我能够使用您的附加项目重现该问题(这可能与 here with connect item here 的问题相同)
计算列首先扩展为基础表达式,然后可能匹配也可能不匹配回计算列。
您计划中的过滤器显示它已扩展到
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)
这些对 nvarchar(max)
的隐式转换似乎造成了损害。不需要 CLR 的简单重现是
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';
Msg 8622, Level 16, State 1, Line 27 Query processor could not produce a query plan because of the hints defined in this query. Resubmit the query without specifying any hints and without using 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);
}
所以字符串参数变成了nvarchar(50)
。然后它能够匹配并给出一个seek
具体来说,是第二个参数传递的文字 UTC
需要这个。如果注释仅应用于第一个参数,那么即使有 with (forceseek)
提示,计划也不会产生查找。如果注释仅应用于第二个参数,则它可以产生搜索 - 尽管计划显示警告。