4

ORが悪いと聞き、 を複数持つとOR性能に大きく影響する場合がある しかし、行独立ORの s はどうでしょうか? 例を見てください:

SELECT
  *
FROM
  some_table t
WHERE
  (
    some_function('CONTEXT') = 'context of selecting by id'
    AND t.id = TO_NUMBER(another_function('ID'))
  )
  OR (
    some_function('CONTEXT') = 'context of filtering by name'
    AND t.name LIKE '%' || another_function('NAME') || '%'
  )
  OR (
    some_function('CONTEXT') = 'context of taking actual rows'
    AND TO_DATE(another_function('ACTUAL_DATE'), '...')
        BETWEEN t.start_date AND t.end_date
  )
  ...

ここでsome_function('CONTEXT')は、行に関係なく同じ値を返します (列の値などの行に依存するデータを引数として使用せず、クエリの実行時に結果に影響を与える内部状態を変更しません)。のような単なるパッケージ変数にすることもできますsome_package.context。私が思うに、オプティマイザーは最初に
計算してから、どちらを取るかを決定する必要があります。 しかし、実際にはどうなるでしょうか?このようなクエリでパフォーマンス リークが発生しないことをどのように確認できますか? some_function('CONTEXT')OR

PS: 11.2

4

2 に答える 2

2

文書化されていないヒントを使用するuse_concat(or_predicates(1))か、を使用してクエリを書き直す必要がありますUNION ALL。オプティマイザーは、関数に関係なく、これらのタイプの述部で問題を抱えています。

期待される計画

次のような計画が必要です。

------------------------------------------------------
| Id  | Operation                     | Name         |
------------------------------------------------------
|   0 | SELECT STATEMENT              |              |
|   1 |  CONCATENATION                |              |
|*  2 |   FILTER                      |              |
|*  3 |    TABLE ACCESS FULL          | SOME_TABLE   |
|*  4 |   FILTER                      |              |
|*  5 |    TABLE ACCESS FULL          | SOME_TABLE   |
|*  6 |   FILTER                      |              |
|*  7 |    TABLE ACCESS BY INDEX ROWID| SOME_TABLE   |
|*  8 |     INDEX UNIQUE SCAN         | SYS_C0010268 |
------------------------------------------------------

FILTERは、Explain PlanのセクションのOperation典型的なものとは大きく異なります。これらは条件を評価し、実行時に使用する実行計画の部分を決定します。関数に渡される値に応じて、プランは完全なテーブル スキャン (名前または日付の非選択的な述語の場合) を使用するか、一意のインデックス スキャン (ID の非常に選択的な述語の場合) を使用します。filterPredicate InformationFILTER

これは、まさにあなたのようなクエリで必要なものです。また、クエリに少数ANDの としかない場合はORFILTER.

実績計画

しかし実際には、複雑な述語を使用すると、計画は次のようになります。

----------------------------------------
| Id  | Operation         | Name       |
----------------------------------------
|   0 | SELECT STATEMENT  |            |
|*  1 |  TABLE ACCESS FULL| SOME_TABLE |
----------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SOME_FUNCTION"('CONTEXT')='context of filtering by name' 
              AND "T"."NAME" LIKE '%'||"ANOTHER_FUNCTION"('NAME')||'%' OR 
              "SOME_FUNCTION"('CONTEXT')='context of taking actual rows' AND 
              "T"."START_DATE"<=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...') AND 
              "T"."END_DATE">=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...') OR 
              "SOME_FUNCTION"('CONTEXT')='context of selecting by id' AND 
              "T"."ID"=TO_NUMBER("ANOTHER_FUNCTION"('ID')))

テーブル全体のスキャンが必ずしも悪いわけではありません。しかし、単一の主キー値を選択するのはかなりひどいものです。

サンプル スキーマ

テーブルと 100 万のサンプル行を作成します。選択性の高い列もあれば、選択性の低い列もあります。それらはすべてヒストグラムを持っているため、オプティマイザーは処理するための多くの優れた情報を持っています.

drop table some_table purge;

create table some_table
(
    id          number primary key,
    name        varchar2(100),
    start_date  date,
    end_date    date
);

begin
    for i in 1 .. 10 loop
        insert into some_table
        select 
            level+(i*100000),
            'Name '||mod(level, 5),
            date '2000-01-01' + mod(level, 10000),
            date '2010-01-01' + mod(level, 10000)
        from dual
        connect by level <= 100000;
    end loop;
end;
/
begin
    dbms_stats.gather_table_stats(user, 'SOME_TABLE'
        ,method_opt => 'for all columns size 254');
