web-dev-qa-db-ja.com

@Table変数への結合が効率的に実行されていない

申し分なく、私は信じられないほど遅く実行していたレポートストアドプロシージャを持っています。レポートが実行されないという顧客からの不満があったため、ストアドプロシージャのどこに問題があるのか​​を正確に調査し始め、この部分が時間の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 

このクエリの@xmlTemp部分は、通常、Webアプリケーションからパラメーターを取得して、レポートで実際に使用できるパラメーターに変換するために使用されます。これをテストするために、これを1つのストアに対して1日に実行する値を挿入しています。この部分の実行には、20分以上かかる場合があります。

したがって、このクエリプランを PlanExplorer で実行したところ、そのストアとその日だけを除外するのではなく、2つのファクトテーブルからすべてのデータを取得していることがわかりました。下の写真のように。

QueryPlan これは明らかに悪いことです。したがって、私が取った次のステップは、@ 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 

これを単純なwhere句に変更すると、20分以上ではなく4秒で実行されます。また、クエリプランは正しいことを示しています。この動作が表示されるはずの理由はありますか?

ここでの編集は QueryPlan への完全なリンクです。

5
Zane

[コピー SQLPerformance.comでの私の答え 。]

他の場所での議論からのいくつかの非常に短い最初の提案:

  1. @xmlTempを、テーブル変数ではなく、(StartDate、EndDate)にクラスター化インデックスを持つ#tempテーブルとして作成してみてください。これにより、SQL Serverにより正確な統計情報が提供される場合があります(ただし、テーブルに行が1つしかない場合は、疑問の余地があります)。
  2. @xmlTempに行が1つしかない場合always、最初にテーブルではなく2つの変数を使用します。
  3. 特に#tempテーブルではなく変数に変換する場合(パラメータースニッフィング)、ステートメントに(RECOMPILE)オプションを追加してみてください。
  4. OPTION(MAXDOP 1)を使用してみてください-並列処理は確実に使用されており、下端ではスレッドが部分的に不均衡に見えます。ここで並列処理が役立つか傷ついているのでしょうか-期間の有無にかかわらず、期間をテストするのに害はありません。
  5. より厳密な統計の更新を実行する必要がある場合があります。これらの見積もりの​​多くは、かなりずれています。
  6. DISTINCTを削除します。この列のセットでは、これが重複を排除しているとは信じがたいのですが、オプティマイザは、削除する複製があるかのように機能する必要があります。
  7. さまざまな会社/店舗でXMLを細断するのではなく、テーブル値パラメーター(TVP)の使用を検討してください。
5
Aaron Bertrand