需要帮助了解一些执行计划

Tud*_*dor 7 sql t-sql sql-server

假设一个简单的表定义为:

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))
Run Code Online (Sandbox Code Playgroud)

值插入如下:

250000 rows with State = 0
1000 rows with State = 5
Run Code Online (Sandbox Code Playgroud)

以下查询及其各自的执行计划:

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))
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

考虑到ID它不是null并且因此@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
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

问题:

  1. 为什么这两种情况之间的执行计划存在差异?为什么非聚集索引仅用于第二种情况?
  2. 虽然第二种情况是执行搜索与扫描的情况@mID为空时,它真的有任何区别吗?工具提示表明性能命中几乎相同,我猜这是由于数据主要是State = 0行.

Vla*_*nov 2

通常,当优化器为查询生成计划时,该计划必须对于任何可能的参数值都有效。通常,计划会被缓存,并且当您再次运行相同的查询时不会重新生成,因此即使您使用不同的参数值重新运行查询,它也必须保持有效(产生正确的结果)。

\n\n

因此,第一个查询的计划必须具有适用于 的任何值@mID(包括 NULL 和非 NULL)的形状。

\n\n

索引IX_NC_F_Media_StateNotDeleted可用于查找表达式两个部分的值OR,但要么优化器不够聪明,无法构建一个对该索引进行两次查找然后合并结果的计划,要么优化器认为这样的计划会更昂贵。

\n\n

因此,要么优化器无法在这里@mid IS NULL看到与 互斥的,ID = @mID要么它决定替代方案会更昂贵。

\n\n

第二个显式查询IF使优化器的选择显而易见。

\n\n
\n\n

这种类型的查询称为“catch-all”或“sink”查询。我建议阅读 Erland Sommarskog 撰写的一篇优秀文章《Dynamic Search Conditions in T\xe2\x80\x91SQL》

\n\n

在许多情况下,向第一个查询添加 an 是合适的OPTION (RECOMPILE),如下所示:

\n\n
declare @mID int = 400000;\nselect State \nfrom Table1 \nwhere (ID = @mID and State in (0, 1, 4, 5))\n   or (@mID IS NULL and State in (0, 4))\nOPTION(RECOMPILE);\n\nSET @mID = NULL;\nselect State \nfrom Table1 \nwhere (ID = @mID and State in (0, 1, 4, 5))\n   or (@mID IS NULL and State in (0, 4))\nOPTION(RECOMPILE);\n
Run Code Online (Sandbox Code Playgroud)\n\n

尝试运行这些查询并检查它们的实际执行计划。您应该看到计划的形状根据执行时参数的实际值而变化。

\n\n

优化器OPTION(RECOMPILE)知道生成的计划不会像往常一样被缓存,因此它采用参数的实际值并将它们作为常量内联到查询中。一旦它们是常量,优化器就能够看到它NULL IS NULL始终为真(或400000 IS NULL始终为假)并折叠/简化逻辑表达式。此外,优化器能够在每种情况下选择最合适的索引。

\n\n
\n\n
\n

尽管第二种情况在 @mID 为 null 时执行查找与扫描,但这真的有什么区别吗?工具提示表明性能影响几乎相同,我猜测这是因为数据大部分是 State = 0 行。

\n
\n\n

在这种情况下,查找索引与扫描整个表几乎相同。它们的大小(以页为单位)相同。如果您的表有很多带有 的行State=2,那么过滤索引会更有效,因为它包含的页面比主表少。

\n