この声明と計画の解釈を助けてください:
https://www.brentozar.com/pastetheplan/?id=Bysy6YtEV
OracleからSQL Serverに移行したところ、かなり奇妙な動作がいくつかありました。移行中の問題に関連している可能性があります。
実行計画を解釈するのは難しいと思いました。両方の環境は同じ構造とインデックスを持つ必要があります。統計は最新である必要があります。 SQL Serverの設定:
DBのサイズは600 Gb、16コア、160 Gbメモリです。
クエリ:
SELECT
COUNT ( t_01.rsecondary_objectu ) AS selectExpr
FROM
PIMANRELATION t_01 ,
PITEM t_02 ,
PITEMREVISION t_03
WHERE
( ( ( ( t_01.rprimary_objectu = t_02.puid )
OR ( t_01.rprimary_objectu = t_03.puid )
)
AND
( t_01.rrelation_typeu = 'w8INy241VJFL2B' )
)
AND t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
)
オラクル
問題は関連しており、データに依存していることがわかりました。 GUIでコピーする別の項目を選択すると(実際にはコピーのものであり、アプリがこれらのステートメントを実行する方法です)、即座に機能します。クエリが正常に機能すると、少し違って見えます: https://www.brentozar.com/pastetheplan/?id=SJo-h2c44
SELECT COUNT ( t_01.rsecondary_objectu ) AS selectExpr FROM PIMANRELATION t_01 , PITEM t_02 , PITEMREVISION t_03 WHERE ( ( ( ( t_01.rprimary_objectu = t_02.puid ) OR ( t_01.rprimary_objectu = t_03.puid ) ) AND ( t_01.rrelation_typeu = 'w8INy241VJFL2B' ) ) AND t_01.rsecondary_objectu IN ('wBHZpD0uVJFL2B' , 'V2PlBGlAVJFL2B' , 'lnHlBGlAVJFL2B' ) )
Sql Serverは、前に述べたもの(無限に実行中)と完全に闘っているようですが、2番目のサーバーはほぼ瞬時に提供されます。それはとても奇妙な奇妙です。バギー製品のように。
ステートメントはアプリによって生成されるため、ステートメントには影響しません。
CE 110/70は同じプランになります。すべてのカスタムインデックスを無効にし、アプリによって提案されたインデックスのみを保持しても、同じように動作します。すべての主キーはクラスター化インデックスです。しかし、おそらく移行中に何かが混乱しました。しかし、それはかなり奇妙です。ほとんどのものは問題なく動作しますが、トピックでのクエリは極端です。 SQL Serverで45分間実行しました。それは単に完了しません。
もう一つの例。最高評価のインデックスが作成されると、製品環境は使用できなくなります。
インデックス:CREATE INDEX EN_PIPRELEASESTATUS_1 ON [TCEUP01]。[dbo]。[PRELEASESTATUS]([pname]、[pdate_released])INCLUDE([puid])
このクエリの結果: https://Pastebin.com/Ax3qTUjd ===> 137.142秒かかりました
テスト環境は同じであるはずですが、動作が異なります。 https://Pastebin.com/0PTZTJpr ===> 3.884秒かかりました
問題のある計画は次のようになります: https://www.brentozar.com/pastetheplan/?id=Skvy0qRNE
Oracleオプティマイザーは OR Expansion を使用してクエリの効率を向上させます。ドキュメントからの引用:
OR展開では、オプティマイザはOR演算子を含むWHERE句を使用してクエリをUNION ALL演算子を使用するクエリに変換します。
データベースは、さまざまな理由でOR=拡張を実行できます。たとえば、デカルト積を回避するより効率的なアクセスパスまたは代替の結合方法を可能にする場合があります。
新しいクエリは次のように記述されていると考えることができます。
SELECT
(
SELECT
COUNT ( t_01.rsecondary_objectu ) AS selectExpr
FROM
PIMANRELATION t_01
INNER JOIN PITEMREVISION t_03 ON t_01.rprimary_objectu = t_03.puid
CROSS JOIN PITEM t_02
WHERE
t_01.rrelation_typeu = 'w8INy241VJFL2B'
AND t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
)
+
(
SELECT
COUNT ( t_01.rsecondary_objectu ) AS selectExpr
FROM
PIMANRELATION t_01
INNER JOIN PITEM t_02 ON t_01.rprimary_objectu = t_02.puid
CROSS JOIN PITEMREVISION t_03
WHERE
t_01.rrelation_typeu = 'w8INy241VJFL2B'
AND t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
AND LNNVL(t_01.rprimary_objectu = t_03.puid)
)
from dual;
これで、クエリの両方の部分に等価条件があるため、Oracleはインデックスを使用して両方に対して効率的なネストされたループ結合を実行できます。クエリの両方の部分でクロス結合を実行する必要がありますが、同じクエリで2つのクロス結合を実行する場合と比較して、中間結果セットのサイズが大幅に削減されます。たとえば、PIMANRELATION
に1つの関連行があり、PITEMREVISION
とPITEM
の両方が100万行ある場合、それらを相互結合すると、1兆行になります。ただし、クエリを分割すると、両方の行が100万行になるだけです。
SQL Serverクエリオプティマイザーには、OR
をUNION ALL
に変換できるルールがあります:SelToIdxStrategy
。これに関するドキュメントはなく、私が見つけることができる唯一のリファレンスは this answer です。ただし、そのルールはこのコンテキストでは適用されません。代わりに、ネストされたループ結合を介してのみ実装できる2つのクロス結合を取得します。 SQL Serverは、PIMANRELATION
内の関連する各行について、PITEM
内のすべての行にクロス結合し、次にPITEMREVISION
内のすべての行にクロス結合し、その後、最後に行をフィルターで除外します。何兆もの行がフィルタリングされてしまう可能性があります。
悪い知らせがあります。クエリテキストの一部を本当に変更できず、そのクエリを適切に実行する必要がある場合、SQL Serverはおそらくアプリケーションに適したプラットフォームではありません。データベースにはさまざまな長所と短所があり、それらの違いに対応するためにクエリを変更する必要がある場合があります。
これは、クエリ構造のみに基づく提案です。
変更前実行計画、テーブル統計とヒストグラム、カーディナリティの推定、分離レベル、メモリ設定、および問題のその他の多くの考えられる理由と解決方法のガチョウ追跡を開始し、それを検討しますアプリケーションのバグである可能性がありますクエリを生成します。
私の推論は単純です。それがジャンクのように見え、ORMによって生成された場合、それはおそらくジャンクです。
以下を確認することをお勧めします。
クエリが元のOracleデータベースでまったく同じように実行される場合、またはクエリが生成される元のOracleデータベース(およびORM /アプリケーション)と異なる場合、ターゲットがSQL Serverデータベースの場合にクエリが少しまたはそれ以上変更されます。
クエリが、適用することを意図しているビジネスロジック/要件と一致していることをテストします。アプリケーションにはそのようなテストがあり、両方の(OracleとSQL Server)環境で正常に合格しますか?
私のポイントは、クエリに正確性の問題がないことを証明する前に、パフォーマンスの問題を追跡する意味がないということです。
詳細には、クエリはほとんど意味がありません。 OR
条件-セカンダリ(PITEM
およびPITEMREVISION
)テーブルの唯一の接続フィルターです-本質的にcross joinを導入します(これは幸運なことにあなたはOracleでいくつかの最適化機能をキックしていましたが、SQL Serverではそうしませんでした。何が起こるかの詳細な説明については、 Joe Obbishの答え を参照してください)。
より明確にするために、次のクエリを検討してくださいこれはあなたのものと同じです:
WITH
t_01 AS
( SELECT rprimary_objectu
FROM PIMANRELATION
WHERE rrelation_typeu = 'w8INy241VJFL2B'
AND rsecondary_objectu = '2yLJkWqiVJFL2B'
),
count_items AS
( SELECT COUNT(*) AS a
FROM t_01
JOIN PITEM t_02
ON t_01.rprimary_objectu = t_02.puid
),
count_revisions AS
( SELECT COUNT(*) AS b
FROM t_01
JOIN PITEMREVISION t_03
ON t_01.rprimary_objectu = t_03.puid
),
count_all_items AS
( SELECT COUNT(*) AS aa
FROM PITEM
),
count_all_revisions AS
( SELECT COUNT(*) AS bb
FROM PITEMREVISION
)
SELECT
(a * bb) + (b * aa) - (a * b) AS selectExpr
FROM
count_items, count_all_items,
count_revisions, count_all_revisions ;
上記がほとんど意味をなさない理由がわかりますか?これら2つのテーブルで、count_all_items
およびcount_all_revisions
の計算がどのようにカウントされるかすべての行に注意してください。この背後にあるビジネスロジックは見えません。
(上記はクロスジョインを行わず、テーブルスキャンを分離し、メインクエリの単純な乗算に計算を転送するため、もちろんより効率的です。オプティマイザがどんなに賢くなっても、常に制限があります。それらが提供できる可能な変換と最適化。)
この奇妙なカウントが使用される可能性がある唯一のケースは、カウントが0
または>= 1
であるかどうかのクエリの直後にチェックがある場合です-一部のORMはより効率的なEXISTS
メソッド。その場合、2つのテーブルのいずれかに関連する行が存在するかどうかを確認することが目的であるため、500万と500万のどちらであっても、カウントは重要ではありません。
この実行計画を示した2番目のステートメントで問題を見つけました。
https://www.brentozar.com/pastetheplan/?id=Skvy0qRNE
問題は、テーブルPRELEASESTATUS
に存在しないクエリの予期しない値/パラメーターでした。そのため、テーブルに存在しない値を完全にスキャンする必要があったと思います。
とにかく、SQLサーバーはこのパラメーターに対して非常に奇妙に反応し、テーブルのインデックスをサーバーがほとんど停止している間、このインデックスがないと、正常に実行されます。したがって、インデックスと一緒にパラメータがその問題を引き起こしました。
そうすれば、テスト環境で再現できます。提供された値がテーブルで見つからない場合、少なくともそのインデックスがない場合と同じくらい高速になると思いますが、SQL Serverはそのクエリがあまり意味がないことを認識せず、節約することで新しいインデックスの方がはるかに高速です。
両方のプランの比較:
インデックスなし: https://www.brentozar.com/pastetheplan/?id=rylVsaCEV
インデックスあり: https://www.brentozar.com/pastetheplan/?id=Skvy0qRNE