web-dev-qa-db-ja.com

非常に歪んだデータを効果的に処理するにはどうすればよいですか?最新の統計ですが、役に立たないようです

130,000行のテーブル_dbo.ClaimBilling_があります。この表では、列OperatorIDvarchar(max)であり、大きく歪んでいます。 125,000行は「user1」であり、残りの5000行は6つの他の値に分割され、「user2」は合計3つのレコードを持ちます。

OperatorIDには非クラスター化インデックスがあり、クラスター化インデックスは主キーIDClaimBillingです。

現在、次のクエリがあります。

_SELECT DISTINCT IDClaimBilling
FROM dbo.ClaimBilling cb
INNER JOIN dbo.BillingItem bi
    ON cb.IDClaimBilling = bi.ClaimID
WHERE OperatorID = @operator
_

_@operator_の値に関係なく、ClaimBillingからの行の推定値は〜4000であり、これはどの値が返すものにも近くなく、常にクラスター化インデックススキャンであり、使用しません。 operatorIDインデックス。結合を削除して実行すると

_SELECT DISTINCT IDClaimBilling
FROM dbo.ClaimBilling
WHERE OperatorID = @operator
_

次に、OperatorIDインデックスを使用しますが、_@operator_の値に関係なく、推定は間違っています。今回は常に約18,000程度を推定しています。

クエリを実行する前に_UPDATE STATISTICS dbo.ClaimBilling WITH FULLSCAN_を実行しました。

統計が値あたりの行数を正確に知っているのに、これらの推定値がそれほど間違っているのはなぜですか?

テストでは、値を宣言して_@operator_に割り当てています。もともとはプロシージャの一部でしたが、それが問題だと思いましたが、アドホックステートメントで使用した場合も同じように動作します。

クエリはユーザーが最初にログインしたときにのみ実行されるため、ユーザーごとに1日に数回しか実行されません。

6
Zaphodb2002

質問に残されたコメントから生成されたコミュニティWiki回答

_@operator_を変数として使用してクエリを実行している場合、SQL Serverは変数の値を「傍受」できないため、統計の平均密度値を使用して推定値を計算します。変数に割り当てる値に関係なく、この平均値の見積もりは常に同じになります。

これを解決する1つの方法は、OPTION (RECOMPILE)クエリヒントを使用することです。これにより、実行時に変数の特定の値に最適化された計画で、実行ごとに新しい計画がコンパイルされます。これには、ステートメントを再コンパイルするたびに(通常は小さい)コストがかかります。

コードをモジュール化することもできます。 IFステートメントを使用してoperatoridの値を確認し、「user1」が1つのストアドプロシージャを呼び出す場合は、_sp_user1_としましょう。 「user1」でない場合は、別のプロシージャを呼び出します。最初のspは「user1」用に最適化され、残りの値は残りの値用に最適化されます。必要に応じて、「user1」以外の値に対して2番目のspでoption (recompile)を使用することもできます。

また、動的SQLの適切な使用例になる場合もあります。これにより、_@operator_変数がリテラル値に変わり、各ユーザーの計画がカスタマイズされます。そのテーブルには7人のユーザーしかいないので、それが本当に問題になるとは思いません。

詳細については、以下を参照してください。

2
Paul White 9

wikiの回答 は正解で、非常に徹底的になっています。しかし、私はこれを捨てるつもりです... where句をあなたの結合に移動します:

SELECT DISTINCT IDClaimBilling
FROM dbo.ClaimBilling cb
INNER JOIN dbo.BillingItem bi
    ON cb.IDClaimBilling = bi.ClaimID
    and cb.OperatorID = @operator

または、多分あなたが見てきたサブクエリで結合します。

SELECT DISTINCT IDClaimBilling
FROM (select IDCLaimBilling from dbo.ClaimBilling where OperatorID = @operator) cb
INNER JOIN dbo.BillingItem bi
    ON cb.IDClaimBilling = bi.ClaimID

そして https://www.brentozar.com/pastetheplan/ を使用して計画を貼り付けます。

1
SqlZim