2

Oracle 11gを使用しており、クエリを最適化しようとしています。

クエリの基本構造は次のとおりです。

SELECT val1, val2, val3,
FROM 
table_name
WHERE
val1 in (subselect statement is here, it selects a list of possible values for 
    val1 from another table) 
and val5>=X and val5<=Y
group by val1
order by val2 desc;

私の問題は、副選択を使用する場合、コストが3130になることです。副選択の結果を手動で入力すると、たとえば、

field1 in (1, 2, 3, 4, 5, 6) 

ここで、(1、2、3、4、5、6)は副選択の結果であり、この場合はフィールド1のすべての可能な値であり、クエリのコストは14であり、oracleは「inlistイテレータ」を使用します。クエリの一部によるグループ化。2つのクエリの結果は同じです。

私の質問は、subselectステートメントを使用してfield1の可能な値を手動でリストする動作を模倣する方法です。クエリにこれらの値をリストしない理由は、可能な値が他のフィールドの1つに基づいて変化するためです。したがって、副選択は、たとえばfield2に基づいて2番目のテーブルからfield1の可能な値を取得します。

私はval1、val5のインデックスを持っているので、全表スキャンを実行していません-どちらの場合も範囲スキャンを実行しますが、副選択の場合、範囲スキャンははるかに高価です。ただし、これは副選択クエリの中で最もコストのかかる部分ではありません。最も高価な部分は、HASHであるgroupbyです。

編集-はい、クエリは構文的に正しくありません-私はあまり具体的なものを載せたくありませんでした。実際のクエリは問題ありません。selectは有効なgroupby関数を使用します。

副選択は6つの値を返しますが、他の値に基づいて1〜50程度の範囲にすることができます。

Edit2-私がやったのは、副選択で使用されるリストを生成できるように、2つの別々のクエリでした。私は実際にsqliteで同様のテストを試しましたが、同じことを行うので、これはOracleだけではありません。

4

3 に答える 3

4

あなたが見ているのは、変数のピークをバインドするIN()biengの結果です。ヒストグラムがある場合、「where a ='a'」のようなクエリを記述します。オラクルはヒストグラムを使用して、返される行数を推測します(各項目を繰り返して行を集約するinlist演算子と同じアイデア)。ヒストグラムがない場合は、行/個別の値の形式で推測されます。サブクエリでは、オラクルはこれを行いません(ほとんどの場合、これを行うユニークなケースがあります)。

例えば:

SQL> create table test
  2  (val1 number, val2 varchar2(20), val3 number);

Table created.

Elapsed: 00:00:00.02
SQL>
SQL> insert into test select 1, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100;

100 rows created.

Elapsed: 00:00:00.01
SQL> insert into test select 2, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 1000;

1000 rows created.

Elapsed: 00:00:00.02
SQL> insert into test select 3, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100;

100 rows created.

Elapsed: 00:00:00.00
SQL> insert into test select 4, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100000;

100000 rows created.

だから私は101200行のテーブルを持っています。VAL1の場合、100は「1」、1000は「2」、100は「3」、100kは「4」です。

ヒストグラムが収集された場合(この場合はヒストグラムが必要です)

SQL> exec dbms_stats.gather_table_stats(user , 'test', degree=>4, method_opt=>'for all indexed columns size 4', estimate_percent=>100);

SQL> exec dbms_stats.gather_table_stats(user , 'lookup', degree=>4, method_opt =>'for all indexed columns size 3', estimate_percent=>100);

次のように表示されます。

SQL> explain plan for select * from test where val1 in (1, 2, 3) ;

Explained.

SQL> @explain ""

Plan hash value: 3165434153

--------------------------------------------------------------------------------------
| Id  | Operation                    | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |       |  1200 | 19200 |    23   (0)| 00:00:01 |
|   1 |  INLIST ITERATOR             |       |       |       |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST  |  1200 | 19200 |    23   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | TEST1 |  1200 |       |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

vs

SQL> explain plan for select * from test where val1 in (select id from lookup where str = 'A') ;

Explained.

SQL> @explain ""

Plan hash value: 441162525

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         | 25300 |   518K|   106   (3)| 00:00:02 |
|   1 |  NESTED LOOPS                |         | 25300 |   518K|   106   (3)| 00:00:02 |
|   2 |   TABLE ACCESS BY INDEX ROWID| LOOKUP  |     1 |     5 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | LOOKUP1 |     1 |       |     0   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS FULL          | TEST    | 25300 |   395K|   105   (3)| 00:00:02 |
----------------------------------------------------------------------------------------

ルックアップテーブルは

SQL> select * From lookup;

        ID STR
---------- ----------
         1 A
         2 B
         3 C
         4 D

(strは一意のインデックスが付けられ、ヒストグラムがあります)。

