web-dev-qa-db-ja.com

OR条件が再調整されると、SQL Serverは異なるプランを作成します

次のようなパフォーマンスの低いクエリを確認しました。

_WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
  AND main.Active = -1
  AND main.StatusID = 1
  AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
  AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode
_

このクエリで誤ってSSMSクエリデザイナを使用し、クエリを次のように書き直しました。

_WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
  AND main.Active = -1
  AND main.StatusID = 1
  AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
  AND main.TextCol1 IS NOT NULL

   OR manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
  AND main.Active = -1
  AND main.StatusID = 1
  AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
  AND main.TextCol2 IS NOT NULL
ORDER BY aux.SortCode
_

よく見ると、すべての条件を繰り返すことによってOR条件が単純に拡張されていることがわかります。つまり、a AND (b OR c)が_(a AND b) OR (a AND c)_に変更されています。

結果のクエリは、コストの点で50%、実行時間の点で33%小さくなりました。ORを再配置する理由がわかりません両方のクエリが同一(?)の場合、条件によってプランが変更されました。条件をコピーして貼り付けることで、OR条件を自分で拡張することもできましたが、なぜ私はそうする必要があるのですか?

計画を貼り付けます とスクリーンショット:

Execution Plans

行の数:

_main     2718
manymany 188761
aux      19
_

ノート:

  • TextCol1とTextCol2はtextデータ型であり、インデックス付けできません
  • 平均があります。ウェブサイトIDごとのmanymanyテーブルの170.20レコード
7
Salman A

しかし、SQLサーバーが両方のクエリを1つとして認識しないのはなぜですか?結局、a AND(b OR c)=(a AND b)OR(a AND c)?

論理的には同じであり、同じ結果が得られます。

前提条件

私の想定では、「より高速な」プランでは、オプティマイザはORの上部にあるいくつかのフィルタステートメントを下部にあるいくつかのフィルタステートメントと同じであると見なしていません。私はここで完全にベースから外れているかもしれません。

これらの仮定を取得する理由は、このフィルター述語に基づいています。

このフィルター述語は、Mainテーブルとmanymanyテーブル間の結合の結果を使用します。 enter image description here

このフィルターのEXPR1021およびEXPR1022はスカラーから作成された式であることに注意してくださいmanymanyテーブルの演算子。

enter image description here

このフィルターは2つの部分で構成され、最初の部分は(.. AND .. OR .. AND ..)を使用し、2番目の部分は単純なANDフィルターを使用します。

(getdate()>=[Expr1021] 
AND getdate()<=[Expr1022] 
AND getdate()>=[DB1].[dbo].[main].[FromDate] 
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL 
OR getdate()>=[Expr1021] 
AND getdate()<=[Expr1022]
 AND getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL) 

 AND (getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)

ご覧のとおり、このフィルタの最初の部分のORの上下の唯一の違いは

AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL

[〜#〜] vs [〜#〜]

AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL

そして、2番目の部分は、ANDのないOR述語であるため、何があってもtrueである必要があります。

同じ関数の余分な計算が発生するため、私の意見では必要ありません。繰り返しますが、SQLサーバーがこれらの計算を行う理由は、それらが同じであることを認識していないためです。

Where句の他の一部については、これらが同じであることを知っています。メインテーブルでは、statusid = 1は1回だけ評価されます。

enter image description here

manymanyテーブルでは、同じステートメントが2回評価されます。

enter image description here

「遅い」プランでは、ステートメントがOR句と一緒に追加されないため、オプティマイザが異なるプランを生成し、テーブルにフィルタ述語を個別に適用します(重複するフィルタはありません)。

enter image description here

enter image description here

仮定の終わり

2つのプランの比較

「高速」プランのパフォーマンスは幸運だったと思いますが、一致するデータが増えると、「高速」プランは醜くなるかもしれません。フィルターを適用する場所と時期(およびその他の要因)によって異なります。

高速計画フィルタリング

「高速」プランでは、SQLサーバーは、2つのmainとのさまざまな組み合わせの結果として、manymanyテーブルとORテーブルを結合した後、いくつかのフィルターを適用します。 +(AND ... AND ... AND...)ブロック。 maintableの列は、manymanyテーブルとのすべての可能な組み合わせを見つけた後にフィルターされます。

その結果、同じ述語がmanymanyテーブルで2回実行されます。

enter image description hereORの上下の述語。

しかし、これはmainテーブルのシーク述語の一部には当てはまりません

enter image description here

この後、結合が発生し、mainmanymanyの間の結合の結果に対するさらに大きなフィルター述語が発生します。これも、可能なすべての組み合わせについてです enter image description here

このフィルターのEXPR1021およびEXPR1022はスカラーから作成された式であることに注意してくださいmanymanyテーブルの演算子。

enter image description here

このフィルターは2つの部分で構成され、最初の部分は(.. AND .. OR .. AND ..)を使用し、2番目の部分は単純なANDフィルターを使用します。

(getdate()>=[Expr1021] 
AND getdate()<=[Expr1022] 
AND getdate()>=[DB1].[dbo].[main].[FromDate] 
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL 
OR getdate()>=[Expr1021] 
AND getdate()<=[Expr1022]
 AND getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL) 

 AND (getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate] 
 AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000') 
 AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)

ご覧のとおり、このフィルタの最初の部分のORの上下の唯一の違いは

AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL

