假设一个简单的表定义为:
CREATE TABLE Table1
(
[ID] [bigint] NOT NULL IDENTITY(1, 1) NOT FOR REPLICATION,
[State] [tinyint] NOT NULL DEFAULT ((0))
)
ALTER TABLE Table1 ADD CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ([ID])
CREATE NONCLUSTERED INDEX [IX_NC_F_Media_StateNotDeleted] ON Table1 ([State]) WHERE ([State]<>(2))
CREATE NONCLUSTERED INDEX [IX_NC_F_Media_State] ON Table1 ([State]) WHERE ([State]=(0))
插入值如下:
250000 rows with State = 0
1000 rows with State = 5
以下查询及其各自的执行计划:
declare @mID int = 400000;
select State
from Table1
where (ID = @mID and State in (0, 1, 4, 5))
or (@mID IS NULL and State in (0, 4))
考虑到ID
不为空,因此@mid IS NULL
与ID = @mID
互斥,我将查询重写为:
declare @mID int = 400000;
if @mID is null
begin
select State
from Table1
where State in (0, 4)
end
else
begin
select State
from Table1
where ID=@mID and State in (0, 1, 4, 5)
end
问题:
@mID
为空时的情况执行搜索与扫描,但它确实有任何区别吗?工具提示表明性能命中率几乎相同,我猜这是因为数据主要是State = 0
行。答案 0 :(得分:2)
您的查询未使用第一个查询中的索引,因为该变量已被用作条件的一部分(@mID IS NULL和State in(0,4)),在这种情况下@mID IS为NULL,因为这是不是列不能包含在索引中,这就是sql无法激活索引的原因。在第二个查询中,如果要删除此问题并且已激活索引。
在if的第二部分中,因为你有聚簇索引,sql更快找到这个特定的记录,然后评估状态是否有任何这些值来显示结果。
在问题范围之外的其他问题,经过一些测试我发现了一些有趣的东西。当状态的值变得更加平衡时,让我们说每个可能的选项形式1到5都有值,执行计划更改,第一个查询变得和使用if的选项一样好。
declare @mID int = 400000;
select State
from Table1
where (ID = @mID and State in (0, 1, 4, 5))
or (@mID IS NULL and State in (0, 4))
答案 1 :(得分:2)
通常,当优化程序为查询生成计划时,此计划必须对任何可能的参数值有效。通常,计划被缓存,并且当您再次运行相同的查询时不会重新生成计划,因此即使您使用不同的参数值重新运行查询,它也必须保持有效(产生正确的结果)。
因此,第一个查询的计划必须具有适用于@mID
的任何值的形状,包括NULL和非NULL。
索引IX_NC_F_Media_StateNotDeleted
可用于查找OR
表达式的两个部分的值,但是任何一个优化器都不够智能,无法构建执行两次查找此索引的计划,然后将结果联合起来或者,优化者决定这样的计划会更加昂贵。
因此,要么优化者无法看到 here @mid IS NULL
与ID = @mID
互斥,或者它决定替代品会更贵。
使用显式IF
的第二个查询使得优化者的选择变得明显。
这种类型的查询称为“catch-all”或“sink”查询。我建议阅读Erland Sommarskog撰写的优秀文章Dynamic Search Conditions in T‑SQL。
在许多情况下,最好在第一个查询中添加OPTION (RECOMPILE)
,如下所示:
declare @mID int = 400000;
select State
from Table1
where (ID = @mID and State in (0, 1, 4, 5))
or (@mID IS NULL and State in (0, 4))
OPTION(RECOMPILE);
SET @mID = NULL;
select State
from Table1
where (ID = @mID and State in (0, 1, 4, 5))
or (@mID IS NULL and State in (0, 4))
OPTION(RECOMPILE);
尝试运行这些查询并检查其实际执行计划。您应该看到计划的形状根据执行时参数的实际值而变化。
使用OPTION(RECOMPILE)
优化器知道生成的计划不会像往常一样缓存,因此它接受参数的实际值并将它们作为常量内联到查询中。一旦它们成为常量, here ,优化器就能看到NULL IS NULL
始终为真(或400000 IS NULL
始终为false)并折叠/简化逻辑表达式。此外,优化者能够在每种情况下选择最合适的指标。
虽然第二种情况执行搜索与扫描的情况 @mID为null,它真的有什么区别吗?工具提示 表明性能命中几乎是相同的,我是 猜测这是因为数据主要是State = 0行。
在这种情况下,寻找索引与扫描整个表几乎相同。它们的大小(以页为单位)是相同的。如果你的表有很多行State=2
,那么过滤后的索引会更有效率,因为它包含的页面数量比主表少。
答案 2 :(得分:1)
以下是我对此问题的理解: 在第一种情况下,SQL Server必须提出一个查询计划,该计划可以满足是否已经传递了@mid值。
第一个查询中的两个条件将受益于不同的索引,但是逻辑上需要加入结果集,因为它是OR条件。因此,它决定了一个能够在聚集索引的单次扫描中满足这两个条件的计划。
在第二种情况下,问题被消除,因此SQL服务器可以在单独的查询上使用不同的索引