9

状況

Oracle11.2.0.2.0の大量のデータに対する中規模のクエリのクエリ実行プランに問題があります。物事をスピードアップするために、私はおおよそ次のようなことをする範囲フィルターを導入しました:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((org_from IS NULL) OR (org_from <= org.no))
   AND ((org_to   IS NULL) OR (org_to   >= org.no)))
  -- [...]

ご覧のとおり、オプションの範囲の組織番号JOINの使用を制限したいと思います。organisationsクライアントコードはDO_STUFF、制限付き(高速と想定)または制限なし(非常に低速)で呼び出すことができます。

トラブル

問題は、PL / SQLが上記のバインド変数org_fromorg_toパラメータを作成することです。これは、ほとんどの場合に予想されることです。

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((:B1 IS NULL) OR (:B1 <= org.no))
   AND ((:B2 IS NULL) OR (:B2 >= org.no)))
  -- [...]

回避策

この場合のみ、値をインライン化するだけで、つまりOracleによって実行されるクエリが実際には次のようなものである場合に、クエリ実行プランがはるかに優れていると測定しました。

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))
  -- [...]

「たくさん」とは、5〜10倍速いという意味です。クエリが実行されることは非常にまれであることに注意してください。つまり、月に1回です。したがって、実行プランをキャッシュする必要はありません。

私の質問

  • PL / SQLで値をインライン化するにはどうすればよいですか?EXECUTE IMMEDIATEについては知っていますが、文字列の連結ではなく、PL/SQLにクエリをコンパイルしてもらいたいと思います。

  • 偶然に起こったことを測定しただけですか、それとも変数のインライン化の方が実際に優れていると思いますか(この場合)?私が尋ねる理由は、バインド変数がOracleに一般的な実行プランを考案させるのに対し、インライン化された値は非常に特定の列とインデックスの統計を分析できると思うからです。ですから、これは単なる偶然ではないと想像できます。

  • 私は何かが足りないのですか?変数のインライン化以外に、クエリ実行プランの改善を実現するまったく別の方法があるかもしれません(私もかなりの数のヒントを試しましたが、私はその分野の専門家ではありません)?

4

5 に答える 5

7

あなたのコメントの1つであなたは言いました:

「また、さまざまなバインド値を確認しました。バインド変数を使用すると、全表スキャンが得られますが、ハードコードされた値を使用すると、計画がはるかに良くなります。」

2つのパスがあります。パラメータにNULLを渡すと、すべてのレコードが選択されます。このような状況では、全表スキャンがデータを取得する最も効率的な方法です。値を渡すと、情報の小さなサブセットのみを選択するため、インデックス付きの読み取りの方が効率的です。

バインド変数を使用してクエリを作成する場合、オプティマイザは決定を下す必要があります。ほとんどの場合、値を渡すか、nullを渡すと想定する必要がありますか?難しい。それでは、別の見方をします。レコードのサブセットのみを選択する必要がある場合に全表スキャンを実行する方が非効率的ですか、それともすべてのレコードを選択する必要がある場合にインデックス付き読み取りを実行する方が非効率的ですか。

オプティマイザは、すべての不測の事態をカバーするための最も非効率的な操作であるとして、全表スキャンに対応しているように見えます。

一方、オプティマイザーがFALSEと評価する値をすぐにハードコーディングする10 IS NULLと、目的のサブセットレコードを見つけるためにインデックス付き読み取りを使用するメリットを比較検討できます。


じゃあ何をすればいいの?このクエリは月に1回だけ実行されるとおっしゃっていますが、個別のクエリを作成するには、ビジネスプロセスにわずかな変更を加えるだけでよいと思います。1つはすべての組織用で、もう1つは組織のサブセット用です。


「ところで、:R1 IS NULL句を削除しても、実行プランはあまり変更されないため、OR条件の反対側である:R1 <= org.noが残ります。ここで、NULLはorgのように意味がありません。いいえはNULLではありません」

さて、問題は、範囲を指定するバインド変数のペアがあるということです。値の分布に応じて、さまざまな範囲がさまざまな実行プランに適合する場合があります。つまり、この範囲は(おそらく)インデックス付き範囲スキャンに適しています...

