次のようなパフォーマンスの低いクエリを確認しました。
_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
条件を自分で拡張することもできましたが、なぜ私はそうする必要があるのですか?
_main 2718
manymany 188761
aux 19
_
text
データ型であり、インデックス付けできませんしかし、SQLサーバーが両方のクエリを1つとして認識しないのはなぜですか?結局、a AND(b OR c)=(a AND b)OR(a AND c)?
論理的には同じであり、同じ結果が得られます。
前提条件
私の想定では、「より高速な」プランでは、オプティマイザはOR
の上部にあるいくつかのフィルタステートメントを下部にあるいくつかのフィルタステートメントと同じであると見なしていません。私はここで完全にベースから外れているかもしれません。
これらの仮定を取得する理由は、このフィルター述語に基づいています。
このフィルター述語は、Main
テーブルとmanymany
テーブル間の結合の結果を使用します。
このフィルターのEXPR1021およびEXPR1022はスカラーから作成された式であることに注意してくださいmanymany
テーブルの演算子。
このフィルターは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回だけ評価されます。
manymany
テーブルでは、同じステートメントが2回評価されます。
「遅い」プランでは、ステートメントがOR
句と一緒に追加されないため、オプティマイザが異なるプランを生成し、テーブルにフィルタ述語を個別に適用します(重複するフィルタはありません)。
仮定の終わり
2つのプランの比較
「高速」プランのパフォーマンスは幸運だったと思いますが、一致するデータが増えると、「高速」プランは醜くなるかもしれません。フィルターを適用する場所と時期(およびその他の要因)によって異なります。
高速計画フィルタリング
「高速」プランでは、SQLサーバーは、2つのmain
とのさまざまな組み合わせの結果として、manymany
テーブルとOR
テーブルを結合した後、いくつかのフィルターを適用します。 +(AND ... AND ... AND...
)ブロック。 maintable
の列は、manymany
テーブルとのすべての可能な組み合わせを見つけた後にフィルターされます。
その結果、同じ述語がmanymany
テーブルで2回実行されます。
しかし、これはmain
テーブルのシーク述語の一部には当てはまりません
この後、結合が発生し、main
とmanymany
の間の結合の結果に対するさらに大きなフィルター述語が発生します。これも、可能なすべての組み合わせについてです
このフィルターのEXPR1021およびEXPR1022はスカラーから作成された式であることに注意してくださいmanymany
テーブルの演算子。
このフィルターは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
テーブルフィルター
manymany
テーブルフィルター
他の、時には重複する情報:
遅い計画
遅い方の計画を見ると、クラスター化インデックスPK_mainが使用され、スカラー計算、フィルター、およびネストされたループ演算子に使用されます。
スキャンの述語によって返されると推定される93行です。
これは実際には予想されたものよりも約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.Main
これは、シーク述語を使用してフィルタリングし、nested loops
結合演算子に27行を返し、dbo.manymany
テーブルと再び結合します。
そして、返される実際の行は、推定行よりもさらに低くなります:
152行の推定値に対して27行の実際の行
フィルタリング
大きな違いは、フィルタリングが発生する場所です。「遅い」プランでは、これはdbo.Main
テーブルで直接行われます。
述語あり:TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
そしてこのフィルターを1943行に適用しています
その他のフィルタリングはdbo.manymany
テーブルで直接行われます
一方、「より高速な」プランの他のOR
は、dbo.Main
からdbo.manymany
への結合後にフィルタリングされ、27行でより大きなフィルターになります。
27行に複数のOR
を含む非常に大きなフィルター。
もう1つの違いは、キールックアップ演算子です。
これは、クラスター化インデックスからさらに10列を取得しますが、これを行う必要があるのは27行のみです。
オプティマイザーが「遅い」プランを選択するもう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番目のクエリではシークに非クラスター化インデックスを利用できました。
バッグに100個のビー玉があり、青と白または青と赤のビー玉だけを抽出するように監査する必要があると考えてください。
最初のクエリでは、100個のビー玉のそれぞれを調べて、すべての青を選びます。それが終わったら、それらをすべてチェックして、白または赤のものが存在するかどうかを確認します。
2番目のクエリは、バッグに入れて青と白または青と赤のビー玉だけをつかむことです。
最初の青だけで各大理石を見る必要がないため、2番目のクエリはより高速になります。そのステップを、青と白、または青と赤でしたが、本当に欲しかったものと組み合わせることができます。
それはとにかく私がそれを見る方法です。最終的に、最初のクエリはテーブルスキャンを必要とし、2番目のクエリは最初からシーク権を使用しました。非クラスター化インデックスには必要な情報がすべて含まれていないため、キーの検索とスキャンを実行する必要がありましたが、その時点では、調べるデータセットがはるかに少ないため、高速でした。
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