一定数の行を返した後にクエリが一時停止する


8

最大41レコード(たとえば)ですばやく(数秒)実行されるビューがありますTOP 41が、44以上のレコードでは数分かかり、TOP 42またはで実行すると中間結果が表示されTOP 43ます。具体的には、数秒で最初の39件のレコードを返し、残りのレコードを返す前に約3分間停止します。このパターンは、TOP 44またはをクエリする場合も同じTOP 100です。

このビューは元々ベースビューから派生したもので、ベースに1つだけフィルターを追加します。以下のコードの最後のフィルターです。子ビューをベースからチェーンする場合でも、ベースからのコードをインラインで子ビューに書き込む場合でも、違いはないようです。ベースビューは、数秒で100レコードを返します。子ビューをベースの50倍ではなく、ベースと同じくらい速く実行できるようにしたいと考えています。誰かがこのような行動を見たことがありますか?原因または解決策について推測はありますか?

この動作は、関連するクエリをテストしたところ、過去数時間一貫していますが、速度が低下し始める前に返される行数はわずかに増減しました。これは新しいものではありません。合計実行時間は許容範囲内(<2分)だったので、今それを調べていますが、少なくとも数か月間、関連するログファイルでこの一時停止が見られました。

ブロッキング

クエリがブロックされたことはありません。また、データベースに他のアクティビティがない場合でも問題が発生します(sp_WhoIsActiveによって検証済み)。ベースビューにはNOLOCK、価値のあるもの全体が含まれます。

クエリ

以下は、簡略化のためにベースビューがインライン表示された、子ビューの縮小バージョンです。それはまだ約40レコードで実行時のジャンプを示しています。

SELECT TOP 100 PERCENT
    Map.SalesforceAccountID AS Id,
    CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
    CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress                 END AS BillingStreet,
    CASE WHEN C.City          = 'Unknown' THEN '' ELSE SUBSTRING(C.City,        1, 40) END AS BillingCity,
                                                       SUBSTRING(C.Region,      1, 20)     AS BillingState,
    CASE WHEN C.PostalCode    = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode,  1, 20) END AS BillingPostalCode,
    CASE WHEN C.Country       = 'Unknown' THEN '' ELSE SUBSTRING(C.Country,     1, 40) END AS BillingCountry,
    CASE WHEN C.PhoneNumber   = 'Unknown' THEN '' ELSE C.PhoneNumber                   END AS Phone,
    CASE WHEN C.FaxNumber     = 'Unknown' THEN '' ELSE C.FaxNumber                     END AS Fax,
    TransC.WebsiteAddress AS Website,
    C.AccessKey AS AccessKey__c,
    CASE WHEN dbo.ValidateEMail(C.EMailAddress) = 1 THEN C.EMailAddress END,  -- Removing this UDF does not speed things
    TransC.EmailSubscriber
    -- A couple dozen additional TransC fields
FROM
    WarehouseCustomers AS C WITH (NOLOCK)
    INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) ON C.CustomerID = TransC.CustomerID
    LEFT JOIN  Salesforce.AccountsMap AS Map WITH (NOLOCK) ON C.CustomerID = Map.CustomerID
WHERE
        C.DateMadeObsolete IS NULL
    AND C.EmailAddress NOT LIKE '%@volusion.%'
    AND C.AccessKey IN ('C', 'R')
    AND C.CustomerID NOT IN (243566)  -- Exclude specific test records
    AND EXISTS (SELECT * FROM Orders AS O WHERE C.CustomerID = O.CustomerID AND O.OrderDate >= '2010-06-28')  -- Only count customers who've placed a recent order
    AND Map.SalesforceAccountID IS NULL  -- Only count customers not already uploaded to Salesforce
-- Removing the ORDER BY clause does not speed things up
ORDER BY
    C.CustomerID DESC

このId IS NULLフィルターは、によって返されるほとんどのレコードを破棄しBaseViewます。TOP句がない場合、それぞれ1,100レコードと267Kを返します。

統計

実行中TOP 40

SQL Server parse and compile time:    CPU time = 234 ms, elapsed time = 247 ms.
SQL Server Execution Times:   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server Execution Times:   CPU time = 0 ms,  elapsed time = 0 ms.