end;
/

サンプル関数

これらの関数は非常に静的であり、オプティマイザはそれを認識している必要があります。この例ではsome_function、何にも一致しない方法で使用しています。これは一種の最良のシナリオです。このクエリが何も返さないことは、Oracle にとって非常に簡単にわかるはずです。

--Static functions.
create or replace function some_function(p_context in varchar2) return varchar2 is
begin
    return p_context;
end;
/
--Btw, returning stringly-typed data is almost always a horrible idea.
--(Althogh if you're dealing with sys_context you may not have a choice.)
create or replace function another_function(p_type in varchar2) return varchar2 is
begin
    if p_type = 'ID' then
        return '1';
    elsif p_type = 'NAME' then
        return 'Name 1';
    elsif p_type = 'ACTUAL_DATE' then
        return '2000-01-01';
    end if;
end;
/

デフォルト - FILTER 操作のない不適切な計画

デフォルトのプランは非常に貧弱です。クエリはほぼ 0 秒で実行されるはずですが、代わりに完全なテーブル スキャンを実行する必要があります。

explain plan for
SELECT * FROM some_table t
WHERE
  (
    some_function('CONTEXT') = 'context of selecting by id'
    AND t.id = TO_NUMBER(another_function('ID'))
  )
  OR (
    some_function('CONTEXT') = 'context of filtering by name'
    AND t.name LIKE '%' || another_function('NAME') || '%'
  )
  OR (
    some_function('CONTEXT') = 'context of taking actual rows'
    AND TO_DATE(another_function('ACTUAL_DATE'), '...')
        BETWEEN t.start_date AND t.end_date
  );

select * from table(dbms_xplan.display);

Plan hash value: 3038250352

--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |   525 | 14700 |  1504  (17)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| SOME_TABLE |   525 | 14700 |  1504  (17)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SOME_FUNCTION"('CONTEXT')='context of filtering by name' 
              AND "T"."NAME" LIKE '%'||"ANOTHER_FUNCTION"('NAME')||'%' OR 
              "SOME_FUNCTION"('CONTEXT')='context of taking actual rows' AND 
              "T"."START_DATE"<=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...') AND 
              "T"."END_DATE">=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...') OR 
              "SOME_FUNCTION"('CONTEXT')='context of selecting by id' AND 
              "T"."ID"=TO_NUMBER("ANOTHER_FUNCTION"('ID')))

use_concat(or_predicates(1)) - FILTER を使用した適切な計画

ヒントはUSE_CONCAT、クエリを個別のステップに変換しUNION ALLます。そして、各述語は単純で、FILTER操作があります。残念ながらUSE_CONCAT、いくつかの奇妙な制限があります。インデックスが使用されている場合にのみ機能する場合があります (My Oracle Support のドキュメント 259741.1 を参照してください)。また、まったく機能しない場合や、回避策が機能しない場合があり、12c ではまだ修正されていません (ドキュメント 14545269.8)。

追加or_predicates(1)すると機能しますが、完全に文書化されていません。

explain plan for
SELECT --+ use_concat(or_predicates(1))
  *
FROM some_table t
WHERE
  (
    some_function('CONTEXT') = 'context of selecting by id'
    AND t.id = TO_NUMBER(another_function('ID'))
  )
  OR (
    some_function('CONTEXT') = 'context of filtering by name'
    AND t.name LIKE '%' || another_function('NAME') || '%'
  )
  OR (
    some_function('CONTEXT') = 'context of taking actual rows'
    AND TO_DATE(another_function('ACTUAL_DATE'), '...')
        BETWEEN t.start_date AND t.end_date
  );

select * from table(dbms_xplan.display);

Plan hash value: 1618041905