[〜#〜] vs [〜#〜]

AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL

そして、2番目の部分は、ANDのないOR述語であるため、何があってもtrueである必要があります。

私の意見では不要な余分な計算が発生します。

スロープランフィルタリング

「遅い」計画では、SQLサーバーはAND (TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL)パーツの結果としてフィルターをメインテーブルに直接適用し、次にmanymanyテーブルと結合して残りを除外して15にします。行。

Mainテーブルフィルター

enter image description here

enter image description here

manymanyテーブルフィルター

enter image description here


他の、時には重複する情報:

遅い計画

遅い方の計画を見ると、クラスター化インデックスPK_mainが使用され、スカラー計算、フィルター、およびネストされたループ演算子に使用されます。

enter image description here

これを、返される推定行と比較すると、違いがわかります。 enter image description here

スキャンの述語によって返されると推定される93行です。

enter image description here

これは実際には予想されたものよりも約20倍少なく、つまり1947行です。

その後、Computeスカラーまたは次のステートメント:

 , CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
 , CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating

これらの1947行で評価されます。

次に、フィルター演算子(main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)を使用して、それを1374行に減らします。

この後、これらの1374行をdbo.manymanyテーブルに結合して、15行を返します。

より速い計画

より高速な計画では、NCインデックスを使用しています:CVR_main_4テーブルのthe dbo.Mainenter image description here

これは、シーク述語を使用してフィルタリングし、nested loops結合演算子に27行を返し、dbo.manymanyテーブルと再び結合します。

そして、返される実際の行は、推定行よりもさらに低くなります:

enter image description here

152行の推定値に対して27行の実際の行

フィルタリング

大きな違いは、フィルタリングが発生する場所です。「遅い」プランでは、これはdbo.Mainテーブルで直接行われます。

述語あり:TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL

enter image description here

そしてこのフィルターを1943行に適用しています

その他のフィルタリングはdbo.manymanyテーブルで直接行われます

enter image description here(seek)述語dbo.manymany

一方、「より高速な」プランの他のORは、dbo.Mainからdbo.manymanyへの結合後にフィルタリングされ、27行でより大きなフィルターになります。

enter image description here

27行に複数のORを含む非常に大きなフィルター。

もう1つの違いは、キールックアップ演算子です。

enter image description here

これは、クラスター化インデックスからさらに10列を取得しますが、これを行う必要があるのは27行のみです。

enter image description here

enter image description here

オプティマイザーが「遅い」プランを選択するもう1つの理由は、オプティマイザーが他の列を検索しない方が良いと考えているためです。


高速プランはさらに高速ですか、それとも常に「高速」になりますか?

フィルターを通過するデータが増えると、「遅い」計画の方が良いと思います。キールックアップだけでなく、計画のより下の方にあるより大きなフィルター演算子も原因です。

その場合は、インデックスの横にあります。 UNIONステートメントを使用してクエリを複数の部分に分割することにより、フィルタリングを改善できます。

そのようです:

SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
     , CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
     , CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
  AND manymany.Check1 = -1
  AND manymany.Active = -1
  AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
  AND main.Active = -1
  AND main.StatusID = 1
  AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
  AND TextCol1 IS NOT NULL

 UNION 

 SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
     , CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
     , CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
  AND manymany.Check1 = -1
  AND manymany.Active = -1
  AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
  AND main.Active = -1
  AND main.StatusID = 1
  AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
  AND TextCol2 IS NOT NULL
ORDER BY SortCode;
2
Randi Vertongen

最初のクエリではスキャンから始める必要がありますが、2番目のクエリではシークに非クラスター化インデックスを利用できました。

バッグに100個のビー玉があり、青と白または青と赤のビー玉だけを抽出するように監査する必要があると考えてください。

最初のクエリでは、100個のビー玉のそれぞれを調べて、すべての青を選びます。それが終わったら、それらをすべてチェックして、白または赤のものが存在するかどうかを確認します。

2番目のクエリは、バッグに入れて青と白または青と赤のビー玉だけをつかむことです。

最初の青だけで各大理石を見る必要がないため、2番目のクエリはより高速になります。そのステップを、青と白、または青と赤でしたが、本当に欲しかったものと組み合わせることができます。

それはとにかく私がそれを見る方法です。最終的に、最初のクエリはテーブルスキャンを必要とし、2番目のクエリは最初からシーク権を使用しました。非クラスター化インデックスには必要な情報がすべて含まれていないため、キーの検索とスキャンを実行する必要がありましたが、その時点では、調べるデータセットがはるかに少ないため、高速でした。

0
Muab Nhoj

Plan Linkが機能していません。

不完全な情報を提供しました。テーブル結合列とそのデータ型を知っておくことが重要です。

aux.SortCodeとは? auxエイリアスとは何ですか?クエリのどこにありますか?

クエリを並べ替えるスコープは他にもあります。

この場合、

AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)

SQLオプティマイザーCardianility Estimateは非常に貧弱です。

ORで同じ条件を繰り返すことは悪い考えです。

manyテーブルレコードを#TempテーブルまたはCTEに置くことができます

次に、manymany#Tempテーブルに結合します。

DECLARE @Dt Datetime=CURRENT_TIMESTAMP

select many.Col,many.OnlyRequiredColumn
from many into #Temp
WHERE main.Active = -1
  AND main.StatusID = 1
  AND (main.FromDate>=@Dt AND main.UptoDate<=@Dt)
  AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode

#Tempに100/200を超えるレコードがある場合は、manymanyで結合する列にindexを作成できます。

select *
from manymany
join #Temp on manymany.somecolumn=#temp.somecolumn
WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  AND (manymany.FromDate>=@Dt AND manymany.UptoDate<=@Dt)
--ORDER BY aux.SortCode
0
KumarHarsh