(40 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 39112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 752, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 458, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:   CPU time = 2199 ms,  elapsed time = 7644 ms.

実行中TOP 45

(45 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 98268, physical reads 1, read-ahead reads 3, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 1788, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 2152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times: CPU time = 41980 ms,  elapsed time = 177231 ms.

実際の出力におけるこのわずかな違いのために、読み取り数が約3倍にジャンプするのを見て、私は驚いています。

実行プランを比較すると、返される行数以外は同じです。上記の統計と同様に、TOP 45クエリの最初のステップの実際の行数は12.5%だけではなく、劇的に高くなっています。

大まかに言うと、Ordersからカバリングインデックスをスキャンして、WarehouseCustomersから対応するレコードを探しています。これをTransactionalCustomersにループ結合します(リモートクエリ、正確な計画は不明)。これをAccountsMapのテーブルスキャンとマージします。リモートクエリは、推定コストの94%です。

雑記

以前、ビューの展開されたコンテンツをスタンドアロンクエリとして実行すると、100レコードで13秒という非常に高速に実行されました。現在、サブクエリを使用せずにクエリの縮小バージョンをテストしています。この非常に単純なクエリでは、スタンドアロンクエリとして実行した場合でも、40行を超えるクエリを返すように要求されるまでに3分かかります。

子ビューにはかなりの数の読み取り(sp_WhoIsActiveあたり〜1M)が含まれますが、このマシン(8コア、32 GB RAM、95%の専用SQLボックス)では通常は問題になりません。

両方のビューを削除して再作成しましたが、変更はありません。

データには、TEXTまたはBLOBフィールドは含まれません。1つのフィールドにはUDFが含まれます。削除しても一時停止は防止されません。

サーバー自体でクエリを実行する場合でも、1,400マイル離れたワークステーションでクエリを実行する場合でも、時間は同じであるため、結果はクライアントに送信されるのではなく、クエリ自体に遅延が内在しているようです。

ノートRe:ソリューション

修正は単純でした:LEFT JOINtoをNOT EXISTS節に置き換えることです。これにより、クエリプランに小さな違いが1つだけ発生し、Mapテーブルに結合する前ではなく、後でTransactionCustomersテーブル(リモートクエリ)に結合します。これは、リモートサーバーから必要なレコードのみを要求することを意味する場合があり、これにより、転送されるボリュームが約100倍に削減されます。

通常、私は最初に応援しNOT EXISTSます。多くの場合、LEFT JOIN...WHERE ID IS NULL構成よりも高速で、ややコンパクトです。この場合、問題のクエリは既存のビューに基づいて構築されており、アンチ結合に必要なフィールドはベースビューによって公開されますが、最初に整数からテキストにキャストされます。したがって、適切なパフォーマンスを得るには、2層パターンを削除する必要があります。代わりに、ほぼ同じ2つのビューを使用し、2番目のビューにはNOT EXISTS節を含めます。

この問題のトラブルシューティングにご協力いただきありがとうございます。それは私の状況に固有でありすぎて、他の誰にも役立たないかもしれませんが、うまく行かないかもしれません。他に何もない場合、それNOT EXISTSはよりもわずかに速いという例ですLEFT JOIN...WHERE ID IS NULL。しかし、本当の教訓は、リモートクエリができるだけ効率的に結合されるようにすることです。クエリプランは、コストの2%を表していると主張していますが、常に正確に見積もられるとは限りません。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
ポールホワイト9

回答:


4

試すこと:

  1. インデックスを確認する

    • すべてのJOINキーフィールドにインデックスが付けられていますか?このビューを頻繁に使用する場合は、ビューの基準にフィルター処理されたインデックスを追加することになります。例えば...

    • CREATE INDEX ix_CustomerId ON WarehouseCustomers(CustomerId, EmailAddress) WHERE DateMadeObsolete IS NULL AND AccessKey IN ('C', 'R') AND CustomerID NOT IN (243566)

  2. 統計を更新する

    • 古くなった統計に問題がある可能性があります。あなたがそれを振ることができれば、私はやりますFULLSCAN。行数が多い場合、自動再計算をトリガーせずにデータが大幅に変更された可能性があります。
  3. クエリをクリーンアップする

    • 作るMap JOINAをNOT EXISTS-あなたは唯一の非一致するレコードをしたいので、あなたは、そのテーブルから任意のデータを必要としません

    • を取り外しますORDER BY。私はコメントがそれが問題ではないと言っているのを知っています、しかし私はそれを信じるのは非常に難しいと思います。データページは既にキャッシュされているため、小さな結果セットでは問題にならない場合があります。


興味深い点re:フィルターされたインデックス。クエリは自動的に使用しませんが、ヒントを使用して強制することをテストします。私は統計を更新しました、そしてこれとあなたの他の推薦を今日後でテストすることができます 適切なデータセットに対してテストできるように、EOWDの後にバックログを蓄積させる必要があります。
Jon of All Trades

私はこれらの微調整のさまざまな組み合わせを試しましたが、キーはマップとの逆結合であるようです。としてLEFT JOIN...WHERE Id IS NULL、私はこの一時停止を取得します。NOT EXISTS節として、実行時間は秒です。びっくりしましたが、結果には論じられません!
Jon of All Trades

2

改善1 Ordersのサブクエリを削除し、結合に変換する

FROM
WarehouseCustomers AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) 
                                                        ON C.CustomerID = TransC.CustomerID
LEFT JOIN  Salesforce.AccountsMap AS Map WITH (NOLOCK) 
                                                        ON C.CustomerID = Map.CustomerID
INNER Join Orders AS O 
                                                        ON C.CustomerID = O.CustomerID

 WHERE
    C.DateMadeObsolete IS NULL
    AND C.EmailAddress NOT LIKE '%@volusion.%'
    AND C.AccessKey IN ('C', 'R')
    AND C.CustomerID NOT IN (243566)
    AND O.OrderDate >= '2010-06-28'
    AND Map.SalesforceAccountID IS NULL

改善2-TransactionalCustomersでフィルター処理されたレコードをローカル一時テーブルに保持する

Select 
    CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
    CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress                 END AS BillingStreet,
    CASE WHEN C.City          = 'Unknown' THEN '' ELSE SUBSTRING(C.City,        1, 40) END AS BillingCity,
                                                       SUBSTRING(C.Region,      1, 20)     AS BillingState,
    CASE WHEN C.PostalCode    = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode,  1, 20) END AS BillingPostalCode,
    CASE WHEN C.Country       = 'Unknown' THEN '' ELSE SUBSTRING(C.Country,     1, 40) END AS BillingCountry,
    CASE WHEN C.PhoneNumber   = 'Unknown' THEN '' ELSE C.PhoneNumber                   END AS Phone,
    CASE WHEN C.FaxNumber     = 'Unknown' THEN '' ELSE C.FaxNumber                     END AS Fax,
    C.AccessKey AS AccessKey__c
