加入到@Table 变量运行效率低下

Zan*_*ane 5 sql-server-2008 sql-server stored-procedures view

好的,所以我有一个运行非常缓慢的报告存储过程。客户抱怨报告无法运行,所以我开始调查问题在存储过程中的确切位置,我发现这部分占用了 99.8% 的时间。

DECLARE @xmlTemp TABLE (
  CompanyID  INT,
  StoreID    INT,
  StartDate  DATETIME,
  DateStaID  INT,
  EndDate    DATETIME,
  DateEndID  INT,
  LastUpdate DATETIME)

INSERT INTO @xmlTemp
VALUES      (50,
             2,
             '3/3/2013',
             0,
             '3/3/2013',
             0,
             '3/3/2013')

SELECT DISTINCT T.CompanyID,
                CompanyName,
                Z.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                Total - Isnull((SELECT Sum(DISTINCT PaymentAmount)
                                FROM   vPullDrawerPayments
                                WHERE  CompanyID = T.CompanyID
                                       AND StoreID = T.StoreID
                                       AND TransactionID = T.TransactionID
                                       AND Isnull(PaymentType, 1) <> 1), 0) AS PaymentAmount,
                'Cash'                                                      AS PaymentDesc,
                CASE
                  WHEN Z.EndDate >= Z.LastUpdate THEN 1
                  ELSE 0
                END                                                         AS MissingData
FROM   vPullDrawerPayments AS T
       INNER JOIN @xmlTemp AS Z
         ON T.CompanyID = Z.CompanyID
            AND T.StoreID = Z.StoreID
WHERE  BusinessDate BETWEEN Z.StartDate AND Z.EndDate
UNION ALL
SELECT DISTINCT NC.CompanyID,
                CompanyName,
                Z.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                PaymentAmount,
                PaymentDesc,
                CASE
                  WHEN Z.EndDate >= Z.LastUpdate THEN 1
                  ELSE 0
                END AS MissingData
FROM   vPullDrawerPayments AS NC
       INNER JOIN @xmlTemp AS Z
         ON NC.CompanyID = Z.CompanyID
            AND NC.StoreID = Z.StoreID
WHERE  BusinessDate BETWEEN Z.StartDate AND Z.EndDate
       AND Isnull(PaymentType, 1) <> 1
UNION ALL
SELECT DISTINCT C.CompanyID,
                CompanyName,
                Z.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                Sum(Abs(LineAmount)) AS PaymentAmount,
                'Coupons'            AS PaymentDesc,
                CASE
                  WHEN Max(Z.EndDate) >= Max(Z.LastUpdate) THEN 1
                  ELSE 0
                END                  AS MissingData
FROM   vPullDrawerPayments AS C
       INNER JOIN @xmlTemp AS Z
         ON C.CompanyID = Z.CompanyID
            AND C.StoreID = Z.StoreID
WHERE  BusinessDate BETWEEN Z.StartDate AND Z.EndDate
GROUP  BY C.CompanyID,
          CompanyName,
          Z.StoreID,
          StoreName,
          CashedOutBy,
          TransactionID,
          RegisterID,
          BusinessDate,
          CashedOut 
Run Code Online (Sandbox Code Playgroud)

此查询的@xmlTemp 部分通常用于从我们的 Web 应用程序中获取参数,并将它们转换为报表可以实际使用的参数。为了测试这一点,我只是插入了为一家商店运行一天的值。运行此部分可能需要 20 分钟以上。

所以我通过PlanExplorer运行这个查询计划,看到它从我的两个事实表中提取所有数据,而不是只过滤掉那个商店和那天。如下图所示。

查询计划 显然这是不好的。因此,我采取的下一步是删除 @xml temp 的连接,然后手动将值放入查询WHERE子句中,以查看效果如何。

SELECT DISTINCT T.CompanyID,
                CompanyName,
                T.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                Total - Isnull((SELECT Sum(DISTINCT PaymentAmount)
                                FROM   vPullDrawerPayments
                                WHERE  CompanyID = T.CompanyID
                                       AND StoreID = T.StoreID
                                       AND TransactionID = T.TransactionID
                                       AND Isnull(PaymentType, 1) <> 1), 0) AS PaymentAmount,
                'Cash'                                                      AS PaymentDesc
--CASE WHEN Z.'3/3/2013' >= Z.LastUpdate THEN 1 ELSE 0 END AS MissingData
FROM   vPullDrawerPayments AS T
WHERE  CompanyID = 50
       AND StoreID = 1
       AND BusinessDate BETWEEN '3/3/2013' AND '3/3/2013'
UNION ALL
SELECT DISTINCT NC.CompanyID,
                CompanyName,
                NC.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                PaymentAmount,
                PaymentDesc
--CASE WHEN Z.'3/3/2013' >= Z.LastUpdate THEN 1 ELSE 0 END AS MissingData
FROM   vPullDrawerPayments AS NC
WHERE  CompanyID = 50
       AND StoreID = 1
       AND BusinessDate BETWEEN '3/3/2013' AND '3/3/2013'
       AND Isnull(PaymentType, 1) <> 1
UNION ALL
SELECT DISTINCT C.CompanyID,
                CompanyName,
                C.StoreID,
                StoreName,
                CashedOutBy,
                TransactionID,
                RegisterID,
                BusinessDate,
                CashedOut,
                Sum(Abs(LineAmount)) AS PaymentAmount,
                'Coupons'            AS PaymentDesc
--CASE WHEN MAX(Z.'3/3/2013') >= MAX(Z.LastUpdate) THEN 1 ELSE 0 END AS MissingData
FROM   vPullDrawerPayments AS C

WHERE  CompanyID = 50
       AND StoreID = 1
       AND BusinessDate BETWEEN '3/3/2013' AND '3/3/2013'
GROUP  BY C.CompanyID,
          CompanyName,
          C.StoreID,
          StoreName,
          CashedOutBy,
          TransactionID,
          RegisterID,
          BusinessDate,
          CashedOut 
Run Code Online (Sandbox Code Playgroud)

通过将其更改为简单的 where 子句,它会在 4 秒内运行,而不是 20 分钟及以上。查询计划也显示正确 有什么理由我应该看到这种行为吗?

在这里编辑是QueryPlan的完整链接。

Aar*_*and 5

[从我在 SQLPerformance.com 上的回答中复制。]

来自其他地方的讨论的一些非常简短的初步建议:

  1. 尝试将 @xmlTemp 创建为 #temp 表,其中聚集索引位于 (StartDate, EndDate) 而不是表变量。这可能会为 SQL Server 提供更准确的统计信息(尽管在表只有一行的情况下很有用)。
  2. 如果 @xmlTemp始终只有一行,则首先使用两个变量而不是表。
  3. 尝试将 (RECOMPILE) 选项添加到语句中,尤其是当您转换为变量而不是 #temp 表(参数嗅探)时。
  4. 尝试使用 OPTION (MAXDOP 1) - 肯定在使用并行性,并且在低端线程似乎部分不平衡。我想知道并行性在这里是有帮助还是有害 - 测试持续时间和不测试都不会有害。
  5. 您可能需要执行更严格的统计更新。很多这些估计都是很遥远的。
  6. 删除 DISTINCT。对于这组列,我发现很难相信这会消除任何重复项,但优化器必须像删除重复项一样工作。
  7. 考虑使用表值参数 (TVP) 而不是为不同的公司/商店分解 XML。