除非 OPTION (RECOMPILE),否则不使用索引 SEEK?

Roy*_*mir 11 index sql-server-2008-r2

(问题移自 SO)

我有一个带有聚集索引的表(虚拟数据)包含 2 列:

在此处输入图片说明

现在我运行这两个查询:

declare 
@productid int =1 , 
@priceid  int = 1




SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid OR @productid IS NULL)
       AND (priceid = @priceid OR @priceid IS NULL)  


SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid)
       AND (priceid = @priceid)
Run Code Online (Sandbox Code Playgroud)

两个查询的实际执行计划是:

在此处输入图片说明

如您所见,第一个使用 SCAN,而第二个使用 SEEK。

但是 - 添加OPTION (RECOMPILE)到第一个查询,使执行计划也使用 SEEK:

在此处输入图片说明

DBA 聊天室的朋友告诉我:

在您的查询中,@productid=1,这意味着(productID=@productID OR @productID IS NULL)可以简化为(productID=@productID)。前者需要扫描才能使用@productID 的任何值,后者可以使用搜索。因此,当您使用 RECOMPILE 时,SQL Server 将查看您在 @productID 中实际拥有的值并为其制定最佳计划。在@productID 中使用非空值时,最好使用查找。如果@productID 的值未知,则计划必须适合@productID 中的任何可能值,这将需要扫描。请注意:每次运行时,OPTION (RECOMPILE) 都会强制重新编译计划,这会为每次执行增加几毫秒。尽管这只是在查询非常频繁地运行时才会出现的问题。

还 :

如果@productID 为空,您会寻求什么值?回答:没有什么可寻求的。所有值都符合条件。

我知道这会OPTION (RECOMPILE)强制 SQL Server 查看参数具有的实际值,并查看它是否可以使用它进行搜索。

但是现在我失去了提前编译的好处。

恕我直言- SCAN 只会在参数为 null 时发生。
没关系 - 让 SQL SERVER 为 SCAN 创建一个执行计划。
但是,如果 SQL Server 发现我多次使用 values: 运行此查询1,1,那么它为什么不创建另一个执行计划并为此使用 SEEK?

AFAIK-SQL 为命中率最高的查询创建执行计划。

  • 为什么 SQL SERVER 不保存以下的执行计划:

    @productid int =1 , @priceid int = 1

(我用这些值多次运行它)

  • 是否可以强制 SQL 保留该执行计划(使用 SEEK) - 以供将来调用?

完整的创建表脚本+数据

Pau*_*ite 10

总结我们聊天室讨论的一些要点:


一般而言,SQL Server为每个语句缓存一个计划。该计划必须对所有可能的未来参数值有效

不可能为您的查询缓存搜索计划,因为例如,如果@productid为空,该计划将无效。

在未来的某个版本中,SQL Server 可能支持根据运行时参数值在扫描和查找之间动态选择的单一计划,但这不是我们今天所拥有的。

一般问题类

您的查询是模式的一个例子,也被称为“一网打尽”或“动态搜索”查询。有各种解决方案,每个都有自己的优点和缺点。在现代版本的 SQL Server (2008+) 中,主要选项是:

  • IF
  • OPTION (RECOMPILE)
  • 动态 SQL 使用 sp_executesql

关于该主题的最全面的工作可能是 Erland Sommarskog,它包含在本答案末尾的参考资料中。无法摆脱所涉及的复杂性,因此有必要花一些时间尝试每个选项以了解每种情况下的权衡。

IF

为了说明IF问题中特定情况的块解决方案:

IF @productid IS NOT NULL AND @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid
        AND T.priceID = @priceid;
END;
ELSE IF @productid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid;
END;
ELSE IF @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.priceID = @priceid;
END;
ELSE
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T;
END;
Run Code Online (Sandbox Code Playgroud)

这包含针对两个参数(或局部变量)中的每一个的四种可能的空或非空情况的单独语句,因此有四个计划。

参数嗅探存在潜在问题,可能需要OPTIMIZE FOR对每个查询进行提示。请参阅参考资料部分以探索这些类型的微妙之处。

重新编译

如上所述,您还可以添加OPTION (RECOMPILE)提示以在每次调用时获取新计划(搜索或扫描)。鉴于您的情况下调用的频率相对较慢(平均每十秒一次,编译时间为亚毫秒),此选项似乎适合您:

SELECT
    T.productID,
    T.priceID
FROM dbo.Transactions AS T
WHERE
    (T.productID = @productid OR @productid IS NULL)
    AND (T.priceID = @priceid OR @priceid IS NULL)
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

还可以以创造性的方式组合上述选项的功能,以充分利用每种方法的优点,同时最大限度地减少缺点。详细了解这些内容,然后在实际测试的支持下做出明智的选择,确实没有捷径可走。

进一步阅读