Into #Temp
From  WarehouseCustomers C
Where C.DateMadeObsolete IS NULL
        AND C.EmailAddress NOT LIKE '%@volusion.%'
        AND C.AccessKey IN ('C', 'R')
        AND C.CustomerID NOT IN (243566)

最終クエリ

FROM
#Temp AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) 
                                                            ON C.CustomerID = TransC.CustomerID
LEFT JOIN Salesforce.AccountsMap AS Map WITH (NOLOCK) 
                                                            ON C.CustomerID = Map.CustomerID
INNER Join Orders AS O 
                                                            ON C.CustomerID = O.CustomerID

WHERE
C.DateMadeObsolete IS NULL
AND C.EmailAddress NOT LIKE '%@volusion.%'
AND C.AccessKey IN ('C', 'R')
AND C.CustomerID NOT IN (243566)
AND O.OrderDate >= '2010-06-28'
AND Map.SalesforceAccountID IS NULL

ポイント3-CustomerID、EmailAddress、OrderDateにインデックスがあると仮定します


1
Re:「改善」1- EXISTS通常JOIN、この状況ではaよりも速く、潜在的な重複を排除します。それが改善になるとは思いません。
JNK、2012年

1
ただし、問題は2つあります。これは結果を変更する可能性があり、両方のテーブルに結合で使用されるフィールドに一意のクラスター化インデックスがない場合、EXISTSよりも効率が低下します。副次句は必ずしも悪いとは限りません。
JNK、2012年

@PankajGarg:提案ありがとうございます。残念ながら、顧客ごとに複数の注文があるのでEXISTS、義務です。また、ビューでは、再利用された顧客データをキャッシュすることはできませんが、パラメーターのないダミーTVFのアイデアをいじっていました。
Jon of All Trades
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.