1

今日はあまりにも多くの会議がありましたが、私はまだ私の頭脳を持っていると思います。クエリのパフォーマンスを向上させるために、次の謎に遭遇しました(テーブル名とフィールドの言い換え)。

SELECT X.ADId FROM
(
    SELECT DISTINCT A.ADId
    FROM P WITH (NOLOCK)
    INNER JOIN A WITH (NOLOCK) ON (P.ID = A.PId)
    INNER JOIN dbo.fn_A(16) AS VD ON (VD.DId = A.ADId)
    LEFT JOIN DPR ON (LDID = A.ADId)
    WHERE ((A.ADId = 1) OR ((HDId IS NOT NULL) AND (HDId = 1))) AND
           (P.PS NOT IN(5,7)) AND (A.ASP IN (2, 3))
) X
WHERE (dbo.fn_B(X.ADId, 16) = 1)

ご覧のとおり、内部クエリの内容はほとんど関係ありません。最初の要点は、ADIdの値が重複しているため、すべてのレコードでfn_B()が呼び出されないようにしたかったので、内部でSELECT DISTINCTを実行してから、個別のレコードをフィルタリングしました。合理的に聞こえますか?

ここから謎が始まります...

内部クエリは、(指定されたパラメータに対して)NORECORDSを返します。「WHEREfn_B()= 1」をコメントアウトすると、クエリはゼロ時間で実行されます(結果は返されません)。元に戻すと、クエリには6〜10秒かかり、結果は返されません。

これは常識、または少なくとも私の常識を打ち負かすようです:-)内部クエリがデータを返さない場合、外部条件は決して正しく評価されるべきではありませんか?

もちろん、時間をかけて実際の実行計画を確認し、保存して、慎重に比較しました。それらは99%同一であり、気付くのは珍しいことではありません。

私はいくつかのCTEをだまして、最初のCTEでクエリ結果を取得し、それをレコードをフィルタリングしないことが保証されているいくつかの条件を持つ2番目のCTEに渡し、すべてのCTEの外部でfn_B()呼び出しを評価しましたが、動作は正確でした同じ。

また、古いクエリ(同じ値でfn_B()を複数回呼び出す可能性がある)を使用するなど、他のバリエーションでも同じ動作をしました。条件を削除すると、ゼロ時間でレコードが取得されません。元に戻すと、10秒以内にレコードがありません。

誰かアイデアはありますか?

御時間ありがとうございます :-)

PS1:簡単なクエリを使用してtempdbの状況を再現しようとしましたが、実現できませんでした。それは私の実際のテーブルでのみ発生します。PS2:このクエリは別の関数内で呼び出されるため、結果を一時テーブルに入れてさらにフィルタリングすることも問題外です。

4

2 に答える 2

0

私たちはこの問題を SQL Server R2 の Microsoft サポートに提出しました (彼らの驚くべき応答時間と全体的なサービス手順についてコメントしなければなりません)。問題を再現する DB のコピーと回避策を彼らに渡しました。彼らはそれを自分で再現し、数日後に返された回答は次のとおりです。

両方の実行計画を分析しましたが、この回避策が本番環境で使用できるかどうかお尋ねします。その背後にある主な理由は、関数にはインデックスのように統計がないためです。そして、このデータの欠如により、オプティマイザーはあまり良くない実行計画を選択することがあります。回避策が既に見つかっている場合は、これを実装することをお勧めします。私たちが試みたインデックスの変更は実行を改善しませんでした。

これは、「はい、オプティマイザーがクエリを台無しにするので、回避策を使用してください」と言う非常に外交的な方法です。それをバグと呼びたいなら、バグと呼んでも構いません。

念のため、回避策は、クエリの SELECT リストの SELECT DISTINCT の 1 レベル上に fn_B() の呼び出しを配置し​​、その結果を WHERE 条件でフィルター処理することでした。ちょっと奇妙ですが、それはうまくいきます。

于 2012-05-10T07:52:22.710 に答える
0

注意として、オプティマイザーはユーザーと同じ方法でクエリを読み取るわけではありません。特定の順序が必要である、または短絡が最も理にかなっている可能性があると考えている場合でも、オプティマイザは、予期しない順序で CTE / サブクエリを評価する可能性があります。試行できる回避策は、#temp テーブルへの最初のクエリを選択し、#temp テーブルで関数フィルターを実行することです。これは、たとえそれが完全に直観的ではなく、はるかに洗練されていなくても、評価の順序を強制する必要があります。

編集

また、パフォーマンスが低下する可能性がありますが、NOLOCK なしで、または代わりに RCSI でクエリを実行するとどうなるか興味があります。ロックのセマンティクスが異なると、オプティマイザが機能しなくなる可能性があります。

于 2012-05-03T16:24:06.837 に答える