当“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 进行第一次排序。如您所见,ids 不按顺序排列。因此,SQL 必须对它们进行排序。或者扫描主键索引(它决定哪个更好)。

如果您希望索引被有效使用,您可以按Name 排序。

【讨论】:

谢谢,我了解索引的工作原理,但我不明白为什么指定文字 (WHERE Name LIKE N'Johnnie%') 和使用参数 (WHERE Name LIKE @filter + N'%') 之间存在差异。这两个查询给出了不同的执行计划。 @Lou 。 . .那是一个优化阈值,它取决于表中的规则和统计信息。如果这个查询被存储了(比如在存储过程中),那么你可能还会遇到参数嗅探问题。

以上是关于当“LIKE”子句包含参数时,SQL Server 不使用索引的主要内容,如果未能解决你的问题,请参考以下文章

使用带参数的 LIKE 时 SQL 引发语法错误 [重复]

在 Sql Server 中将集合作为参数传递时使用 IN 子句

如何在 SQL Server Like 子句中转义通配符

多个Like子句按大多数匹配包含

SQL Server 上表包含点时生成错误的 FROM 子句导致 Sqoop 无效对象名称错误

尝试使用 where 子句中使用的 like 语句删除记录时,Advantage 数据库引发异常