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


11

(问题移至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)

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

在此处输入图片说明

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

但是-添加OPTION (RECOMPILE)到第一个查询中,还制定了执行计划以使用SEEK:

在此处输入图片说明

DBA聊天的朋友告诉我:

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

另外:

如果@productID为null,您将寻求什么价值?答:没有什么可寻求的。所有值均合格。

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

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

仅当参数为null时,才会发生IMHO-SCAN。
很好-让SQL SERVER为SCAN创建执行计划。
但是,如果SQL Server看到我使用值多次运行此查询:1,1,那为什么不创建另一个执行计划并为此使用SEEK呢?

AFAIK-SQL为最热门的查询创建执行计划。

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

    @productid int =1 , @priceid int = 1

(我用这些值运行了很多次)

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

完整创建表脚本和数据


Answers:


10

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


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

无法为查询缓存搜索计划,因为例如@productid为null时,该计划将无效。

在将来的某些发行版中,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;

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

参数嗅探存在潜在的问题,可能需要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);

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

进一步阅读

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.