インリストと適切な計画のカーディナリティが1200であることに気づきましたが、サブクエリでは非常に不正確です。Oracleは、結合条件のヒストグラムを計算していません。代わりに、「見て、IDがどうなるかわからないので、合計行数(100k + 1000 + 100 + 100)/個別の値(4)=25300を推測して使用します。そのため、全表スキャンを選択しました。

それはすべて素晴らしいですが、それを修正する方法は?このサブクエリが少数の行に一致することがわかっている場合(私たちはそうします)。次に、外部クエリにインデックスを使用させるようにヒントを与える必要があります。お気に入り:

SQL> explain plan for select /*+ index(t) */ * from test t where val1 in (select id from lookup where str = 'A') ;

Explained.

SQL> @explain

Plan hash value: 702117913

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         | 25300 |   518K|   456   (1)| 00:00:06 |
|   1 |  NESTED LOOPS                |         | 25300 |   518K|   456   (1)| 00:00:06 |
|   2 |   TABLE ACCESS BY INDEX ROWID| LOOKUP  |     1 |     5 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | LOOKUP1 |     1 |       |     0   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID| TEST    | 25300 |   395K|   455   (1)| 00:00:06 |
|*  5 |    INDEX RANGE SCAN          | TEST1   | 25300 |       |    61   (2)| 00:00:01 |
----------------------------------------------------------------------------------------

私の特定のケースでは別のことがあります。val1 = 4がテーブルの大部分であるため、標準のクエリがあるとします。 select * from test t where val1 in (select id from lookup where str = :B1);

可能な:B1入力について。渡された有効な値がA、B、およびCであることがわかっている場合(つまり、id = 4にマップされるDではない)。私はこのトリックを追加することができます:

SQL> explain plan for select  * from test t where val1 in (select id from lookup where str = :b1 and id in (1, 2, 3)) ;

Explained.

SQL> @explain ""

Plan hash value: 771376936

--------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                  |   250 |  5250 |    24   (5)| 00:00:01 |
|*  1 |  HASH JOIN                    |                  |   250 |  5250 |    24   (5)| 00:00:01 |
|*  2 |   VIEW                        | index$_join$_002 |     1 |     5 |     1 (100)| 00:00:01 |
|*  3 |    HASH JOIN                  |                  |       |       |            |          |
|*  4 |     INDEX RANGE SCAN          | LOOKUP1          |     1 |     5 |     0   (0)| 00:00:01 |
|   5 |     INLIST ITERATOR           |                  |       |       |            |          |
|*  6 |      INDEX UNIQUE SCAN        | SYS_C002917051   |     1 |     5 |     0   (0)| 00:00:01 |
|   7 |   INLIST ITERATOR             |                  |       |       |            |          |
|   8 |    TABLE ACCESS BY INDEX ROWID| TEST             |  1200 | 19200 |    23   (0)| 00:00:01 |
|*  9 |     INDEX RANGE SCAN          | TEST1            |  1200 |       |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

オラクルが妥当なカードを持っていることに注意してください(1、2、3をTESTテーブルにプッシュし、1200を取得しました。100%正確ではありません。

于 2012-11-09T23:30:49.983 に答える
2

私はいくつかの調査を行いましたが、すべてがここで説明されていると思います:oracledocs
「CBOがINリストイテレータを評価する方法」を調べて、「CBOがIN演算子を評価する方法」と比較してください。

「field1in(1、2、3、4、5、6)」を使用したクエリは最初のケースに一致しますが、副選択を使用したクエリはOracleによって書き換えられます。

したがって、サブ選択または結合を使用するすべてのクエリは、サブクエリからの戻り値をパラメータとして配置する非常にトリッキーな方法を見つけない限り、同じようなコストがかかります。

ソートするメモリをいつでも増やすことができます。

于 2012-11-09T23:05:53.640 に答える
1

副選択にインデックスを追加することで、ステートメントを修正できる場合があります。ただし、それを理解するには、クエリと実行プランを投稿する必要があります。ちなみに、副選択自体にはどれくらい時間がかかりますか?

次の2つのバージョンのいずれかを試すことができます。

select val1, val2, val3
from table_name join
     (select distinct val from (subselect here)) t
     on table_name.val1 = t.val
where val5>=X and val5<=Y
group by val1, val2, val3
order by val2 desc;

また:

select val1, val2, val3
from table_name
where val5>=X and val5<=Y and
      exists (select 1 from (subselect here) t where t.val = table_name.val1)
group by val1, val2, val3
order by val2 desc;

これらは意味的に同等であり、そのうちの1つがより適切に最適化される可能性があります。

動作する可能性のあるもう1つの可能性は、groupbyのにフィルタリングを実行することです。何かのようなもの:

select t.*
from (select val1, val2, val3
      from table_name
      where val5>=X and val5<=Y and
      group by val1, val2, val3
     ) t
where val1 in (subselect here)
order by val2 desc;
于 2012-11-09T22:34:18.160 に答える