当“LIKE”子句包含参数时,SQL Server 不使用索引
Posted
技术标签:
【中文标题】当“LIKE”子句包含参数时,SQL Server 不使用索引【英文标题】:SQL Server doesn't use the index when `LIKE` clause contains a parameter 【发布时间】:2021-03-22 14:38:40 【问题描述】:我正在尝试优化 Entity Framework 创建的某个查询,但我偶然发现了 SSMS 中一个奇怪的执行计划差异。
表格是:
-- int Id, string Name
CREATE TABLE [Clients]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](550) NOT NULL
CONSTRAINT [PK_Clients] PRIMARY KEY CLUSTERED (Id) ASC
)
-- create index on Name, and include Id desc
CREATE NONCLUSTERED INDEX [IX_Clients_Name] ON [Clients]
(
[Name] ASC,
[Id] DESC
)
我将问题缩小到以下简单查询:
DECLARE @filter nvarchar(4000)
SET @filter = N'Johnnie'
-- use the parameter in WHERE clause
-- (this is what EF core basically generates)
SELECT Id, Name
FROM Clients
WHERE Name LIKE @filter + N'%'
ORDER BY Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
-- use hardcoded string clause
SELECT Id, Name
FROM Clients
WHERE Name LIKE N'Johnnie%'
ORDER BY Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
如果是第一个,实际执行计划显示一个索引scan,然后是Top 10(并扫描所有30000行),而第二个执行索引seek,然后是一个排序(并且只寻找一个匹配的行)——显然第二个在这种情况下是更可取的,在一般情况下(因为我通常只有几个匹配的名称)。
我更新了统计数据,但它没有改变(虽然我不明白为什么首先会有差异)。
有人可以说明为什么会有差异吗?
(顺便说一句,如果我删除 ORDER BY ... OFSET ... FETCH
部分,那么它在两种情况下都使用索引查找)
【问题讨论】:
如果在第一个查询中添加OPTION (RECOMPILE)
,有什么变化吗? OPTION (OPTIMIZE FOR (@filter = UNKNOWN))
或 OPTION (OPTIMIZE FOR (@filter = N'Johnnie'))
呢?您可能会得到一个糟糕的缓存计划,或者基于不知道过滤器值和具有非常同质的数据而过于保守的计划。
@JeroenMostert:谢谢!事实上,在某些情况下,这个选项似乎给出了不同的结果。所以可能参数化的版本被缓存了,但第二个没有?
在一般情况下,@filter
可能以通配符开头。因此,它必须生成考虑到“搜索”可能必须触及表中所有行的估计值。除非您使用OPTION (RECOMPILE)
,否则不会嗅探变量的值,因此您将获得SET @filter = N'Johnnie'
和SET @filter = N'%'
的相同计划
@MartinSmith:啊,没错!你认为可能是这种情况吗?我希望它至少在优化之前检查@filter
的值(但我显然根本不了解优化器)。
对于一个参数,它检查计划编译时碰巧传递的值(如果该参数值是非典型值,那么容易受到“参数嗅探”问题的影响)l。您的 repro 正在使用 variable,除非使用 OPTION (RECOMPILE)
,否则它们永远不会被嗅探
【参考方案1】:
您的数据可能如下所示:
Name ID
John1 300
John3 301
John2 302
索引按Name
进行第一次排序。如您所见,id
s 不按顺序排列。因此,SQL 必须对它们进行排序。或者扫描主键索引(它决定哪个更好)。
如果您希望索引被有效使用,您可以按Name
排序。
【讨论】:
谢谢,我了解索引的工作原理,但我不明白为什么指定文字 (WHERE Name LIKE N'Johnnie%'
) 和使用参数 (WHERE Name LIKE @filter + N'%'
) 之间存在差异。这两个查询给出了不同的执行计划。
@Lou 。 . .那是一个优化阈值,它取决于表中的规则和统计信息。如果这个查询被存储了(比如在存储过程中),那么你可能还会遇到参数嗅探问题。以上是关于当“LIKE”子句包含参数时,SQL Server 不使用索引的主要内容,如果未能解决你的问题,请参考以下文章
在 Sql Server 中将集合作为参数传递时使用 IN 子句