WHERE org.id BETWEEN 10 AND 11

...これは全表スキャンにより適している可能性があります...

WHERE org.id BETWEEN 10 AND 1199999

そこで、BindVariablePeekingが登場します。

(もちろん、値の分布によって異なります)。

于 2011-03-18T15:56:22.890 に答える
4

クエリプランは実際には一貫して異なるため、オプティマイザのカーディナリティの見積もりが何らかの理由でオフになっていることを意味します。クエリプランから、バインド変数が使用されている場合、オプティマイザーが条件の選択が不十分であると予想していることを確認できますか?11.2を使用しているので、Oracleはアダプティブカーソル共有を使用する必要があります。これにより、バインド変数のピークの問題になることはありません(テストでバインド変数を使用してバージョンを何度も呼び出していると仮定しますNO

良い計画のカーディナリティの推定値は実際に正しいですか?列の統計は正確であるとおっしゃっていましたNOが、たとえば、通常の統計収集プロセスでは更新されない可能性のある漂遊ヒストグラムが疑われます。

クエリで常にヒントを使用して、特定のインデックスを強制的に使用することができます(ただし、長期的なメンテナンスの観点からは、保存されたアウトラインまたはオプティマイザープランの安定性を使用することをお勧めします)。これらのオプションはいずれも、動的SQLに頼るよりも望ましいでしょう。

ただし、試行する追加のテストの1つは、SQL99結合構文をOracleの古い構文に置き換えることです。

SELECT <<something>>
  FROM <<some other table>> cust,
       organization org
 WHERE cust.org_id = org.id
   AND (    ((org_from IS NULL) OR (org_from <= org.no)) 
        AND ((org_to   IS NULL) OR (org_to   >= org.no)))

それは明らかに何も変更しないはずですが、SQL 99構文にパーサーの問題があったので、それを確認する必要があります。

于 2011-03-18T15:54:36.023 に答える
3

Bind Peekingのような匂いがしますが、私はOracle 10のみを使用しているため、11にも同じ問題が存在するとは言えません。

于 2011-03-18T15:45:07.807 に答える
3

これは、SQLPlanの安定性と組み合わせた、アダプティブカーソル共有の必要性によく似ています。何が起こっているのかと思いますcapture_sql_plan_baselines parameter is true。そして、同じですuse_sql_plan_baselines。これが当てはまる場合、次のことが起こります。

  1. クエリが最初に開始されたときに解析され、新しいプランが取得されます。
  2. 2回目は、このプランは承認済みプランとしてsql_plan_baselinesに保存されます。
  3. このクエリの以降のすべての実行では、バインド変数が何であるかに関係なく、このプランが使用されます。

アダプティブカーソル共有がすでにアクティブになっている場合、オプティマイザは新しい/より良いプランを生成し、それをsql_plan_baselinesに格納しますが、誰かがこの新しいプランを受け入れ可能な代替プランとして受け入れるまで、それを使用できません。dba_sql_plan_baselinesクエリにエントリが含まれているかどうかを確認します。新しいプランを展開し、プランのパフォーマンスが新しいプランがない場合よりも少なくとも1.5倍優れている場合は、それを自動的に受け入れるaccepted = 'NO' and verified = null ことができます。dbms_spm.evolve

これがお役に立てば幸いです。

于 2011-03-18T16:27:11.657 に答える
2

これをコメントとして追加しましたが、ここでも提供します。これが過度に単純化されていないことを願っています。詳細な応答を見ると、正確な問題を誤解している可能性がありますが、とにかく...

組織のテーブルに、数値として定義されている列番号(org.no)があるようです。ハードコードされた例では、数値を使用して比較を行います。

JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))

手順では、varchar2を渡します。

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

したがって、varchar2をnumberと比較するには、Oracleが変換を実行する必要があるため、フルスキャンが発生する可能性があります。

解決策:数値を渡すようにprocを変更します

于 2011-03-18T18:07:48.943 に答える