当 value 为 null 时,对 WHERE 语句中变量的行为感到困惑

iri*_*ias 2 sql-server-2008 sql-server t-sql

我有一个像(SQLServer 2008)的表:

CREATE TABLE [dbo].[my_test_table](
    [productId] [int] NOT NULL,
    [purchaseId] [bigint] NOT NULL,
    (some other columns....),
 CONSTRAINT [PK_my_test_table] PRIMARY KEY CLUSTERED 
(
    [productId] ASC,
    [purchaseId] ASC
))
Run Code Online (Sandbox Code Playgroud)

大约有 1000 万行。

我想要一个查询,返回产品的总行数,或者如果产品未指定,则返回所有产品的总行数。就像是:

declare @productId int

set @productId = 320

select count(*)
from my_test_table t with(nolock)
where productId = @productId
or @productId is null
Run Code Online (Sandbox Code Playgroud)

问题是查询比等效查询花费时间要多得多:

select count(*)
from my_test_table t with(nolock)
where productId = 320
or 320 is null
Run Code Online (Sandbox Code Playgroud)

我们如何解释这种行为?

以下是执行计划: 在此处输入图片说明

Joe*_*ish 5

考虑这个查询:

select count(*)
from my_test_table t
where null is null;
Run Code Online (Sandbox Code Playgroud)

优化器在那里进行搜索是否有意义?没有什么可以反对的。试图用提示强制它会引发错误:

select count(*)
from my_test_table t with (forceseek)
where null is null;
Run Code Online (Sandbox Code Playgroud)

消息 8622,级别 16,状态 1,第 15 行

由于此查询中定义的提示,查询处理器无法生成查询计划。在不指定任何提示且不使用 SET FORCEPLAN 的情况下重新提交查询。

对于您现在拥有的查询,您可能需要根据参数值进行索引查找。但是,索引搜索并非对所有可能的参数值都有效,因此您的缓存计划不能包含搜索。此外,当查询优化器构建计划时,它不会首先检查局部变量的值。

您指出查询很复杂,因此最好的解决方案可能是升级到至少 SQL Server 2008 R2 SP2,以便您可以利用参数嵌入优化 (PEO)。结合OPTION (RECOMPILE)提示允许查询优化器在执行计划之前嗅探局部变量的值。查询计划不能被另一个查询重用。

让我们看看它的实际效果。以下查询的实际查询计划显示了索引查找:

declare @productId int = 320;

select count(*)
from my_test_table t
where productId = @productId
or @productId is null
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

下面的实际查询计划显示了一个扫描:

declare @productId int = NULL;

select count(*)
from my_test_table t
where productId = @productId
or @productId is null
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

如果升级不是一种选择,我想你可以试试这个:

select *
from my_test_table t 
where productId = @productId
and @productId is not null

UNION ALL

select *
from my_test_table t
where @productId is null;
Run Code Online (Sandbox Code Playgroud)

这可以根据局部变量的值进行查找或扫描,但可能会产生意想不到的副作用。

如果这也不可接受,我认为您唯一的选择是使用另一个答案中已经涵盖的动态 SQL。