最大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 JOIN
toを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%を表していると主張していますが、常に正確に見積もられるとは限りません。