----------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 52500 |  1435K|  2721   (8)| 00:00:01 |
|   1 |  CONCATENATION                |              |       |       |            |          |
|*  2 |   FILTER                      |              |       |       |            |          |
|*  3 |    TABLE ACCESS FULL          | SOME_TABLE   |  2500 | 70000 |  1362   (8)| 00:00:01 |
|*  4 |   FILTER                      |              |       |       |            |          |
|*  5 |    TABLE ACCESS FULL          | SOME_TABLE   | 49999 |  1367K|  1356   (7)| 00:00:01 |
|*  6 |   FILTER                      |              |       |       |            |          |
|*  7 |    TABLE ACCESS BY INDEX ROWID| SOME_TABLE   |     1 |    28 |     3   (0)| 00:00:01 |
|*  8 |     INDEX UNIQUE SCAN         | SYS_C0010269 |     1 |       |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("SOME_FUNCTION"('CONTEXT')='context of taking actual rows')
   3 - filter("T"."START_DATE"<=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...') AND 
              "T"."END_DATE">=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...'))
   4 - filter("SOME_FUNCTION"('CONTEXT')='context of filtering by name')
   5 - filter("T"."NAME" LIKE '%'||"ANOTHER_FUNCTION"('NAME')||'%' AND 
              (LNNVL("SOME_FUNCTION"('CONTEXT')='context of taking actual rows') OR 
              LNNVL("T"."START_DATE"<=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...')) OR 
              LNNVL("T"."END_DATE">=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...'))))
   6 - filter("SOME_FUNCTION"('CONTEXT')='context of selecting by id')
   7 - filter((LNNVL("SOME_FUNCTION"('CONTEXT')='context of filtering by name') OR 
              LNNVL("T"."NAME" LIKE '%'||"ANOTHER_FUNCTION"('NAME')||'%')) AND 
              (LNNVL("SOME_FUNCTION"('CONTEXT')='context of taking actual rows') OR 
              LNNVL("T"."START_DATE"<=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...')) OR 
              LNNVL("T"."END_DATE">=TO_DATE("ANOTHER_FUNCTION"('ACTUAL_DATE'),'...'))))
   8 - access("T"."ID"=TO_NUMBER("ANOTHER_FUNCTION"('ID')))

UNION ALL - FILTER を使用した優れた計画

クエリを手動で展開する方がおそらく安全な方法です。ただし、クエリがどれほど複雑かによっては、非常に見苦しくなる場合があります。

explain plan for
SELECT * FROM some_table t
WHERE some_function('CONTEXT') = 'context of selecting by id' AND t.id = TO_NUMBER(another_function('ID'))
union all
SELECT * FROM some_table t
WHERE some_function('CONTEXT') = 'context of filtering by name' AND t.name LIKE '%' || another_function('NAME') || '%'
union all
SELECT * FROM some_table t
WHERE some_function('CONTEXT') = 'context of taking actual rows' AND TO_DATE(another_function('ACTUAL_DATE'), '...') BETWEEN t.start_date AND t.end_date

select * from table(dbms_xplan.display);

(Plan not shown - it's basically the same as the `USE_CONCAT` version.)

CASE - FILTER のない悪い計画

述語を単一CASEに書き直すことは良い考えでしたが、ここではうまくいかないようです。それは私の特定の例の問題だけかもしれませんが。

explain plan for
SELECT *
FROM some_table t
WHERE
    case
    when some_function('CONTEXT') = 'context of selecting by id'
        AND t.id = TO_NUMBER(another_function('ID')) then 1
    when some_function('CONTEXT') = 'context of filtering by name'
        AND t.name LIKE '%' || another_function('NAME') || '%' then 1
    when some_function('CONTEXT') = 'context of taking actual rows'
        AND TO_DATE(another_function('ACTUAL_DATE'), '...') BETWEEN t.start_date AND t.end_date then 1
    else 0 end
    = 1;

select * from table(dbms_xplan.display);

(Plan not shown - it's basically the same as the default version with the full table scan.)
于 2013-08-23T05:38:22.250 に答える
1

その通りです。それがオプティマイザが行うべきことです。しかし、私の経験では、それはそうではありません。

奇妙なことに、この場合でも、次のように述語を case ステートメントに変換すると、必要な動作を得ることができます。

case
    when some_function('CONTEXT') = 'context of selecting by id'
    AND t.id = TO_NUMBER(another_function('ID')
    then 1 -- satisfied

    when some_function('CONTEXT') = 'context of filtering by name'
    AND t.name LIKE '%' || another_function('NAME') || '%'
    then 1 -- satisfied

    when some_function('CONTEXT') = 'context of taking actual rows'
    AND TO_DATE(another_function('ACTUAL_DATE'), '...')
        BETWEEN t.start_date AND t.end_date
    then 1 -- satisfied

    ...

    else 0 -- unsatisfied

end = 1  -- rows from candidate set are only in the result set when
         -- they are "satisfied"

その後、Oracle は通常、これを共用体ではなくフィルター操作として解決します。これにより、論理 OR を使用することでよく遭遇する「通常の」パフォーマンスの問題が回避されます。

おまけとして、このメソッドは、「some_function(...)」の非行静的コンテキストでも機能することがよくあります!

于 2013-08-22T18:56:29.130 に答える