私に問題を引き起こしているクエリは大きすぎますが、コアであると思われる部分は非常に単純です。私はそれを試してみます:
クエリの構造は次のとおりです。
_SELECT
... a lot of stuff ...
WHERE
... lots of complex clauses ...
AND my_id in ( select my_id from small_table where .. something simple ..)
_
そして、それは数十億のカーディナリティ、約12GBのバイトの読み取りを持っていますと私の一時を爆発させます。
ただし、(常に)7つのレコードを生成する_select my_id from small_table
_を実行する場合、これらのレコードを取得してクエリを次のように変更します。
_SELECT
... a lot of stuff ...
WHERE
... lots of complex clauses ...
AND my_id in ( 1, 2, 3, 4, 5, 6, 7 )
_
(つまり、値はハードコーディングされています)
コスト、カーディナリティ、読み取りバイト数は劇的に減少し、クエリは数分分で実行されます。
今、私はwith
句で「小さなクエリ」を分離しようとしましたが、サブクエリではなく結合を使用しようとしましたが、結果は常に同じです。
なぜそれがこのようになり、どうすればそれを防ぐことができるでしょうか?
両方のケース(高速と低速)でクエリのコストがかかる部分は、結合で使用される大きなテーブルの1つのFTSであることを言及する価値があるかもしれません。
また、私はOracle 11gR2を使用しています
[編集]これらは、2つの実行例の説明プランです
悪いもの。私がin ( )
を使用していないことに注意してください。_small_table
_をfrom
句に追加する単純な結合です。
_Plan
SELECT STATEMENT ALL_ROWSCost: 5,736,441
22 HASH JOIN RIGHT SEMI Cost: 5,736,441 Bytes: 52,324,480 Cardinality: 158,080
11 VIEW VIEW VW_NSO_1 Cost: 20 Bytes: 13 Cardinality: 1
10 NESTED LOOPS
8 NESTED LOOPS Cost: 18 Bytes: 91 Cardinality: 1
6 NESTED LOOPS Cost: 4 Bytes: 70 Cardinality: 1
4 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.SMALL_TABLE Cost: 1 Bytes: 35 Cardinality: 1
3 INDEX UNIQUE SCAN INDEX (UNIQUE) MYUSER.PK_SMALL_TABLE Cost: 0 Cardinality: 1
2 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.SMALL_TABLE Cost: 2 Bytes: 27 Cardinality: 1
1 INDEX RANGE SCAN INDEX MYUSER.IDX_SMALL_TABLE_1ATUAL Cost: 1 Cardinality: 6
5 TABLE ACCESS FULL TABLE MYUSER.SMALL_TABLE Cost: 3 Bytes: 35 Cardinality: 1
7 INDEX RANGE SCAN INDEX (UNIQUE) MYUSER.PK_MEDIUM_TABLE Cost: 1 Cardinality: 53
9 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.MEDIUM_TABLE Cost: 14 Bytes: 420 Cardinality: 20
21 HASH JOIN Cost: 5,736,193 Bytes: 15,281,925,342 Cardinality: 48,056,369
19 NESTED LOOPS
17 NESTED LOOPS Cost: 951,151 Bytes: 500,185,440 Cardinality: 1,736,755
15 NESTED LOOPS Cost: 3 Bytes: 792 Cardinality: 22
13 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.SMALL_TABLE Cost: 2 Bytes: 27 Cardinality: 1
12 INDEX RANGE SCAN INDEX MYUSER.IDX_SMALL_TABLE_1ATUAL Cost: 1 Cardinality: 6
14 INDEX RANGE SCAN INDEX (UNIQUE) MYUSER.PK_MEDIUM_TABLE Cost: 1 Bytes: 477 Cardinality: 53
16 INDEX RANGE SCAN INDEX MYUSER.IDX_HUGE_TABLE_1 Cost: 18,849 Cardinality: 1,322,763
18 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.HUGE_TABLE Cost: 413,818 Bytes: 19,620,720 Cardinality: 77,860
20 TABLE ACCESS FULL TABLE MYUSER.HUGE_TABLE Cost: 3,958,129 Bytes: 12,063,594,150 Cardinality: 402,119,805
_
良いもの。ここで行ったのは、_select my_id from small_table where .. something simple ..
_(7つのレコード)の結果を取得し、my_id in ( 1, 2, 3, 4, 5, 6, 7)
を大きなクエリの最後に追加することだけです。説明と同じ:
_Plan
SELECT STATEMENT ALL_ROWSCost: 4,558,125
18 HASH JOIN Cost: 4,558,125 Bytes: 36,100,625 Cardinality: 122,375
16 NESTED LOOPS
14 NESTED LOOPS Cost: 413,809 Bytes: 5,271,671 Cardinality: 122,597
12 VIEW VIEW VW_NSO_1 Cost: 20 Bytes: 13 Cardinality: 1
11 HASH UNIQUE Bytes: 91 Cardinality: 1
10 NESTED LOOPS
8 NESTED LOOPS Cost: 18 Bytes: 91 Cardinality: 1
6 NESTED LOOPS Cost: 4 Bytes: 70 Cardinality: 1
4 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.SMALL_TABLE Cost: 1 Bytes: 35 Cardinality: 1
3 INDEX UNIQUE SCAN INDEX (UNIQUE) MYUSER.PK_SMALL_TABLE Cost: 0 Cardinality: 1
2 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.SMALL_TABLE Cost: 2 Bytes: 27 Cardinality: 1
1 INDEX RANGE SCAN INDEX MYUSER.IDX_SMALL_TABLE_1ATUAL Cost: 1 Cardinality: 5
5 TABLE ACCESS FULL TABLE MYUSER.SMALL_TABLE Cost: 3 Bytes: 35 Cardinality: 1
7 INDEX RANGE SCAN INDEX (UNIQUE) MYUSER.PK_MEDIUM_TABLE Cost: 1 Cardinality: 53
9 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.MEDIUM_TABLE Cost: 14 Bytes: 420 Cardinality: 20
13 INDEX RANGE SCAN INDEX MYUSER.IDX_HUGE_TABLE_1 Cost: 18,849 Cardinality: 1,322,763
15 TABLE ACCESS BY INDEX ROWID TABLE MYUSER.HUGE_TABLE Cost: 413,788 Bytes: 3,677,910 Cardinality: 122,597
17 TABLE ACCESS FULL TABLE MYUSER.HUGE_TABLE Cost: 3,962,804 Bytes: 3,655,562,652 Cardinality: 14,506,201
_
それが役に立てば幸い。
ほとんどの場合、テーブルには最新の統計情報がありますが、オプティマイザはカーディナリティの推定を単純化しすぎるために困惑することがあります。
これは動的サンプリングの良い候補のようです。デフォルト値( 10gと11gの2 )では、動的サンプリングのみが使用されます テーブルの1つに統計がない場合 。あなたのケースでは、オプティマイザが統計を収集してより良い計画を構築できるように、その値を変更する必要があります。
DYNAMIC_SAMPLING
単一のクエリのオプティマイザの動作を変更できるヒント。私はサブクエリでテストしましたが、次の構文のいずれかを使用する必要があります。
完全なヒントをクエリの一番上に直接入力すると、すべてのテーブルがサンプリングされます。これは確実に機能しますが、時間がかかりすぎる可能性があります。
SELECT /*+ DYNAMIC_SAMPLING (10) */ FROM ...
サブクエリの完全なヒント:
SELECT
... a lot of stuff ...
WHERE
... lots of complex clauses ...
AND my_id in ( select /*+ DYNAMIC_SAMPLING (10) */ my_id
from small_table
where .. something simple ..)
クエリブロック名のヒント:
SELECT /*+ DYNAMIC_SAMPLING (@my_block 10) */
... a lot of stuff ...
WHERE
... lots of complex clauses ...
AND my_id in ( select /*+ QB_NAME(my_block) */ my_id
from small_table
where .. something simple ..)
2番目と3番目のオプションは同じ結果を生成するはずです:1つのテーブルでのみサンプリングします。
良い計画と悪い計画の主な違いは、悪い計画は大きなテーブルからほとんど/すべてのデータを返すと予想されるのに対し、良い計画はその約5%を返すと予想されることです。
ハードコードされた値がこの違いを生む理由は、オプティマイザが探している値を明示的に知っているためです。別のテーブルに結合すると、予想される行数がわかりますが、必ずしもそれらの行の値であるとは限りません。
次の例では、RW
値が1〜9の小さなテーブルがあり、VAL = 'N'
とRW = 10
とVAL = 'Y'
があります。大きなテーブルは小さなテーブルにリンクしていますが、行の大部分はRW値10を持っています。大きなテーブルで一般的でないRW値(1〜9)を見つけたい場合は、小さなテーブルに結合できます。これらの行にはVAL = 'N'
が含まれるか、数値1〜9が明示的にリストされます。どちらの場合も、大きなテーブルから9行だけが返されることを期待しています。
結合された場合、オプティマイザはSMALL_TAB
に対するクエリが9行を返すことを認識しますが、これらがLARGE_TAB
の値にどのようにリンクしているかを正確に判断できません。その結果、これは、9行のインデックス範囲スキャンではなく、予想される10,000行に対して全テーブルスキャンを実行します。
create table small_tab as
select rownum rw, decode(rownum, 10, 'Y', 'N') val
from dual
connect by level <= 10;
create table large_tab as
select case when rownum >= 10 then 10 else rownum end rw, --9990 rows have same value
dbms_random.string('x', 20) filler
from dual
connect by level <= 10000;
create index sm_i on small_tab(rw);
create index lg_i on large_tab(rw);
--calc stats, SIZE 10 to ensure we get histograms on the large table
exec dbms_stats.gather_table_stats(user, 'small_tab', cascade => true);
exec dbms_stats.gather_table_stats(user, 'large_tab', method_opt => 'FOR ALL COLUMNS SIZE 10', cascade => true);
explain plan for
SELECT * FROM large_tab l
where l.rw in (select s.rw from small_tab s where val = 'N');
SELECT * FROM table(dbms_xplan.display(NULL, NULL, 'BASIC +ROWS'));
-- the optimizer doesn't know how the SMALL_TAB.RW values returned by the
-- subquery match back to LARGE_TAB.RW, so assumes we need to full scan the whole table
--------------------------------------------------
| Id | Operation | Name | Rows |
--------------------------------------------------
| 0 | SELECT STATEMENT | | 5000 |
| 1 | HASH JOIN RIGHT SEMI| | 5000 |
| 2 | TABLE ACCESS FULL | SMALL_TAB | 5 |
| 3 | TABLE ACCESS FULL | LARGE_TAB | 10000 |
--------------------------------------------------
explain plan for
SELECT * FROM large_tab
where rw IN (1, 2, 3, 4, 5, 6, 7, 8, 9);
SELECT * FROM table(dbms_xplan.display(NULL, NULL, 'BASIC +ROWS'));
--because we've explicity asked for values, these can be directly compared
--against the stats information and the optimizer knows we should expect 9 rows.
----------------------------------------------------------
| Id | Operation | Name | Rows |
----------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 |
| 1 | INLIST ITERATOR | | |
| 2 | TABLE ACCESS BY INDEX ROWID| LARGE_TAB | 9 |
| 3 | INDEX RANGE SCAN | LG_I | 9 |
----------------------------------------------------------
これを修正するために、クエリが静的で(変更されない)、チューニングパックのライセンスを取得している場合は、SQLチューニングアドバイザーを確認することをお勧めします。これにより、クエリを適切な計画にロックするSQLプロファイルを作成できます。うまくいけば、アドバイザーが自動的に適切なプランを見つけてくれますが、そうでない場合は手動で作成する必要があります。いくつかのリンク:
チューニングアドバイザーパッケージの使用: http://www.Oracle-base.com/articles/10g/automatic-sql-tuning-10g.php SQLプロファイルを手動で作成: http:/ /kerryosborne.Oracle-guy.com/2010/07/sqlt-coe_xfr_sql_profilesql/
チューニングパックのライセンスを取得していない場合は、クエリを再構築し、インデックスを追加して、ヒントをいじる必要があると思います。どの変更がメリットをもたらすかを見つけることは、少し試行錯誤することになります。実際のデータセットにアクセスせずに何をすべきかを正確に述べることは困難です。
私が働いている場所では、最大のデータベースでCBOにさまざまな問題が発生しています。 11gR1を使用していますが、R2でもオプティマイザは非常に似ていると思いますが、そのバージョンの他のデータベースには目立った問題はないようです。
私はこれを行う/推奨するのが嫌いですが(必要に応じて数回実行しました)、クエリに「ルール」ヒントを追加して、それがどのように機能するかを確認しましたか?
あなたは次のようなことをするでしょう:select/* + rule */...たくさんそしてたくさんのもの;
ただし、CBOで問題が発生しないように、そのクエリを作成する別の方法がおそらくあります。この方法は、ヒントを使用するよりも望ましいでしょう。少なくともヒントを試してみると、それが障害のあるオプティマイザーであるかどうかを確認でき、別の質問をするポイントがあるように見えます。
まだ確認していませんが、R1にはいくつかのオプティマイザの問題を修正するパッチがあり、R2にも同様のパッチが利用できる可能性があります。メタリンクのログイン情報が私のマシンにあります。