2017-05-18 12 views
1

私はそれに1,941,092行のデータベースと、クラスタ化された列ストアのインデックスを持っています。私は先日それを照会するときに奇妙な振る舞いに気付き、説明を求めていたので、問題を特定するためにいくつかの質問を書いた。なぜselect文でnullパラメータをチェックすると、クエリの実行が非常に遅くなりますか?

クエリA

DECLARE @loannumber INT = 2222222; 

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    @loanNumber IS NULL 
    OR LOAN_NUMBER = @loanNumber; 

クエリB

DECLARE @loannumber INT = 2222222; 

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    LOAN_NUMBER = @loanNumber; 

両方のクエリが同じ結果セットを生成します。クエリAの経過時間は1分39秒です。クエリーBの経過時間は11秒です。私が知る限り、ヌルパラメータをチェックしないクエリBは、87%高速に実行されます。キャッシングはしていません -

Statistic    | Query A | Query B | 
-------------------------|---------|---------| 
Cache Plan Size   | 224 KB | 432 KB | 
Degree of Parallellism |  1 |  8 | 
Estimated Operator Cost | 0 (0%) | 0 (0 %) | 
Estimated Subtree Cost | 22.103 | 6.32266 | 
Estimated Number of Rows | 660449 | 1.00017 | 

を繰り返し、同じ結果を示しているバッチを実行すると次のように選択のための

Query A       | Query B 
-----------------------------------|----------------------------------- 
Select        | Select 
Cost: 0%       | Cost: 0% 
-----------------------------------|----------------------------------- 
Filter        | Parallelism 
Cost: 6%       | (Gather Streams) 
            | Cost: 1% 
-----------------------------------|----------------------------------- 
Columnstore Index Scan (Clustered) | Columnstore Index Scan (Clustered) 
Cost: 94%       | Cost: 99% 

統計は以下のとおりです。次のように

クエリの実行計画があります結果に影響を及ぼすように見える。

質問

nullパラメータは、このような大幅に異なる結果を生成するために、なぜチェックするのですか?

私はクエリを書く別の方法を探していないことに注意してください。私はなぜこれが起こるのかについての説明を探しています。

+0

クエリを実行する前にバッファキャッシュとプランキャッシュをクリアしました –

+0

あなたのケースについてはわかりませんが、クエリヒントを追加することで大幅なスピードアップを達成しました。このページを参照してください。多くの種類があります:https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query –

+0

多くの行にNULL値がありますかそのフィールド?統計に基づいて、約半分の行がNULLであるように見えます。 –

答えて

2

Erland Sommarskog explains whyこのようなキャッチオールクエリは悪いものであり、いくつかの修正方法があります。この特定のケースでは、nullチェックにより、クエリはバッチモードではなく行モードで実行されます。

各実行計画の操作を確認すると、実行チェックのすべての操作が強制的に行モードで機能することがわかります。これは、SQL Serverが列ストアインデックスをアンパックし、すべての行を再構成してからスキャンとフィルタ操作を開始することを意味します。

次のようなものが表示されます:「あなたがフィルターとインデックス・スキャン・ノード上にカーソルを移動した場合、あなたはあなたをチェックし、ヌルがないと推定実行モードが行

であることがわかります

SELECT <- FILTER <- Columnstore Index Scan 

をサーバーが実際にcolumnstore辞書を使用できることを意味するバッチモードでこの場合の作業、中に

SELECT <- Parallelism <- Filter <- Columnstore Index scan 

フィルターおよび索引スキャン:以下exeuction計画を持っているでしょう実際にパラメータ値を含む1つのブロックを見つけます。

Erland Sommarskogが示唆するように、OPTIMIZE FORヒントを使用して、オプティマイザに特定のパラメータ値に適した実行計画を生成させることができます。この場合、両方のクエリにバッチモードを使用する実行計画が同じになります。

SELECT 
    * 
FROM 
    MASDATA_CURRENT.BDE.LOAN 
WHERE 
    @loanNumber IS NULL 
    OR LOAN_NUMBER = @loanNumber 
OPTION (OPTIMIZE FOR (@loanNumber = -1)) ; 

多くのパラメータがある場合、この修正を適用しても、クエリは非常に複雑になります。私はストレートORまたはAND条件より複雑なものがあれば、どのように見えるか想像したくありません。

さらに簡単な解決策は、EFやDapperのようなORMを使用してクライアント側でクエリを生成することによって「オプションのパラメータ」を取り除くことです。どちらも必要なときにパラメータ化されたクエリを生成するので、パフォーマンスやセキュリティのメリットを失うことはありません。 ORMは指定したフィルタ条件のみを生成するので、クエリはより簡単になります

+0

OPTION(RECOMPILE)が最高のパフォーマンスを発揮することがわかりました。奇妙なことに、OPTIMIZE FOR(@loanNumber = x)を使用すると、パフォーマンスが低下しました。 –

1

OR演算子を見てください。最初の条件は、フィルタなしでテーブル内のすべてのレコードを見ることです。 サーバーはこのケースの実行計画を作成し、その直後に2番目の条件を適用します。

関連する問題