带有视图的 T-SQL 查询中的文字与变量会产生截然不同的查询时间

Literal vs variable in T-SQL query with view produces vastly different query time

当我在针对视图的查询中的 WHERE 子句中使用文字时,结果基本上是即时的。当我使用一个设置为相同值的变量时,一个完全不同且非常慢的查询计划取而代之。这怎么可能?这些怎么会有很大的不同:

DECLARE @a INT = 5

SELECT ... 
WHERE myview.mycol = @a 

SELECT ... 
WHERE myview.mycol = 5

这是我遇到的确切查询和时间(我可以 post 有关视图本身的其他信息,但我不知道 post 视图定义本身是有用:它很复杂并且依赖于十几个其他视图和表格。如果它对回答问题有帮助,我可以post它。)

DECLARE @productdbuid INT = 5

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

---------------------
SET @t1 = GETDATE();

SELECT 
  * 
FROM 
  vwPublishingActions 
WHERE 
  productdbuid = 5 AND storedbuid = 1

SET @t2 = GETDATE();
SELECT DATEDIFF(MILLISECOND,@t1,@t2) time1;

---------------------
SET @t1 = GETDATE();

SELECT 
  * 
FROM 
  vwPublishingActions 
WHERE 
  productdbuid = @productdbuid AND storedbuid = 1

SET @t2 = GETDATE();
SELECT DATEDIFF(MILLISECOND,@t1,@t2) time2;

是什么导致 SQL 服务器对待文字 5 的方式与 @productbuid INT = 5 如此不同?

非常感谢您的指导。

更新:我被要求包含视图定义:

SELECT
    T2.productdbuid, 
    T2.storedbuid, 
    ISNULL(paca.publishactiondbuid, 8) publishingaction,  -- 8 = ERROR_REPORT
    T2.change_product,
    T2.change_price,
    T2.change_stockstatus,
    T2.inventory_belowtrigger,
    T2.ruledbuid ruledbuid
FROM
    (SELECT
         T.productdbuid, 
         T.storedbuid, 
         -- pick first fully matching set of conditions
         (SELECT TOP 1 paca.dbuid
          FROM dbo.z_PublishingActionCalcs paca
          WHERE (paca.storetypedbuid = T.storetypedbuid) 
            AND (paca.publishingcommanddbuid = T.publishcommanddbuid OR paca.publishingcommanddbuid IS NULL) 
            AND (ISNULL(paca.publishingstatusdbuid, 0) = ISNULL(T.publishstatusdbuid, 1007) OR paca.publishingstatusdbuid IS NULL)  -- 1007 = NOTSET
            AND (ISNULL(ABS(paca.change_product),0) = ISNULL(ABS(T.change_product),0) OR paca.change_product IS NULL) 
            AND (ISNULL(ABS(paca.change_price),0) = ISNULL(ABS(T.change_price),0) OR paca.change_price IS NULL) 
            AND (ISNULL(ABS(paca.change_stockstatus),0) = ISNULL(ABS(T.change_stockstatus),0) OR paca.change_stockstatus IS NULL) 
            AND (ISNULL(ABS(paca.inventory_belowtrigger),0) = ISNULL(ABS(T.inventory_belowtrigger),0) OR paca.inventory_belowtrigger IS NULL)
            AND (ISNULL(paca.stockstatusdbuid, 0) = ISNULL(T.stockstatusdbuid, 0) OR paca.stockstatusdbuid IS NULL) 
      ORDER BY paca.sort) ruledbuid,
      ABS(ISNULL(T.change_product,0)) change_product,
      ABS(ISNULL(T.change_price,0)) change_price,
      ABS(ISNULL(T.change_stockstatus,0)) change_stockstatus,
      ABS(ISNULL(T.inventory_belowtrigger,0)) inventory_belowtrigger
  FROM            
      (SELECT
           p.productid, 
           s.storetypedbuid, 
           CASE 
              WHEN pdpcm.publishcommanddbuid <> 4 
                 THEN NULL  -- STOCKSTATUS
                 ELSE pss.stockstatusdbuid
           END product_stockstatus, 
           CASE 
              WHEN pdpcm.publishcommanddbuid <> 5 
                 THEN NULL  -- INVENTORY
                 ELSE itr.inventory_belowtrigger
           END inventory_belowtrigger, 
           p.dbuid productdbuid, 
           s.dbuid storedbuid, 
           pdpc.change_product, 
           pdpc.change_price, 
           pdpc.change_stockstatus, 
           pdpcm.publishcommanddbuid,
           pdps.publishstatusdbuid,
           pss.stockstatusdbuid
       FROM
           dbo.ProductDetailsPublishingCommands pdpcm
       INNER JOIN 
           dbo.Stores s ON s.dbuid = pdpcm.storedbuid 
       INNER JOIN 
           dbo.Products p ON pdpcm.productdbuid = p.dbuid 
       INNER JOIN 
           dbo.StoreTypeSet st ON st.dbuid = s.storetypedbuid 
       LEFT JOIN 
           dbo.vwPublishingChanges pdpc ON pdpc.productdbuid = p.dbuid 
                                        AND pdpc.storedbuid = s.dbuid
       LEFT JOIN 
           dbo.ProductDetailsPublishingStatuses pdps ON pdps.productdbuid = p.dbuid 
                                                     AND pdps.storedbuid = s.dbuid
       LEFT JOIN 
           dbo.vwProductStockStatus pss ON pss.productdbuid = p.dbuid
       LEFT JOIN 
           dbo.vwProductInventory pri ON pri.productdbuid = p.dbuid
       LEFT JOIN 
           dbo.vwInventoryTriggers itr ON itr.storedbuid = s.dbuid 
                                       AND itr.productdbuid = p.dbuid) T
    ) T2
LEFT JOIN 
    dbo.z_PublishingActionCalcs paca ON T2.ruledbuid = paca.dbuid

您需要查看执行计划才能确定。

当您使用变量 mycol = @a SQL 服务器将根据 mycol 中的值的平均列密度创建一个计划。

mycol = 5 谓词可能明显高于或低于平均水平。当您使用文字 SQL 服务器可以在列统计信息中查找值 5 并可能获得更准确的估计,从而获得更合适的计划。

此外,使用文字可以进行一些额外的优化和简化。

例如,具有 PARTITION BY mycol 的视图可以具有字面谓词 pushed further down 而变量或参数通常可以(使用 OPTION (RECOMPILE) 时除外)。

此外,在编译时可以使用文字值 SQL 服务器可以简化表达式并使用矛盾检测来消除 运行 时的一些工作。