2024年12月27日 (金)

先生!、全行アクセスしてるのに Nested Loop Join しちゃうんです! (東京都 ITエンジニア 男性) - optimizer_mode は正しく設定しましょう!

ということで、(どういうことだw
(今日のねたはアドベントカレンダーのネタを考えていた時に思い出したネタです)

 

さて、本題。

Oracle DatabaseのSQLチューニングや実行計画の読み方を学び始めたとき、最初に学ぶ(多分)、コストベースオプティマイザが作り出す実行計画に大きく影響を与える初期化パラメータ。
そのパラメータは何か?、
みなさん、パッと思う浮かでしょうか?

_optim_peek_user_binds ってアンダースコアパラメータを思い浮かべた方は居ないはず!(と断言してしまったが、意外と結構居たりして..

それは絶対にないと信じwww、
今日は、 optimizer_mode 初期化パラメータのお話をしてみたいと思います。

 

冒頭で書いたようにに、 Oracle Database の実行計画やSQLチューニングを学び始めたときに、最初に習う、覚えるのは、このパラメータだったと。思う(私の記憶ではw 私の場合、その最初の頃が昔すぎて怪しい)

それが、
optimizer_modeパラメータです。

この類のパラメータがあるのは Oracle Database だけではないかと思います。
例えば、Hash Join/Merge Joinを無効にして、Nested Loop Joinだけにするなど複数のオプションを組み合わせて似たような挙動にすることはできるものは多いですが、Oracle Databaseのように単一パラメータで、緩めに制御できるのは他にはないと思います。

 

このパラメータ、むかーーーーーーーーーーーーーーーしからあって、現在の ALL_ROWS/FISRT_ROWS_N というオプションになる前はCHOOSEやRULEというオプションがありました。
これは、Oracle Database 10gリリース1(10.1) 10gR1でルールベースオプティマイザが非サポートとなったタイミングで廃止され、現在この初期化パラメータがサポートしているオプションは以下のようになっています。デフォルトは ALL_ROWS です。

Database / Oracle / Oracle Database / Release 19 / Database Reference / 1.248 OPTIMIZER_MODE https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/OPTIMIZER_MODE.html

 

OPTIMIZER_MODE = { FIRST_ROWS_[1 | 10 | 100 | 1000] | FIRST_ROWS | ALL_ROWS }

このパラメータのオプションの特徴をざっくり解説してしまうと、
optimizer_mode = ALL_ROWSだとスループットを最大限にする実行計画が選択されます。(一般的にバッチだったり、分析系のSQLの実行計画に向いているのがこのモードで、デフォルトはこれです)
optimizer_mode = FIRST_ROWS_Nに設定した場合、N行の結果を如何に速く返すか、つまり、レスポンスタイムの良さを実現するための実行計画が選択されます。(一般的にはOLTP向きとされる実行計画になります。結合方法として Nested Loop Joinが選ばれやすくなります。全行じゃなくても最初の1行を早くクライアントへ返してあげられるような実行計画になりやすいですのがこのモードです)

ちなみに FIRST_ROWS は下位互換として残されているだけなので最近では使うことはないです。

ALL_ROWSがデフォルトなので、多くの場合、デフォルトのままで、OLTP系のSQLでは、index scanや、nested loop joinになるようにWHERE句を記述したり、ヒント等で制御したりしているケースが多いのではないでしょうか?
一方、optimizer_mode = first_rows_n に設定しておき, OLTP向きの実行計画をデフォルトで選択しやすい状況にしているケースもそれなりに見かけます(かなり少ないと思いますが、ちゃんと考えて設定しているという意味では、自分たちのシステムのワークロードで重要なのはどれだ! 認識している証かもしれません。どちらにするかは方針次第ではあるのですが。)

と、ここまでが、ながーーーい前説ですw

 

今回のタイトル ”先生!、全行アクセスしてるのに、Nested Loop Joinしちゃうんです!”  

Sql_20250105102401

もうお分かりですねw 今日のネタ。

 

今日の患者さん、 optimizer_mode = first_rows_1 となっている環境で、where句もない結合を伴うクエリーが、Nested Loop Joinで、全行読み込んでしまったことに悩んでいました。
optimizer_mode = first_rows_n という設定になっていることにも気づいてなかったようですね。 all_rows の感覚のままでいると戸惑うのも当然です。

チューニング前に、オプティマイザに影響する初期化パラメータを確認しておくことをお勧めします!!!
チューニングをお願いされた場合、該当する初期化パラメータの設定も一緒に提供してもらう。
セッションレベルで変更されている場合もあるので、それらの情報も提供してもらうことが大切ですよ。忘れないでくださいね。
それらパラメータ情報も、ERに運び込まれた患者SQLを救うには大切な情報なのです!

このケースの場合、治療は非常に簡単で、初期化パラメータ optimizer_mode はそのままで、 ALL_ROWS ヒントを該当SQLに埋め込むのが手っ取り早いと思います。セッションレベルで optimizer_mode = all_rowsにするのもありです。

強力なヒントではないので軽視されがちな初期化パラメータですが、実は、ひょんなことで、その実力に気付いたりするものですwwwwwwww
ただ、ざっくりとした実行計画の傾向を支持するものなので、追加のヒントで矯正したりする必要もあることは忘れないでください。

 

では、早速、その効力を確認することにします。 21cを利用します。古くても新しくても挙動は同じです、
また、ネタ的に面白いので一時表での挙動も含めています :)
一時表は統計情報の持ち方等が永続表とは異なるので別の注意が必要です。(参考 津島博士のパフォーマンス講座 第35回 オプティマイザ統計の運用について(2)

統計情報に影響され難い例なので、知っておくと、どこかで役立つと思いますよ! 多分。:)



 

まず、環境と今回の主役となる初期化パラメータの確認から。

optimizer_dynamic_sampling
optimizer_mode
をセッションレベルで制御。ヒント制御しても同じ。
(なお、optimizer_adaptive_plansが発動すると分かりにくくなるので無効化しておきます)

 

SCOTT@orclpdb1> select banner_full from v$version;

BANNER_FULL
----------------------------------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0

SCOTT@orclpdb1> show parameter optimizer_mode

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_mode string ALL_ROWS

SCOTT@orclpdb1> show parameter optimizer_dynamic_sampling

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_dynamic_sampling integer 2

SCOTT@orclpdb1> show parameter optimizer_adaptive_plans

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_adaptive_plans boolean TRUE

前提条件は以下の通り。

1. 表には大量のデータが登録されている。
2. 2表をINNNER JOINするが、WHERE句のないクエリー。
3. 統計の無い状態、無いが動的統計有効、それに統計のある状態、それぞれで検証

普通に考えれば、table full scan + Hash Joinが実行計画として選択されるケースですが、、、どうなりますか。ニヤニヤ。(想像できる結果なのでw

一時表での実行計画から確認してみます。

統計情報なし、動的統計取得なし、Adaptive plansも無効です。 データ量、SQL文ではWHERE句による絞り込み条件も無いため、全表走査+ハッシュ結合となって欲しいケースですが、 first_row_1 と all_rows の違いは如何に。。。

 

SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> create global temporary table hoge_tmp (id number not null primary key, memo varchar2(100)) on commit preserve rows;

表が作成されました。

SCOTT@orclpdb1> create global temporary table hoge_tmp2 (id number not null primary key, memo varchar2(100)) on commit preserve rows;

表が作成されました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

 

あらびっくり!(知ってたけどw
100,000行の結合がNested Loop Joinになっています。駆動表は Table full scan しちゃってます。って(WHERE句ないのでここは当然ですがw)
問題は、Nested Loop Joinになっている。INDEX UNIQUE SCANを 100,000回ぐるぐる繰り返しているということになります! まじですw

一時表で統計情報もなくて、動的統計取得も無効されている影響だな! そう思ったあなた。そういうケースもありますがw
WHERE句もないSQLでNested Loop Joinを選択してしまうのは危険ですよ。(昔は特殊な事情で、それでもこれで行くか〜というレアなこともなくはなかったですがw 最近はほぼないですからね)

(後半で、統計情報なんて関係ねぇってネタをご用意してありますので、長いですがお付き合いくださいw)

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4089392018

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | HOGE_TMP | 8168 | 518K| 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 65 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010735 | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
25 recursive calls
0 db block gets
119340 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

では、 all_rows にして再実行します。他の設定は同じです。
はい、見事に、 Table full scan + Hash Join の実行計画が選択されました!!!!

違いは、first_rows_1 であるか、 all_rows であるかだけです。それだけなんです。

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 1036K| 150 (3)| 00:00:01 |
|* 1 | HASH JOIN | | 8168 | 1036K| 150 (3)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 8168 | 518K| 74 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 8168 | 518K| 74 (2)| 00:00:01 |
--------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
22 recursive calls
0 db block gets
9334 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

さらに深掘りしてみましょう。
統計情報の有無が影響しないことを確認してみましょう。一時表なので永続表とは異なる統計情報の持ち方になっていることをお忘れなく。でも、大丈夫ですよ。持ってますからw

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計取得
SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED
HOGE_TMP2 100000 SESSION
HOGE_TMP 100000 SESSION

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED
HOGE_TMP SYS_C0010733 100000 SESSION
HOGE_TMP2 SYS_C0010735 100000 SESSION

 

一時表のセッション固有統計により駆動表の見積もり行数が、100K 担っている点に注目。大量にデータがヒットすることが、見えていながら、 first_roww_1 という1行目のレスポンスタイムを最短にするため、Nested Loop Joinが行われているます!

統計情報なんて、関係ねぇっ、って感じなのが確認できたので、実は、ほっとしていたりw...

 

SCOTT@orclpdb1> -- dyamic sampling off
SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計あり
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4089392018

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 424 | 5 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | HOGE_TMP | 100K| 10M| 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 106 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010735 | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

Note
-----
- Global temporary table session private statistics used

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
115889 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

確認するまでもないですが、 all_rows で他の条件は同一のケースも見てみましょう。
こちらは安定の、table full scan + hash joinのままですね。(予想通りですw)

 

SCOTT@orclpdb1> -- dyamic sampling off
SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計あり
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 20M| | 6154 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 100K| 20M| 11M| 6154 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 100K| 10M| | 1110 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 100K| 10M| | 1110 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- Global temporary table session private statistics used

統計
----------------------------------------------------------
705 recursive calls
14 db block gets
10430 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
170 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

一時表でもう一つ確認しておきましょう。
一時表(Global Temporary Table)のセッション固有統計なしで、動的統計有効にした場合、 first_rows_1 / all_rows の実行計画はどうなるでしょうか。

一旦、セッションを終了して、一時表を空にします。

 

SCOTT@orclpdb1> exit
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
[oracle@localhost ~]$ sqlplus scott@orclpdb1

...略...

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

 

お〜〜〜〜。変化しました〜、動的統計取得で挙動が変わりますね。。。。とはいえ、 Merg Joinです!
実行計画、最悪ですよね。
重いソート処理を回避するために、主キーをindex full scan(主キー順に読み込む)した後に、 Table Access by index rowid ですよ。みなさん!
次に、table access fullの後に、SORT JOIN してます。。consistent getsもこれまでで最も多いですね。どうせなら table full scan + hash join を選んで欲しかったw
とはいえ、optimizer_mode = first_rows_1にするぐらいだから、動的統計って無効化していることも多いので、有効にするまでは気が回らなそうな気もしますね。
いずれにしてもあまり良い設定の相性ではないのは街がないですね。このケースでは。動的統計のレベルによっても変化してより良い実行計画に変化するとは思いますが。(今回の目的ではないのでその確認まではしません)

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 2412335391

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 569 (2)| 00:00:01 |
| 1 | MERGE JOIN | | 1 | 130 | 569 (2)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 97069 | 6161K| 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | SYS_C0010735 | 2 | | 2 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 95480 | 6060K| 566 (2)| 00:00:01 |
| 5 | TABLE ACCESS FULL | HOGE_TMP | 95480 | 6060K| 566 (2)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")
filter("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
23 recursive calls
0 db block gets
19853 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

同じ条件で、 all_rows に変えてみましょう。おそらくいい感じになるのではないでしょうか(これまで同様に)

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 95481 | 11M| | 4699 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 95481 | 11M| 7184K| 4699 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 95480 | 6060K| | 1109 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 97069 | 6161K| | 1110 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
13 recursive calls
0 db block gets
9426 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off
SCOTT@orclpdb1> exit

 

最後に、永続表での実行計画も確認しておきます。

 

SCOTT@orclpdb1> drop table hoge_tmp purge;

表が削除されました。

SCOTT@orclpdb1> drop table hoge_tmp2 purge;

表が削除されました。

SCOTT@orclpdb1> create table hoge_tmp (id number not null primary key, memo varchar2(100));

表が作成されました。

SCOTT@orclpdb1> create table hoge_tmp2 (id number not null primary key, memo varchar2(100));
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010737
HOGE_TMP2 SYS_C0010739

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010737 SHARED
HOGE_TMP2 SYS_C0010739 SHARED

 

永続表のケースで、統計なし、動的統計オフで、first_rows_1の場合は、一時表と同様に駆動表を全表走査した上で、Nested Loop Joinしています。first_rows_1の影響をそのまま受けています。

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4183149614

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | HOGE_TMP | 82 | 5330 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010739 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 65 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
84 recursive calls
23 db block gets
126963 consistent gets
231 physical reads
4336 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
13 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

 

他の条件は同じで、 all_rows に変更した場合同様に、全表走査+ハッシュ結合(いいですねぇ。バッチ処理ならこれが一番良いですね。

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 82 | 10660 | 6 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 82 | 10660 | 6 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 82 | 5330 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 82 | 5330 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
189 recursive calls
5 db block gets
9700 consistent gets
1 physical reads
184 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
28 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

永続表、統計情報なし、動的統計取得有効、first_rows_1。一時表同様の結果です。永続表と一時表による違いは無さそうですね。これはNLJより避けたいw

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1178023564

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 795 (2)| 00:00:01 |
| 1 | MERGE JOIN | | 1 | 130 | 795 (2)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP | 124K| 7911K| 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | SYS_C0010737 | 2 | | 2 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 92574 | 5876K| 792 (2)| 00:00:01 |
| 5 | TABLE ACCESS FULL | HOGE_TMP2 | 92574 | 5876K| 792 (2)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")
filter("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
181 recursive calls
26 db block gets
16729 consistent gets
182 physical reads
140 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
24 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

永続表、統計情報なし、動的統計取得有効、all_rowsも、一時表のケースと同様。安定して、全表走査+ハッシュ結合が行われています。 all_rows にするべきSQLですからね。

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 3316548036

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 92574 | 11M| | 5148 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 92574 | 11M| 6968K| 5148 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP2 | 92574 | 5876K| | 1173 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP | 124K| 7911K| | 1177 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
32 recursive calls
0 db block gets
9438 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

いよいよ最後、動的統計取得を無効化して、静的統計による挙動を確認します。

 

SCOTT@orclpdb1> -- 統計取得
SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP 100000
HOGE_TMP2 100000

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010737 100000
HOGE_TMP2 SYS_C0010739 100000

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP 100000 SHARED
HOGE_TMP2 100000 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010737 100000 SHARED
HOGE_TMP2 SYS_C0010739 100000 SHARED

 

統計情報あり、動的統計有効ですが、動作しないはずですね。統計情報は最新ですし。 first_rows_1では期待した結果(良いという意味ではないw)が得られています。
駆動表を全表走査してNested Loop Joinが行われています。動的統計取得の副作用で、Merge Joinになることもなかったようですね。

 

SCOTT@orclpdb1> -- 統計あり
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

セッションが変更されました。

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

セッションが変更されました。

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4183149614

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 424 | 5 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | HOGE_TMP | 100K| 10M| 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010739 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 106 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
5 recursive calls
0 db block gets
126786 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

同一条件で、 all_rows の場合です。こちらも想定通り、全表走査+ハッシュ結合になっています。

最後に、 optimizer_mode をチューニングのゴールに合わせて、バッチ、分析系、そして、OLTPと、
all_rows または、first_rows_n のいずれか正しく設定することも想定外の実行計画を防ぐことに役立つか、お分かりいただけたのではないでしょうか?
効果の薄い機能ではなく、重要な役目をもつ、 optimizer_mode、お忘れなく。ヒントでも使えます。状況に合わせて使い分けることをお勧めします:)

 

おまけ 昔、OTHER_XML列からOUTLINEを取り出すなんてネタ書いてましたが、しっかりと、optimizer_modeに対応するヒントが含まれています。
OTHER_XMLの中身 / Mac De Oracle / 2015年12月 4日 (金) https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2015/12/other_xml-7f15.html

では、また。

良いお年をお迎えください。

Enjoy! SQL and Optimizer Features! :)

 



今回利用したSQLなど

 

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;


create global temporary table hoge_tmp (id number not null primary key, memo varchar2(100)) on commit preserve rows;
create global temporary table hoge_tmp2 (id number not null primary key, memo varchar2(100)) on commit preserve rows;


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- 一時表(Global Temporary Table)のセッション固有統計取得
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- dyamic sampling off
-- first_rows_1
-- 一時表(Global Temporary Table)のセッション固有統計あり
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- dyamic sampling off
-- all_rows
-- 一時表(Global Temporary Table)のセッション固有統計あり
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off



+++ 一時表(Global Temporary Table)のセッション固有統計なしで、動的統計有効 +++

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = first_rows_1;
alter session set optimizer_dynamic_sampling = 2;

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


-- first_rows_1
-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = first_rows_1;
alter session set optimizer_dynamic_sampling = 2;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = all_rows;
alter session set optimizer_dynamic_sampling = 2;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off




--パーマネント表でも同じ

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;


create table hoge_tmp (id number not null primary key, memo varchar2(100));
create table hoge_tmp2 (id number not null primary key, memo varchar2(100));


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


-- first_rows_1
-- 統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- all_rows
-- 統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- first_rows_1
-- 統計なし
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 統計なし
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- 統計取得
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- first_rows_1
-- 統計あり
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 統計あり
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

 

 

| | | コメント (0)

2024年9月 7日 (土)

VirtualBox-7.1.0_BETA2-164697 (2024-09-06T20:27:41Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 (VM起動せず)

TestBuild Development Snapshotが 7.1.0_BETA2-164697 (2024-09-06T20:27:41Z) になっていましたが。前回、前々回同様、7.0の初期のころに先祖返りしたような感じで、x86系VMは以下のメッセージとともに起動できず。(起動時間計測が毎回の楽しみになってきたのでw、早く組み込んでもらいたいですね7.1.0 BETAにも :)

M1

oracle@Mac-Studio ~ % ./print_env.sh

*** mac info. ***
ProductName: macOS
ProductVersion: 14.6.1
BuildVersion: 23G93

*** maxOS ver. ***
Model Name: Mac Studio
Chip: Apple M1 Ultra
Total Number of Cores: 20 (16 performance and 4 efficiency)
Memory: 64 GB

*** VirtualBox ver. ***
7.1.0_BETA2r164697


20240907-70915

VMが起動するとかしないとか以前の状況、この部分だけみると先祖返りしちゃってるんで、組み込まれるまではしばらくかかりそうですね。
(7.1.0_BETAになった3度目の更新ですが....)

この状況は、以前のリリースで起動していたVMでも、VirtualBox-7.1.0_BETA2-164697 にインポートしたx86系VMでも同様です。

20240907-71555

次の更新を楽しみにして、じっと待つ:)



一瞬、秋?みたいな感じの気温だったが、真夏に逆戻りの東京より

ではまた。:)



MySQL 8.0.32 , PostgreSQL 13.4 and Oracle Database 21c on Oracle Linux 8 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160702
ySQL 8.0.32 , PostgreSQL 13.6 and Oracle Database 21c on Oracle Linux 8.5 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r161342
MySQL 8.0.32 , PostgreSQL 13.6 and Oracle Database 21c on Oracle Linux 8.5 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r161709
MySQL 8.0.36 , PostgreSQL 13.14, Oracle Database 21c, Oracle Database 23ai on VirtualBox for Apple Silicon Test Build 7.0.97_BETA r162957
VirtualBox TestBuild for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 / 7.0.97r162957(2024/4/26) / 7.0.97r163029(2024/5/3)
VirtualBox TestBuild 7.0.97r163376 (2024-05-28T15:08:56Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.0.97r163425 (2024-06-05T13:13:46Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.0.97r163606 (2024-06-21T11:55:16Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.1.0_BETA1r164292 (2024-08-07T18:27:07Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.1.0_BETA1r164387 (2024-08-15T17:27:33Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

| | | コメント (0)

2024年7月10日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #15 - 実行計画でスカラー副問合せの見せ方にも癖がでる

さて、今日はまた、癖の話をしたいと思います!
今回のネタには標準があるわけではないですが、SELECTリストに記述するスカラー副問合せの実行計画上の見せ方の癖というか違いw

実は、このネタ、2020年ぐらいに、目黒方面(ご存知の方だけwww)にある某所で定期開催される内部勉強会的なLT大会で使ったネタだったのですが、そのあとゴタゴタしていて、ブログで書き漏らしていたことを、昨日ネタリストを纏めていた時に思い出した次いでに小ネタとして書いておきます。 (その時のKeynoteのタイトルページだけ載せておきますw)

20240710-141853


この癖を把握していれば、SELECTリストに記述されたスカラー副問合せチューニングするような案件に遭遇してしまったときでも何かの役に立つかもしれません。
(少なくとも実行計画を見ただけで、これはSELECTリストにスカラー副問合せがある! ということは一瞬で理解できるようになるはず。。。)

では早速見てみましょう。(Oracle Databaseではお馴染みの表とデータをMySQL/PostgreSQLでも事前に作成してあります)

SCOTT@orclpdb1> select * from dept;

DEPTNO DNAME LOC
---------- ------------------------------------------ ---------------------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON

SCOTT@orclpdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画で見えるSELECT中のスカラー副問合せの位置に注目してください。(赤字にしてあります)

Oracle Databaseでは本体のクエリーより上に表示されますが、PostgreSQL/MySQLでは逆で、下に表示されます。

このような見せ方の違いが逆になるのって以前もご紹介したの覚えているでしょうか?
そう、帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画の見せ方にも癖がでるで紹介した癖ですね。
HASH JOINのBUILD/PROBEは実行計画上、Oracle DatabaseとPostgreSQL/MySQLでは順序が逆に表現されていましたよね!

これに気づけば、あなたも、道にまようこともなく実行計画を追っていけるはず!! :)

Oracle Database (21c)
(このようにスカラー副問合せ部分が性能上ネックになりそうな場合、Oracle Databaseのオプティマイザは、スカラー副問合せを結合に書き換えて最適化することがあるため、この例ではそれを無効化するNO_UNNESTヒントを利用しています。)

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> !cat scalar_subquery_plan.sql
SELECT
deptno
,dname
,(
SELECT
/*+ NO_UNNEST */
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> @scalar_subquery_plan.sql

経過: 00:00:00.18

実行計画
----------------------------------------------------------
Plan hash value: 1445953226

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 65 | 9 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 4 | 28 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IX_DEPT | 4 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPT | 5 | 65 | 3 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_DEPT | 5 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

3 - access("EMP"."DEPTNO"=:B1)


見ての通り、PostgreSQL/MySQLはスカラー副問合せ部分の実行計画の位置がOracle Databaseのそれとは異なることがわかると思います。:)
PostgreSQL(13.14)

perftestdb=> \! cat scalar_subquery_plan.sql
EXPLAIN ANALYZE
SELECT
deptno
,dname
,(
SELECT
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;
perftestdb=> \i scalar_subquery_plan.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Sort (cost=5.87..5.88 rows=4 width=46) (actual time=0.246..0.247 rows=4 loops=1)
Sort Key: dept.deptno
Sort Method: quicksort Memory: 25kB
-> Seq Scan on dept (cost=0.00..5.83 rows=4 width=46) (actual time=0.065..0.095 rows=4 loops=1)
SubPlan 1
-> Aggregate (cost=1.19..1.20 rows=1 width=32) (actual time=0.016..0.017 rows=1 loops=4)
-> Seq Scan on emp (cost=0.00..1.18 rows=5 width=5) (actual time=0.003..0.006 rows=4 loops=4)
Filter: (deptno = dept.deptno)
Rows Removed by Filter: 10
Planning Time: 1.916 ms
Execution Time: 0.843 ms
(11 行)


MySQL(8.0.36)

mysql> \! cat scalar_subquery_plan.sql
EXPLAIN FORMAT=tree
SELECT
deptno
,dname
,(
SELECT
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

mysql> \. scalar_subquery_plan.sql
+----------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------+
| -> Index scan on dept using PRIMARY (cost=0.65 rows=4)
-> Select #2 (subquery in projection; dependent)
-> Aggregate: max(emp.sal) (cost=1.28 rows=1)
-> Filter: (emp.deptno = dept.deptno) (cost=1.14 rows=1.4)
-> Table scan on emp (cost=1.14 rows=14)
|
+----------------------------------------------------------------------+
1 row in set, 1 warning (0.05 sec)
mysql> show warnings;
+-------+------+------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------+
| Note | 1276 | Field or reference 'perftestdb.dept.deptno' of SELECT #2 was resolved in SELECT #1 |
+-------+------+------------------------------------------------------------------------------------+
1 row in set (0.01 sec)


ただし、Oracle DatabaseだけはSELECTリストのスカラー副問合せをUNNESTして結合に書き換える最適化を行うこともあるので、実行計画だけだと元のSQL文に記述されているSELECTリスト中のスカラー副問合せに気付けないこともあります.
とはいえ、一般的には、そこに至るまでの間に、SQL文は抜き出せているでしょうから困ることはないでしょうね。(現場がリモートで、実行計画だけ送られてきた!なんてことでもなければw)

SELECTリスト中に記載したスカラー副問合せがUNNESTされてMERGE JOINに書き換えられた例(UNNESTヒント利用)
2013年、Oracle Database 12cR1で実装された最適化機能で、Scalar Subquery Unnesting Transformation (Oracle Database 12c R1 New Feature)でも説明していますので、詳しく知りたい方は参考にしてみてください。

SCOTT@orclpdb1> !cat scalar_subquery_unnest.sql
SELECT
deptno
,dname
,(
SELECT
/*+ UNNEST */
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

SCOTT@orclpdb1> @scalar_subquery_unnest.sql

経過: 00:00:00.17

実行計画
----------------------------------------------------------
Plan hash value: 2834279049

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 145 | 11 (19)| 00:00:01 |
| 1 | MERGE JOIN OUTER | | 5 | 145 | 11 (19)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 5 | 65 | 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_DEPT | 5 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 4 | 64 | 8 (25)| 00:00:01 |
| 5 | VIEW | VW_SSQ_1 | 4 | 64 | 7 (15)| 00:00:01 |
| 6 | HASH GROUP BY | | 4 | 28 | 7 (15)| 00:00:01 |
| 7 | TABLE ACCESS FULL | EMP | 14 | 98 | 6 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

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

4 - access("ITEM_1"(+)="DEPT"."DEPTNO")
filter("ITEM_1"(+)="DEPT"."DEPTNO")


Enjoy SQL! and 癖

ではまた。






関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!
帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある
帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)

| | | コメント (0)

2024年5月16日 (木)

GROUP BY列の別名または位置の指定が可能に! / 23ai〜 / SQL / FAQ

23c 改め、23ai になった Oracle Database 23aiですが、SQLの使い勝手の改善がいくつか。
有名なのは、from dual を書かなくても良くなったこと。ですが、有名すぎるのであえて書きません!w

ということで、ちょっとマイナーだけど便利ですよね! という 「GROUP BY列の別名または位置の指定が可能に! / 23ai〜」 というお話。

参考 ー Oracle Databaseリリース23cの変更点
https://docs.oracle.com/cd/F82042_01/sqlrf/Changes-in-This-Release-for-Oracle-Database-SQL-Language-Reference.html

自分用アップデートメモでもありますw

まずこれまでのおさらいということで、Oracle Database 21c EEで挙動を確認しておきます。

SCOTT@orclpdb1> select banner_full from v$version;

BANNER_FULL
-----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0


SCOTT@orclpdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.

group by 別名指定。。見事にエラーになります!

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 year
8 ORDER BY
9* year
SCOTT@orclpdb1> /
year
*
行7でエラーが発生しました。:
ORA-00904: "YEAR": 無効な識別子です。


group by 位置指定。。これも間違いなくエラーです!。。。

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9* year
SCOTT@orclpdb1> /
TO_CHAR(hiredate,'YYYY') AS year
*
行2でエラーが発生しました。:
ORA-00979: GROUP BYの式ではありません。


ということで、これまではこんな面倒が書き方してたわけです。はい。。。

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 TO_CHAR(hiredate,'YYYY')
8 ORDER BY
9* year
SCOTT@orclpdb1> /

YEAR HIRED
------------ ----------
1980 1
1981 10
1982 1
1987 2


これまでは、こんな感じ。まあ面倒臭いですよね。


しかーーーーし、23ai以降では、そんな面倒は忘れてください。

SCOTT/freepdb1> select banner_full from v$version;

BANNER_FULL
-------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05


SCOTT/freepdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.


group by 別名指定。おおおおおおーーぅ!  できた。

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 year
8 ORDER BY
9* year
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2


group by 位置指定....? 

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9 year
10*
SCOTT/freepdb1> /
TO_CHAR(hiredate,'YYYY') AS year
*
ERROR at line 2:
ORA-03162: "HIREDATE": must appear in the GROUP BY clause or be used in an aggregate function as 'group_by_position_enabled' is FALSE
Help: https://docs.oracle.com/error-help/db/ora-03162/


なんと、23ai free developerではデフォルトで無効化されてる!?。。。とは言っても、位置指定は、order by でも使わない場合が多いので、デフォルトオフでも影響はないですかね。一般的には。

SCOTT/freepdb1> show parameter group_by_position_enabled
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
group_by_position_enabled boolean FALSE

SCOTT/freepdb1> alter session set group_by_position_enabled = true;

Session altered.

SCOTT/freepdb1> show parameter group_by_position_enabled

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
group_by_position_enabled boolean TRUE

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9 year
10*
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2


strong>最後に、従来の面倒臭い構文の確認。

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 TO_CHAR(hiredate,'YYYY')
8 ORDER BY
9* year
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2

Enjoy SQL!

では、また

| | | コメント (0)

2023年9月 1日 (金)

Oracle Database 23c Free Developer Releaseの”_optimizer_max_permutations” parameterの設定値について

Previously on Mac De Oracle..
前回のお話は、帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出るでした。Oracle/PostgreSQL/MySQLそれぞれ、再帰問合せで試してみましたが、興味深い違いが出ました。思わず、PostgreSQLのソースコード読み始めてしまいましたwwww

ということで、本日は、その流れで気づいてOracle 23c Freeのひみつ!


たまたま気づいたのですけど、 Oracle Database 23c Free Developer Release で、
under scored parameterの "_optimizer_max_permutations" って、 300なんですね。随分少ない設定にされていました。

これ

5! (120) < 6! (720) ということを意味するので、6表以上の結合では、通常のOracleより実行計画をミスりやすいということを意味しています。
(なぜなのでしょう、Free Developer Releaseだからではないか? というコメントももらいましたが、そうなのですかねぇ。でもそうかもしれないw。理由はなぜなのかわからんので何とも言えないですね)
なので、多数の結合を伴うSQLの検証には注意した方が良いですね。もしくは、通常の 2000 ぐらいまであげて試すとかしておいた方が良いかもね。
FreeのDeveloper Releaseだから 300 になっているのか確認できる資料は見つからなかったけど。


念の為、調べてみるとやはり、23c Free Developer Releaseのみ少なく設定されていました。

SYS@free> select banner from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 23c Free, Release 23.0.0.0.0 - Developer-Release

parameter name parameter value
-------------------------------- ------------------------------
_optimizer_max_permutations 300
_optimizer_search_limit 5



BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
PL/SQL Release 12.2.0.1.0 - Production
CORE 12.2.0.1.0 Production
TNS for Linux: Version 12.2.0.1.0 - Production
NLSRTL Version 12.2.0.1.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


Oracle 23c の正式版がリリースされたら皆さんも、2000になっているか確認しましょうね!(多分、2000になっていると思うけどw)

東京の8月が毎日真夏日だったなんで、話題。来年は毎日、猛暑日じゃなければ良いのですけども。。。
残暑厳しい東京からお送りしました。

では、また。

| | | コメント (0)

2023年1月27日 (金)

SQL*Plus -Fastオプション / FAQ

Previously on Mac De Oracle.
前回は、アドベントカレンダーのおまけのおまけwでした

今日は、そこで仕込んでおいたネタを使い、SQL*Plusも機能拡張されてたのすっかり忘れていた! ので、
高Fetch圧症の話に絡めてSQL*Plusの-F[ast]オプション書いておこうと思います。

軽めですが。

Fetch回数削減に効果があるので、多数の行をFetchするような時は思い出すと良いですね。
Client/Server間のrount tripが減ることに繋がるわけで、そこが慢性病の原因なら少しでも楽になれたら良いと思いますし。
(ということで、Fetch Sizeも忘れないでね。という気持ちを込めて。)

最初は、-Fastオプションなしで。arraysizeのデフォルトは 15です。なお、この -F[ast]オプションは、Oracle Database 12c 12.2以降でサポートされています。

[oracle@localhost ~]$ sqlplus scott/tiger@orclpdb1

...略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> @dayx2
1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

1000001行が選択されました。

経過: 00:02:18.83

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 1451M| | 119K (1)| 00:00:05 |
|* 1 | HASH JOIN OUTER | | 1000K| 1451M| 497M| 119K (1)| 00:00:05 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 1000K| 486M| | 19593 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 750K| 723M| | 38796 (1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

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

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)


統計
----------------------------------------------------------
1297 recursive calls
0 db block gets
229599 consistent gets
374715 physical reads
0 redo size
1551273711 bytes sent via SQL*Net to client
735146 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
---------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key ,description
FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL ) SELECT /*+ MONITOR */ *
FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------

...略...

Duration : 173s

...略...

Fetch Calls : 66668

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 17 | 15 | 1.59 | 0.84 | 66668 | 230K | 6878 | 3GB | 5169 | 1GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 172 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 119K | 173 | +1 | 1 | 1M | 5169 | 1GB | 5169 | 1GB | 184MB | 1GB | 76.92 | Cpu (9) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (1) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 1 | +2 | 1 | 1M | 575 | 559MB | | | . | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 38796 | 47 | +2 | 1 | 1M | 1134 | 1GB | | | . | . | 15.38 | Cpu (2) |
==============================================================================================================================================================================================================


次に、-F[ast]オプションで接続します。このオプションにより、ARRAYSIZE = 100に設定されます。それ以外にも3.5.1.5 FASTオプションいくつかの設定が変更されます。

[oracle@localhost ~]$ sqlplus -Fast scott/tiger@orclpdb1

...略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> @dayx2
1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

1000001行が選択されました。

経過: 00:01:55.03

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 1451M| | 119K (1)| 00:00:05 |
|* 1 | HASH JOIN OUTER | | 1000K| 1451M| 497M| 119K (1)| 00:00:05 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 1000K| 486M| | 19593 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 750K| 723M| | 38796 (1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

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

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)


統計
----------------------------------------------------------
1297 recursive calls
0 db block gets
216797 consistent gets
374715 physical reads
0 redo size
1539940308 bytes sent via SQL*Net to client
110262 bytes received via SQL*Net from client
10001 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
----------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key ,description
FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL ) SELECT /*+ MONITOR */ *
FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------

...略...

Duration : 146s

...略...

Fetch Calls : 10001

Global Stats
============================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
============================================================================================
| 17 | 14 | 1.76 | 1.72 | 10001 | 216K | 6878 | 3GB | 5169 | 1GB |
============================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 145 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 119K | 146 | +1 | 1 | 1M | 5169 | 1GB | 5169 | 1GB | 184MB | 1GB | 100.00 | Cpu (14) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (4) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 1 | +2 | 1 | 1M | 575 | 559MB | | | . | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 38796 | 42 | +2 | 1 | 1M | 1134 | 1GB | | | . | . | | |
==============================================================================================================================================================================================================


-Fastオプションなしの場合と比較してみるとSQL*Net roundtrips to/from clientなど減ってますよね:)
このround tripは、待機イベントSQL*Net more data to clientなどで現れます。(SQL監視のActivity Detailsにも現れていますので、覚えておくと良いと思います)

-Fastオプションなし(auto trace)

1551273711  bytes sent via SQL*Net to client
735146 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client

-Fastオプションあり(auto trace)

1539940308  bytes sent via SQL*Net to client
110262 bytes received via SQL*Net from client
10001 SQL*Net roundtrips to/from client

-Fastオプションなし(SQL監視)

Global Information
------------------------------
...略...
Duration : 173s
...略...
Fetch Calls : 66668

-Fastオプションあり(auto trace)

Global Information
------------------------------
...略...
Dration : 146s
...略...
Fetch Calls : 10001

早く、ポカポカ陽気にならないかなぁ。

と思う寒い日々。

では、また。

| | | コメント (0)

2023年1月16日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ

年を跨いで, ”実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけ”のおまけです.

前回の投稿から間隔が空いていたので, まずは, 簡単な復習から.

ANSI構文のON句の結合条件でORが利用されているという, スーパータイプ, サブタイプテーブルの実装崩れというか, 大人の事情に押し切られて負けた感じありありの半端な状態.
あ, そうだ, Oralceの外部結合だとOR使えないけど, ANSIなら使えるじゃん!
という流れを感じるSQL文を, Oracleのオプティマイザは, LATERALへの書き換え(VW_LAT_E87C3AAF)や, OR EXPANDの書き換え(VW_ORE_FDF394AE)を駆使して, 物凄い最適化を行っていました.

この例では, 外部表, 内部表の多重度は, 1:0..1. かつ, スーパータイプ, サブタイプでいうところの不完全なサブタイプ.
さらに, 内部表は, 単純にニコイチにしただけのようなサブタイプテーブルで外部表との結合列が2列(おそらく本来同一列に統合されていただろう. . と思われる)ある. 惜しい!という感じのモデル.
比較的軽度のモデリング障害ではあるので, このまま使うのであれば, LATERAL変換されるのを避けるような書き換え, 比較的単純な HASH JOIN なるようにすればそこそこ改善できそうな感じはしますよね(いわゆるTemp落ちはある程度発生する前提で)

なお, この例で AUTO TRACEでの実行時間と, SQL MONITORの実行時間(DB内部)に差異があることに気づいた方もいると思いますが. これ, クライアントがデータをFETCHしている時間ですね. 行数が多いので. SQL*Plusの場合, デフォルトのFETCH SIZEが15なので行数が多いと, FETCHの際, サーバーからの受信で時間がかかります.
(この症状は以前, 高フェッチ圧症として紹介したこともあるので, 覚えている方も多いのではないかと思います. この例では1行の行サイズも大きめかつ, 行数も多めにして SELECT * にしているのでそこそこ目立つ時間になるようにしています. これも別のエントリーでネタにするための仕込みではあるのですが, 今回の記事では気にしないでください. SQLモニターのサーバー内部での純粋な処理時間だけで, 書き換え前後での差を見て行きます!)

SCOTT@orclpdb1> @dayx
1 SELECT *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

10001行が選択されました.

経過: 00:00:01.41

実行計画
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40226 (1)| 00:00:02 |
| 1 | MERGE JOIN OUTER | | 20002 | 20M| 40226 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 40021 (1)| 00:00:02 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 4 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
32231 consistent gets
0 physical reads
0 redo size
15493776 bytes sent via SQL*Net to client (別エントリ向け)
7378 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client (別エントリ向け)
10001 sorts (memory)
0 sorts (disk)
10001 rows processed


1 SELECT /*+ MONITOR */ *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
--------------------------------------------------------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN nikoichi_mitaina_subtype nmst ON st.pkey = nmst.col1 OR st.pkey = nmst.col2

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 3s(別ネタ向け仕込み)

...略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.37 | 0.31 | 0.06 | 668 | 32231 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=2133431102)
=====================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 4 | +0 | 1 | 10001 | . | | |
| 1 | MERGE JOIN OUTER | | 20002 | 40226 | 4 | +0 | 1 | 10001 | . | | |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 204 | 4 | +0 | 1 | 10001 | . | | |
| 3 | BUFFER SORT | | 2 | 40021 | 4 | +0 | 10001 | 10000 | 2048 | | |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 4 | 4 | +0 | 10001 | 10000 | . | | |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 4 | 4 | +0 | 10001 | 10000 | . | | |
| 6 | UNION-ALL | | | | 4 | +0 | 10001 | 10000 | . | | |
| 7 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 2 | 4 | +0 | 10001 | 5000 | . | | |
| 8 | INDEX UNIQUE SCAN | UK1 | 1 | 1 | 4 | +0 | 10001 | 5000 | . | | |
| 9 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 2 | 4 | +0 | 10001 | 5000 | . | | |
| 10 | INDEX UNIQUE SCAN | UK2 | 1 | 1 | 4 | +0 | 10001 | 5000 | . | | |
=====================================================================================================================================================================

では, 書き換えて, LATERAL変換を避け, HASH JOINになるようにしてみましょう. (WITH句を利用していますが, 再利用ではなく読みやすさ狙いです. Oracleもそれを理解できるのでインラインビューとして扱われます)
今回のようなデータモデル障害の場合は, 治療もシンプルで良いのですがw(例に取り上げるのがメンドクサイやつだと, 解説するのもメンドクサイし良いことないので)

現場どのようになっているかを理解する必要があります. この例では, col1列とcol2列は実は同一列で良いだろうということになるので, 以下のように書き換えれば, JOIN ON ... OR なんて現時点のオプティマイザでは, ほぼ危険な感じしかしない実行計画になるようなSQLへの書き換えも回避できるのではないでしょうか?
結果は見ての通り, 別エントリ向けの仕込みであるFETCH時間を除いたデータベース内部のみの処理時間は, 0.37sec から 0.15secと62%ほど改善しています(ただ, このデータ量で私の環境だとPGA内に収まっているのでTemp落ちの影響は見えないですね. オンメモリなら勝ちは確実ですが)

  1  WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

10001行が選択されました.

経過: 00:00:01.28

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10001 | 14M| | 1224 (1)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 10001 | 14M| 5096K| 1224 (1)| 00:00:01 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 10001 | 4971K| | 204 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 7500 | 7390K| | 410 (1)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

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

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2748 consistent gets
0 physical reads
0 redo size
15483707 bytes sent via SQL*Net to client(別ネタ向け仕込み)
7378 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client(別ネタ向け仕込み)
0 sorts (memory)
0 sorts (disk)
10001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
----------------------------------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key ,description
FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL ) SELECT /*+ MONITOR */ *
FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 2s(別ネタ向け仕込み)

...略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.15 | 0.13 | 0.02 | 668 | 2748 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
======================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
======================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 3 | +0 | 1 | 10001 | . | | |
| 1 | HASH JOIN OUTER | | 10001 | 1224 | 3 | +0 | 1 | 10001 | 7MB | | |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 204 | 1 | +0 | 1 | 10001 | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 7500 | 410 | 3 | +0 | 1 | 10000 | . | | |
======================================================================================================================================================

将来のデータ量が100倍だとして. . . . HASH JOIN化してTemp落ちの影響も含めて見ておきましょう

SCOTT@orclpdb1> @dayx-1
1* DROP TABLE supertype

表が削除されました.

経過: 00:00:00.47
1 CREATE TABLE supertype
2 (
3 pkey NUMBER PRIMARY KEY
4 , attr1 NUMBER NOT NULL
5 , attr2 NUMBER NOT NULL
6 , note VARCHAR2(500)
7* )

表が作成されました.

経過: 00:00:00.15
1* DROP TABLE nikoichi_mitaina_subtype

表が削除されました.

経過: 00:00:00.05
1 CREATE TABLE nikoichi_mitaina_subtype
2 (
3 pkey NUMBER PRIMARY KEY
4 , col1 NUMBER
5 , col2 NUMBER
6 , description VARCHAR2(1000)
7 , CONSTRAINT uk1 unique (col1) USING INDEX
8 , CONSTRAINT uk2 unique (col2) USING INDEX
9* )

表が作成されました.

経過: 00:00:00.03
1 DECLARE
2 cMAX_ROWS CONSTANT NUMBER := 1000000;
3 BEGIN
4 FOR i IN 1..cMAX_ROWS LOOP
5 INSERT INTO supertype VALUES(i,0,0,LPAD(i,500,'*'));
6 INSERT INTO nikoichi_mitaina_subtype VALUES(
7 i
8 , CASE WHEN MOD(i,2) = 0 THEN i ELSE null END
9 , CASE WHEN MOD(i,2) = 1 THEN i ELSE null END
10 , LPAD(i,1000,'*')
11 );
12 IF MOD(i,100) = 0 THEN COMMIT; END IF;
13 END LOOP;
14 INSERT INTO supertype VALUES(cMAX_ROWS+1,0,0,null);
15 COMMIT;
16 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'SUPERTYPE',no_invalidate=>false,cascade=>true);
17 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'NIKOICHI_MITAINA_SUBTYPE',no_invalidate=>false,cascade=>true);
18* END;

PL/SQLプロシージャが正常に完了しました.

経過: 00:04:37.66


まず元ネタのLATERALとOR EXPAND書き換えされている方はどうか. . SQLモニターのExecが綺麗に増加(当然ですが)

SCOTT@orclpdb1> @dayx
1 SELECT *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

1000001行が選択されました.

経過: 00:02:23.03

実行計画
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000K| 2004M| 6022K (1)| 00:03:56 |
| 1 | MERGE JOIN OUTER | | 2000K| 2004M| 6022K (1)| 00:03:56 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1000K| 486M| 19524 (1)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 6002K (1)| 00:03:55 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 6 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 6 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1012 | 3 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 2 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1012 | 3 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3561836 consistent gets
71485 physical reads
0 redo size
1552273780 bytes sent via SQL*Net to client(別ネタ向け仕込み)
734925 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client(別ネタ向け仕込み)
1000001 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 SELECT /*+ MONITOR */ *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
-----------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
ON st.pkey = nmst.col1 OR st.pkey = nmst.col2

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 219s(別ネタ向け仕込み)

...略...

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 37 | 32 | 0.22 | 4.20 | 66668 | 4M | 573 | 558MB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=2133431102)
====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 220 | +0 | 1 | 1M | | | . | | |
| 1 | MERGE JOIN OUTER | | 2M | 6M | 220 | +0 | 1 | 1M | | | . | 6.90 | Cpu (2) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19524 | 220 | +0 | 1 | 1M | 573 | 558MB | . | 10.34 | Cpu (3) |
| 3 | BUFFER SORT | | 2 | 6M | 220 | +0 | 1M | 1M | | | 2048 | 17.24 | Cpu (5) |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 6 | 220 | +0 | 1M | 1M | | | . | | |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 6 | 220 | +0 | 1M | 1M | | | . | | |
| 6 | UNION-ALL | | | | 220 | +0 | 1M | 1M | | | . | 6.90 | Cpu (2) |
| 7 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 3 | 220 | +0 | 1M | 500K | | | . | | |
| 8 | INDEX UNIQUE SCAN | UK1 | 1 | 2 | 220 | +0 | 1M | 500K | | | . | 17.24 | Cpu (5) |
| 9 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 3 | 220 | +0 | 1M | 500K | | | . | | |
| 10 | INDEX UNIQUE SCAN | UK2 | 1 | 2 | 220 | +0 | 1M | 500K | | | . | 6.90 | Cpu (2) |
====================================================================================================================================================================================


LATERAL変換を避け, HASH JOINにしてくれるような書き換えを行った方はどうかというと.
やはり, PGA内に収まっていたHASH JOINと比較して, Temp落ち(1GBほど)の影響で改善幅は減っていますが, 37sec が 32secと, 15%程度は勝っていますね. Temp落ちは避けられないですからね.

であれば, Temp落ちの落ちている先を速くすれば良いではないか. . . ということで, メモリにさえ余裕があれば, 使いすぎないようにした上で, tmpfs を使ってみましょうか. (一時表領域はなければOracleが再作成してくれるので)

  1  WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

1000001行が選択されました.

経過: 00:02:24.20

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 1451M| | 120K (1)| 00:00:05 |
|* 1 | HASH JOIN OUTER | | 1000K| 1451M| 497M| 120K (1)| 00:00:05 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 1000K| 486M| | 19524 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 750K| 723M| | 39559 (1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

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

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)

統計
----------------------------------------------------------
1297 recursive calls
0 db block gets
229696 consistent gets
374740 physical reads
0 redo size
1551273711 bytes sent via SQL*Net to client(別ネタ向け仕込み)
734925 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client(別ネタ向け仕込み)
0 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
----------------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key
,description FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL )
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 223s

...略...

Global Stats
================================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
================================================================================
| 32 | 17 | 15 | 66668 | 230K | 6887 | 3GB | 5169 | 1GB |
================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 222 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 120K | 223 | +1 | 1 | 1M | 5169 | 1GB | 5169 | 1GB | 184MB | 1GB | 90.62 | Cpu (9) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (7) |
| | | | | | | | | | | | | | | | | direct path write temp (13) |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 39559 | 67 | +7 | 1 | 1M | 1145 | 1GB | | | . | . | 6.25 | Cpu (2) |
==============================================================================================================================================================================================================


ということで, メモリに余裕があるので tmpfsを使って遊んでみましょう. 2GB固定サイズの一時表領域を作成して, SCOTTユーザーのデフォルト一時表領域にしました

[master@localhost ~]$ df -TH
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
tmpfs tmpfs 9.3G 17k 9.3G 1% /dev/shm
tmpfs tmpfs 9.3G 9.7M 9.3G 1% /run
tmpfs tmpfs 9.3G 0 9.3G 0% /sys/fs/cgroup
/dev/mapper/ol-root xfs 48G 44G 4.3G 92% /
/dev/mapper/ol-work xfs 11G 109M 11G 2% /work

...略...

[master@localhost ~]$ sudo mkdir /oratemp
...略...
[master@localhost ~]$ ls -l / | grep oratemp
drwxrwxrwt. 2 root root 60 1月 14 12:03 oratemp
[master@localhost ~]$ sudo mount -t tmpfs tmpfs /oratemp
[sudo] master のパスワード:
[master@localhost ~]$ df -TH
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
devtmpfs devtmpfs 9.3G 0 9.3G 0% /dev
tmpfs tmpfs 9.3G 17k 9.3G 1% /dev/shm
tmpfs tmpfs 9.3G 9.7M 9.3G 1% /run
tmpfs tmpfs 9.3G 0 9.3G 0% /sys/fs/cgroup
/dev/mapper/ol-root xfs 48G 44G 4.3G 92% /
/dev/mapper/ol-work xfs 11G 109M 11G 2% /work

...略...

tmpfs tmpfs 9.3G 0 9.3G 0% /oratemp
一時表領域を作成して, scottのデフォルト一時表領域にする
...略...
SYS@orclpdb1> create temporary tablespace hogetemp tempfile '/oratemp/hogetmp.dbf' size 2g;

表領域が作成されました.

SYS@orclpdb1> alter user scott temporary tablespace hogetemp;

ユーザーが変更されました.


では, オリジナルから. こちらそもそもTemp落ちしないので, LATERALビューへのアクセス回数が積み上がるだけなので, 該当表の物理読み込みが影響しなければほぼCPUタイムですね

SCOTT@orclpdb1> @dayx
1 SELECT /*+ MONITOR */ *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
ON st.pkey = nmst.col1 OR st.pkey = nmst.col2

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 188s(別ネタ向け仕込み)

...略...

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 39 | 34 | 0.21 | 4.62 | 66668 | 4M | 575 | 559MB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=2133431102)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 189 | +0 | 1 | 1M | | | . | | |
| 1 | MERGE JOIN OUTER | | 2M | 6M | 189 | +0 | 1 | 1M | | | . | 2.78 | Cpu (1) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 189 | +0 | 1 | 1M | 575 | 559MB | . | 13.89 | Cpu (4) |
| | | | | | | | | | | | | | direct path read (1) |
| 3 | BUFFER SORT | | 2 | 6M | 189 | +0 | 1M | 1M | | | 2048 | 22.22 | Cpu (8) |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 6 | 189 | +0 | 1M | 1M | | | . | | |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 6 | 189 | +0 | 1M | 1M | | | . | | |
| 6 | UNION-ALL | | | | 189 | +0 | 1M | 1M | | | . | 2.78 | Cpu (1) |
| 7 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 3 | 189 | +0 | 1M | 500K | | | . | 8.33 | Cpu (3) |
| 8 | INDEX UNIQUE SCAN | UK1 | 1 | 2 | 189 | +0 | 1M | 500K | | | . | 5.56 | Cpu (2) |
| 9 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 3 | 189 | +0 | 1M | 500K | | | . | 8.33 | Cpu (3) |
| 10 | INDEX UNIQUE SCAN | UK2 | 1 | 2 | 189 | +0 | 1M | 500K | | | . | 13.89 | Cpu (5) |
=========================================================================================================================================================================================


では, HASH JOINになるように書き換えた方のTemp落ちの時間は...想定通り短縮していますね. 15secほどあったIO Waits(s)ものが1/15程度まで減っています.
結果的に, 39sec -> 21secとなりました. Temp落ちする前提だから落ちた先のIOレイテンシーが小さければこうなるわけですけども. 逆に落ちた先のIOレイテンシーが大きければ影響も大きくなりますよね.

  1  WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
--------------------------------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key
,description FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL )
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------
Status : DONE (ALL ROWS)

...略...

Duration : 211s(別ネタ向け仕込み)

...略...

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 21 | 16 | 1.65 | 3.28 | 66668 | 247K | 5566 | 3GB | 3855 | 934MB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 210 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 119K | 211 | +1 | 1 | 1M | 3855 | 934MB | 3855 | 934MB | 367MB | 1GB | 85.71 | sort segment request (1) |
| | | | | | | | | | | | | | | | | Cpu (10) |
| | | | | | | | | | | | | | | | | SQL*Net message to client (1) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (6) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 3 | +2 | 1 | 1M | 575 | 559MB | | | . | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 38796 | 120 | +4 | 1 | 1M | 1134 | 1GB | | | . | . | 14.29 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
==============================================================================================================================================================================================================


既存表定義を変えないでということになるとこの辺りが限界でしょうね.

オプティマイザは進化し続けていますが, モデル起因だったり構文起因だったり, まだまだ頑張っているけど, 何でもかんでもい感じに最適化できるわけではないので, モデリング頑張りましょうね. というのは不変ですよね. と思います.
今回の例は比較的単純かしていますが, 多重度が 1:* で結合カーディナリティが多くなるタイプや, スーパータイプ, サブタイプの共存的サブタイプだとさらに結合カーディナリティが増加するので, UNIONに分割してあげるなど別の手を駆使しないと対応しにくいタイプもあるので, 頭の片隅に置いておくと良さそうです.

2022年分のおまけのおまけ. これで, おしまい.

では, また.



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ

| | | コメント (0)

2022年12月26日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ

さて, 恒例のアドベントカレンダーのおまけw

Day25で, 性能面で気になる部分のコメントしていた点を思い出してください. INDEX UNIQUE SCANを繰り返している点. 問題にならないなら良いのですが, (データ件数がそれ以上増加しなければ, 安定した処理時間にはなるわけですが....)
データ量次第の危さを感じますとコメントしていた点を思い出してください.

では, 私が, 懸念しているINDEX UNIQUE SCANの回数をSQLモニターという名の内視鏡的な方法で診ていきましょう.
VW_LAT_E87C3AAFのLATERALビューに変換されている操作以降がSUPERTYPE表のヒット件数分繰り返されています. この部分こそ, "データ量次第の危さ”と言った理由です.

 

1  SELECT /*+ MONITOR */ *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
--------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ MONITOR */ * FROM supertype st LEFT OUTER JOIN nikoichi_mitaina_subtype nmst ON st.pkey = nmst.col1 OR st.pkey = nmst.col2

...略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.42 | 0.37 | 0.06 | 668 | 32231 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=2133431102)
=====================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 3 | +0 | 1 | 10001 | . | | |
| 1 | MERGE JOIN OUTER | | 20002 | 40226 | 3 | +0 | 1 | 10001 | . | | |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 204 | 3 | +0 | 1 | 10001 | . | | |
| 3 | BUFFER SORT | | 2 | 40021 | 3 | +0 | 10001 | 10000 | 2048 | | |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 4 | 3 | +0 | 10001 | 10000 | . | | |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 4 | 3 | +0 | 10001 | 10000 | . | | |
| 6 | UNION-ALL | | | | 3 | +0 | 10001 | 10000 | . | | |
| 7 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 2 | 3 | +0 | 10001 | 5000 | . | | |
| 8 | INDEX UNIQUE SCAN | UK1 | 1 | 1 | 3 | +0 | 10001 | 5000 | . | | |
| 9 | TABLE ACCESS BY INDEX ROWID | NIKOICHI_MITAINA_SUBTYPE | 1 | 2 | 3 | +0 | 10001 | 5000 | . | | |
| 10 | INDEX UNIQUE SCAN | UK2 | 1 | 1 | 3 | +0 | 10001 | 5000 | . | | |
=====================================================================================================================================================================

 

今回のケースでは, CPUバウンドになっていますが, キャッシュヒット率が高ければ, CPUバンドでしょうし, 乗り切らないほど巨大であれば, IOバウンドになって現れそうですよね.

どちらの表のデータ件数も今以上に増加(現時点の処理時間が想定範囲内であることを前提としています)する可能性があり, 読みきれない部分があるのであれば, LATERALビュー変換されたこの実行計画は避けた方が良いだろうという意見に反対される方は少ないのではないでしょうか.

であれば, 方法は一つ.
そう, 自分で, 書き換えれば良いですね. データ量が増加しても安定して, 多少無駄なアクセスがあっても処理時間が安定しやすい方向へ最適化が行われるSQL構文へ. (多少, SQL文が長くなってたとしても)

ということで, どう構文変更して書き換えたらいい感じになりそうか考えてみてね, 冬休みの頭の体操になるのではないかと思います:)
(答え合わせはしませんよ)

 

ではまた. (Oracleネタとしては, 今年はここまで) また来年お会いしましょう. みなさん, 良いお年を!


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN

 

| | | コメント (0)

2022年12月25日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN

Previously on Mac De Oracle...
Day 24は, SQL MACROにフォーカスをあてました. SEMI JOINだろうと思ってた方々 m(_ _)m 元々今日のネタの伏線をはろうとしていたので, いずれにしても, SEMI JOINではなかったのですがw

と, いうことで Day 25!
ついに, アドベントカレンダー 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022も千秋楽です

最終日ですので, 大人の事情に金縛りに会いながらも, 頑張って作ってしまったと思われる, 稀に, よくみるタイプのモデルを, それぞれのバージョンのOracleオプティマイザが, どう最適化しようと苦労しているのか, 実行計画というレントゲンを通し, 生暖か, いや, 熱い眼差しでワイン片手に, 観察しつつ, アドベントカレンダー 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022を締めくくりたいと思います. (オチはないかもw)

当医院に担ぎ込まれてきたSQL文を問診してみると, こんな感じに見えました. (スーパータイプ, サブタイプテーブルを大人の事情で無理強いされて出来上がってしまったような危うい雰囲気であります. 本題はモデルの良し悪しではないので, これ以上, ツッコまないよ)

ということで, 稀によく見るタイプの患者さんを図に起こしてみました. ん〜, かなり複雑な事情がありそうですね!
参考までに, スーパータイプ, サブタイプの概念モデルの実装方法についてはいくつかのパターンがあります. サブタイプがニコイチになっている割に, 結合キーが2個というところが大人の事情を強く感じますが.
(スーパータイプ, サブタイプよくわからんという方は, 斜め読みするならスーパータイプ/サブタイプのテーブルへの実装 / hmatsu47が良いかなと思います)

Sql_20221224173301

 

他に, 外傷がないか SQL文 を診ておきましょう, SQL文はこんなでした. ANSI構文でなければ, こうはならないですよね. Oracleの方言では結合条件のORは書けないので, UNIONで書くことになるのですが, Oracleの方言でこれを書こうとすると割と大変です. UNIONで書いた方が割と無難な実行計画に最適化されやすいというのもありますが, さて, ANSI構文ではどうなりますか. .
(SQLは雰囲気が伝わるように作ってありますw)

SELECT *
FROM
supertype st
LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
ON
st.pkey = nmst.col1
OR st.pkey = nmst.col2
;

 

では, 実行計画という名のレントゲンを11gR1, 11gR2, 12cR1, 12cR2, 18c, 19c, 21cで, 最適化の違いを診ていきましょう. なお, 隠しパラメータ含めパラメータはデフォルト設定です.
データの準備

SCOTT@orclpdb1> @day25-0
1* DROP TABLE supertype

表が削除されました.

経過: 00:00:00.05
1 CREATE TABLE supertype
2 (
3 pkey NUMBER PRIMARY KEY
4 , attr1 NUMBER NOT NULL
5 , attr2 NUMBER NOT NULL
6 , note VARCHAR2(500)
7* )

表が作成されました.

経過: 00:00:00.03
1* DROP TABLE nikoichi_mitaina_subtype

表が削除されました.

経過: 00:00:00.05
1 CREATE TABLE nikoichi_mitaina_subtype
2 (
3 pkey NUMBER PRIMARY KEY
4 , col1 NUMBER
5 , col2 NUMBER
6 , description VARCHAR2(1000)
7 , CONSTRAINT uk1 unique (col1) USING INDEX
8 , CONSTRAINT uk2 unique (col2) USING INDEX
9* )

表が作成されました.

経過: 00:00:00.03
1 BEGIN
2 FOR i IN 1..10000 LOOP
3 INSERT INTO supertype VALUES(i,0,0,LPAD(i,500,'*'));
4 INSERT INTO nikoichi_mitaina_subtype VALUES(
5 i
6 , CASE WHEN MOD(i,2) = 0 THEN i ELSE null END
7 , CASE WHEN MOD(i,2) = 1 THEN i ELSE null END
8 , LPAD(i,1000,'*')
9 );
10 IF MOD(i,100) = 0 THEN COMMIT; END IF;
11 END LOOP;
12 INSERT INTO supertype VALUES(10001,0,0,null);
13 COMMIT;
14 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'SUPERTYPE',no_invalidate=>false,cascade=>true);
15 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'NIKOICHI_MITAINA_SUBTYPE',no_invalidate=>false,cascade=>true);
16* END;

PL/SQLプロシージャが正常に完了しました.

経過: 00:00:02.83

 

現時点でリリースされている最新版, 21cから順に, 11gR2まで遡って診ていきます

やはり, 最新のオプティマイザは, なかなかやりますね.

結合条件のORId=5, VW_ORE_FDF394AEで分かるように, OR_EXPANDしているようです.
さらに, その結果を, Id=6, VW_LAT_E87C3AAFで分かるように, LATERALビューに変換し, MERGE JOIN OUTERしています. LATERALが使えるようになったリリースであることも大きく影響しているように見えます.
(実は, 昨日のネタは, LATERALして伏線にしようと思ってたのですが, SQL MACROの面白さ先にしたくなったのでしたw)

さらに, 興味深いのは, NIKOICHI_MITAINA_SUBTYPEのユニーク索引をIS NOT NULLでフィルタリングしながらアクセスしているところ. 流石です. とは言っても, INDEX UNIQUE SCANしているのでデータ量次第の危さも感じますよね. キャッシュに乗ってたら早そうですが.

SCOTT@orclpdb1> select banner from v$version;

BANNER
------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

SCOTT@orclpdb1> @day25
1 SELECT *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

10001行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40226 (1)| 00:00:02 |
| 1 | MERGE JOIN OUTER | | 20002 | 20M| 40226 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 40021 (1)| 00:00:02 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 4 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
32231 consistent gets
0 physical reads
0 redo size
15493776 bytes sent via SQL*Net to client
7378 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
10001 sorts (memory)
0 sorts (disk)
10001 rows processed

 

冒頭で, ”Oracleの方言でこれを書こうとすると割と大変です. ”と書きましたが, 以下が理由です.
同じような表現はできないのです. 方言でこれを書こうとすると割と大変と言ったのはこの理由からなんです.

  1  SELECT *
2 FROM
3 supertype st
4 , nikoichi_mitaina_subtype nmst
5 WHERE
6 st.pkey = nmst.col1(+)
7* OR st.pkey = nmst.col2(+)
OR st.pkey = nmst.col2(+)
*
行7でエラーが発生しました. :
ORA-01719: ORまたはINオペランドの中で外部結合演算子(+)は使用できません

 

余談はこれぐらいにして, 19cではどうか見てみましょう(結果飲み)
同じ実行計画です. LATERALやOR_EXPANDが実装が利用されていることから, それらが実装されたあたりからはこのような実行計画が生成されている可能性が高いですね.

SCOTT@ORCL> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Execution Plan
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40219 (1)| 00:00:02 |
| 1 | MERGE JOIN OUTER | | 20002 | 20M| 40219 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 40015 (1)| 00:00:02 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 4 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

 

18cではどうでしょう(結果のみ)
やはり, 同じ, Plan hash valueになっていますね. なるほどなるほど.

SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production

Execution Plan
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40218 (1)| 00:00:02 |
| 1 | MERGE JOIN OUTER | | 20002 | 20M| 40218 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 40014 (1)| 00:00:02 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 4 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

 

お!, 12cR2も同じ実行計画になりますね!

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
PL/SQL Release 12.2.0.1.0 - Production
CORE 12.2.0.1.0 Production
TNS for Linux: Version 12.2.0.1.0 - Production
NLSRTL Version 12.2.0.1.0 - Production

Execution Plan
----------------------------------------------------------
Plan hash value: 2133431102

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40217 (1)| 00:00:02 |
| 1 | MERGE JOIN OUTER | | 20002 | 20M| 40217 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | 1082 | 40013 (1)| 00:00:02 |
| 4 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 5 | VIEW | VW_ORE_FDF394AE | 2 | 1082 | 4 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
|* 9 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

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

8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)
9 - filter(LNNVL("ST"."PKEY"="NMST"."COL1"))
10 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)

 

12cR1はどうでしょう. おっと, ここで差が出ました. OR_EXPANDではなく, USE_CONCATが行われています. たた, VW_LAT_E87C3AAFがあることからLATERALビューが内部的に利用されていることが見えますね.
性能に影響しそうなのは, NESTED LOOPS OUTERになっているあたりでしょうね. データ量が大きい想定見積もりに倒れている最近のリリースとは明らかに異なり. 危険な感じの実行計画ではあります.

SCOTT@pdborcl12c> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE 12.1.0.2.0 Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production

実行計画
----------------------------------------------------------
Plan hash value: 3799136614

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40216 (1)| 00:00:02 |
| 1 | NESTED LOOPS OUTER | | 20002 | 20M| 40216 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 204 (0)| 00:00:01 |
| 3 | VIEW | VW_LAT_E87C3AAF | 2 | 1082 | 4 (0)| 00:00:01 |
| 4 | CONCATENATION | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
|* 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

6 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)
7 - filter(LNNVL("NMST"."COL2" IS NOT NULL) OR LNNVL("ST"."PKEY"="NMST"."COL2"))
8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)

 

11gR2です. LATERALビューも消え, USE_CONCATが行われているだけで, さらに, NESTED LOOPS OUTERなのでデータ量が多いとやはり危険なタイプの実行計画になっているのがわかります.

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE 11.2.0.4.0 Production
TNS for Linux: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

orcl@SCOTT> @day25
1 SELECT *
2 FROM
3 supertype st
4 LEFT OUTER JOIN nikoichi_mitaina_subtype nmst
5 ON
6 st.pkey = nmst.col1
7* OR st.pkey = nmst.col2

Execution Plan
----------------------------------------------------------
Plan hash value: 2117741269

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20002 | 20M| 40236 (1)| 00:08:03 |
| 1 | NESTED LOOPS OUTER | | 20002 | 20M| 40236 (1)| 00:08:03 |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 10001 | 4971K| 205 (1)| 00:00:03 |
| 3 | VIEW | | 2 | 1082 | 4 (0)| 00:00:01 |
| 4 | CONCATENATION | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | UK2 | 1 | | 1 (0)| 00:00:01 |
|* 7 | TABLE ACCESS BY INDEX ROWID| NIKOICHI_MITAINA_SUBTYPE | 1 | 1009 | 2 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | UK1 | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

6 - access("ST"."PKEY"="NMST"."COL2")
filter("NMST"."COL2" IS NOT NULL)
7 - filter(LNNVL("NMST"."COL2" IS NOT NULL) OR LNNVL("ST"."PKEY"="NMST"."COL2"))
8 - access("ST"."PKEY"="NMST"."COL1")
filter("NMST"."COL1" IS NOT NULL)

 

このケース, 最新のリリースでは, LATERALビューを使い, MERGE JOINも利用するような多くの内部的な書き換えが行われ, いい感じの実行計画が生成されています.

ただ, 稀に, そうでもないケースもあります. (ググってみると結構ある. あった. ことがわかります)
そのような場合, 結合条件でORを利用する構文を書き換え, UNIONを使った構文(このケースではオプティマイザが内部的に変換していますが)へ書き換えてしまった方が良い実行計画にできる場合があります. (その方がヒントでの細かい制御もしやすい場合があります)
その辺りは, 状況に応じ臨機応変に対応すれば良いと思います.
今回のMERGE OUTER JOINで想定より遅い場合には, やはり構文変更してしまった方が処理時間は安定して, 無難な方向になるケースもありそうに思います.

ということで, 今年のアドベントカレンダーは, しゅうーーーりょう!!

参考) Internal Views / Oracle Scratchpad

 

I wish you all a Merry Christmas and a Happy New Year!

ではまた ;)

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)

 

| | | コメント (0)

2022年12月24日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)


Previously on Mac De Oracle...

Day 23は, ANTI JOINにフォーカスをあてました. そうくれば, 次は, SEMI JOINだろう? と思ったあなた! ハズレですw

では, Day 24 クリスマスイブの窓を開けましょう!
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
で, パイプラインテーブルファンクションだと, ファンクションが実行されていることしかExplain等で見る事はできなくて, COLLECTION ITERATOR PICKLER FETCH だけなんだよね, と言うお話をしました.

1  SELECT
2 *
3 FROM
4* day9_pkg.list_2_latest_sales(2, 20, 5745)

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
20 5745 01-12-31 2 999 1 628.89

実行計画
----------------------------------------------------------
Plan hash value: 457385954

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| LIST_2_LATEST_SALES | 8168 | 16336 | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

レントゲン屋さん的にはw 辛いよね. と. 間接的に見るしかないわけで. 少々面倒, 対応を意識していないと難しいですし.


今日は, クリスマスイブですし, Oracleさんもどんどん新機能提供してくれてて, それファンクションじゃないの? いえ, いえ. ?? 今日は, SQLマクロ(SQM)の実行計画という名のレントゲンです.

SQLマクロ(SQM)を作ります. パイプラインテーブルファンクションと似てますけども, SQL_MACROです.

SCOTT@orclpdb1> @day24
1* DROP FUNCTION sq_macro_sample

ファンクションが削除されました.

1 CREATE FUNCTION sq_macro_sample
2 (
3 p_first_rows NUMBER
4 , p_table_name DBMS_TF.TABLE_T
5 )
6 RETURN VARCHAR2
7 SQL_MACRO
8 IS
9 BEGIN
10 RETURN
11 'SELECT
12 *
13 FROM
14 p_table_name
15 FETCH FIRST sq_macro_sample.p_first_rows ROWS ONLY'
16 ;
17* END;

ファンクションが作成されました.


おおおおおおおおおお〜っと. マクロが展開されて, emp表の直接アクセスしている. (マクロだからそりゃそうだw)
レントゲンを見てみましょう.

パイプラインテーブルファンクションと外見からはあまり違いに気づかないというか, 気づけないですが. レントゲンの結果は全く違います! マクロが展開されて, 普通の SQL文の実行計画という名のレントゲンを見ることができます.

  1  SELECT
2 *
3 FROM
4* sq_macro_sample(3, emp)

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30

実行計画
----------------------------------------------------------
Plan hash value: 3611411408

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 300 | 2 (0)| 00:00:01 |
|* 1 | VIEW | | 3 | 300 | 2 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 3 | 117 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | EMP | 3 | 117 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_009"."rowlimit_$$_rownumber"<=3)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=3)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1262 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed


おおおおおおおおおお〜っと. 2回目w  マクロが展開されて, dept表の直接アクセスしている. (マクロだからそりゃそうだw)

  1  SELECT
2 *
3 FROM
4* sq_macro_sample(2, dept)

DEPTNO DNAME LOC
---------- ------------------------------------------ ---------------------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS

実行計画
----------------------------------------------------------
Plan hash value: 3452784451

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 86 | 3 (0)| 00:00:01 |
|* 1 | VIEW | | 2 | 86 | 3 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 2 | 40 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | DEPT | 2 | 40 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_009"."rowlimit_$$_rownumber"<=2)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
827 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed


みなさん, SQL_MACRO, 実は, 物凄く, いいいやつ, っぽい感じがします. 慣れるまでは敷居高そうでもありますがw 
マニュアルを真面目に読んで, 倒れそうになりました. (斜め読みでは理解しきれない深さを感じます)


入門としては, HNakaieさんのエントリがわかりやすいですかね.
Oracle DatabaseのSQLマクロを検証する /

参考)
Oracle Database 21c Database PL/SQL Language Reference / 14.64 SQL_MACRO Clause


ということで,  Happy Holidays中の方も, Hard Wroking Now!な方も, I wish you all a Merry Christmas.

そういえば, もう1週間以上前ですがw, アメリカのOracleの中の人からのメッセージが, Happy Holidays! で締めくくられていました.
日本人の感覚だと, え, と思わなくもないわけですが, アメリカとかだとそうだよねーーー. そう言う時期だよねーーと, 思いつつ,
アドベントカレンダー全部俺のネタ作りに追われているw 俺, なんなの? みたいに思ったりw

では, アドベントカレンダー最終日の担当も, 私です.

ではまた.



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN

| | | コメント (0)

2022年12月23日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN

Day 22は, Subquery Unnestingを取り上げました. 最近は積極的にUnnestingしているように思うのですが, 皆さんはどう思いますか?

では, Day 23の窓を開けましょう!

今日は, 昨日話題にした, Subquery Unnesting の発動も必要ですが ANTI JOIN の実行計画という名のレントゲンから, いくつか特徴のある物をピックアップ(全バリエーション取れなかった言い訳w).

ANTI JOIN(結合されなかった主問い合わせの対象行を返す)もJOINのナカーマなであるわけで, Nested Loop Join, Hash Join, Merge joinがあり, さらに, NULLを考慮する必要のある結合列がないか, 一つか, それ以上かで, Null-Awareなし, NA(Null-Aware), SNA(Single Null-Aware) でOperation列に表示されるOperation名にいくつかのバリエーションがあります.
とはいえ, ANTI JOINかNULLを意識する必要があるかないか大きな分類で, JOIN方式は通常のJOINと同じ種類があるのはご存知の通り.

NOT EXISTS演算子や, NOT IN条件かつSubquery Unnestingが発動していると, 直感的にイメージできるようになってればいいかもしれないですね. かつ, NULLを意識してるなどもOperation名から見切れるとなお.
たった1文のOperationにそこまで情報が詰め込まれているんですよね.

参考)

Oracle Database 21c SQL Tuning Guide / 9.3.4 Antijoins

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


ヒントまで使ってなんとか, NESTED LOOPS ANTI が取れました(なかなか言うこと聞いてくれなかったw)
結合列でNULLを意識する必要のない場合に現れます.

HR@orclpdb1> @day23-2
1 SELECT
2 /*+
3 NO_INDEX(departments DEPT_ID_PK)
4 */
5 department_id
6 , department_name
7 FROM
8 departments
9 WHERE
10 department_id NOT IN
11 (
12 SELECT
13 /*+
14 NL_AJ
15 */
16 department_id
17 FROM
18 employees
19 WHERE
20 department_id IS NOT NULL
21* )

DEPARTMENT_ID DEPARTMENT_NAME
------------- ----------------------
120 Treasury
130 Corporate Tax
140 Control And Credit

...略...

240 Government Sales
250 Retail Sales
260 Recruiting
270 Payroll

16行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 3082375452

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS ANTI | | 17 | 323 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

3 - access("DEPARTMENT_ID"="DEPARTMENT_ID")
filter("DEPARTMENT_ID" IS NOT NULL)

一つ前のSQL文とほぼ同じですが, IS NOT NULL条件を取り除いてあります.
NESTED LOOPS ANTI SNAです, SNAとなっているので, NULLを意識する必要のあることがわかります.

  1  SELECT
2 /*+
3 NO_INDEX(departments DEPT_ID_PK)
4 */
5 department_id
6 , department_name
7 FROM
8 departments
9 WHERE
10 department_id NOT IN
11 (
12 SELECT
13 department_id
14 FROM
15 employees
16* )

レコードが選択されませんでした.

実行計画
----------------------------------------------------------
Plan hash value: 2953329389

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 6 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS ANTI SNA| | 17 | 323 | 6 (50)| 00:00:01 |
| 3 | TABLE ACCESS FULL | DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EMPLOYEES | 1 | 3 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

1 - filter( NOT EXISTS (SELECT 0 FROM "EMPLOYEES" "EMPLOYEES" WHERE
"DEPARTMENT_ID" IS NULL))
4 - access("DEPARTMENT_ID"="DEPARTMENT_ID")
5 - filter("DEPARTMENT_ID" IS NULL)


MERGE JOIN ANTIが現れています. NA, SNAが現れていないことから, NULLは考慮しないANTI JOINであることがわかります.

SCOTT@orclpdb1> @day23

1 SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 deptno NOT IN
8 (
9 SELECT
10 deptno
11 FROM
12 emp
13 WHERE
14 deptno IS NOT NULL
15* )

DEPTNO DNAME
---------- ------------------------------------------
40 OPERATIONS

実行計画
----------------------------------------------------------
Plan hash value: 1353548327

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI | | 1 | 16 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 4 | SORT UNIQUE | | 12 | 36 | 4 (25)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EMP | 12 | 36 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

4 - access("DEPTNO"="DEPTNO")
filter("DEPTNO"="DEPTNO")
5 - filter("DEPTNO" IS NOT NULL)


MERGE JOIN ANTI NAが出ています. NAがあるので, NULLを考慮したMERGE JOIN ANTIであることがわかります. でもこれ本当は, SNAではないのか?(時間があったら10053でも追ってみようかなぁ)

  1  SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 deptno NOT IN
8 (
9 SELECT
10 deptno
11 FROM
12 emp
13* )

DEPTNO DNAME
---------- ------------------------------------------
40 OPERATIONS

実行計画
----------------------------------------------------------
Plan hash value: 218628244

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI NA | | 1 | 16 | 6 (17)| 00:00:01 |
| 2 | SORT JOIN | | 4 | 52 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 5 | SORT UNIQUE | | 12 | 36 | 4 (25)| 00:00:01 |
| 6 | TABLE ACCESS FULL | EMP | 12 | 36 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

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

5 - access("DEPTNO"="DEPTNO")
filter("DEPTNO"="DEPTNO")


HASH JOIN ANTIが現れています. NULLを考慮させないために, IS NOT NULL条件を追加しています.

 1  SELECT
2 empno
3 ,ename
4 FROM
5 emp
6 WHERE
7 emp.deptno NOT IN
8 (
9 SELECT
10 /*+
11 HASH_AJ
12 */
13 dept.deptno
14 FROM
15 dept
16 WHERE
17 dept.deptno = 50
18 AND dept.deptno IS NOT NULL
19 )
20* AND emp.deptno IS NOT NULL

EMPNO ENAME
---------- ------------------------------
7499 ALLEN
7521 WARD

...略...

7369 SMITH
7566 JONES
7902 FORD

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 4131168823

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 11 | 176 | 3 (0)| 00:00:01 |
|* 1 | HASH JOIN ANTI | | 11 | 176 | 3 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| EMP | 12 | 156 | 3 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| PK_DEPT | 1 | 3 | 0 (0)| 00:00:01 |
------------------------------------------------------------------------------

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

1 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
2 - filter("EMP"."DEPTNO" IS NOT NULL)
3 - access("DEPT"."DEPTNO"=50)


HASH JOIN ANTI SNAが現れています. NULLを考慮する必要のあるSNAが付いた, HASH JOIN ANTIですね.

  1  SELECT
2 empno
3 ,ename
4 FROM
5 emp
6 WHERE
7 deptno NOT IN
8 (
9 SELECT
10 deptno
11 FROM
12 dept
13 WHERE
14 deptno = 50
15* )

EMPNO ENAME
---------- ------------------------------
7499 ALLEN
7521 WARD
7654 MARTIN

...略...

7369 SMITH
7566 JONES
7902 FORD

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 2782438375

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 11 | 176 | 3 (0)| 00:00:01 |
|* 1 | HASH JOIN ANTI SNA| | 11 | 176 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 12 | 156 | 3 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| PK_DEPT | 1 | 3 | 0 (0)| 00:00:01 |
------------------------------------------------------------------------------

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

1 - access("DEPTNO"="DEPTNO")
3 - access("DEPTNO"=50)


次は, HASH JOIN ANTI NAが出ているので, NULLを複数列で考慮してもらう必要があるので, 無理感はありますがw. 一応, 取れたw NA付きのHASH JOIN ANTIのOperationです.

SCOTT@orclpdb1> desc foo
名前 NULL? 型
----------------------------------------- -------- ----------------------------
COL1 NOT NULL NUMBER
COL2 NOT NULL NUMBER
COL3 NUMBER

SCOTT@orclpdb1> desc bar
名前 NULL? 型
----------------------------------------- -------- ----------------------------
COL0 NOT NULL NUMBER
COL1 NUMBER
COL2 NUMBER
COL4 NUMBER
 1  SELECT
2 col1
3 FROM
4 foo
5 WHERE
6 col3 NOT IN
7 (
8 SELECT
9 col2
10 FROM
11 bar
12* )

レコードが選択されませんでした.

実行計画
----------------------------------------------------------
Plan hash value: 3985729167

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 4 (0)| 00:00:01 |
|* 1 | HASH JOIN ANTI NA | | 1 | 39 | 4 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| FOO | 1 | 26 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| BAR | 1 | 13 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------

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

1 - access("COL3"="COL2")


狙った通りのOperationを行わせるのって難しいですねwwwwwwww (^^;;;;;;; NA, SNAまで考えるとw 

なかなか, これだけのバリエーション, 現場で見ることはないわけですけどもwww 今回のレントゲンシリーズで一番疲れたw
今日は, ここまで,

残り2日だ.

明日も, 頑張って, 窓を開きますよ. ここまできたらw



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting

| | | コメント (0)

2022年12月22日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting

Previously on Mac De Oracle...

Day 21は, INLIST ITERATOR と Sub Query と STATISTICS COLLECTORを取り上げました. 古くからあるOperationを最適化する比較的新しい機能ですね. とはいえ, 現場で見たことない! なんていうのは多そうではあります.

では, Day 22の窓を開けましょう!

昨日の記事でちょっとだけ話題にした, Subquery Unnesting なので, Subquery Unnestingの代表的な3例を診ておきましょう.

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


まず, これも通常は, SQLを書き換えないと対応タイプですが, Oracleは結合に書き換えてくれます. 大量にデータを返すのに, スカラー副問い合わせが使われていて辛かった経験って一度ぐらいはありそうですね.
(私も過去一度だけこいつのチューニングしたことがありますが, 当時はスカラー副問い合わせをUnnestingできなかったので, SQLをOUTER JOINに書き換えてもらいました!)

以下の例では, スカラー副問い合わせが, OUTER JOINかつ, このケースではMERGE JOINに書き換えられています.

SCOTT@orclpdb1> @day22
1 SELECT
2 deptno
3 ,dname
4 ,(
5 SELECT
6 MAX(sal)
7 FROM
8 emp
9 WHERE
10 emp.deptno=dept.deptno
11 ) AS max_sal
12 FROM
13 dept
14 ORDER BY
15* deptno

DEPTNO DNAME MAX_SAL
---------- ------------------------------------------ ----------
10 ACCOUNTING 5000
20 RESEARCH 3000
30 SALES 2850
40 OPERATIONS

実行計画
----------------------------------------------------------
Plan hash value: 2834279049

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 116 | 7 (29)| 00:00:01 |
| 1 | MERGE JOIN OUTER | | 4 | 116 | 7 (29)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 3 | 48 | 5 (40)| 00:00:01 |
| 5 | VIEW | VW_SSQ_1 | 3 | 48 | 4 (25)| 00:00:01 |
| 6 | HASH GROUP BY | | 3 | 21 | 4 (25)| 00:00:01 |
| 7 | TABLE ACCESS FULL | EMP | 12 | 84 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

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

4 - access("ITEM_1"(+)="DEPT"."DEPTNO")
filter("ITEM_1"(+)="DEPT"."DEPTNO")

では, Scalar Subquery Unnestingされなかった場合はどう見えるでしょうか?(これが, SELECTリストで利用されたScalar subqueryの実行計画です. NO_UNNESTヒントで抑止します.
スカラーサブクエリーが実行計画の最初に現れるのが特徴です(Oracleの場合)

以下の例では, dept表の行数分, Nested Loop Joinのように, emp表へのアクセスが繰り返し実行されます. なので, 行数が多い場合は, HASH JOINなどに書き換えた方が効率が良いわけです. Scalar Subquery UnnestingはそれをOracleが内部的に実施してくれている便利な機能なんですよ.

  1  SELECT
2 deptno
3 ,dname
4 ,(
5 SELECT
6 /*+
7 NO_UNNEST
8 */
9 MAX(sal)
10 FROM
11 emp
12 WHERE
13 emp.deptno=dept.deptno
14 ) AS max_sal
15 FROM
16 dept
17 ORDER BY
18* deptno

DEPTNO DNAME MAX_SAL
---------- ------------------------------------------ ----------
10 ACCOUNTING 5000
20 RESEARCH 3000
30 SALES 2850
40 OPERATIONS

実行計画
----------------------------------------------------------
Plan hash value: 1283604845

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 52 | 8 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 4 | 28 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IX_EMP | 4 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

3 - access("EMP"."DEPTNO"=:B1)


次は, IN条件内のサブクエリーがUnnestingされた例です.
相関関係のないIN副問合せや, IN(集計ファンクションまたはGROUP BY句を含まない場合)に書き換えるとされています. この例は, 相関関係のない副問合せにしてあります. IN LIST ITERATION等ではなく, SubqueryがUnnestingされ, Nested Loop Joinに書き換えられています!

  1  SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 deptno IN (
8 SELECT
9 deptno
10 FROM
11 emp
12 WHERE
13 sal < 1000
14 )
15 ORDER BY
16* deptno

DEPTNO DNAME
---------- ------------------------------------------
20 RESEARCH
30 SALES

実行計画
----------------------------------------------------------
Plan hash value: 35786210

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 4 (25)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 20 | 4 (25)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 20 | 4 (25)| 00:00:01 |
| 3 | SORT UNIQUE | | 1 | 7 | 2 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 7 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | IX_EMP | 12 | | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

4 - filter("SAL"<1000)
6 - access("DEPTNO"="DEPTNO")

IN条件のSubqueryのUnnestingをNO_UNNESTヒントで抑止すると元の実行計画という名のレントゲンが現れてきます.
Unnestingが抑止されたことで, 結合ではなくFILTER条件(EXISTS書き換えられ)になっていることがわかりますよね. どちらの実行計画が有利にかは状況次第ですが, 以前も書いたように, 最近の傾向では, 積極的にUnnestingしているように感じています.

  1  SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 deptno IN (
8 SELECT
9 /*+
10 NO_UNNEST
11 */
12 deptno
13 FROM
14 emp
15 WHERE
16 sal < 1000
17 )
18 ORDER BY
19* deptno

DEPTNO DNAME
---------- ------------------------------------------
20 RESEARCH
30 SALES

実行計画
----------------------------------------------------------
Plan hash value: 3458873336

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 8 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | DEPT | 4 | 52 | 2 (0)| 00:00:01 |
|* 2 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 3 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 7 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_EMP | 4 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

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

2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "EMP" "EMP" WHERE "DEPTNO"=:B1 AND
"SAL"<1000))
3 - filter("SAL"<1000)
4 - access("DEPTNO"=:B1)

最後は, EXISTS演算子内のサブクエリーがUnnestingされた例ですよー. こちらも同じようにUnnestingは積極的に行われているように思います. 私が関わった範囲ですけども.
なお, 書き換え条件として, EXISTS相関副問合せ(集計ファンクションまたはGROUP BY句を含まない場合)となっています. 以下の例でも, 集計ファンクション, GROUP BYを含まない相関服問い合わせにしてあります.

  1  SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 EXISTS (
8 SELECT
9 1
10 FROM
11 emp
12 WHERE
13 sal < 1000
14 AND emp.deptno = dept.deptno
15 )
16 ORDER BY
17* deptno

DEPTNO DNAME
---------- ------------------------------------------
20 RESEARCH
30 SALES

実行計画
----------------------------------------------------------
Plan hash value: 35786210

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 4 (25)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 20 | 4 (25)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 20 | 4 (25)| 00:00:01 |
| 3 | SORT UNIQUE | | 1 | 7 | 2 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 7 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | IX_EMP | 12 | | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

4 - filter("SAL"<1000)
6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

NO_UNNESTヒントでUnnestingを抑止してもとの実行計画を覗いています!
Plan hash valueを見ると, なんと, IN条件のNO_UNNESTヒントを付加した実行計画と同じに!

  1  SELECT
2 deptno
3 ,dname
4 FROM
5 dept
6 WHERE
7 EXISTS (
8 SELECT
9 /*+
10 NO_UNNEST
11 */
12 1
13 FROM
14 emp
15 WHERE
16 sal < 1000
17 AND emp.deptno = dept.deptno
18 )
19 ORDER BY
20* deptno

DEPTNO DNAME
---------- ------------------------------------------
20 RESEARCH
30 SALES

実行計画
----------------------------------------------------------
Plan hash value: 3458873336

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 8 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | DEPT | 4 | 52 | 2 (0)| 00:00:01 |
|* 2 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 3 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 7 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_EMP | 4 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

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

2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "EMP" "EMP" WHERE
"EMP"."DEPTNO"=:B1 AND "SAL"<1000))
3 - filter("SAL"<1000)
4 - access("EMP"."DEPTNO"=:B1)

ということで, 残り3日まで窓を開けました- ;)

安心して, 寝坊しないようにしないと.

ではまた.


参考)
Oracle Database 21c SQL Language Reference / Unnesting of Nested Subqueries



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR

| | | コメント (0)

2022年12月21日 (水)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR

Previously on Mac De Oracle...

Day 20は, DISTINCT Eliminationを取り上げました. 古くから実装されているElimination機能ですが, 知ってる方いましたかね?w 古すぎでしょうかw. とはいえ, この恩恵を得ている方も実は多いかもしれませんよ. クソデカクエリー追いきれてないかもしれないですし.

では, Day 21の窓を開けましょう!

久々に, 実行計画は, SQL文のレントゲン写真だ! っぽく, 実行計画に現れるOperationを楽しんで診ていきましょう!

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

今日のテーマはIN条件とサブクエリー. IN条件といえば, 昔, 悩ませ過ぎは及ばざるがごとし #1という記事を書いてました. (そのころはまだIT業界に居なかった? 方も多そうですが)
IN条件, 索引を使って少量のデータをアクセスするには, 以下の実行計画にあるように,  INLIST ITERATORで繰り返しアクセスするは問題ないわけですが, 大量にあるとかなり性能面で影響が出ます. (悩ませ過ぎは及ばざるがごとし #1などは, ハードパース時間に影響がでたケースです)

SCOTT@orclpdb1> @day21
1 SELECT *
2 FROM
3 emp
4 WHERE
5* empno IN (7369,7499)

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30

実行計画
----------------------------------------------------------
Plan hash value: 2355049923

---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 78 | 2 (0)| 00:00:01 |
| 1 | IINLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 2 | 78 | 2 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | PK_EMP | 2 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------

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

3 - access("EMPNO"=7369 OR "EMPNO"=7499)


性能問題も多かった影響なのか?!, Oracle Database 9i R2頃以降, IN条件が, 結合に書き換えられるように(私の感覚でしかないのですが, 最近は, より積極的に書き換えが行われる傾向があるように感じます)なっています.
以下のようにサブクエリーを利用しているケースが典型例ですね. IN条件だけでなく, EXISTS演算子や, スカラー副問合せなどもこの書き換えの対象です. この書き換えは, Subquery Unnestingと呼ばれています. ご存知の方も多いですよね. 今日の主役はそれでなく, INLIST ITERATOR の方ですが, これ両方話さないとOperationの向き不向きが見えないのでw

  1  SELECT
2 *
3 FROM
4 emp
5 WHERE
6 deptno IN (
7 SELECT
8 deptno
9 FROM
10 dept
11 WHERE
12 dname IN ('SALES','ACCOUNT')
13* )

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30

6行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 4207756064

---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 416 | 5 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 8 | 416 | 5 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 8 | 416 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | DEPT | 2 | 26 | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_EMP | 4 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| EMP | 4 | 156 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------

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

3 - filter("DNAME"='ACCOUNT' OR "DNAME"='SALES')
4 - access("DEPTNO"="DEPTNO")

Note
-----
- this is an adaptive plan

前述した実行計画の Noteに, - this is an adaptive plan と気になる情報が出ています. これ, 実行計画上は, Subquery Unnestingされて, Nested Loop Join (NLJ) になっていますが, 駆動表のヒット件数に応じて, それ以外の結合メソッドに
へ切り替わる可能性があることを示しています. これは, adaptive planと呼ばれている機能です.
実際, NLJなのかそれ以外なのかをみる方法は, SQL Monitor, Actual Planを利用する方法と, 以下のように, Adaptive Planを表示させ, どちらで動作したのかを確認する方法があります.

ここで登場するのが, Adaptive Planの鍵になる, STATISTICS COLLECTOR というOperationです. ここで駆動表の件数をみつつ, これは! HJ向きのと判断すれば, NLJ から HJ へ切り替えることになります.
今回は, NLJ のままですね. ()

  1* SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'adaptive'))

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------
SQL_ID cuxwr51s6gs61, child number 0
-------------------------------------
SELECT * FROM emp WHERE deptno IN ( SELECT deptno
FROM dept WHERE dname IN ('SALES','ACCOUNT') )

Plan hash value: 4207756064

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5 (100)| |
|- * 1 | HASH JOIN | | 8 | 416 | 5 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 8 | 416 | 5 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 8 | 416 | 5 (0)| 00:00:01 |
|- 4 | STATISTICS COLLECTOR | | | | | |
| * 5 | TABLE ACCESS FULL | DEPT | 2 | 26 | 3 (0)| 00:00:01 |
| * 6 | INDEX RANGE SCAN | IX_EMP | 4 | | 0 (0)| |
| 7 | TABLE ACCESS BY INDEX ROWID| EMP | 4 | 156 | 1 (0)| 00:00:01 |
|- 8 | TABLE ACCESS FULL | EMP | 4 | 156 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

1 - access("DEPTNO"="DEPTNO")
5 - filter(("DNAME"='ACCOUNT' OR "DNAME"='SALES'))
6 - access("DEPTNO"="DEPTNO")

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)

残り, 4日, 最終日はクリスマスで, 日曜日じゃないか!今年は.
大きめのネタをぶん投げて, おまけブログでまとめる感じにするか. 悩みどころだ. それとも軽めのネタで最後まで通すか.

明日も担当は, 私ですよ. (全部俺アドベントカレンダー, 来年はどうしようw なんの苦行だという感じw でも, それ楽しんでる俺は...w)

では, また :)



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination

| | | コメント (0)

2022年12月20日 (火)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination

Previously on Mac De Oracle... 
Day 19は, かなり地味にデビューしていた機能ですが、変なところで目立った Group by Elimination にフォーカスを当てました。この手のEliminationは、実行計画という名のレントゲンからは見えなくなってしまう。何か悪さをしているときの調査は難易度高めです。基本的に操作が行われなくなるわけですが、例としてお見せしたような単純なSQLなら別ですが、クソデカクエリーだと、キッツイですよねw 結果不正って。

と言うことで、Day 20の窓を開けましょう!

 

今日は、 DISTINCT Eliminationです。またか! と。そういうEliminationもある。ということ知っておいた方が良いと思うので、eliminationネタの最後として、ちょっと飽きてきた感じはありますが取り上げてみました。この機能の提供も古く、機能が提供されていなかった時代を知る人の方が少ないかもしれませんw

ちなみに、今回も該当機能をを局所的に無効化する例を書いていますが、ヒントが提供されていない最適化もあるので、そう言う場合は、隠しパラメータからそれらしいのを探してして、検証して効果の有無を確かめると良いと思います。MOSに書かれてないケースも多いのでBlogなどから情報を集めたりして、最終的には動作確認。もし不具合などと関連しているようであればサポートへ問い合わせても対応してくれるでしょう。コミュニティーにこれどうよ?と投げてみるのもありだと思います。Jonathanもネタもとは、コミュニティーでのやりとりだったりすることも多く、調べてみたら、そうだった!という記事も多く、本当に助かった!って経験は何度もあります!!!。

parameterはこんなあたりから見つけると楽ですよん。
Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden params
Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0)

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

前回と同じデータを使います。(こんな使いまわせるテストデータを作るスクリプト用意しておくと何かと便利ですよ!)


SCOTT@orclpdb1> @day20
1* DROP TABLE business_day_calendar

表が削除されました。

1 CREATE TABLE business_day_calendar AS
2 WITH
3 FUNCTION get_num_of_dates
4 RETURN NUMBER IS
5 l_dummy_date DATE;
6 --
7 eORA01839 exception;
8 pragma exception_init(eORA01839, -1839);
9 BEGIN
10 -- validate leap year
11 l_dummy_date := TO_DATE(TO_CHAR(SYSDATE, 'YYYY') || '0229', 'YYYYMMDD');
12 RETURN 366;
13 EXCEPTION
14 WHEN eORA01839 THEN
15 RETURN 365;
16 END;
17 SELECT
18 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1) AS business_date
19 , CASE
20 WHEN TO_CHAR(
21 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1)
22 , 'DY'
23 , 'NLS_DATE_LANGUAGE=AMERICAN'
24 ) IN ('SUN','WED')
25 THEN '1'
26 ELSE '0'
27 END AS is_holiday
28 FROM
29 dual
30 CONNECT BY
31* level <= get_num_of_dates

表が作成されました。

1 ALTER TABLE business_day_calendar
2 ADD CONSTRAINT pk_business_day_calendar PRIMARY KEY
3 (
4 business_date
5 )
6* USING INDEX

表が変更されました。

 

はい、主キー列にDISTINCTを使ってますが、無駄ですよね!(いきなり本題w)

DISTINCT操作は見事に実行計画から排除されています!(簡単ですね。とは言っても実行計画を見ただけでは、DISTINCT Eliminationが行われていることには気付けないわけですけども)


  1  SELECT
2 DISTINCT business_date
3 FROM
4* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

では、元栓というか、Optimizer自体を、とーーーく昔の状態にしてみましょう(DISTINCT Eliminationがバックポートもされてない時代の10g 10.2.0.3。こういうことができるOracle面白いですよね)
このようなことを本番でやるのはかなりレアで、よっぽど新しい機能を使いたくないか、めちゃめちゃキツイ大人の事情があるんだと思います(知らんけど)

Optimizerを10g R2ぐらいに戻したことで、DISTINCT操作として、SORT UNIQUE NOSORTが合わられましたが、INDEX FULL SCANでユニークキーをアクセスしているのでNOSORTとなり、SORT UNIQUE操作はスキップされていることがわかります。ソート順に索引を全捜査する INDEX FULL SCANだからこそできる動きですね。


  1  SELECT
2 /*+
3 OPTIMIZER_FEATURES_ENABLE('10.2.0.3')
4 */
5 DISTINCT business_date
6 FROM
7* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 311283176

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT| | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

Optimizerの能力を10gに戻してしまうのも勿体ないので、よく使う局所的な方法。特定のSQLだけが影響するのであれば、この方法がおすすめです。
元々の機能に影響を受けていない。逆に恩恵を受けているものあるかもしれません。
前回の結果不正などもそうですが、どの方法で治療するというかチューニングかは、対応するエンジニア考え方や、該当する患者さん(システムや、お客様の大人の事情w)にもよりますが、私は、基本的に局所的な対処で済むのなら、そちらを選ぶ方針です。
なるべく狭い範囲、SQL、セッションあたりで無効、有効にして、経過観察、副作用有無、対処した範囲外で、同一理由による問題が発生していないか。もし狭い範囲の対処では無理なら徐々に広げる。最終系がインスタンス全体で。みたいな流れにすることで、無駄に全て止めてしまうということを避けたい(恩恵を受けているのもあるはずということを前提にしています)。この辺りは考え方次第なので、絶対、こうするのが良いとか悪いという話では無いですが。長い目で考えるとそれが良いのでは無いかと個人的には思います。
この機能、直接利用できるヒントはないので、隠しパラメータでon/offできます。冒頭で紹介したパラメータ一覧には隠しパラメータもリストしているので、こういう時はクエリ投げずに該当ページを検索すると楽ですよん:)


  1  SELECT
2 /*+
3 OPT_PARAM('_optimizer_distinct_elimination','false')
4 */
5 DISTINCT business_date
6 FROM
7* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 311283176

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT| | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

別テストケースで確認してみましょう。


  1* DROP TABLE case2

表が削除されました。

1 CREATE TABLE case2
2 (
3 id NUMBER PRIMARY KEY
4 , col2 NUMBER NOT NULL
5 , col3 NUMBER
6 , col4 VARCHAR2(10) NOT NULL
7 , CONSTRAINT uix_case2 UNIQUE (col2,col3) USING INDEX
8* )

表が作成されました。

1 BEGIN
2 FOR i IN 1..2000 LOOP
3 INSERT INTO case2 VALUES(i, i, NULL, LPAD(TO_CHAR(i),10,'*'));
4 IF MOD(i,100) = 0 THEN COMMIT; END IF;
5 END LOOP;
6* END;

PL/SQLプロシージャが正常に完了しました。

 

このテストケースでは、複合一意キーかつ、第二キーに null を許しています。
Oraleの索引は、null が含まれない!という都市伝説がありましたが、そんなことはないのは以前解説していた通りです。一部の列にnullを許可した一意キーでもDISTINCT Eliminationは発動することを確認するテストケースです)

この例では、第二キー列がnullableで、この状態では、第二キー列全てを null にしています。DISTINCT Eliminationの条件を満たすため、DISTINCT 捜査が排除されていることがわかります!


  1  SELECT
2 DISTINCT col2, col3
3 FROM
4* case2

COL2 COL3
---------- ----------
541 [null]
542 [null]
543 [null]

...略...

1998 [null]
1999 [null]
2000 [null]

2000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1081843087

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 5 (0)| 00:00:01 |
| 1 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
----------------------------------------------------------------------------------

 

OPT_PARAM('_optimizer_distinct_elimination','false')でDISTINCT Eliminationを無効化すれば、DISTINCT 操作は現れますが、INDEX FAST FULL SCANとなっているため HASH UNIQUE操作が行われていることがわかります。
実際には、ユニーク索引なので、不要ではあるのですが:)


  1  SELECT
2 /*+
3 OPT_PARAM('_optimizer_distinct_elimination','false')
4 */
5 DISTINCT col2, col3
6 FROM
7* case2

COL2 COL3
---------- ----------
555 [null]
585 [null]
586 [null]

...略...

1986 [null]
1989 [null]
1990 [null]

2000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 523836292

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 6 (17)| 00:00:01 |
| 1 | HASH UNIQUE | | 2000 | 52000 | 6 (17)| 00:00:01 |
| 2 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

 

では、ちょっと初歩的な確認で、1, nullというすでに存在する値をINSERTしてみましょう。当然エラーです。UIX_CASE2は一意キーなので。


  1* INSERT INTO case2 VALUES(2001,1,NULL,'test')
INSERT INTO case2 VALUES(2001,1,NULL,'test')
*
行1でエラーが発生しました。:
ORA-00001: 一意制約(SCOTT.UIX_CASE2)に反しています

 

第二キーがnullではない値を登録しておきます。


  1* INSERT INTO case2 VALUES(2002,1,1,'test')

1行が作成されました。

1* COMMIT

コミットが完了しました。

 

特にどうだということもないのですがw 正しく、DISTINCT EliminationされINDEX FAST FULL SCANだけの実行計画という名のレントゲンが現れています。ニッコリ(どちらかというと、索引に null 入らないという本当の意味を知らずにいる方もいるのではないだろうかという余計な心配をしただけのお節介なテストケース。というだけだったかもしれません)


  1  SELECT
2 DISTINCT col2, col3
3 FROM
4* case2

COL2 COL3
---------- ----------
541 [null]
542 [null]
543 [null]

...略...

1 1
1 [null]
2 [null]
3 [null]

...略...

538 [null]
539 [null]
540 [null]

2001行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1081843087

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 5 (0)| 00:00:01 |
| 1 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
----------------------------------------------------------------------------------

 

ということで、 Day 20はここまで。

 

残り5日。追い込みだーーーーっ
明日も、私が担当なので、よろしくお願いします。


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination

| | | コメント (0)

2022年12月19日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination

このポストは, JPOUG Advent Calendar 2022 Day 19帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022へクロスポストしています.

 

JPOUG Advent Calendar 2022 Day 18は, tomoさんのSPM使ってる?でした. 使ってますか?ヒント埋込派, パッケージなんでSPMっす!とか大人の事情で色々ありそうですね.

Previously on Mac De Oracle... Day 18は, Join Elimination にフォーカスをあてました. Join Eliminationも他のElimination系書き換えは, 実行計画上存在が消されてしまうので, 実際行われたかどうか気づきにくいケースが多いです. 以前も書きましたが, Explain plan等で, Elimination Informationとかリストしてくれると便利かもしれません.

ということで, 今日, Day 19は, そんなElimination InformationがExplain plan等で表示されたら嬉しいかもね, という気持ちが強くなるネタにしました.

 

では, Day 19のお題, Group by Eliminationです.
この機能, 目立たないですが, 変なところで目立ったので意外に有名かもしれません. 機能として登場したのは, 12cR2ごろでした. Jonathanも書いてるから間違いない!

変なところで目立ってしまった. そんなに目立たない存在だった, Group by Eliminationの確認
目立ってしまった理由は, 結果不正.  できれば7445の方が分かりやすくて良かったわけですが, 出会いたくないですよねー. 結果不正.
(なお, すでに修正されており, 想定する結果が返されるようになっているようです. 詳細は本ページ末の参考リンクのJonathanのエントリー参照のこと)

まず, 検証用のデータ準備から(ちょいと凝ったことやってますが, 単純に当年のカレンダーで, その日が営業日か休業日かをフラグで持たせるだけのでデータで, 2022年の一年分のデータを作りました. day18.sqlを実行していますが, こちらの都合w公開日を入れ替えた影響なので, 気にしないでくださいw

SCOTT@orclpdb1> @day18
1* DROP TABLE business_day_calendar

表が削除されました.

1 CREATE TABLE business_day_calendar AS
2 WITH
3 FUNCTION get_num_of_dates
4 RETURN NUMBER IS
5 l_dummy_date DATE;
6 --
7 eORA01839 exception;
8 pragma exception_init(eORA01839, -1839);
9 BEGIN
10 -- validate leap year
11 l_dummy_date := TO_DATE(TO_CHAR(SYSDATE, 'YYYY') || '0229', 'YYYYMMDD');
12 RETURN 366;
13 EXCEPTION
14 WHEN eORA01839 THEN
15 RETURN 365;
16 END;
17 SELECT
18 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1) AS business_date
19 , CASE
20 WHEN TO_CHAR(
21 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1)
22 , 'DY'
23 , 'NLS_DATE_LANGUAGE=AMERICAN'
24 ) IN ('SUN','WED')
25 THEN '1'
26 ELSE '0'
27 END AS is_holiday
28 FROM
29 dual
30 CONNECT BY
31* level <= get_num_of_dates

表が作成されました.

1 ALTER TABLE business_day_calendar
2 ADD CONSTRAINT pk_business_day_calendar PRIMARY KEY
3 (
4 business_date
5 )
6* USING INDEX

表が変更されました.

 

今回は, この Order BY Elimination をちょっと有名にしてしまった, 結果不正から. 12cR2で試してみます. CASE 2の結果が想定と異なっています.

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

CASE 1特に問題は起きていません. 想定通りの結果が帰り, Group By Eliminationの発動条件にはなっていないため, Group By操作が行われています.

  1  SELECT
2 EXTRACT(MONTH from business_date)
3 , COUNT(
4 CASE
5 WHEN TO_CHAR(
6 business_date
7 , 'DY'
8 , 'NLS_DATE_LANGUAGE=AMERICAN'
9 ) IN ('SUN','WED')
10 THEN 1
11 END
12 ) AS holidays
13 FROM
14 business_day_calendar
15 GROUP BY
16* EXTRACT(MONTH from business_date)

EXTRACT(MONTHFROMBUSINESS_DATE) HOLIDAYS
------------------------------- ----------
1 9
6 9
11 9
2 8
4 8
5 9
8 9
3 9
7 9
9 8
10 9
12 8

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2 おやおやおや, おかしなことになってます. 実行計画をみると, 動作対象ではないはずなのに, GROUP BY 操作が排除されているのが分かります! 結果不正です. HASH GROUP BYが排除されています! その影響で, GROUP BYが行われていません!!!!

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202201 1
202201 1
202201 1

...略...

202212 1
202212 1
202212 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2 の結果不正回避 では, 結果不正回避策を取ってみましょう. 何度か利用している, no_elim_groupbyヒントで回避できるか試します!
結果不正の理由は, 本来発動しない条件であるにもかかわらず発動してしまった Group By Eliminationにる影響であることが分かります.

これをみると, なおさら, Explain Plan等でどのような書き換えが動作したのかしないのか一覧できるような情報が欲しいなと思ったりするわけです. .

orcl@SCOTT> @day18-2
1 SELECT
2 /*+
3 no_elim_groupby
4 */
5 TO_CHAR(business_date, 'YYYYMM')
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202205 31
202209 30
202211 30
202212 31
202202 28
202208 31
202207 31
202210 31
202201 31
202204 30
202203 31
202206 30

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 3 こちらは正しく GROUP BY Eliminationが発動していますね. GROUP BYしなくても問題ないクエリですから!

  1  SELECT
2 business_date
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* business_date

BUSINESS_ COUNT(1)
--------- ----------
01-JAN-22 1
02-JAN-22 1
03-JAN-22 1

...略...

29-DEC-22 1
30-DEC-22 1
31-DEC-22 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 4 GROUP BY Eliminationを無効化しても他のバージョンと結果同じで, 想定した結果通りです. CASE 3と結果は同じなのは当たり前.

  1  SELECT
2 /*+
3 no_elim_groupby
4 */
5 business_date
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* business_date

BUSINESS_ COUNT(1)
--------- ----------
01-JAN-22 1
02-JAN-22 1
03-JAN-22 1

...略...

29-DEC-22 1
30-DEC-22 1
31-DEC-22 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 3672056694

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 365 | 2920 | 1 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

 

では, 私が確認したテストケースの範囲で結果不正が起きていない, 21cは全テストケース, それ以外のバージョンでは, 結果不正の発生していた CASE 2 の実行計画という名のレントゲンを並べておきたいと思います. 頭に浮かんだイメージは, レントゲン写真を光るボードに沢山貼り付けてる感じw

Oracle Database 21cのCASE 1 - CASE 4まで全て

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

CASE 1:Group By Eliminationは発生しないケース

  1  SELECT
2 EXTRACT(MONTH from business_date)
3 , COUNT(
4 CASE
5 WHEN TO_CHAR(
6 business_date
7 , 'DY'
8 , 'NLS_DATE_LANGUAGE=AMERICAN'
9 ) IN ('SUN','WED')
10 THEN 1
11 END
12 ) AS holidays
13 FROM
14 business_day_calendar
15 GROUP BY
16* EXTRACT(MONTH from business_date)

EXTRACT(MONTHFROMBUSINESS_DATE) HOLIDAYS
------------------------------- ----------
1 9
2 8
3 9
4 8
5 9
6 9
7 9
8 9
9 8
10 9
11 9
12 8

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2:Group By Eliminationは発生しないケース(12cR2でGroup By Eliminationの誤作動で結果不正となったテストケース)

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHAR(BUSINESS_D COUNT(1)
------------------ ----------
202201 31
202202 28
202203 31
202204 30
202205 31
202206 30
202207 31
202208 31
202209 30
202210 31
202211 30
202212 31

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 3:Group By Eliminationが発生するケース

  1  SELECT
2 business_date
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* business_date

BUSINESS_DATE COUNT(1)
--------------- ----------
20220101 000000 1
20220102 000000 1
20220103 000000 1

...略...

20221228 000000 1
20221229 000000 1
20221230 000000 1
20221231 000000 1

365行が選択されました.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 4:Group By Eliminationが発生するケースで, no_elim_groupby ヒントでGroup By Eliminationを抑止したケース

  1  SELECT
2 /*+
3 no_elim_groupby
4 */
5 business_date
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* business_date

BUSINESS_DATE COUNT(1)
--------------- ----------
20220101 000000 1
20220102 000000 1
20220103 000000 1
20220104 000000 1

...略...

20221228 000000 1
20221229 000000 1
20221230 000000 1
20221231 000000 1

365行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 3672056694

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 365 | 2920 | 1 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

 

Oracle Database 19cです. CASE 2だけ確認します.

SCOTT@orcl> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

CASE 2:このリリースでも問題は無さそうですね

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202206 30
202203 31
202210 31
202212 31
202202 28
202211 30
202204 30
202208 31
202209 30
202201 31
202205 31
202207 31

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

Oracle Database 18c です. 同様に, CASE 2のみ確認しています.

SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production

CASE 2:このリリースでも問題は無さそう

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202206 30
202203 31
202210 31
202212 31
202202 28
202211 30
202204 30
202208 31
202209 30
202201 31
202205 31
202207 31

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

ちょっと思ったのですが, 現状, 10053トレース等をみるしかないタイプの書き換えでも, OTHER_XML列からOUTLINEを抜き出すと比較的簡単に発動有無がわかるものもあるかもしれないですね. 今回のように固有のヒントでON/OFFできるタイプだとOUTLINEには, elim_groupby のようなヒントが含まれているだろうし. (全てではないとは思いますが)

ふ〜〜〜っ. JPOUG Advent Calendar 2022 の自分のターンも終わって, 自分の分をなんとかするだけだーーーーー.

 

ということで,
帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022は, 明日も私が担当です. よろしくお願いします.
そして, JPOUG Advent Calendar 2022 Day 20は, 凌直孝さんの担当です. よろしくお願いします!

 

では, また:)

参考)

Group by Elimination / Oracle Scratchpad
A Look at the Oracle Group-by Bug / Database Journal

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination

 

| | | コメント (0)

2022年12月18日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination

Previously on Mac De Oracle...
Day 17は, order by Elimination にフォーカスをあてました. チューニングの現場で気づいたのが最初だったと思いますが, SQL文を見ていて, これ無駄なソートだなぁなんて思いながら実行計画という名のレントゲンをみていたら, おお! NOSORTとかではなく, ORDER BY自体が消されてる! 賢い! と.

では, Day 18のお題, Join Eliminationです. (登場したのは10gR2 ぐらいのはずですが, 間違ってたらツッコミ歓迎)
この排除系書き換えも, 実行計画という名のレントゲンシリーズでは, まだ紹介していなかったので, 今回は, ヒントで無効化する例も含め, 軽めの内容でw, 診ていきたいと思います. (参照整合性制約アレルギーネタを思い出すw)


いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

対象となる表には外部参照整合制約があることが前提です.
20190321-144842

customers表を結合していますが, 参照整合性制約でcustomers表に存在する顧客しか注文できないという制約があります. つまり, この参照整合性制約があるため, INNER JOINやEXISTSを利用して存在チェックする必要はないということを意味しています.
Join EliminationによりSQLが書き換えられ結合が排除されていることがわかります. 実行計画には, ORDER_PKをIndex Only Scanしているだけで, customers表やcustomersの索引を結合しているOperationは含まれていない!!!
結合しないので, 結合のコスト及び, customers表やcustomersの索引へのアクセスコストが削減されています. 実行統計からは, Pysical Readや, Buffer Getsの低下という形で現れてきます.

とはいえ, 参照整合性制約アレルギーのお持ちの方も多く, 一生目にすることのない方々も, 残念ながら多いのも事実です.
参照整合性制約アレルギーが発症してしまうと, 一生付き合っていくことになちゃいますからね(大抵の場合)


Day19の内容とDay18のネタを入れ替えたので, day19.sqlを実行しているところは気にしないでくださいw

OE@orclpdb1> @day19
1 SELECT
2 order_id
3 FROM
4 orders o
5 INNER JOIN customers c
6 ON
7 o.customer_id = c.customer_id
8 WHERE
9* order_id < 2400

ORDER_ID
----------
2354
2355
2356
2357
2358

...略...

2396
2397
2398
2399

実行計画
----------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

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

1 - access("O"."ORDER_ID"<2400)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
0 physical reads
0 redo size
1482 bytes sent via SQL*Net to client
85 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
46 rows processed
/code>


NO_ELIMINATE_JOINヒントを利用し, Join Eliminationを抑止してみましょう.
NO_ELIMINATE_JOINは, 使うことがあるのか? と思う方もいるかもしれませんが, 例えば, 不具合で7445とか, その他結果不正などに当たった時, かつ, 局所的対応で回避できそうなケースでは, 該当SQLにヒントを埋め込み, Join Eliminationの抑止で回避したりします.
どこで起きるかわからんので, インスタンスレベルで止めるケースもなくなないですが, そういう場合は, 隠しパラメータで無効化するのが一般的です. (ほぼ使わないと思いますが, ELIMINATE_JOIN でJoin Eliminationを利用できます. インスタンスレベルで無効化している状態で, 特定のSQLだけはJoin Eliminationしたいという場合に使うぐらいですね. 滅多にないと思いますが)

  1  SELECT
2 /*+
3 NO_ELIMINATE_JOIN(c)
4 */
5 order_id
6 FROM
7 orders o
8 INNER JOIN customers c
9 ON
10 o.customer_id = c.customer_id
11 WHERE
12* order_id < 2400

ORDER_ID
----------
2354
2355
2356
2357
2358

...略...

2392
2393
2394
2395
2396
2397
2398
2399

実行計画
----------------------------------------------------------
Plan hash value: 875022219

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 552 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 46 | 552 | 2 (0)| 00:00:01 |
|* 2 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 46 | 368 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ORDER_PK | 46 | | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | CUSTOMERS_PK | 1 | 4 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

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

2 - filter("O"."CUSTOMER_ID">0)
3 - access("O"."ORDER_ID"<2400)
4 - access("O"."CUSTOMER_ID"="C"."CUSTOMER_ID")

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
1482 bytes sent via SQL*Net to client
85 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
46 rows processed
/code>

アドベントカレンダー終わったら, もう, 今年も残りわずか....

ということで, 出口が見えつつある, アドベントカレンダー全部俺, 明日も, 俺が書きますw
では, また.


参考)
Join Elimination(結合の排除)と 参照整合性制約 / FAQ
db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ, SQLチューニングの「参照整合性制約アレルギー」を参照のこと



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination

| | | コメント (0)

2022年12月17日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination

Previously on Mac De Oracle... Day 16は, Concurrent Execution of Union All and Union にフォーカスをあてました. 使う機会があったら試してみてくださいね. 私は, この機能が登場した頃, Spatialな機能でRDFを使った際, Exadataでバリバリ活用してたというか, されていたという記憶があります.

では, Day 17のお題, Order by Eliminationです. 文字通り, Order byが排除される, 書き換え機能の一種です. だからと言って, 無駄なOrder by句書いても大丈夫ってことではないですから, 注意しましょうw

 

10gR2ぐらいから実装されている最適化の一つですね. 多少無駄なOrder byを書いちゃっても無駄だと判断されれば, 行われません. (だからと言って, 気にしないでOrder By書いちゃってもいいという話ではありませんがw)

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

emp表の索引などはいかの通り(scottのemp表を知らない方向け. いるのか?)

SCOTT@orclpdb1> desc emp
名前 NULL? 型
----------------------------------------- -------- ----------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

SCOTT@orclpdb1> select index_name,column_name from user_ind_columns where
2 table_name = 'EMP' order by index_name,column_position;

INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------
IX01_EMP DEPTNO
PK_EMP EMPNO

 

SQL中に ORDER BY句がありますが, NOSORT操作もないのにソートされていません. そもそも書き換えられて, ORDER BY句が削除されているんですよーーーー.
このケースでは, empno順にソートされた結果返ってきているかのう様に見えますが, それは索引をempno順に読無ことで, ソートを排除できるからなんですよね. 索引がなければ, ソートは必要なのです. ちなみに, INDEX FULL SCAN はソートされたキー順に索引を全てスキャンするところがポイント, INDEX FAST FULL SCANはとは違うのはみなさんご存知のはずなのでコマけーことは省略.

SCOTT@orclpdb1> @day17
1 SELECT
2 *
3 FROM
4 emp
5 ORDER BY
6* empno

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 4170700152

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 468 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 12 | 468 | 2 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_EMP | 12 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
1669 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
12 rows processed

 

こちらも, 無用なORDER BY句が削除されています. 賢い. オプティマイザー!
以下のORDER BYは, みるからに無駄なソートですが, オプティマイザーは気づいて排除しています.

  1  SELECT
2 COUNT(ename)
3 FROM
4 (
5 SELECT
6 *
7 FROM
8 emp
9 ORDER BY
10 ename
11* )

COUNT(ENAME)
------------
12

実行計画
----------------------------------------------------------
Plan hash value: 2083865914

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
| 2 | TABLE ACCESS FULL| EMP | 12 | 72 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
595 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

EXPLAIN等改善してほしいなと思うのはこれらのElimination関連情報, 今回のように無駄なORDER BYを排除したという情報を, Elimination Informationとしてリストしたら良いのではないかと思っています. 最近, SQLヒントに関する情報をリストするようになったのでできるのではないだろうかと. ただ, 諸々内部でSQL文を書き換えたりするから, 実は面倒なのかもねw  10053トレースなら見える訳ですが, 毎回取得するわけにもいかないしね.

ついに,  Day 17 done. あと少しだ頑張れ, 俺w

 

ということで, 残念ですが, 明日も私です.
ではまた.

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union

 

| | | コメント (0)

2022年12月16日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union

Previously on Mac De Oracle... Day 15は, 分散, リモートクエリーにフォーカスをあてました. 好き嫌い多いネタだったかもしれませんねぇw

では, Day 16のお題, Concurrent Execution of Union All and Union

これは何かというと, 12c R1以降だったか?(定かでないw)に, UNION/UNION ALLの各分岐がパラレルで実行されるという機能です. それまではシリアルに処理されていたので, 分岐が多いほど処理時間も増加していた訳ですが, 分岐がそれぞれパラレルで実行される分処理時間は短くなるというやつですね.

Concurrent Execution of Union All and Unionが実行されている時の特徴は, PX SELECTOR があるかどうかで見分けます.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

準備として, 12c R1以前ではシリアルに処理される代表格, リモート表を用意します. (昨日のネタはこの準備のためでもあったり)



SCOTT@orclpdb2> @day16
1* CREATE DATABASE LINK remote_scott CONNECT TO scott IDENTIFIED BY tiger USING 'orclpdb1'

データベース・リンクが作成されました.

1 CREATE TABLE local_emp
2 AS
3 SELECT *
4 FROM
5 emp@remote_scott
6 ORDER BY
7 empno
8* FETCH FIRST 5 ROWS ONLY

表が作成されました.

1* INSERT INTO local_emp(empno, ename) VALUES(1, 'NULL')

1行が作成されました.

 

クエリーの結果、たまたま、empno順に並んでいるように見える結果もありますが、ソートしたい場合は、必ず!!! ORDER BY句が必要ですからねw(誰となくw)

シリアルで実行してみます. よく見る実行計画という名のレントゲンです. リモート表は Operationが REMOTE として現れています. 2つある分岐は, シリアルに実行されます.

  1  SELECT
2 *
3 FROM
4 local_emp
5 UNION ALL
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 1607333107

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | |
| 1 | UNION-ALL | | | | | | | |
| 2 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | | |
| 3 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

3 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

次に, パラレルクエリーにして, かつ, Concurrent Execution を抑止する NO_PQ_CONCURRENT_UNION  を付加して実行してみました.
PX SELECTOR は現れていません. パラレルクエリーではありますが, 分岐は, 順に処理されていきます.

  1  SELECT
2 /*+
3 PARALLEL(3)
4 NO_PQ_CONCURRENT_UNION
5 */
6 *
7 FROM
8 local_emp
9 UNION ALL
10 SELECT
11 *
12 FROM
13* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 3305250800

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | | |
| 1 | UNION-ALL | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM)| :TQ10000 | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 4 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 5 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 6 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
----------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

6 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

本日の主役 PX SELECTORREMOTE 操作の上に出てきました. この状態であれば, 各分岐はパラレルに処理されることになり, 分岐が順に処理されるより処理時間は短縮されることになります(REMOTE処理が重かったら, その処理時間に引きづら訳ですが, 順に処理するよりは早く終わると予想できますよね)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 UNION ALL
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 2359762278

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | | | | | Q1,00 | P->S | QC (RAND) |
| 3 | UNION-ALL | | | | | | Q1,00 | PCWP | |
| 4 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 5 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 6 | PX SELECTOR | | | | | | Q1,00 | PCWP | |
| 7 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | Q1,00 | PCWP | |
----------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

7 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing ':Q1000' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

以下, UNIONでも同様に, 実行計画というレントゲンを確認しておきましょう. まずはシリアル.


  1  SELECT
2 *
3 FROM
4 local_emp
5 UNION
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 2189749816

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | |
| 1 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | | |
| 2 | UNION-ALL | | | | | | | |
| 3 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | | |
| 4 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
-------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

4 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

NO_PQ_CONCURRENT_UNIONでConcurrent Execution を抑止してみると, 順に処理されていることがわかります.

  1  SELECT
2 /*+
3 PARALLEL(3)
4 NO_PQ_CONCURRENT_UNION 5 */
6 *
7 FROM
8 local_emp
9 UNION
10 SELECT
11 *
12 FROM
13* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7902 FORD ANALYST 7566 81-12-03 3000 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
1 NULL
7900 JAMES CLERK 7698 81-12-03 950 30
7934 MILLER CLERK 7782 82-01-23 1300 10
7839 KING PRESIDENT 81-11-17 5000 10
7782 CLARK MANAGER 7839 81-06-09 2450 10
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30

実行計画
----------------------------------------------------------
Plan hash value: 622056383

-----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,01 | PCWP | |
| 8 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 9 | TABLE ACCESS FULL | LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 10 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 11 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | PX SEND ROUND-ROBIN| :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | RND-ROBIN |
| 13 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-----------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

13 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP" "EMP"
(accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

UNIONでもPX SELECTORが現れ, 分岐がパラレルに処理されていることが見えます. :)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 UNION
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
1 NULL
7900 JAMES CLERK 7698 81-12-03 950 30
7934 MILLER CLERK 7782 82-01-23 1300 10
7839 KING PRESIDENT 81-11-17 5000 10
7782 CLARK MANAGER 7839 81-06-09 2450 10

実行計画
----------------------------------------------------------
Plan hash value: 3647789528

--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 4 | PX RECEIVE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 5 | PX SEND HASH | :TQ10000 | 18 | 990 | 7 (29)| 00:00:01 | Q1,00 | P->P | HASH |
| 6 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,00 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,00 | PCWP | |
| 8 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 9 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 10 | PX SELECTOR | | | | | | Q1,00 | PCWP | |
| 11 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | Q1,00 | PCWP | |
--------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

11 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing ':Q1000' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

ちなみに, Concurrent Execution of Union All and Union と書いている通り, MINUS/EXCEPT/INTERSECTでは今まで通りです.  EXCEPTとINTERSECTの実行計画で見てみます. PX SELECTOR は現れていません. (MINUSとEXCEPTはどちらもMINUSとして扱われているので片方だけで十分です)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 EXCEPT
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
1 NULL

実行計画
----------------------------------------------------------
Plan hash value: 331225933

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | | | | | Q1,02 | P->S | QC (RAND) |
| 3 | MINUS HASH | | | | | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | | | | | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | | | | | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 6 | 522 | 3 (34)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 8 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 9 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 10 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 11 | PX SEND HASH | :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | HASH |
| 12 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

12 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 INTERSECT
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30

実行計画
----------------------------------------------------------
Plan hash value: 1417135745

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | | | | | Q1,02 | P->S | QC (RAND) |
| 3 | INTERSECTION HASH | | | | | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | | | | | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | | | | | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 6 | 522 | 3 (34)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 8 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 9 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 10 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 11 | PX SEND HASH | :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | HASH |
| 12 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

12 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

ということで, Day 16 done

残り9日だ. ふ〜〜〜〜っ. 明日も絶対, 担当は私です.

ではまた.

 

参考) Oracle Database 21c - VLDB and Partitioning Guide / 8.5.3.14 Concurrent Execution of Union All


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE

 

| | | コメント (0)

2022年12月15日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE

Previously on Mac De Oracle...
Day 14は, the DUAL Table にフォーカスをあてました. 23cでは省略できるようになる. やっと, と言う感じもしなくないですが. やっとと言う言葉しかないわけです. はいw

帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022も遂に, 残り10回. 頑張ってネタ考えようw
それでは, Day 15 の窓を開けましょう!.

REMOTE と聞いて, ピンと来た方w 多いと思いますが, そうです, 変なおじ, いや, 変なSQL, 違うw,  リモートクエリーか, 分散クエリーのどちらかで現れるOperationです.  リモートクエリーと分散クエリーの違いは, この辺り, とかググっていただくとして.

リモートクエリーとか分散クエリー嫌いな方も多いとは思いますがw とにかく簡単な例で診てみましょう.


いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

検証を簡単に行うため, 2つのPDBを用意し, 一方のPDBのSCOTTへDatabase Linkを作成しています.

SCOTT@orclpdb2> @day15
1* CREATE DATABASE LINK remote_scott CONNECT TO scott IDENTIFIED BY tiger USING 'orclpdb1'

データベース・リンクが作成されました.

経過: 00:00:00.01
1 CREATE TABLE local_emp
2 AS
3 SELECT *
4 FROM
5 emp@remote_scott
6 ORDER BY
7 empno
8* FETCH FIRST 5 ROWS ONLY

表が作成されました.

経過: 00:00:00.09


今回の例は分散クエリーです(複数のデータベースを問い合わせてるので). Database Linkを介してアクセスされる表へのOperationは, REMOTE となり, REMOTEデータベースへSQL文の実行がリクエストされることになります.
リクエストされるSQLは, 最近では, AUTO TRACEのRemote SQL Informationセクションでも確認できるようになっています(便利ですよね). 該当SQLをリモートデータベースで実行して実行計画を確認することができます.

  1  SELECT
2 *
3 FROM
4 local_emp
5 INTERSECT
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30

実行計画
----------------------------------------------------------
Plan hash value: 63876577

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 903 | 7 (29)| 00:00:01 | | |
| 1 | INTERSECTION HASH | | | | | | | |
| 2 | TABLE ACCESS FULL| LOCAL_EMP | 5 | 435 | 2 (0)| 00:00:01 | | |
| 3 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

3 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1352 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

9年ほど前, クエリー分裂症の治療の様子を見たい方はこちらのスライド見てね. :)


ということで, 明日も私です:)




Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table

| | | コメント (0)

2022年12月14日 (水)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table

Previously on Mac De Oracle... Day 13は, MULTI-TABLE INESRT にフォーカスをあてました. 癖は多いですけども, 便利ですよねーと思っている文の一つでした.

 

それでは, Day 14 の窓を開けましょう!.

Oracle Databaseでは有名すぎる, Selecting from the DUAL Tableですが, 遂に, 21c23cで省略できるようになる!!らしい. まだ, 23cは触ったことはないですが, リリースされたら試してみたいですよね. というか, 多分, みんな, 間違いなく, ブログに書くネタだろうと思いますw

今日は, そんな消えゆく, dual 表, ありがとう. という意味も込めて, 魚拓として, 実行計画という名のレントゲンを残しておこうと思います. (23cリリースされた時の引用にもなるのでw)

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

dual表って実在する表なんですよねw.
昔, 誰かが, dual表へ行をINSERTして, dual表が複数行返すようにしたらどうなるか? なんていう, おイタをしていたような...誰だろうなぁ, あれやってたのw 



SCOTT@orclpdb1> @day14
1* SELECT * FROM dual

DUM
---
X

経過: 00:00:00.01

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
587 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

Oracle Database 10g R1以降では, DUMMY列にアクセスしない場合には, dual表へのconsistent getsの発生が抑止されるようになりました. その場合 FAST DUAL としてOperationに現れます


  1* SELECT null FROM dual

N
-

経過: 00:00:00.00

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1388734953

-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
584 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

SELECT * FROM dual の動きで面白いところの一つは, 前回のエントリーで, Multi Table Insertのような構文では, SELECT * FROM dual を使っていますが, SELECT * FROM dual 単体で使う時とは異なり, 実行計画中では, しっかり, コストの低い FAST DUAL が現れている点です. 21cまでは, 文法上必須なわけですが, SELECT * FROM dual と書いた場合でも, ただのお約束上の話なので, よしなに FAST DUAL してくれていると言うことになります. ただ、23cになったら記述不要になるようなので, SQL文には書かなくなりそうな気がします(下位互換で残るのはあるでしょうけども).

と, 言うことで, 本日はここまで.

 

はぁ. アドベントカレンダー全部俺も折り返しましたが, まだ, 先は長いw
バテ気味な感じが, しつつも, 明日も私が担当します!
では, また.

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT

 

| | | コメント (0)

2022年12月13日 (火)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT

Previously on Mac De Oracle... Day 12は, TEMP TABLE TRANSFORMATIONにフォーカスをあてました. WITH句以外でも内部的に行われますが, 一時表へのマテリアライズを意図した話ではありますが, クソでかSQLの可読性向上のために利用されることも少なくなく, そのようなケースではインラインビューとして展開されるとうお話しでした. オプティマイザが間違えなければ:)

それでは, Day 13 の窓を開けましょう!.

今日は, MULTI-TABLE INSERT です. え!, INSERT文なのに? みたいに思った方もいるかもしれませんが, MULTI-TABLE INSERTは特徴的なOperationgが現れるので, 知っていた方が良いですよ. ただ, この構文自体はOracleの方言なので, なかなかお目にかからない(マルチテーブルインサート自体は他のRDBMSにもありますが, 癖の多い部分の一つなので)のですがねw 良い機能だと思いますけどね!
では, 診ていこうと思います.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

Oracle Database 23cからdualを省略できるようになるらしいですが! 21cまでは, MULTI TABLE INSERTを利用する場合, SELECT * FROM dual が必須です. 不要になるとかなり便利ですよね.
以下では, 文字通り複数表へINSERTしていますが, a href="https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2020/12/post-10aa61.html">同一表へ複数行INSERTすることもできます. 何気に便利でしょこれ.


SCOTT@orclpdb1> @day13
1 INSERT ALL
2 INTO emp(empno, ename) VALUES(7788, 'Lucky')
3 INTO dept(deptno, dname) VALUES(88, 'QA')
4* SELECT * FROM dual

2行が作成されました.

 

さて, 実行計画は, どうでしょうか. MULTI-TABLE INSERTに加えて, INTOが複数現れます. 現状, FAST DUALも出ますけど:)


実行計画
----------------------------------------------------------
Plan hash value: 2939908344

--------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
--------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | MULTI-TABLE INSERT | | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
| 3 | INTO | EMP | | | |
| 4 | INTO | DEPT | | | |
--------------------------------------------------------------------

統計
----------------------------------------------------------
6 recursive calls
11 db block gets
10 consistent gets
4 physical reads
1288 redo size
188 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
2 rows processed

この手の方言って, SQL標準で使用禁止みたいに書かれてたりすることが多くて, なんで?という気はします. 方言でも性能面で有利に働くのであれば例外は設けておくべきだと, 個人的は思うんですよね.
昔. 階層問合せ使わずに(典型的なSQLアンチパターンで)ヒーヒー言ってた現場を思い出したり. かなり昔ですけどね ー> その現場へ有用性説明し階層問い合わせに書き換えたことで, 皆さん幸せな感じになってましたよーと:)

 

調子が出てきたので, また明日も, 担当は, 私です!

ではでは.

 


Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION

| | | コメント (0)

2022年12月12日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION

Previously on Mac De Oracle... Day 11は, GROUPING SETS, ROLLUP, CUBEでした. ROLLUP, CUBEが登場したのは, Oracle Database 8i なので, 1999年あたりだったと思います. それまでUNIONを使ったクソ重, クソクエリーが多かった記憶はありますw ROLLUP, CUBEが神様に見えましたものw 

それでは, Day 12 の窓を開けましょう!.

今日の実行計画という名のレントゲンは, TEMP TABLE TRANSFORMATION です. 内部的に変換されて行われて現れることもあるこのOperationですが, 意図的にTEMP TABLE TRANSFORMATIONに行う場合で有名なのはCTEではないでしょうか?
類似する話題LOAD AS SELECT (CURSOR DURATION MEMORY)を取り上げていました, 内容としては11g,12c,18c,19cの差異の話。今回はその元となるTEMP TABLE TRANSFORMATIONにフォーカスをあてます。

では診ていこうと思います.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

ちなみに, WITH句でCTEを使ったら必ず, TEMP TABLE TRANSFORMATION でマテリアライズされるということでもなく, マテリアライズするよ理, インラインビューのままでいいよねとオプティマイザが判断してインラインビューとして展開する場合もあります. それを強制する INLINE というヒントもあります.
WITH句が利用されるケースの1つとして, 可読性向上だけを目的としている場合WITH句で定義されたクエリーが一度しか参照されていないようなケースでは, マテリアライズして一時表作るよりインラインビューで十分という判断でそのような最適化が行われます:)
逆に, マテリアライズして, 一時表に変換してほいしケースもあります. (複数回参照しているのに....みたいな場合ですね. 場合は, MATERIALIZE ヒントでオプティマイザヒントに教えてあげましょう. 最近ミスらなくなった気がしないでもない. オプティマイザも進化してますからね)

 

以下, オプティマイザはCTEと認識して, 一時表へマテリアライズした上で, 一時表を再利用しています.


SCOTT@orclpdb1> @day12
1 WITH temp_emp AS
2 (
3 SELECT
4 emp.*
5 ,dept.dname
6 FROM
7 emp
8 INNER JOIN dept
9 ON
10 emp.deptno = dept.deptno
11 )
12 SELECT
13 *
14 FROM
15 temp_emp
16 WHERE
17 temp_emp.empno = 7369
18 UNION
19 SELECT
20 *
21 FROM
22 temp_emp
23 WHERE
24* temp_emp.deptno = 20

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DNAME
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ---------- ------------------------------------------
7369 SMITH CLERK 7902 80-12-17 800 20 RESEARCH
7902 FORD ANALYST 7566 81-12-03 3000 20 RESEARCH
7566 JONES MANAGER 7839 81-04-02 2975 20 RESEARCH

実行計画
----------------------------------------------------------
Plan hash value: 1235767800

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 768 | 6 (34)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D665A_DDA815 | | | | |
| 3 | MERGE JOIN | | 4 | 208 | 6 (17)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 4 | 156 | 4 (25)| 00:00:01 |
|* 7 | TABLE ACCESS FULL | EMP | 4 | 156 | 3 (0)| 00:00:01 |
| 8 | HASH UNIQUE | | 8 | 768 | 6 (34)| 00:00:01 |
| 9 | UNION-ALL | | | | | |
|* 10 | VIEW | | 4 | 384 | 2 (0)| 00:00:01 |
| 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D665A_DDA815 | 4 | 196 | 2 (0)| 00:00:01 |
|* 12 | VIEW | | 4 | 384 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D665A_DDA815 | 4 | 196 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

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

6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
filter("EMP"."DEPTNO"="DEPT"."DEPTNO")
7 - filter("EMP"."DEPTNO"=20 OR "EMP"."EMPNO"=7369)
10 - filter("TEMP_EMP"."EMPNO"=7369)
12 - filter("TEMP_EMP"."DEPTNO"=20)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1362 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
3 rows processed

 

CTEの一時表へのマテリアライズを INLINE ヒントでインラインに展開するようにした例です. 一時表へのマテリアライズが抑止され, SQL本文に同一クエリーが展開されている様子が見えます.
WITH句がサポートされていなかった頃は, 事前に一時表を作成して使ったり苦労していたことを思うと, これも非常に便利な機能ですよね.


  1  WITH temp_emp AS
2 (
3 SELECT
4 /*+
5 inline
6 */
7 emp.*
8 ,dept.dname
9 FROM
10 emp
11 INNER JOIN dept
12 ON
13 emp.deptno = dept.deptno
14 )
15 SELECT
16 *
17 FROM
18 temp_emp
19 WHERE
20 temp_emp.empno = 7369
21 UNION
22 SELECT
23 *
24 FROM
25 temp_emp
26 WHERE
27* temp_emp.deptno = 20

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DNAME
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ---------- ------------------------------------------
7369 SMITH CLERK 7902 80-12-17 800 20 RESEARCH
7566 JONES MANAGER 7839 81-04-02 2975 20 RESEARCH
7902 FORD ANALYST 7566 81-12-03 3000 20 RESEARCH

実行計画
----------------------------------------------------------
Plan hash value: 119758422

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 208 | 5 (20)| 00:00:01 |
| 1 | HASH UNIQUE | | 4 | 208 | 5 (20)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | NESTED LOOPS | | 1 | 52 | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | EMP | 1 | 39 | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 3 | 156 | 2 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 3 | 117 | 1 (0)| 00:00:01 |
|* 12 | INDEX RANGE SCAN | IX01_EMP | 3 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

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

5 - access("EMP"."EMPNO"=7369)
7 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
10 - access("DEPT"."DEPTNO"=20)
12 - access("EMP"."DEPTNO"=20)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1362 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed

 

サッカーの判定に利用されているVAR(Video Assistant Referee)ですが, 人の判断だと微妙なところとは人それぞれのブレが現れるので, テニスでもそうですがわかりやすいので良いんじゃないかと思いますが. ね. (ここ数日そういうブレによる判定へ意義申し立てしたりしてるw しかも見落としだし原因は. )

 

でちょいと, めんどくせーなーとなっていたりしているわけですけども.

それはさておき.

 

明日も, アドベントの担当は私ですw

ではまた.

 

参考)


SCOTT@orclpdb1> select name,inverse, sql_feature from v$sql_hint where name in ('INLINE');

NAME INVERSE SQL_FEATURE
-------------------- -------------------- ------------------------------
INLINE MATERIALIZE QKSFM_TRANSFORMATION

 

 


Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE

 

| | | コメント (0)

2022年12月11日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE

Previously on Mac De Oracle... Day 10は, MAT_VIEW REWRITE ACCESS FULLでした. Materialized Viewアクセスへ内部でRewriteする機能は随分前からありますが, Oracle Databaseらしい機能の一つだなぁ. と思います. 6.7 Using Real-time Materialized Viewsのように既存機能のブラッシュアップなど継続的に細かい改善が行われていたりします.

それでは, Day 11 の窓を開けましょう!.

まだ紹介していない既存の実行計画多いんですよね. SQLもそうですが, 実行計画になるとますます奥が深いですといいますか, このシリーズ, ネタは沢山あるので当分持ちそうですw

まず, 2011年の面倒くさい大人の事情縛りシリーズのネタですが, 考え方は今でも同じなので, 一度, 軽く眺めておいてください. その間に準備しますのでw.. (違
なお, SQLヒントの書き方で, 最近, ヒントが利用されたかどうかレポートされるようになったことで, USE_HASH, USE_NLのヒントの書き方が云々とかいう話をたまに聞きますが, エラーではなくて無視されるというのが仕様なので, それをうまく利用した使い方を, 面倒くさい大人の事情だらけの現場で考えて今に至った結果であるということは, いずれどこかで説明したいとは思います. (ポイントはチューニングする際の手間削減だったり, どこまで固定するべきか, しないほうが良いかという判断だったりします. 経年で変化もしますからね. ただ, それが手癖になっているという点は否めないわけですけども)
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #2
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #3
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #4
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #5
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #7 おまけ
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング8 おまけのおまけ
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング番外編

 

昔の2011年ぐらいの頃の Oracle Database の実行計画を見たことない年齢のエンジニア多くなってきているとは思いますが, どうでしたか? 上記の過去エントリー. 今でも考え方の基本のキです.

ということで, やっと, 今日の本題です.

データはなくても想定した実行計画のキーワードは取得できるので, 表(データなし)と索引だけ作っておきます. (データ登録するのが面倒だったということでもありますが, 影響はないのでw)
なお, 以下表は, ・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1で利用したものと同一です.

SCOTT@orclpdb1> @day11
1* drop table test1

表が削除されました.

経過: 00:00:00.04
1 create table test1
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test1 primary key (starting_date,item_code,shop_code) using index nologging
8 )
9* nologging

表が作成されました.

経過: 00:00:00.02
1* drop table test2

表が削除されました.

経過: 00:00:00.05
1 create table test2
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test2 primary key (starting_date,item_code,shop_code) using index global nologging
8 )
9 partition by hash(starting_date)
10 (
11 partition test201,
12 partition test202,
13 partition test203,
14 partition test204
15 )
16* nologging

表が作成されました.

経過: 00:00:00.02
1* drop table test3

表が削除されました.

経過: 00:00:00.07
1 create table test3
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test3 primary key (starting_date,item_code,shop_code) using index local
8 (
9 partition test301idx,
10 partition test302idx,
11 partition test303idx,
12 partition test304idx,
13 partition test305idx,
14 partition test306idx,
15 partition test307idx,
16 partition test308idx,
17 partition test309idx,
18 partition test310idx,
19 partition test311idx,
20 partition test312idx,
21 partition testmaxidx
22 )
23 nologging
24 )
25 partition by range(starting_date) (
26 partition test301 values less than ('20110201') ,
27 partition test302 values less than ('20110301') ,
28 partition test303 values less than ('20110401') ,
29 partition test304 values less than ('20110501') ,
30 partition test305 values less than ('20110601') ,
31 partition test306 values less than ('20110701') ,
32 partition test307 values less than ('20110801') ,
33 partition test308 values less than ('20110901') ,
34 partition test309 values less than ('20111001') ,
35 partition test310 values less than ('20111101') ,
36 partition test311 values less than ('20111201') ,
37 partition test312 values less than ('20120101') ,
38 partition testmax values less than (maxvalue)
39 )
40* nologging

表が作成されました.

経過: 00:00:00.05

GROUPING SETSから.
GROUPING SETSというキーワードは実行計画には現れまん. 内部で後半で説明するROLLUPが含まれる一時表が生成されるように書き換えられる挙動が特徴です. 2011の頃の実行計画と大きく違うのは, CURSOR DURATION MEMORYなんて操作が行われてるあたりですね.
GROUPING SETSというキーワードはないですが, 内部的には, CTEが利用されてROLLUP等と併用されている挙動なので, CTEによる一時表の生成コストがポイントになります. 最近はSSDがほとんどだろうと思うので, HDDだった頃ほど, 一時表の生成コストは気にならないケースも多いかもしれないですけども, 状況次第, どこまでチューニングするか次第というところはありますよね.

  1  SELECT
2 CASE
3 WHEN quarter IS NULL THEN month
4 ELSE quarter
5 END AS month
6 ,CASE
7 WHEN grouping_id = 1 THEN 'ALL'
8 ELSE shop_code
9 END AS shop_code
10 ,sales_figure
11 FROM (
12 SELECT
13 grouping_id(shop_code) as grouping_id
14 ,quarter
15 ,month
16 ,shop_code
17 ,SUM(sales_figure) AS sales_figure
18 FROM
19 (
20 SELECT
21 CASE
22 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
23 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
24 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
25 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
26 END AS quarter
27 ,SUBSTR(starting_date,1,6) AS month
28 ,shop_code
29 ,sales_figure
30 FROM
31 test2
32 WHERE
33 SUBSTR(starting_date,1,6) BETWEEN '201101' AND '201103'
34 )
35 GROUP BY GROUPING SETS (
36 (month, shop_code),
37 (quarter, shop_code),
38 (month),
39 (quarter)
40 )
41 )
42 WHERE
43 shop_code = '1000'
44 OR grouping_id = 1
45 ORDER BY
46 month
47* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.03

レコードが選択されませんでした.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 2567395266

----------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 50 | 11 (28)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 50 | 11 (28)| 00:00:01 | | |
| 2 | VIEW | | 1 | 50 | 10 (20)| 00:00:01 | | |
| 3 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 4 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6620_DD3617 | | | | | | |
| 5 | PARTITION HASH ALL | | 1 | 29 | 2 (0)| 00:00:01 | 1 | 4 |
|* 6 | TABLE ACCESS FULL | TEST2 | 1 | 29 | 2 (0)| 00:00:01 | 1 | 4 |
| 7 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6621_DD3617 | | | | | | |
| 8 | HASH GROUP BY ROLLUP | | 1 | 29 | 3 (34)| 00:00:01 | | |
| 9 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6620_DD3617 | 1 | 29 | 2 (0)| 00:00:01 | | |
| 10 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6621_DD3617 | | | | | | |
| 11 | HASH GROUP BY ROLLUP | | 1 | 23 | 3 (34)| 00:00:01 | | |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6620_DD3617 | 1 | 23 | 2 (0)| 00:00:01 | | |
| 13 | VIEW | | 1 | 50 | 2 (0)| 00:00:01 | | |
|* 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6621_DD3617 | 1 | 29 | 2 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------------------------------------------

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

6 - filter(SUBSTR("SYS_TBL_$1$"."STARTING_DATE",1,6)>='201101' AND SUBSTR("SYS_TBL_$1$"."STARTING_DATE",1,6)<='201103')
14 - filter("SYS_TEMP_0FD9D6621_DD3617"."C1"='1000' OR BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D6621_DD3617"."D
0"),1))=1)

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

13 - SEL$80FD2AB9
U - NO_MERGE

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
2 db block gets
0 consistent gets
0 physical reads
384 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed

次に, ROLLUP. GROUPPING SETSで内部的に行われていますが, 実際にROLLUPを実行するとこうなります. (最初のSQL分とは集計内容が異なるので結果は同一ではないことはお気づきだと思います. データが無い状態で実行しているのでわかりづらいですが, ご注意くさだい)

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 ROLLUP(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.02

レコードが選択されませんでした.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 3343531542

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
|* 2 | FILTER | | | | | | | |
| 3 | HASH GROUP BY ROLLUP | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 4 | VIEW | | 1 | 33 | 2 (50)| 00:00:01 | | |
| 5 | HASH GROUP BY | | 1 | 29 | 2 (50)| 00:00:01 | | |
| 6 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| TEST2 | 1 | 29 | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 7 | INDEX RANGE SCAN | PK_TEST2 | 1 | | 1 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------------------------------

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

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
7 - access("STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed

最後は, CUBEです. これは, GROUPING SETSで行っていたことと同じことをCUBEを使って実現しています. 同じ結果を得るのであれば, CUBEの方が有利そうだ, という点に気づいた方は多いだろう思います:)

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.01

レコードが選択されませんでした.

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 2588666537

------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
|* 2 | FILTER | | | | | | | |
| 3 | SORT GROUP BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 4 | GENERATE CUBE | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 5 | SORT GROUP BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 6 | VIEW | | 1 | 33 | 2 (50)| 00:00:01 | | |
| 7 | HASH GROUP BY | | 1 | 29 | 2 (50)| 00:00:01 | | |
| 8 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| TEST2 | 1 | 29 | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 9 | INDEX RANGE SCAN | PK_TEST2 | 1 | | 1 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------

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

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
9 - access("STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
0 rows processed

 

アドベントカレンダー書いているだけで, 12月が終わってしまう気がするw

ということを, 言っても, やはり, 明日も担当は, 私ですw

では, また.

 

参考) Oracle Database 21c - Data Warehousing Guide / 21 SQL for Aggregation in Data Warehouses


Related article on Mac De Oracle ・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL

| | | コメント (0)

2022年12月10日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL

Previously on Mac De Oracle...
Day 9は, COLLECTION ITERATOR PICKLER FETCHでした. パイプラインテーブルファンクションだけに限りませんが, 利用者定義プロシージャやファンクション内から実行されるSQL文の有無, そして, 実行計画の把握という一手間多くなるタイプです. 特にパイプラインテーブルファンクションについては, 実行計画に特殊な操作として現れるのが特徴というお話しでした.

では, Day 10 の窓を開けましょう.

今日も, 紹介済みだと勘違いして忘れていた, MAT_VIEW REWRITE ACCESS FULL です!

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

データ作りが面倒なので, 再びサンプルスキーマのSHの力を借りましょうw (アドベントカレンダー, 全部俺は, ある意味体力, 気力の勝負w でもあるので, 楽できるところは楽に行きましょう :)


SH@orclpdb1> @day10
1 CREATE MATERIALIZED VIEW LOG ON sales
2 WITH
3 SEQUENCE
4 , ROWID
5 (
6 prod_id
7 , quantity_sold
8 , amount_sold
9 )
10* INCLUDING NEW VALUES

マテリアライズド・ビュー・ログが作成されました.

1 CREATE MATERIALIZED VIEW LOG ON products
2 WITH
3 ROWID
4 (
5 prod_id
6 , prod_name
7 , prod_category
8 , prod_subcategory)
9* INCLUDING NEW VALUES

マテリアライズド・ビュー・ログが作成されました.

1 CREATE MATERIALIZED VIEW mv_sales_4_day10
2 REFRESH FAST ON DEMAND
3 ENABLE QUERY REWRITE
4 AS
5 SELECT
6 prod_name
7 , SUM(quantity_sold) AS sum_quantity
8 , SUM(amount_sold) AS sum_amount
9 FROM
10 sales
11 INNER JOIN products
12 ON
13 sales.prod_id = products.prod_id
14 GROUP BY
15* prod_name

マテリアライズド・ビューが作成されました.


マテビューの準備ができたので, 本日の主役. Query Rewriteが働いで, sales, products表ではなく, materialize viewが full scan されるように書き換えられ, MAT_VIEW REWRITE ACCESS FULLが現れています!
処理時間もいい感じですよね.  経過: 00:00:00.02 となっています.

  1  SELECT
2 prod_name
3 , SUM(quantity_sold)
4 , SUM(amount_sold)
5 FROM
6 sales
7 INNER JOIN products
8 ON
9 sales.prod_id = products.prod_id
10 GROUP BY
11* prod_name

PROD_NAME SUM(QUANTITY_SOLD) SUM(AMOUNT_SOLD)
-------------------------------------------------- ------------------ ----------------
5MP Telephoto Digital Camera 6002 6312268.4
17" LCD w/built-in HDTV Tuner 6010 7189171.77
Envoy 256MB - 40GB 5766 5635963.08
Y Box 6929 2082330.3
Mini DV Camcorder with 3.5" Swivel LCD 6160 8314815.4
Envoy Ambassador 9591 15011642.5

...略...

Smash up Boxing 7844 260436.75
Martial Arts Champions 6711 148558.92
Comic Book Heroes 4572 101214.6
Fly Fishing 4091 34547.82
Finding Fido 6168 78881.08
Adventures with Numbers 12742 175563.92
Extension Cable 7576 60713.47
Xtend Memory 15191 366858.31

71行が選択されました.

経過: 00:00:00.02

71行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 3048942819

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 71 | 2627 | 3 (0)| 00:00:01 |
| 1 | MAT_VIEW REWRITE ACCESS FULL| MV_SALES_4_DAY10 | 71 | 2627 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
4356 bytes sent via SQL*Net to client
96 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
71 rows processed

ついでなの, Query Rewirteを無効にしてみるとどうなるでしょうか? 当然, オリジナルのsales, products表が結合されます! 
処理時間も大幅に増加して,  経過: 00:00:00.28 ですね! かなりの差ですよねこれ. このあたりもうまく使いたい, Oracleの便利な機能の一つではありますね.

  1  SELECT
2 /*+
3 NO_REWRITE
4 */
5 prod_name
6 , SUM(quantity_sold)
7 , SUM(amount_sold)
8 FROM
9 sales
10 INNER JOIN products
11 ON
12 sales.prod_id = products.prod_id
13 GROUP BY
14* prod_name

PROD_NAME SUM(QUANTITY_SOLD) SUM(AMOUNT_SOLD)
-------------------------------------------------- ------------------ ----------------
5MP Telephoto Digital Camera 6002 6312268.4
17" LCD w/built-in HDTV Tuner 6010 7189171.77
Envoy 256MB - 40GB 5766 5635963.08
Y Box 6929 2082330.3
Mini DV Camcorder with 3.5" Swivel LCD 6160 8314815.4

...略...

Martial Arts Champions 6711 148558.92
Comic Book Heroes 4572 101214.6
Fly Fishing 4091 34547.82
Finding Fido 6168 78881.08
Adventures with Numbers 12742 175563.92
Extension Cable 7576 60713.47
Xtend Memory 15191 366858.31

71行が選択されました.

経過: 00:00:00.28

71行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 504757596

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 71 | 4260 | 574 (11)| 00:00:01 | | |
| 1 | HASH GROUP BY | | 71 | 4260 | 574 (11)| 00:00:01 | | |
|* 2 | HASH JOIN | | 72 | 4320 | 573 (11)| 00:00:01 | | |
| 3 | VIEW | VW_GBC_5 | 72 | 2160 | 570 (11)| 00:00:01 | | |
| 4 | HASH GROUP BY | | 72 | 864 | 570 (11)| 00:00:01 | | |
| 5 | PARTITION RANGE ALL| | 918K| 10M| 523 (3)| 00:00:01 | 1 | 28 |
| 6 | TABLE ACCESS FULL | SALES | 918K| 10M| 523 (3)| 00:00:01 | 1 | 28 |
| 7 | TABLE ACCESS FULL | PRODUCTS | 72 | 2160 | 3 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------

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

2 - access("ITEM_1"="PRODUCTS"."PROD_ID")

Note
-----
- this is an adaptive plan

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1641 consistent gets
0 physical reads
0 redo size
4356 bytes sent via SQL*Net to client
96 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
71 rows processed

1* DROP MATERIALIZED VIEW LOG ON sales

マテリアライズド・ビュー・ログが削除されました.

1* DROP MATERIALIZED VIEW LOG ON products

マテリアライズド・ビュー・ログが削除されました.

1* DROP MATERIALIZED VIEW mv_sales_4_day10

マテリアライズド・ビューが削除されました.


さてさて, やっと, Day 10です. まだ半分も終わってないのかと, 遠ーーーーーーーーーーくをみるなど.

そんなこと, してても, 明日も私が担当なので, 何か考えますw

参考)
Oracle Database 21c / Data Warehousing Guide - 6 Advanced Materialized Views



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH

| | | コメント (0)

2022年12月 9日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH

Previously on Mac De Oracle...
Day 8は, レントゲンで紹介済みだと, すっかり勘違いして紹介し忘れていた TABLE ACCESS BY INDEX ROWID BATCHED でした. 索引スキャンはしているけど, 実行回数が多かったり, 実行回数は少ないが, 一回あたりのBuffer gets, Physical Readsが多いケースでは, ボディーブローのような感じで結構影響出るタイプであるケースも少なくないので, もし必要があれば, Index Only ScanなどでIO数削減して(重箱の隅を突くような)治療に繋がることも多い身近なOperationでもあるので知っておくと何かの時には助けになりますよ. きっと.

ということで, Day 9 の窓を開けましょう.

今日の主役は, COLLECTION ITERATOR PICKLER FETCHです.
どのような時に現れるOperationか, 既にググってる方はお気づきだと思いますがw パイプラインテーブルファンクションを利用してコレクションを返している場合です. これ意外に多くなってきているようにも思いますが, PJ次第なのかなとは思います. 上手く使えば味方になったり. .

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


Pipelined table function で ascii artで使ったパイプラインテーブルファンクションで実行計画を見てみましょう. (ちなみに, ASCII ARTのYouTube動画には無音です:)


前述のパイプラインテーブルファンクションでは, 表データをアクセスしていませんが, 表をアクセスしている場合でも, パイプラインテーブルファンクション内部から実行されているSQLは表面上現れません.

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from tree(50, 0.2);

4651行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 1806254315

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| TREE | 8168 | 16336 | 29 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
23 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
311383 bytes sent via SQL*Net to client
3462 bytes received via SQL*Net from client
312 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4651 rows processed


以下, サンプルスキーマであるshのsales表をアクセスするパイプラインテーブルファンクションですが, 実行計画には, COLLECTION ITERATOR PICKLER FETCHが現れるだけであることが分かります.
通常このように, PL/SQLなどのUDFやプロシージャ内部から実行されるSQL文はAWR等で別途確認していく必要があるという点は, 21cでも同様です. この辺りは, 仕方ないかなという感じはしますが, もう少し楽に該当SQL文の実行計画が確認できたら楽になるかな. という気はします.

データを確認し, 引数に利用する値を選んでいるようす.

SH@orclpdb1> desc sales
名前 NULL? 型
----------------------------------------- -------- ----------------------------
PROD_ID NOT NULL NUMBER
CUST_ID NOT NULL NUMBER
TIME_ID NOT NULL DATE
CHANNEL_ID NOT NULL NUMBER
PROMO_ID NOT NULL NUMBER
QUANTITY_SOLD NOT NULL NUMBER(10,2)
AMOUNT_SOLD NOT NULL NUMBER(10,2)

SH@orclpdb1>
SH@orclpdb1> select * from sales order by time_id desc fetch first 10 rows only;

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
14 1472 01-12-31 3 351 1 1193.02
20 3042 01-12-31 3 351 1 628.89
20 8182 01-12-31 2 999 1 628.89
20 7231 01-12-31 2 999 1 628.89
20 5745 01-12-31 2 999 1 628.89
20 3973 01-12-31 2 999 1 628.89
20 1978 01-12-31 3 999 1 628.89
20 118 01-12-31 3 999 1 628.89
20 1978 01-12-31 2 999 1 628.89
16 4958 01-12-31 3 999 1 298.11


パイプラインテーブルファンクションの作成中

SH@orclpdb1> @day9
1 CREATE OR REPLACE PACKAGE day9_pkg AS
2 CURSOR cur_2_latest_sales (
3 in_channel_id sales.channel_id%TYPE
4 ,in_prod_id sales.prod_id%TYPE
5 ,in_cust_id sales.cust_id%TYPE
6 ) IS
7 SELECT
8 *
9 FROM
10 sales
11 WHERE
12 channel_id = in_channel_id
13 AND prod_id = in_prod_id
14 AND cust_id = in_cust_id
15 ORDER BY
16 time_id DESC
17 FETCH FIRST 2 ROWS ONLY;
18
19 TYPE outtable_type IS TABLE OF sales%ROWTYPE;
20
21 FUNCTION list_2_latest_sales (
22 in_channel_id IN sales.channel_id%TYPE
23 ,in_prod_id IN sales.prod_id%TYPE
24 ,in_cust_id IN sales.cust_id%TYPE
25 )
26 RETURN outtable_type PIPELINED;
27* END day9_pkg;

パッケージが作成されました.

経過: 00:00:00.00
エラーはありません.


1 CREATE OR REPLACE PACKAGE BODY day9_pkg AS
2 FUNCTION list_2_latest_sales (
3 in_channel_id IN sales.channel_id%TYPE
4 ,in_prod_id IN sales.prod_id%TYPE
5 ,in_cust_id IN sales.cust_id%TYPE
6 )
7 RETURN outtable_type PIPELINED IS
8 sales_rec outtable_type;
9 BEGIN
10 FOR sales_rec IN cur_2_latest_sales(in_channel_id, in_prod_id, in_cust_id) LOOP
11 PIPE ROW(sales_rec);
12 END LOOP;
13 RETURN;
14 END list_2_latest_sales;
15* END day9_pkg;

パッケージ本体が作成されました.

経過: 00:00:00.00
エラーはありません.

以下, テーブルファンクションでsales表をアクセスしていますが, 見える範囲は21cになっても同じで, テーブルファンクションの場合は, COLLECTION ITERATOR PICKLER FETCHという形で表に見える状態なんですよ.
内部で, SQLが実行されているかどうかは, 実行計画からだけでは判断できない例の一つでもあります. このOperationを見つけたら, ファンクション内部で利用されているSQL文を特定しておくと後々役に立つこともあります.

  1  SELECT
2 *
3 FROM
4* day9_pkg.list_2_latest_sales(2, 20, 5745)

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
20 5745 01-12-31 2 999 1 628.89

経過: 00:00:00.00

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 457385954

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| LIST_2_LATEST_SALES | 8168 | 16336 | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
2 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
1093 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

テーブルファンクションから実行されるSQLは以下のような感じです. 実際にはバインド変数が利用されるため, バインドピークによる影響も合わせて見る必要もあります(バインドピーク無効にしているところって, まだありそうですしね. 昔からの大人の事情に縛られまくっているところとか)

  1  SELECT
2 *
3 FROM
4 sales
5 WHERE
6 channel_id = 2
7 AND prod_id = 20
8 AND cust_id = 5745
9 ORDER BY
10 time_id DESC
11* FETCH FIRST 2 ROWS ONLY

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
20 5745 01-12-31 2 999 1 628.89

実行計画
----------------------------------------------------------
Plan hash value: 3545264548

-------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 218 | 59 (2)| 00:00:01 | | |
|* 1 | VIEW | | 2 | 218 | 59 (2)| 00:00:01 | | |
|* 2 | WINDOW SORT PUSHED RANK | | 1 | 29 | 59 (2)| 00:00:01 | | |
| 3 | PARTITION RANGE ALL | | 1 | 29 | 58 (0)| 00:00:01 | 1 | 28 |
|* 4 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| SALES | 1 | 29 | 58 (0)| 00:00:01 | 1 | 28 |
| 5 | BITMAP CONVERSION TO ROWIDS | | | | | | | |
| 6 | BITMAP AND | | | | | | | |
|* 7 | BITMAP INDEX SINGLE VALUE | SALES_CUST_BIX | | | | | 1 | 28 |
|* 8 | BITMAP INDEX SINGLE VALUE | SALES_PROD_BIX | | | | | 1 | 28 |
-------------------------------------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=2)
2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("SALES"."TIME_ID") DESC )<=2)
4 - filter("CHANNEL_ID"=2)
7 - access("CUST_ID"=5745)
8 - access("PROD_ID"=20)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
1093 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

v$sqlareaビューからはこんな感じでSQL_IDが取得できるので, AWRなどから問題になっていそうならば. こいつの実行計画をおっていく感じ. まあ, ファンクションやプロシージャで実装されている場合だと一手間増える感じなのは21cでも変わらんですね. (この例ではplan_hash_valueを指定していますが、バインド変数が利用されているケースでは同一とは限らないのでご注意ください)
SH@orclpdb1> select sql_id,plan_hash_value,sql_text from v$sqlarea where sql_text like '%SALES%' and plan_hash_value = 3545264548;

SQL_ID                                  PLAN_HASH_VALUE SQL_TEXT
--------------------------------------- --------------- ----------------------------------------------------------------------------------------------------
5ang5upk282ga 3545264548 SELECT * FROM SALES WHERE CHANNEL_ID = :B3 AND PROD_ID = :B2 AND CUST_ID = :B1 ORDER BY TIME_ID DESC
FETCH FIRST 2 ROWS ONLY


さて, さて, 続きのネタ考えてると, 睡眠不足になりそうな週にw突入してきたぞw

参考)
Oracle Database 21c / 13.5 Chaining Pipelined Table Functions for Multiple Transformations


眠くても, 明日の担当は, 私しかいませんので, やりますw はい.



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED

| | | コメント (0)

2022年12月 8日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED

Previously on Mac De Oracle...
Day 7は, 実行計画という名のレントゲンにもしっかり現れる安心感のあるJINDEX RANGE SCAN (MULTI VALUE)という操作というJSON絡みの機能のお話でした. やはり, レントゲンだけで診れた方が楽ですねw
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022もやっと1/3ぐらいw 毎年思うけど, 大変. 全部俺だとw

ということで, Day 8 の窓を開けましょう.

今日は, すでに紹介済みと勘違いして, すっかり忘れていた TABLE ACCESS BY INDEX ROWID BATCHED について診ていきたいというか, 改めて確認しておきましょう.

TABLE ACCESS BY INDEX ROWID BATCHEDが登場したのは12cの頃です. 2014年に本ブログでも扱っていました. その時の解説で復習しておきましょう.

実は, この, TABLE ACCESS BY INDEX ROWID BATCHED, 実行計画のOperationに現れるようになったのは, 12cからですが, 内部的には, 11gでも同様の挙動を示していました.
11gの頃は, Operation上は, TABLE ACCESS BY INDEX ROWIDとして現れていましたが, 内部的には, 待機イベント, db file parallel readとして起きている、ということが分かる程度でした. そう, 実行計画という名のレントゲンだけでは判別できない部類の動きでした. その後, 12cになってからOperationとして簡易に判断できるようになりました.

以下, 2014年の記事ですが, SQLトレースを取得して待機イベントも含め調べてた思い出.
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #3


では, レントゲンを見てみましょう. (索引レンジスキャンするケースでは, TABLE ACCESS BY INDEX ROWID BATCHEDになる場合が圧倒的に多くなったようにも感じるので, 比較的よく見るOperationだと思います)
INDEX RANGE SCAN + TABLE ACCESS BY INDEX ROWID BATCHEDが行われており, かつ、索引のクラスタリングファクターが大きめ(行数に近い)である場合, Index Only Scanによって, ギリギリまでチューニングできる可能性が高いケースが多いのも, このタイプのOperationあ現れた時の特徴だったりします.

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

データの準備は以下.

SCOTT@orclpdb1> @day8
1* DROP TABLE day8

表が削除されました.

1 CREATE TABLE day8
2 (
3 id NUMBER PRIMARY KEY
4 ,string_data VARCHAR2(500)
5* )

表が作成されました.

1 DECLARE
2 i NUMBER(4) := 0;
3 num_of_rows CONSTANT NUMBER(4) := 1000;
4 done BOOLEAN := false;
5 BEGIN
6 WHILE NOT done LOOP
7 BEGIN
8 INSERT INTO day8 VALUES(TRUNC(DBMS_RANDOM.VALUE(1,3001)), LPAD(TO_CHAR(i),500,'*'));
9 i := i + 1;
10 IF i >= num_of_rows THEN EXIT; END IF;
11 EXCEPTION
12 WHEN DUP_VAL_ON_INDEX THEN
13 NULL;
14 END;
15 IF MOD(i,100) = 0 THEN COMMIT; END IF;
16 END LOOP;
17* END;

PL/SQLプロシージャが正常に完了しました.

1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>UPPER('day8'),no_invalidate=>false,cascade=>true);
3* END;

PL/SQLプロシージャが正常に完了しました.

1 SELECT id
2 FROM
3 day8
4 ORDER BY id
5* FETCH FIRST 5 ROWS ONLY

ID
----------
4
8
10
12
15

良かったw 綺麗に, TABLE ACCESS BY INDEX ROWID BATCHEDが出てますね.

SCOTT@orclpdb1> @day8-2 4 15
1 SELECT
2 id
3 ,substr(string_data,1,10)
4 FROM
5 day8
6 WHERE
7* id BETWEEN &1 AND &2
旧 7: id BETWEEN &1 AND &2
新 7: id BETWEEN 4 AND 15

ID SUBSTR(STRING_DATA,1,10)
---------- -----------------------------------
4 **********
8 **********
10 **********
12 **********
15 **********

旧 7: id BETWEEN &1 AND &2
新 7: id BETWEEN 4 AND 15


実行計画
----------------------------------------------------------
Plan hash value: 145644201

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 2525 | 7 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| DAY8 | 5 | 2525 | 7 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | SYS_C008604 | 5 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

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

2 - access("ID">=4 AND "ID"<=15)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
779 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

やっと, 実行計画は, SQL文のレントゲン写真だ!というタイトルっぽいネタになったような気がしたところで, 本日はこれまで.

明日の担当は, 私しかいないので, 私が書きますw (全部俺)



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)

| | | コメント (0)

2022年12月 5日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT

Previously on Mac De Oracle...
Day 4は, SQLというより, in-memory operationに追加された, In-Memory Hybrid Scans の実行計画という名のレントゲンをみながら, どのような状況で発動するのかを簡単に確認しましたー.

 

なかな面白い動きでしたね. 別途時間を取って深掘りしないとね, と. :)

 

 

ということで, Day 5 の窓を開けましょう. 今日は, 新機能というより以前からあったのにレントゲン取ってなかったよね? ということで PIVOT and UNPIVOT を診てみたいと思います.

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

 

最初は, pivot 向けデータの準備から


SCOTT@orclpdb1> @day5

表が削除されました.

1 CREATE TABLE for_pivot
2 AS
3 SELECT
4 stat_name
5 , value
6 FROM
7 v$sys_time_model
8 WHERE
9 stat_name IN (
10 'DB time'
11 , 'DB CPU'
12 , 'background elapsed time'
13 , 'background cpu time'
14* )

表が作成されました.

1* CREATE INDEX ix_for_pivot ON for_pivot(stat_name)

索引が作成されました.

1 SELECT
2 *
3 FROM
4* for_pivot

STAT_NAME VALUE
------------------------------ ----------
DB time 143415979
DB CPU 127540525
background elapsed time 7381483
background cpu time 6340262
/code>

 

 

 

PIVOTで行持ちを列持ちに変換してみます.

 

おお, 実行計画には, PIVOT とか出ないのですよーーーー!! (知ってましたか!
こいつも実行計画を見ただけでは, PIVOTが行われているかは読み取れないですねw アクセスパス以外は注意する箇所はあまりないわけですけども:)


  1  SELECT
2 db_time
3 ,db_cpu
4 FROM
5 (
6 SELECT
7 stat_name
8 , value
9 FROM
10 for_pivot
11 WHERE
12 stat_name IN ('DB time', 'DB CPU')
13 )
14 PIVOT
15 (
16 MAX(value)
17 FOR stat_name IN
18 (
19 'DB time' AS db_time
20 ,'DB CPU' AS db_cpu
21 )
22* )

DB_TIME DB_CPU
---------- ----------
143415979 127540525


実行計画
----------------------------------------------------------
Plan hash value: 1690715989

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 2 (0)| 00:00:01 |
| 1 | VIEW | | 1 | 26 | 2 (0)| 00:00:01 |
| 2 | SORT AGGREGATE | | 1 | 22 | | |
| 3 | INLIST ITERATOR | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| FOR_PIVOT | 2 | 44 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IX_FOR_PIVOT | 2 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

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

5 - access("STAT_NAME"='DB CPU' OR "STAT_NAME"='DB time')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
677 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
/code>

 

次に, UNPIVOT 向けデータの準備


表が削除されました. 

1 CREATE TABLE for_unpivot
2 AS
3 SELECT
4 db_time
5 ,db_cpu
6 ,bg_time
7 ,bg_cpu
8 FROM
9 (
10 SELECT
11 stat_name
12 ,value
13 FROM
14 v$sys_time_model
15 )
16 PIVOT
17 (
18 MAX(value)
19 FOR stat_name IN
20 (
21 'DB time' AS db_time
22 ,'DB CPU' AS db_cpu
23 ,'background elapsed time' AS bg_time
24 ,'background cpu time' AS bg_cpu
25 )
26* )

表が作成されました.

1* COMMIT

コミットが完了しました.

1 SELECT
2 *
3 FROM
4* for_unpivot

DB_TIME DB_CPU BG_TIME BG_CPU
---------- ---------- ---------- ----------
143522885 127634777 7381483 6340262
/code>

 

 

UNPIVOT では, PIVOT とは異なり, 実行計画上 UNPIVOT であることが読み取れます!!! ここ試験に出ますよ!(嘘w


  1  SELECT
2 stat_name
3 , value
4 FROM
5 for_unpivot
6 UNPIVOT
7 (
8 value FOR stat_name IN
9 (
10 db_time
11 , db_cpu
12 , bg_time
13 , bg_cpu
14 )
15 )
16* ORDER BY stat_name

STAT_NAME VALUE
------------------------------ ----------
BG_CPU 6340262
BG_TIME 7381483
DB_CPU 127634777
DB_TIME 143522885

実行計画
----------------------------------------------------------
Plan hash value: 3659757171

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 72 | 6 (17)| 00:00:01 |
| 1 | SORT ORDER BY | | 4 | 72 | 6 (17)| 00:00:01 |
|* 2 | VIEW | | 4 | 72 | 5 (0)| 00:00:01 |
| 3 | UNPIVOT | | | | | |
| 4 | TABLE ACCESS FULL| FOR_UNPIVOT | 1 | 26 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------

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

2 - filter("unpivot_view_005"."VALUE" IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
781 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
4 rows processed
/code>

 

ということで,  Operation には, なぜ, UNPIVOT は現れるのに, PIVOT が現れないのだろう. . . その謎を求め, 一向は洞窟の奥深くへ進んでいくのであった. . . .

 

昔からサポートされている PIVOT / UNPIVOT ですが, 改めて実行計画というレントゲンを診てみると, 新しい気づきとかあって良いですね.

 

参考 SQL Language Reference / PIVOT and UNPIVOT

 

 

Oracle Database 11g:Oracle ACEディレクター, Arup Nanda - PIVOT and UNPIVOT

 

 

ということで, 明日の担当は, またまた私ですw

 

 

 


Related article on Mac De Oracle ・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans

 

| | | コメント (0)

2022年12月 4日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans

reviously on Mac De Oracle...
Day3は, Oracleの場合の EXCEPT/EXCEPT ALLは, 内部で MINUS/MINUS ALLになってるね. やってること同じだし. SQLのキーワードが違うだけなので, まあ, そりゃそうだという感じの実装になっているようだ.
というところを, 実行計画という名のレントゲンで確認しましたw

 

それでは, Day 4 の窓を開けましょう!

タイトルの通り, SQL単体ではなく, 実行計画上の新機能を診てみたいと思います. 狙い通りのレントゲンが取れるでしょうか. . . .

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

なお, 今回は, in-memory機能を利用するのでお約束の inmemory_size パラメータを確認しておきますよ.
以前, 何度かセットし忘れで in-memory 発動せず!
みたいなこと何度かやらかしてたのことを思い出しますw
ちなみに, この inmemory_size パラメータのデフォルトは 0


SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 112M

 

まず, 準備から. データサイズが大したことないのと, データのカーディナリティもあまり深く考えてなかったのでイマイチかも知れません.

in-memory columnsと no in-memory columnsを持つ表を作成します. In-Memory Hybrid Scansは, 同一表の列がインメモリーだったり非インメモリーだったという状況で効果のある操作なので. :)


SCOTT@orclpdb1> @day4

表が削除されました.

経過: 00:00:04.17

1 CREATE TABLE day4
2 (
3 no_im_id NUMBER
4 , im_subid NUMBER
5 , no_im_str VARCHAR2(1000)
6 , im_str VARCHAR2(1000)
7 )
8 INMEMORY PRIORITY HIGH
9 MEMCOMPRESS FOR CAPACITY LOW
10 NO INMEMORY (
11 no_im_id
12 , no_im_str
13* )

表が作成されました.

経過: 00:00:00.02


1 BEGIN
2 FOR i IN 1..100000 LOOP
3 INSERT
4 INTO day4
5 VALUES(i, i+1000, LPAD(TO_CHAR(i),1000,'*'), LPAD(TO_CHAR(i+1000),1000.,'*'));
6 IF MOD(i,100) = 0
7 THEN
8 COMMIT;
9 END IF;
10 END LOOP;
11* END;

PL/SQLプロシージャが正常に完了しました.

経過: 00:00:16.88

 

定義を確認しておきましょうね.


  1  SELECT
2 table_name
3 , inmemory_compression "compression"
4 , inmemory_priority "priority"
5 FROM
6 user_tables
7 WHERE
8* table_name = 'DAY4'

TABLE_NAME compression priority
---------- --------------------------------------------------- ------------------------
DAY4 FOR CAPACITY LOW HIGH

経過: 00:00:00.00


1 SELECT
2 obj_num
3 , segment_column_id
4 , inmemory_compression
5 FROM
6 v$im_column_level im
7 INNER JOIN dba_objects do
8 ON
9 im.obj_num = do.object_id
10 WHERE
11* do.object_name = 'DAY4'

OBJ_NUM SEGMENT_COLUMN_ID INMEMORY_COM
---------- ----------------- ------------
77251 1 NO INMEMORY
77251 2 DEFAULT
77251 3 NO INMEMORY
77251 4 DEFAULT

経過: 00:00:00.01

 

データのPOPULATEを行います. 必要な列はすべてインメモリーにPOPULATEされたようです:)


  1  SELECT
2 /*+
3 FULL(day4)
4 NO_PARALLEL(day4)
5 */
6 COUNT(*)
7 FROM
8* day4

COUNT(*)
----------
100000

経過: 00:00:00.08


1 SELECT
2 segment_name
3 , inmemory_size
4 , bytes_not_populated
5 FROM
6* v$im_segments

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED
------------ ------------- -------------------
DAY4 15990784 0

経過: 00:00:00.01

 

準備運動からw SELECT LISTにある2列は, どちらも非インメモリー列なので, 結果としてはよく見る table full scanになります. 当然の結果ですね.


  1  SELECT
2 MAX(no_im_id)
3 , COUNT(no_im_str)
4 FROM
5* day4

MAX(NO_IM_ID) COUNT(NO_IM_STR)
------------- ----------------
100000 100000

経過: 00:00:00.09

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 515 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 515 | | |
| 2 | TABLE ACCESS FULL| DAY4 | 87887 | 43M| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
33544 consistent gets
0 physical reads
0 redo size
685 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

ベタな検証その2. 今度は, SELECT LISTの2列はどちらもインメモリー列です. 結果はみなさんの想像の通り, TABLE ACCESS INMEMORY FULLです.
consistent getsが非常に少なくなっています. (処理時間的なところはまあ, データ量次第なので大きな差にはなってないですね. 深く分析していないので, 別の機会にデータ量などを変えつつ検証しておきたいですよね, この辺り)


  1  SELECT
2 MAX(im_subid)
3 , COUNT(im_str)
4 FROM
5* day4

MAX(IM_SUBID) COUNT(IM_STR)
------------- -------------
101000 100000

経過: 00:00:00.10

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 515 | 382 (13)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 515 | | |
| 2 | TABLE ACCESS INMEMORY FULL| DAY4 | 87887 | 43M| 382 (13)| 00:00:01 |
------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
683 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

次に, in-memory hybrid scan発動の条件に該当しない例をみてみましょう.
このクエリーでは, SELECT LISTにインメモリー列のみ. WHERE句に非インメモリー列のみがありますが, これは, in-memory hybrid scan発動条件に一致しません.

結果として, 通常のTABLE ACCESS FULLとなってしまいますので注意しましょうね.


  1  SELECT
2 MAX(im_subid)
3 , COUNT(im_str)
4 FROM
5 day4
6 WHERE
7* no_im_id < 5000

MAX(IM_SUBID) COUNT(IM_STR)
------------- -------------
5999 4999

経過: 00:00:00.08

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 528 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 528 | | |
|* 2 | TABLE ACCESS FULL| DAY4 | 4794 | 2471K| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------

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

2 - filter("NO_IM_ID"<5000)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
33544 consistent gets
0 physical reads
0 redo size
684 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

最後に, 本日の主役の登場です!

SELECT LISTには, 非インメモリー列のみ. WHERE句には, インメモリー列のみが利用されています. これが発動条件です.
結果として, これまで, みたことのないOperationである,  TABLE ACCESS INMEMORY FULL (HYBRID) が現れています. ふむふむ.
consistent getsもかなり少ないですし, このケースでは, 処理時間も早くなっていますね. ますます別途詳しく調査したくなりまし:)

なんとなくですが, この in-memory hybrid scan を見て, MySQL Heatwaveが浮かんできますよね. 諸々応用しているのでしょうかね...


  1  SELECT
2 MAX(no_im_id)
3 , COUNT(no_im_str)
4 FROM
5 day4
6 WHERE
7* im_subid < 5000

MAX(NO_IM_ID) COUNT(NO_IM_STR)
------------- ----------------
3999 3999

経過: 00:00:00.02

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 528 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 528 | | |
|* 2 | TABLE ACCESS INMEMORY FULL (HYBRID)| DAY4 | 1598 | 823K| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

2 - filter("IM_SUBID"<5000)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1342 consistent gets
0 physical reads
0 redo size
687 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 


アドベントカレンダー, 全部俺をやらなきゃ, 師走は走り回るほど忙しいわけではないはずだったのになぁw と, 遠ーーーくを見てるw

では, また,

明日の担当も! 私です.


参考 Database In-Memory Office Hours / In-Memory Hybrid Scans

 

そして, Ask Tomがこんな感じになるとは:) いい感じですよね.
Database In-Memory Office Hours / ASK TOM

Database In-Memory Guide / 21c


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL

| | | コメント (0)

2022年12月 3日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL

Day 2は, Oracleの方言である集合演算 MINUS ALL の実行計画というレントゲンを確認しました. MINUSでも19cまでは, ALLはサポートされていませんでした. サポートされるようになった理由は, 今日レントゲンを見ていくEXCEPT/EXCEPT ALLのサポートが影響してそうですね. やることは同じですので:)

では, お約束のw
なお, 今回のSQL文の結果セットはソートされているように並んでいますが, たまたま昇順に並んでいるだけなので, ソートが必要な場合は. 必ず. ORDER BY句を付けてくださいね.

ということで, Day 3の窓を開けましょう!

MINUSは方言ですが, 標準SQLに合わせた形で同様の機能を持つEXCEPTがサポートされました. また, 方言であるMINUSも同様にサポートは継続されています. 下位互換の意味もあると思いますが, MINUS/MINUS ALLの実行計画というレントゲンと比較しながら見ると良いと思います.

 

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

Day 1と同じ表とデータを利用します(再掲). 意図的に, 該当表には索引を作成していません.

SCOTT@orclpdb1> @day3
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.

 

最初は, EXCEPT ALL から診てみます 実行計画を見て〜〜〜〜〜〜くださ〜〜〜〜い, 驚きますよ!!!! 

 

内部では, MINUS ALL に書き換えられている!! これが MINUS ALLをサポートした理由ですねw やってることが同じなので, まあ無駄が無いのは確かだw

EXCEPT ALLが内部で, MINUS ALLに書き換えられているのでしょうか. 今回はトレースを取得して追いかける余力はないので, その辺りは別の機会にでも.
また, このケースでもHASH操作が現れています.

  1  SELECT
2 num
3 FROM
4 groupa
5 EXCEPT ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
2
5

実行計画
----------------------------------------------------------
Plan hash value: 895579533

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
648 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed

 

次は, EXCEPT 同様に, HASH操作を伴うMINUSとして現れています. やっている集合演算も同じですから, 実行計画だけ見せられたら, EXCEPTなのかMINUSなのかは区別できないでしょうね. 区別する必要もないわけですけども.
プロジェクトのSQL標準がどちらを使うか定めていれば, どちらかなのかは想像できるかも知れませんがw

  1  SELECT
2 num
3 FROM
4 groupa
5 EXCEPT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5

実行計画
----------------------------------------------------------
Plan hash value: 2352437695

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
640 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed

 

次に, 索引アクセスがある場合はどうなるかを確認します. 利用するデータや表はDay1と同じです.(再掲) なお, 索引等の情報は前回のエントリーをご覧ください

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.

1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.

まずは, EXCEPT ALLから Table Full Scanであろうが, Index Range Scanであろうが, HASH操作になる点も含め, MINUS ALLそのものですね:)

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

27行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 381125746

---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
|* 2 | INDEX RANGE SCAN| TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------

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

2 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
3 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)v
統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
1040 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
27 rows processed

 

EXCEPTの実行計画という名のレントゲンはどうでしょう?

 

思った通り MINUS と同じ実行計画が現れています

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

24行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 2829086308

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
993 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
24 rows processed

 

19cでは, EXCEPT/EXCEPT ALLともに未サポートなので, どちらもシンタックスエラーです. 当然と言えば当然の結果です:)

SCOTT@orcl> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75
EXCEPT ALL
*
ERROR at line 7:
ORA-00933: SQL command not properly ended

 

1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75
EXCEPT
*
ERROR at line 7:
ORA-00933: SQL command not properly ended

 


まだまだ, Day 3 ですねーーーーw

 

意外に多くの, ITアドベントカレンダー全部俺, または, 準全部俺(タイトルに全部俺って書いてないだけで, 多分, 全部俺になっちゃうでしょうーーっなカレンダー立てた方含め,
楽しんでやってきましょう.  w

 

明日の Day 4の担当は, なんとーーーっ, 私ですw

 

では, また.


参考)

19c UNION [ALL], INTERSECTおよびMINUS演算子

21c The Set Operators


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL

 

| | | コメント (0)

2022年12月 2日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL

Previously on Mac De Oracle...
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022始まっちゃいましたw

Day 1は, 19cまでのINTERSECTに加え, 21c(正確には20c)からサポートされたINTRESECT ALLの実行計画というレントゲンはどう見えるのか?を確認しました.
21cからはHASH操作が加わっているとことは興味深いので, HASH操作について観点を変えて実行計画というレントゲンを見ておかねば, という気がしていますね. 今のところ, おまけとして考えているところですw(ネタがキツくなったら普通にやるかもw)


ということで, Day 2の窓を開けましょう!

なお, 今回のSQL文の結果セットはソートされているように並んでいますが, たまたま昇順に並んでいるだけなので, ソートが必要な場合は, 必ず, ORDER BY句を付けてくださいね.


INTERSECTと同様に, 19cまでは, ALLがサポートされていなかったOracleの方言である集合演算 MINUS にも MINUS ALLがサポートされました.
21c以降で利用できます. 実際には20cですが世に出てくることは無かったわけで, 事実上21c以降といってもいいですよね
(おそらく)

21cでみてみましょう.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


Day 1と同じ表とデータを利用します(再掲). 意図的に, 該当表には索引を作成していません.

SCOTT@orclpdb1> @day2
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.


今回の主役, MINUS ALLの実行計画という名のレントゲンから診てみます.

OperationにMINUS ALLと現れています. わかりやすいですね. また, ここでもHASH操作が現れています. 21cの特徴でもあるので頭の片隅に置いといた方が良さそうです.

  1  SELECT
2 num
3 FROM
4 groupa
5 MINUS ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
2
5

実行計画
----------------------------------------------------------
Plan hash value: 895579533

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
648 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed


次にこれまでもサポートされていた, MINUSの実行計画という名のレントゲンから診てみます.

これも判断しやすいですね. キーワードとなるMINUSが, Operationに現れています. 同様に, HASHも.

  1  SELECT
2 num
3 FROM
4 groupa
5 MINUS
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5


実行計画
----------------------------------------------------------
Plan hash value: 2352437695

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
640 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed


次に, 索引アクセスがある場合はどうなるかを確認します. 利用するデータや表はDay1と同じです.(再掲)
なお, 索引等の情報は前回のエントリーをご覧ください

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.

1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.


まずは, MINUS ALLから

Table Full Scanであろうが, Index Range Scanであろうが, HASH操作になるというのも特徴のようですね. 興味深いです.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 MINUS ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

27行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 381125746

---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
|* 2 | INDEX RANGE SCAN| TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------

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

2 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
3 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
1040 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
27 rows processed

MINUSはどうでしょう?

お! やはり, 挙動が変わりますね. これは, 索引を利用することを重要視した結果と言えそうです. SORT UNIQUE NOSORTなので, ソートなしでUNIQUE操作を行っています. ソートのバイバスを重要視したということになりますね.
ここも非常に興味深い動きですね.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 MINUS
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

24行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 2829086308

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
993 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
24 rows processed


では, 最後に, 19cでも実行計画という名のレントゲンを診ておきたいと思います.

SYS@orclcdb> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production


MINUS ALLは未サポートなので, シンタックスエラーになります.

SCOTT@orcl> @day2
1 SELECT
2 num
3 FROM
4 groupa
5 MINUS ALL
6 SELECT
7 num
8 FROM
9* groupb
MINUS ALL
*
ERROR at line 5:
ORA-00928: missing SELECT keyword


以前からサポートされているMINUS, やはり, SORT UNIQUE操作が利用されています. 21cのMINUSとは異なる動きのあることが見えてきます.

1  SELECT
2 num
3 FROM
4 groupa
5 MINUS
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5

Execution Plan
----------------------------------------------------------
Plan hash value: 2070324559

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE | | 7 | 21 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 8 | 24 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
599 bytes sent via SQL*Net to client
421 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
2 rows processed

アドベントカレンダー, 今のところ順調だが, 25回分全てのネタを書き終わったわけではないので, 先はナゲーーーーーーーなーーーと, 遠ーーーくを見てるw

Day3の担当は, 私です. よろしくお願いしますw

では, また, 明日.



参考)

19c
UNION [ALL], INTERSECTおよびMINUS演算子

21c
The Set Operators



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL

| | | コメント (0)

2022年12月 1日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL

さあ, 今年もやってきましたよ. 12月の風物詩ITアドベントカレンダーの季節が loL. 今年は久々に, 復活

 

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺

 

最後まで体力とネタが持つでしょうかww (いや, なんとしてもネタを持たせないと
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022

 


 

Day 1の窓は, 他のデータベースでも話題になっている標準SQLへの対応ネタから.
Oracle 19cまでは未対応だった, いくつかの集合演算子が, 20c以降でサポートされるようになりました.

 

簡単なとこからですよー!(最初から飛ばしすぎると後半息切れするのでw)

 

前述の集合演算子の拡張で特徴的だなと感じたのは, 今までALLがサポートされていなかった演算子でALLが使えるようになった事ですよね!

 

今回は, ALLがサポートされたINTERSECTの実行計画を見て見ましょう.

 

INTERSETのレントゲンは以前も紹介していました(Oracle 19c以前の実行計画)
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2019/12/post-9e4acc.html

 

今回のアドベントカレンダーでは, Oracle Database 21c Enterprise Edition を使います. (比較の為に, 旧リリースを使う場合もあります)

SCOTT@orclpdb1> select banner from v$version;

BANNER
------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

groupa表と, groupbh表を用意しました. num列だけの単純な表です(なお, 索引は作っていません. 理由は単純な集合演算を行いたかったから:)

SCOTT@orclpdb1> @day1
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.

 

上記のようなデータがあったとして, INTERSECT ALLするとどうなるか.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4
4

 

INTERSECTION ALL HASH と出てますね. 21cではこれを手がかりに, INTERSECT ALL行われていると判断できますね.

INTERSECTION ALL, このケースだとHASH操作を組み合わせてる. ふむむむ 21cだからですかね. 以前まではなかったはずなので, 後半で19cを利用して試してみましょう.

実行計画
----------------------------------------------------------
Plan hash value: 2581184127

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION ALL HASH| | | | | |
| 2 | TABLE ACCESS FULL | GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------

 

では, 以前のリリースからあったALL無しだとどうなるか. HASH操作も出てますね. 興味深い. 大量データを扱う場合は有利でしょうね.

  1  SELECT
2 num
3 FROM
4 groupa
5 IINTERSECT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4

実行計画
----------------------------------------------------------
Plan hash value: 3344747026

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

 

では, HASHが選ばれない場合の簡単な例を.

表定義はこんな感じ. (削除フラグ!!!があるけど気にしないでね. 他のネタでも使いたいのでw)

名前                                    NULL?    型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

名前 NULL? 型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

 

索引関連はこんな感じ(索引スキャンしてもらうことを狙っています)

TABLE_NAME                     INDEX_NAME                     INDEX_TYPE                     UNIQUENESS
------------------------------ ------------------------------ ------------------------------ ---------------------------
TAB311 TAB311_PK NORMAL UNIQUE

TAB311 TAB311_IX_SUB_ITEM_CODE NORMAL NONUNIQUE

TAB312 IX01_TAB312 NORMAL NONUNIQUE

TAB312 IX02_TAB312 NORMAL NONUNIQUE


TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
TAB311 TAB311_IX_SUB_ITEM_CODE SUB_ITEM_CODE

TAB311 TAB311_PK UNIQUE_ID
TAB311 TAB311_PK SUB_ITEM_CODE

TAB312 IX01_TAB312 UNIQUE_ID
TAB312 IX02_TAB312 SUB_ITEM_CODE

TAB312 IX01_TAB312 SUB_ITEM_CODE

 

以下のようなデータを用意しました.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.


1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.

 

INTERSECにしてみました. 索引が利用できる程度の取得件数なので索引を利用したSORT UNIQNE NOSORTが実行されています.
状況によっては, このようなケースもあることを思えておきましょうね. 索引利用が妥当なのかは個別の条件に応じて判断していく必要があります.
この場合は問題はないでしょう. ポイントはIndex only scanで必要最小限の範囲をアクセスして, かつ, 索引を利用してソートを回避しているところですね.

 1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 INTERSECT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

26行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 547955931

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)

 

おまけ, 19cでは INTERSECT ALLはサポートされいませんが, 実行計画の違いを見ておきましょう.

SCOTT@ORCL> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

SCOTT@ORCL> @day1
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7 rows selected.


1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

 

19cでは当然, シンタックスエラーです.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT ALL
6 SELECT
7 num
8 FROM
9* groupb
INTERSECT ALL
*
ERROR at line 5:
ORA-00928: missing SELECT keyword

 

21cでは, HASH操作が行われてましたが, 19cでは, HASHの代わりに, SORT UNIQUEが現れていますね. 興味深い違いです. それぞれのリリースで同一統計情報で操作が変化する点は, しっかり押さえておきましょう. 今後のチューニングに役立つかもしれませんよー.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4


Execution Plan
----------------------------------------------------------
Plan hash value: 2012227029

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE | | 7 | 21 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 8 | 24 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------

 

参考)

19c UNION [ALL], INTERSECTおよびMINUS演算子

21c The Set Operators

 

ひとまず, Day 1の窓をあけた. 明日に続く.

 

最後に、今回は、ORDER BY句を付加していませんが、付けてくださいよ。ソートが必要な場合は!!!!

今回の例ではたまたま昇順に並んでいるだけですからね。


 

Related article on Mac De Oracle



・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー

 

| | | コメント (0)

2022年4月11日 (月)

実行計画は、SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー

Previously on Mac De Oracle
前回は、その前のエントリーの流れから、標準はあるにはあるが癖の多いSQL - #27 LNNVL is 何? と思った方向けでちょいと脱線してました。
今日は、話を元に戻しますw

USE_CANTATとOR_EXPAND、レントゲン(実行計画)をみて、どこがどう違うのかは理解できたのではないかと思います。ではなぜ、今後使うとしたら、OR_EXPANDなのかは、USE_CONCATとより言うことを聞いてくれやすいという他にもう一つあるのですが、それは何かわかりますか?
大人の事情で、しばらく関わりが薄かった時期(w にこのヒントの効果を知ったのですが、もう一つのメリットまでは知らなかったんですよw。 斜め読みだけしてると取りこぼしちゃいますねw

答えはパラレルクエリーにした場合の違い。

OR_EXPANDによる書き換えとUNION-ALLへの内部的な書き換えの効果で、パラレルクエリーとの相性が良くなっているんですよね。

早速、レントゲンをみてみましょう :)
(あ、書き忘れてましたが、Oracle Database 21cを使ってます)

USE_CONCATを使ってCONCATENATION(Id=1のoperation)を強制してかつパラレルクエリーにしています。PX COORDINATOR が Id=2とId=9に現れているのでUNIONの各SELECT文はシリアルに実行されているようですね。この挙動は変わってなさそうです。

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 use_concat
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.44

実行計画
----------------------------------------------------------
Plan hash value: 1305058436

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 8 (0)| 00:00:01 | | | |
| 1 | CONCATENATION | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ20001 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 6 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | | | |
| 9 | PX COORDINATOR | | | | | | | | |
| 10 | PX SEND QC (RANDOM) | :TQ10001 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 13 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 14 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 15 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | | | |
-----------------------------------------------------------------------------------------------------------------------------------------------

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

8 - access("SUB_ITEM_CODE"='0001000000')
15 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

OR_EXPANDでU内部的にUNION-ALLに書き換えてパラレルクエrーにすると。。。。。。おーーーーー。違う!!! Id=1にあるPX COORDINATOR だけになってますね。各SELECT文もパラレル化されているようです。:)
結構違いますね。やはり、使うなら、USE_CANTATよりOR_EXPANDのようが良さそうですね。これで思い出した! ORDERED と LEADINGヒントのような感じですかねー。同じ機能を持つ後発ヒントの方が色々と使い勝手が良くなってることって意外に多いです!

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 or_expand
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.14

実行計画
----------------------------------------------------------
Plan hash value: 3317360125

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 8 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | VW_ORE_5F0E22D2 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 1 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

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

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


前々回手動でunionに書き換えたSQLをパラレルにするとどうなるだろう。。。
ほう。

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

実行計画
----------------------------------------------------------
Plan hash value: 3983264199

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (19)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10003 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 5 | PX SEND HASH | :TQ10002 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | P->P | HASH |
| 6 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 8 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 10 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 11 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 12 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 13 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
---------------------------------------------------------------------------------------------------------------------------------------------------

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

12 - access("UNIQUE_ID"=1)
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

同じく、前々回手動でunion all + フィルタ条件追加に書き換えたSQLをパラレルにするとどうなるだろう。。。
おおおおおーーーーーーっと。これはCONCATENATIONの実行計画にそっくりですね。CONCATENATIONの部分がUNION-ALLになっている程度の違い。2つのPX COORDINATOR がある点も共通しています。。。むむ。

このSQLをOR_EXPANDの実行計画と同じようにするには......あ! あれだ!

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

実行計画
----------------------------------------------------------
Plan hash value: 1844591072

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 9 (0)| 00:00:01 | | | |
| 1 | UNION-ALL | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10001 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 6 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 8 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 9 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 10 | PX COORDINATOR | | | | | | | | |
| 11 | PX SEND QC (RANDOM) | :TQ20001 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 2 | | 3 (0)| 00:00:01 | Q2,00 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q2,00 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q2,00 | SCWP | |
-----------------------------------------------------------------------------------------------------------------------------------------------

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

9 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


ということで、
前々回手動でunion all + フィルタ条件追加に書き換えたSQLを単純にパラレルクエリーにしてもイマイチだったので、OR_EXPANDのような実行計画にするために、インラインビューにしてみました!!! どうでしょう? OR_EXPANDの実行計画と同じようになりました。
ポイントは、前々回のOR_EXPANDの実行計画中に現れるインラインビュー VW_ORE_5F0E22D2 です。内部的にインラインビューを追加してるんですよね! OR_EXPANDのUNION ALL書き換え。
インラインビュー化したことで、Id=4にビューが登場しています。OR_EXPANDでは、VW_ORE_* と名付けられるOR_EXPANDトランスフォームにより追加されるインラインビューと同じ役割を持っていますが、内部的に書き換えられて追加されるインラインビューとは異なり動的に名称が付加されません。

インラインビューが決めて! というか、意外と忘れがちなので注意しないとね。

select
/*+
parallel(4)
*/
*
from
(
select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1)
);

経過: 00:00:00.03

実行計画
----------------------------------------------------------
Plan hash value: 3706965944

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 9 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

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

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

4月はじめだと言うのに、夏日とか、北の方面の友人からは31度だとか、最近の異常気象ほんとに農家泣かせな感じ。最近は天気予想が細かい範囲ででるので以前より対応しやすいのかもしれないけど。
こんな、陽気だとぶらりと湘南あたりからリモートワークしたいw

ではまた。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

| | | コメント (0)

2022年4月 9日 (土)

実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

Previously on Mac De Oracle
前回のレントゲンは、BITMAP CONVERSION TO ROWIDSでした。複数の索引を同時に使うという昔からあるオペレーションでした。

今回は単にレントゲンを見ていくだけではなく、同じ問い合わせ結果(よくある間違いなどもいれてありますw)になるものの微妙に違うレントゲンをみつつ、元のSQL文、それに今回ヒントになにが使われているか、見ていきたいと思います。

これが前回のエントリで使ったSQL分です. 問い合わせ結果と実行計画(前回のエントリで取り上げたBITMAP CONVERSIONです。この問い合わせ結果と実行計画という名のレントゲンをよーーーーーーーーーく、覚えておいてくださいね。

いくつかのレントゲンを使って、これなーーーーーーーんだ? wみたいなw

これが原型なので、覚えておいてください。

select
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

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

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1248 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
4 rows processed

 

前回のエントリでも少し書いたのですが、CONCATENATION がレントゲンに現れるときは、BITMAP CONVERTなど効率が悪いOR条件の実行計画を改善するため、OR条件部分を分離排除し、2つの索引それぞれを有効に利用させるためのヒントによるチューニングで行なった場合が多いです。オプティマイザが選択するケースもありますが。

ヒントで強制的にすることもありますが、ヒントが効かないケースは多も多いのは事実です。理由は内部的に2つのクエリーに分解しているわけですが、それぞれで利用する索引のアクセス効率が悪いオプティマイザに見えている場合にはヒントが効かない場合が多いように思います。
なんとなーーーくざっくりなイメージですが、UNIONのような形に内部的に書き換えていると思うとわかりやすいかもしれないですね。。UNIONとでてないのでUNIONのようなものとしかかけないのですがW
とにかく、CONCATENATIONを見つけたら USE_CONCAT ヒントでチューニングされてるね!
と脊髄反応できるようになっているとよいですね!

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
2 0001000000 fooooooo2 0
1 0001000000 2**** 0
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1228 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

上記のレントゲンの元はこれです。USE_CONCATヒント使われてますよね。このケースではオプティマイザは言うことを聞いてくれたようですね。w

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

 

では、オプティマイザが言うことを聞いてくれなかった場合、Oracle Database 12c R1まではどうやって、治療していたか。知りたく無いですか?
USE_CONCATでUNIONのように内部的書き換えてくれると、SQLへはヒントの追加だけで済むので、同値検証等も不要で患者さんの痛みは少なくてすむわけですが、先にも買いたように必ず効くわけでもないという、ちょっと癖のあるヒントなんです。で、12c R1までは、しかたないの、SQL構文変更という中程度の難易度の手術(SQL書き換えw)が必要でした。

 

先ほと、UNIONのようにと書きましたが、まさに、それで、UNIONまたは、UNION ALLに書き換えてしまうという手術ですw

 

どちらでやってもよいのですが、重複データの排除がどれだけの負荷になるかというところかなと思います。重複排除するデータ量が多いのであれば UNION にしてHASH UNIQUEによる重複行排除の方がよいかもしれませんし、少量なら UNION ALLでフィルタリングによる重複行排除のほうがよいかもしれません。HASH UNIQUEにしてもPGA不足でTEMP落ちしてしまうようなことがあるのならフィルタリングのほうがよさそうですし、その時の状況次第かと思います。

 

では、ずは、UNION を使った書き換えから。

 

ソートしていないので並びが変わってますが、あえてソートしていません。Id=2のUNION-ALLとId=1にHASH UNIQUEというoperationがありますが、これが現在のUNIONの典型的なoperationです。HASH UNIQUEがなかったころは、SORT UNIQUEだったわけですが、その影響でデータがソートされていたので、諸々勘違いしてデフォルトでソートされるんだー、みたいな勘違いしている方も一定数存在していた時期があり、HASH UNIQUEがなって順序通りになってない! と勝手にザワザワしていたこともありましたね。それ知ってる方々はOracleにながーーーーいこと関わっている方だと思いますw 注意しましょうね。思った通りの並びにしたい場合は、ちゃんとORDER BY句でソートしましょうね。(これ言いたかっただけw)
あと、最近は、UNIONをパラレル実行できるようになったので、その場合も、順序はバラバラになります。シリアルに実行している場合は上位にあるクエリから処理されるのでその順序で行が戻されていましたが、パラレルだと何が来るかはその時々ですね。

 

脇道にそれましたが、手書きでSQLを UNION に書き換えた場合のレントゲンはこんな感じです。UNION-ALLのオペレーションの後に重複行排除のUNIQUE操作が必ず入るので覚えやすいと思います。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

もともとOR条件でしたが、それぞれの索引を有効に活用させるため、2つの文に分解し、それらを UNIONしています。UNIONで重複排除も行なっているわけです。

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

 

 

次に、UNION ALLへ手動で書き換えた場合はどうなるかレントゲンを見てみましょう。

 

結果も正しいです。レントゲンで見える UNION と UNION-ALLとの違いは、重複行排除のUNIQUEオペレーションが無いところです。Id=1にあるUNION-ALL だけで、 HASH UNIQUEがありません。
これ大丈夫なのでしょうか? 重複行を排除するオペレーションがないなんで、たまため結果が正しいだけでしょうか???

 

実は、実行計画に現れない違いが述語部分にあります。 Predicate Information (identified by operation id):セクションに 4 - filter(LNNVL("UNIQUE_ID"=1)) とあるのに気づきましたか?

 

4は、実行計画の Id = 4を示しています。これは Id = 4の TAB311のアクセス時に、"UNIQUE_ID"=1 であれば falseとして該当行をフィルタリングして捨てていることを意味しています。
つまり、UNIONで HASH UNIQUEを行なっていた重複行を排除と同様の効果をえるフィルター条件なんです。この条件にで、 Id = 2 と Id = 3で取得されたUNIQUE_ID=1の行を捨てています

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

UNION ALLでの書き換えは、以下のようなSQL文でした。and LNNVL(unique_id=1)という条件追加がポイントです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

 

では、つい忘れそうな、フィルター条件追加をわすれてUNION ALLにしてしまった場合はどうなるでしょう? もう想像できますよねw

 

SQLの違いからみてみましょう。 and LNNVL(unique_id=1) と言う条件が無いこと以外前述のSQLと同じです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000';

 

実行してみると。。。。。あららららら、ちゃっしゃいましたな感じの結果ですねw Predicate Information (identified by operation id): には重複排除のフィルター条件は見当たりません。(当然ですね。書き忘れているわけですから)

UNIQUE_ID = 1 でもあり、SUB_ITEM_CODE = '0001000000' である行が2回リストされています。ざんねーーーん。注意しましょうね。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0


----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1239 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

 

よーーーーし、バグになるのが怖いから、常に UNION だーーーと安全策に流れそうですが、性能面ではフィルタリングと HASH UNIQUE、または、SORT UNIQUEかということであれば、データ量次第でどちらが良いか判断した方がよいだろうと。個人的には思っています。フィルターしたほうが有利なケースも当然ありますし、しないほうが良い場合もあるでしょう。

最後に、今日のタイトルにも書いた NO_EXPAND これ少々気難しい、USE_CONCATの後継として 12c R2 で登場したヒントです。内部の書き換えもそれまでのCONCATENATIONではなく、UNION ALLとフィルタリングにより重複行排除が行われるように、内部的に書き換えられるのが特徴です。なんでこれもっと早く実装してくれなかったんですかね? 強強ですね。USE_CONCATの気まぐれ感は消えてる感じがします。

事実、某所で、USE_CONCAT効かなくて、しぬーーーーーみたいな状況で、たまたま 12c R2だったので、NO_EXPAND で回避したーーーーなんてこともありました。そういことで、USE_EXPANDをUSE_CONCATの代わりにUSE_EXPANDを使うことをおすすめしますw (それ以外にもメリットも多いですし、それはまた、次回にでも)

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

 

レントゲンを見ると、手書きで書いた UNION ALLへの書き換えと微妙に違うの気づきますか? これまで紹介してきた手書きでの書き換えとヒントに夜書き換えは4つありますが、それぞれ実行計画に特徴があるんです。(もしかしたら将来は区別しにくくなるかもしれませんが、現状は区別できます!!!

OR_EXPANヒントでUNION ALL変換した場合 Id = 1にあるような、インラインビューがは登場します。VW_ORE_5F0E22D2 とオプティマイザが動的に名称をつけますが、ポイントは VW_ORE_* というprefixが作ろころですね。VWはびゅー。OREは、OR_Expand の大文字部分みたいですねw (そのうち内部的に生成されるインラインビュー名もまとめて紹介したいですね。すでに誰かやってそうな気もしますがw)

実行計画
----------------------------------------------------------
Plan hash value: 3148130991

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

長くなったので、まとめです。
ヒントによる OR条件のUNIONのような書き換えや、UNION ALLへの書き換え、または、人が UNION や UNION ALLへ手書きで書き換えたSQLのレントゲン、それぞれに特徴があり、4つとも、レントゲンから元のSQLがイメージできるんですよ!!!

USE_CONCATによる書き換え Oracle 8i 8.1以降〜

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

 

OR_EXPANDによる書き換え Oracle 12cR2以降〜

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION ALL + 重複排除フィルター条件追加

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

 

 

レントゲンから諸々読み取るスキルは大切だと思っているので、みんなもレントゲンというなの実行計画は読んでみるといいよーーーっ。おすすめ。

 

では、次回へつづく

 

 



Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS

| | | コメント (0)

2022年4月 7日 (木)

実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS


Previously on Mac De Oracle
前回は外部表特有のoperationであるEXTERNAL TABLE ACCESS FULL / INMEMORY FULL のレントゲンでした。

今日は、昔からあるBITMAP CONVERSION TO ROWIDSを見てみたいと思います。

SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換
SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換 / 21c


このオペレーションは、複数の索引からbitmapを生成しその結果のrowidを用いて表をアクセスするところにあります。通常一つの索引が利用されますが、この場合は複数の索引が利用されるところが特徴です。
ただ、bitmapに変換コストより、unionに書き換えたり(内部的な書き換えも含む)したほうが効率が良かったりします。なので意外と嫌われてたりw なので、STAR TRANSFORM などで見るぐららいで、結構それ以外の方向へチューニングされているケースのほうが多いかもしれません。でもこれで問題なければそのままでも問題はないわけですが。

あ、そういえば、以前、CONCATENATIONのレントゲンを紹介していましたね。
ちょうどよいので、CONCATENATIONのレントゲン撮影時と同じ表とSQL文を使って BITMAP CONVERSION TO ROWIDS のレントゲンを見てみましょう :)

SCOTT@orclpdb1> desc tab311
名前 NULL? 型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

SCOTT@orclpdb1> select count(1) from tab311

COUNT(1)
----------
2000000

経過: 00:00:00.09

実行計画を見てわかると思いますが、 2つの索引(TAB311_PK, TAB311_IX_SUB_ITEM_CODE)のROWIDからBITMAPを作り(Id=3,7)、それを BITMAP OR (SQL文の7行目 Id=3)した結果をROWIDへ変換(Id=2)、複数のROWIDをまとめ、IOリクエストを少なくするための ROWID BATCHED(Id=1)で表(TAB311)をアクセスしていことが読み取れます。
ROWIDでアクセスするので、基本的に少量の行にアクセスする場合には有利ではあります。ただ、BITMAPへの変換コスト次第というところではあるわけです。なので、BITMAPの変換のないタイプのトランスフォームを狙ったHINTを利用したり、SQL文自体を書き換えたりするケースは少なくありません。意外に嫌いな方が多くてw 大抵チューニングされてしまい、あまり見かけることはないかもしれませんw 

SCOTT@orclpdb1> r
1 select
2 *
3 from
4 tab311
5 where
6 unique_id= 1
7* or sub_item_code = '0001000000'

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1263461875

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

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

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
1103 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

ちなみに、CONCATENATIONのエントリーを見ていただくのがよいとは思いますが、これも比較的古くからある、CONCATENATIONを使ったSQL変換のレントゲンも改めて載せておきます。
(USE_CONCATヒントで強制しています。みなさん、知っているとは思いますが、NO_EXPANDヒントが逆のヒントです)

実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 Day 17 / CONCATENATION
UNION のような実行計画ですが、UNIONとは出てませんw これはまた別の機会に。ただ、ほぼ同等の意味で、OR条件でそれぞれに最適な索引を使うことでindex range scanやindex unique scanを効かせて高速にアクセスしようとしています。
BITMAPとの相互変換などが無い分、安定して早いケースは経験的にも多いのは確かです。どちらを選ぶかはやはり、登録されているデータの傾向と検索条件次第ではあります。ただ一般的BITMAP変換を避ける傾向が強いのは確かではありますね。

SCOTT@orclpdb1> r
1 select
2 /*+
3 use_concat
4 */
5 *
6 from
7 tab311
8 where
9 unique_id= 1
10* or sub_item_code = '0001000000'

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 1344230703

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1091 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

GWも間近だ。そろそろ予定考えないとな。その前にACEのKPIはクリアしておかないと。追い込み追い込みw


ということで、次回へつづく。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

| | | コメント (0)

2022年4月 1日 (金)

実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

Previously on Mac De Oracle
前回は、ルーティーンとなったw、19cと20cのパラメータ差分チェックでした。

今日は、元の路線に戻り、「実行計画は、SQL文のレントゲン写真だ!」シリーズです :)

前回のパラメータ差分チェックで、外部表を利用していたので、18c以降で変更された In-Memory External Tables とそれまでの External Tableのレントゲンを見ておこうと思います。

利用するのはOaracle Database 21cですが、In-Memory External Tablesは、18c以降であれば使える機能なので使えるはず!


12cまでのnon In-Memory External Tablesなころのレントゲンからです。

外部表は EXTERNAL TABLE ACCESS FULL というオペレーションになっています。CSVファイルを全て読み込んでいることを表ています。ここ大切なので、覚えておきましょう。

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 2727K| | 629 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 8168 | 2727K| 2984K| 629 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 8168 | 2727K| | 30 (4)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 8168 | 2727K| | 30 (4)| 00:00:01 |
| 4 | VIEW | | 838 | 139K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 838 | 58660 | | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_11_1_0_7_0 | 8168 | 1363K| | 29 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

外部表をフルスキャンしている。実際にはcsvファイルをまるっと読んで、ですよねーー。という感じ。外部表を利用されたことのある方であれば、ふむふむというところだと思います。

では21cの環境に切り替えて、前回Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden paramsを行った sysユーザーに接続して違いを見ていきましょう。

まずは、インメモリーを使わない状態で見てみます。(rpmでインストールしてconfigureしただけの21cデフォルトの状態。。のはずw。 こちらではカスタマイズしてないので)

ビルド表とプローブ表が12cR1の実行計画と逆になってますが、まあ気にしないw
外部表は、12cR1と同様に EXTERNAL TABLE ACCESS FULL ですね。

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 Version 21.3.0.0.0

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5997 | 2002K| | 457 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 5997 | 2002K| 2192K| 457 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 5997 | 2002K| | 15 (7)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 5997 | 1399K| | 15 (7)| 00:00:01 |
| 4 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 5412 | 359K| | 14 (0)| 00:00:01 |
| 5 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 6 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
----------------------------------------------------------------------------------------------------------


では、外部表をインメモリー対応に作り変えます。
INMEMORY句を追加してる箇所がポイントですね。

DROP TABLE ksppi_19_3_0_0_0;
CREATE TABLE ksppi_19_3_0_0_0 (
ksppinm VARCHAR2(80)
,ksppdesc VARCHAR2(255)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '|'
(
ksppinm
,ksppdesc
)
)
LOCATION (
'19.3.0.0.0.ksppi.csv'
)
)
INMEMORY MEMCOMPRESS FOR CAPACITY
;


おおお?? ビルド表とプローブ表が入れ替わりましたが、外部表は、EXTERNAL TABLE ACCESS FULL のままですね。
なにか設定し忘れているようです。

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

ん、外部表自体のインメモリー定義は問題なさそうですね。

SYS@ORCLCDB> r
1 select table_name, inmemory, inmemory_compression
2* from user_tables where EXTERNAL = 'YES'

TABLE_NAME INMEMORY INMEMORY_COMPRESSION
------------------------------ ------------------------ ---------------------------------------------------
OPATCH_XML_INV DISABLED
KSPPI_19_3_0_0_0 ENABLED FOR CAPACITY LOW


ポピュレーションされてないのか?

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


あ”〜〜っ! Oracle ACEのKPI入力期限まであと3ヶ月ある。あせるなwwww 大丈夫だw(謎

では、手動で。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


ん? なんで?
あれか!

SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 0


やはり、だよね〜w デフォルトのままだもの。そりゃそうだ。

では、変更しましょう。inmemory_sizeは、最低 100MBは指定しないといけないので、今回は最低サイズ(十分だと思うので)に。

SYS@ORCLCDB> create pfile from spfile;

ファイルが作成されました。

SYS@ORCLCDB> alter system set inmemory_size = 100m scope=spfile;

システムが変更されました。

SYS@ORCLCDB> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@ORCLCDB> startup
ORACLEインスタンスが起動しました。

Total System Global Area 5586811432 bytes
Fixed Size 9697832 bytes
Variable Size 956301312 bytes
Database Buffers 4496293888 bytes
Redo Buffers 7077888 bytes
In-Memory Area 117440512 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 112M


よし!! これで大丈夫なはず。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED POPULATE_STATUS IS_EXTERNAL
------------------------------ ------------- ------------------- --------------------------------------- ---------------
KSPPI_19_3_0_0_0 1179648 0 COMPLETED TRUE

では、こんどこそ!!

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

あれ〜〜〜〜〜〜〜っ。 もう一つ忘れてた。query_rewrite_integrity.

Database Reference 2.294 QUERY_REWRITE_INTEGRITY
https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/QUERY_REWRITE_INTEGRITY.html

SYS@ORCLCDB> alter session set query_rewrite_integrity=stale_tolerated;

セッションが変更されました。


外部表のオペレーションが EXTERNAL TABLE ACCESS INMEMORY FULL に変化しました。

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------------


まとめ

18cで登場した In-Memory External Tables のレントゲンは以下の通り。
EXTERNAL TABLE ACCESS INMEMORY FULL というオペレーションがポイントです。
また、INMEMORY_SIZEパラメータの設定や、QUERY_REWRITE_INTEGRITYパラメータをstale_toleratedにする必要あります。忘れがち?!


21cでの non in-memory external tables と in-memory external tables のレントゲンをSQL MONITORの実行計画を。

non in-memory external tables : EXTERNAL TABLE ACCESS FULL

Global Stats
=====================================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
=====================================================================================================
| 0.06 | 0.05 | 0.00 | 0.00 | 0.00 | 406 | 121 | 1 | 512KB | 16 | 683 |
=====================================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (%) | (# samples) |
========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 6066 | | | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 1 | +1 | 1 | 6066 | | | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | 2MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 6 | EXTERNAL TABLE ACCESS FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 1 | +1 | 1 | 5412 | 1 | 512KB | 16 | | . | | |
========================================================================================================================================================================================

in-memory external tables : EXTERNAL TABLE ACCESS INMEMORY FULL

Global Stats
======================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
======================================================================================
| 0.04 | 0.04 | 0.00 | 0.00 | 0.00 | 406 | 48 | 16 | 683 |
======================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
==================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 2 | +0 | 1 | 6066 | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 2 | +0 | 1 | 6066 | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +0 | 1 | 6066 | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +0 | 1 | 6066 | | | 3MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 2 | +0 | 1 | 5412 | 16 | | . | | |
==================================================================================================================================================================================

レントゲンは大切 !!!! :)


東京は一気に葉桜になりつつある。。。ではまた。





Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

| | | コメント (0)

2022年3月26日 (土)

小ネタ : Live SQL! でもexplain plan を使う

Oracle Live SQL

でauto traceはできないけど、explain plan for文で解析した後に

 

SELECT plan_table_output FROMTABLE(DBMS_XPLAN.DISPLAY(upper('plan_table'),null,'typical'));


を実行すればできます!
新たな気づき :)

PLAN_TABLEは大文字にするところがポイントです!

ちなみに、

誰がどんな名前のペットを飼っているのかな? 解答編 / JPOUG Advent Calendar Day 23 
のSQL文は全て実行できた!
ただ、まだ 21cではなくて、19cですね 21c版出るのだろうか...

20220326-114537

| | | コメント (0)

実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

さて、実行計画のバリエーションの数だけ、レントゲンはありますよー(まだネタには余裕があるw)

ということで、今回は、12c前後で変化したところを見ておきたいと思います。

タイトルにも書いた

TEMP TABLE TRANSFORMATION
LOAD AS SELECT (CURSOR DURATION MEMORY)

は、12c以降のリリースから見られるWITH CTEで繰り返し利用されることが自明で性能改善につながると想定される場合に、一時表にマテリアライズされたときのオペレーションですよね。
(みなさんご存知だとおもいます。

12c以降のリリースしか利用したことのない方は、気づかないと思いますが、11gまで少々違いました。

TEMP TABLE TRANSFORMATION
  LOAD AS SELECT


違いといっても、CURSOR DURATION MEMORY があるかないかなのですがw、細かい改善の一つでだよね。と。


ちょうど良いネタなので、昨年末のAdvent CalendarのSQL文と実行計画でバージョン間のレントゲン写真の差を確認しておきましょう:)

まず、一つまえの 19cから。
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Id=2のオペレーションは、LOAD AS SELECT (CURSOR DURATION MEMORY) ですね。

もう一点、Predicate Informationセクションには、/*+ CACHE ("T1") */ というヒントが内部的に利用されていますね。ふーむ。
また、アダプティブな挙動もレポートされていないようですね。計画自体もindex only scanですし、まあ、想定通りというところですね。

参考までに、OPTIMIZER_DYNAMIC_SAMPLING = 2 と。デフォルトのままです。

CURSOR DURATION MEMORYの挙動については、
SQLチューニング・ガイド cursor-duration一時表

にあるように、シリアル実行では、PGAを利用するようですね。パラレルだと違うのか... でいずれもメモリ上に乗らなくなると、一時セグメントがストレージ上に確保されると記載されているのでTEMP表領域が利用されそうですね。
メモリに余裕があり、かつ、繰り返し参照されるケースでは効果はありそうですよね。ストレージに落ちてしまうと、direct path read from tempが発生するでしょうし、ストレージを繰り返しアクセスするかしないかの違いは大きいかも。
とはいえ、WITH句のCTEで性能改善を狙うケースでポイントになるところは同じなので、その点は忘れないようにしておきたいところですね。(例外は巨大なSQLで可読性向上を目的とした場合ぐらい)

Execution Plan
----------------------------------------------------------
Plan hash value: 3964084889

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 553 | 12 (0)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6640_6FF953 | | | | |
| 3 | WINDOW SORT | | 7 | 119 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 119 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 7 | 77 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0012896 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0012900 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0012898 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6641_6FF953 | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | VIEW | | 7 | 553 | 2 (0)| 00:00:01 |
| 19 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 20 | FILTER | | | | | |
| 21 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 23 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 24 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 25 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 26 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

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

7 - filter("NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "NAME"='Steve' AND "ANIMALS"."KIND"<>'Dog' AND
"ANIMALS"."KIND"<>'Snake' OR "NAME"='Hiro' AND "ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR
"NAME"='Larry' AND "ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("NAME"='Wendy' AND "KIND"='Dog' OR "NAME"='Tiger' AND "KIND"<>'Dog' AND
"KIND"<>'Turtle' OR "NAME"='Lisa' AND "KIND"<>'Snake' AND "KIND"<>'Dog' OR "NAME"='Taro' AND "KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND
NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2"
"PET_NAME","C3" "ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
20 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT
EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
23 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
922 bytes sent via SQL*Net to client
2090 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed

では次、12R1の場合です。(12R2は19cと同じなので省略)
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

基本的にデフォルト設定のままなので、 OPTIMIZER_DYNAMIC_SAMPLING = 2 でもろもろ止めたりしていませんw

12cR2以降との違いの1つめ!
12cR1では、CURSOR DURATION MEMORY オペレーションがありません!! 

12cR2以降との違いの2つめ!
/*+ CACHE ("T1") */ではなく、/*+ CACHE_TEMP_TABLE ("T1") */ という一時表専用のヒントが担っている点。
CACHE_TEMP_TABLEヒントって解説がないヒントなのですが、12cR2以降はCACHEヒントという解説のある通常の表と同じヒントに置き換えられいますね。一時表だけ特別なわけではないので統一したのでしょうか(中の人のみぞ知るw)

Note部分にもでてますが、動的統計とプランディレクティブが動いてますね。再起コールが減らないのもその影響のようです。19cの挙動とは興味深い違いですね。データ量が多くなった場合にどう変化するのかなという気はしますがw

ちなみに、この挙動は、11cR2とくらべて、プランディレクティブが無い以外の挙動は同じ。


Execution Plan
----------------------------------------------------------
Plan hash value: 3787387246

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6647_566AED | | | | |
| 3 | WINDOW SORT | | 8 | 136 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 8 | 136 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 8 | 88 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0014925 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0014929 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0014927 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT | SYS_TEMP_0FD9D6648_566AED | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 19 | FILTER | | | | | |
| 20 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 24 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 25 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |

--------------------------------------------------------------------------------------------------------

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

7 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR
"PETS"."NAME"='Tiger' AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR
"PETS"."NAME"='Lisa' AND "ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR
"PETS"."NAME"='Taro' AND "ANIMALS"."KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
19 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1
AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
22 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
24 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- 5 Sql Plan Directives used for this statement

Statistics
----------------------------------------------------------
4 recursive calls
36 db block gets
126 consistent gets
2 physical reads
1160 redo size
811 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


そして、最後は、私の環境で最も古いOracle 11.1.0.7

Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production

わかっちゃいたけど、オプティマイザの進化が見えて嬉しいですねw

なかなか苦しい実行計画ですね。これは。。。w

Index only Scanにはなっていますが、マテリアライズする部分の実行計画が.. オプティマイザありがとう。(主に最新版のほうですけどw)

本題にもどると、このリリースではCURSOR DURATION MEMORYがないのは当然ですが、CACHE_TEMP_TABLEヒントが利用されているという点に関しては、12cR1と同じ
そして、アダプティブな挙動も一切ない自体の実行計画ですね。ここには戻りたくないですよね。みなさんw

アダプティブな挙動はないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw

Plan hash value: 3067991639

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 14 (15)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 8 | 240 | 9 (12)| 00:00:01 |
| 4 | CONCATENATION | | | | | |
| 5 | NESTED LOOPS | | 7 | 210 | 7 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 9 | 225 | 7 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 5 | 100 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 9 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 10 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 11 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 12 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 13 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 14 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 15 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 16 | NESTED LOOPS | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | NESTED LOOPS | | 2 | 50 | 1 (0)| 00:00:01 |
| 18 | NESTED LOOPS | | 1 | 20 | 0 (0)| 00:00:01 |
| 19 | NESTED LOOPS | | 1 | 15 | 0 (0)| 00:00:01 |
| 20 | NESTED LOOPS | | 1 | 10 | 0 (0)| 00:00:01 |
|* 21 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 22 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
|* 23 | INDEX UNIQUE SCAN | SYS_C009962 | 4 | 20 | 0 (0)| 00:00:01 |
|* 24 | INDEX UNIQUE SCAN | SYS_C009960 | 4 | 20 | 0 (0)| 00:00:01 |
|* 25 | INDEX FULL SCAN | SYS_C009964 | 2 | 10 | 1 (0)| 00:00:01 |
|* 26 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
| 27 | LOAD AS SELECT | | | | | |
| 28 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 29 | FILTER | | | | | |
| 30 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 32 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 33 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 34 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 35 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 36 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 37 | FILTER | | | | | |
| 38 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 39 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 40 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 41 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 42 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

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

11 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
12 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
13 - access("PETS"."NAME"="PETS"."NAME")
filter("PETS"."NAME"<>'Tiger')
14 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
15 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"<>'Hiro')
21 - access("PETS"."NAME"='Tiger')
22 - access("OWNERS"."NAME"='Hiro')
filter(LNNVL("OWNERS"."NAME"<>'Hiro') OR LNNVL("PETS"."NAME"<>'Tiger'))
23 - access("PETS"."NAME"="PETS"."NAME")
24 - access("OWNERS"."NAME"="OWNERS"."NAME")
25 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
26 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
29 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
32 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
34 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
37 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
40 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
42 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
85 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


アダプティブな挙動がないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw
NO_EXPANDヒントでCONCATENATIONを抑止してみましたw
それでもイマイチだ。古いオプティマイザとの挙動の違いをみると、ほんと、最新版のオプテマイザの良さが身にしみますw

Plan hash value: 2841796482

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 7 | 210 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 210 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 12 | 300 | 8 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 7 | 140 | 4 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 9 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 11 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 12 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 13 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 14 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 15 | LOAD AS SELECT | | | | | |
| 16 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 17 | FILTER | | | | | |
| 18 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 19 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 20 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 24 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 25 | FILTER | | | | | |
| 26 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 28 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 30 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

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

10 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
11 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
12 - access("PETS"."NAME"="PETS"."NAME")
13 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
14 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger')
17 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
20 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
22 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
28 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
30 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
84 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


SQLのレントゲン写真、バージョンと共に基本形wが変化していくので、常に差分と最新の内容を把握しておきたいですよね。いざ、診療するとなったときには役に立つ、はず!!

今回利用したSQL文や表定義およびデータは、誰がどんな名前のペットを飼っているのかな? 解答編 / JPOUG Advent Calendar Day 23を参照ください。

では、また。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT

| | | コメント (0)

2022年3月21日 (月)

Oracle vagrant-projects

今日は約3ヶ月振りにVIrtualBoxの起動やら、Oracle VMの起動でハマって、このあたりの環境どうしようかなあぁ

と思いつつ 21cとかもあるしなぁと考えていたら。横目でみつつも、大人の事情でスルーしていたやつが結構育ってて来てたので、これやるのもいいかなぁと。まだ軽く調べ始めたところだが、来月早々にメインマシン変えるからそのあとな。

 

ひとまず、メモ 

Oracle Vagrant-projects

https://github.com/oracle/vagrant-projects

 

20220321-203926

| | | コメント (0)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #13

随分間が空いてしまったのです、覚えてますかねw 前回のネタ。私もほぼ忘れてました orz. w

思い出しながら進めましょう:)

Previously on Mac De Oracle
前回は、CTASだと、内部的にdirect path writeされてしまうのでクリーンアウトが必要な状況にはならないという動きを確認しました。

scattered read同様に、sequential readだとどうなの?(という遠くから声が聞こえたきがしたのでw)

とはいえ、遅延ブロッククリーンアウトって、自分の理解だと、全表走査時の動きだと思っているので、多分、起きないよなーと。頭の上に雲型の吹き出した出た状態でイメージしているところ。


では、試してみましょう。

これまでと異なる箇所は、sequential read でぐるぐるループさせたいので、該当表に主キー索引を作成します。また、table full scanではなく、index unique scan で全行アクセスさせてみます。(この動きが大きく異なる部分です)





strong>0) 対象表のdrop/create と主キー作成
対象表のHOGE2は削除して作り直し。このテストケースでは主キーアクセスさせるため、合わせて主キーも作成しておきます。
SCOTT@orcl> @droppurge_create_hoge2_with_pk.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.51
1* create table hoge2 (id number, data varchar2(2000), constraint pk_hoge2 primary key (id) using index)

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.13


1) 統計をクリアするためOracle再起動


$ sudo service oracle restart

[sudo] password for oracle:

Restarting oracle (via systemctl): [ OK ]



2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略! (思い出したいかたはこの辺りを参照ください。。


4) PDBのSCOTTユーザーでNOAPPENDヒント付きIASを実行(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ)/ 未コミット

NOAPPENDヒントを利用している理由は前回までの内容を見ていただければわかりますが、direct path writeさせたないためです。これによりコミットクリーンアウトではクリーンアウトできない状況、つまり、遅延ブロッククリーンアウトが必要となる状態を作っています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:19.63
14:32:40 SCOTT@orcl>


5) CDBのSYSで統計取得(未コミット)

検証中に統計値が変動したもののみ記載

狙い通りdirect path writeは抑止できていることは確認できます。
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22,918
sysstat DBWR checkpoints 72
sysstat DBWR thread checkpoint buffers written 22,918
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 983
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 1,601
sysstat commit cleanouts successfully completed 1,601
sysstat consistent gets 122,604
sysstat db block changes 564,554
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 70,607
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 1,752
sysstat no work - consistent read gets 67,108
sysstat physical reads 66,750
sysstat physical reads direct 66,709
sysstat physical writes 22,918
sysstat physical writes from cache 22,918
sysstat physical writes non checkpoint 22,918
sysstat redo blocks written 967,108
sysstat redo size 479,412,088
sysstat redo writes 378

(PDB) SCOTTのセッション統計
こちらから見てもdirect path writeは発生していません

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1,589
sesstat commit cleanouts successfully completed 1,589
sesstat consistent gets 122,204
sesstat db block changes 564,506
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 70,595
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 1,748
sesstat no work - consistent read gets 66,884
sesstat physical reads 66,740
sesstat physical reads direct 66,709
sesstat redo size 478,945,336


該当期間の待機イベント記録されていないですよね。狙い通りなので準備としては問題なしです。

EVENT                                                            WAIT_CLASS
---------------------------------------------------------------- -----------------
SQL*Net message from client Idle
log buffer space Configuration
events in waitclass Other Other
direct path read User I/O
log file switch (private strand flush incomplete) Configuration
log file switch completion Configuration
log file sync Commit
db file sequential read User I/O
SQL*Net message to client Network
Disk file operations I/O User I/O
db file scattered read User I/O

6) CDB or PDBのSYSユーザで接続し、buffer cacheをflushする

SYS@orclcdb> @flush_buffercache.sql
1* alter system flush buffer_cache

System altered.

Elapsed: 00:00:09.04
14:36:26 SYS@orclcdb>


7) CDBのSYSで統計取得(buffer cacheをflush後)
strong>(CDB)システム統計

検証中に統計値が変動したもののみ記載

バッファキャッシュをフラッシュしたのでキャッシュから書き出されたことを示す physical writes from cache および関連する統計値が上昇しています。これも想定通りの挙動ですね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 8
sysstat DBWR undo block writes 1912
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 43
sysstat commit cleanouts successfully completed 43
sysstat consistent gets 20793
sysstat db block changes 429
sysstat deferred (CURRENT) block cleanout applications 21
sysstat free buffer requested 1013
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 9
sysstat no work - consistent read gets 12321
sysstat physical reads 977
sysstat physical reads direct 0
sysstat physical writes 47900
sysstat physical writes from cache 47900
sysstat physical writes non checkpoint 47900
sysstat redo blocks written 2017
sysstat redo size 994652
sysstat redo writes 23

(PDB) SCOTTのセッション統計

キャッシュをフラッシュしただけなので、該当セッションの統計には変化がありません。(まあ、そうですよねw)


SOURCE NAME VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistentread gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 0
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 0
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 0
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 0
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 0

8) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.

Elapsed: 00:00:00.01


9) CDBのSYSで統計取得(コミット後)
strong>(CDB)システム統計
コミット時点ではクリーンアウト仕切れないほどの更新ブロック数かつ、コミット前にバッファキャッシュをフラッシュしているので、コミット時のクリーンアウトもできない状態なっています(これも事前の準備の通りの結果。なので問題なし)
commit cleanouts と commit cleanout failures: block lost がポイントです。コミット時にクリーンアウトできる量(過去の記事を参照のこと)の閾値以下の量ですら、事前にキャッシュからフラッシュしまっているので、クリーンアウトに失敗(できないようにしたので当然ですが)しています。両方の統計値が同一であるとくことは、コミットクリーンアウトしようとした全てでくりアウトできなかった。理由はキャッシュに該当ブロックがキャッシュから落ちていたから(意図的に落としたわけですが)ということを意味します。予定通りですね。

コミットの時点で最低現おこなうクリーンアウトもさせていないですが、このケースでは 11140 ブロックをコミット時にクリーンアウトしようとしているので、コミット時のクリーンアウトが行われていたとしても 50,000 ブロック以上は遅延されていたことになりますね。今回は、 66,667 ブロックがク後続のSQLで参照されるついでにクリーンアウトされることになります。

検証中に統計値が変動したもののみ記載

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 0
sysstat DBWR undo block writes 0
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 11140
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 11140
sysstat commit cleanouts successfully completed 0
sysstat consistent gets 34
sysstat db block changes 1
sysstat deferred (CURRENT) block cleanout applications 0
sysstat free buffer requested 8
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 0
sysstat no work - consistent read gets 5
sysstat physical reads 8
sysstat physical reads direct 0
sysstat physical writes 0
sysstat physical writes from cache 0
sysstat physical writes non checkpoint 0
sysstat redo blocks written 3
sysstat redo size 456
sysstat redo writes 3

(PDB) SCOTTのセッション統計
セッションレベルでみるよりはっきり見るとことができます。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 11140
sesstat commit cleanouts 11140
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 1
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 1
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 1
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 176


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表を主キーによる一意検索で全行アクセス)

さて、事前に想定している実行計画になっているかプランだけ確認しておきましょう。主キーによる索引ユニークスキャンであることが確認できます。想定通りです。この実行計画で、200,000 行をグルグル参照した場合、遅延されたブロッククリーンアウトが行われるでしょうか。。。。(してくれないと思いますが。。。試してみましょう)

SCOTT@orcl> set autot trace exp
14:41:47 SCOTT@orcl> select * from hoge2 where id = 1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3319133482

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1015 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE2 | 1 | 1015 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_HOGE2 | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

2 - access("ID"=1)

SCOTT@orcl> set autot off
SCOTT@orcl>
SCOTT@orcl> @table_unique_scan_hoge2.sql
1 begin
2 for i in 1..&1 loop
3 for hoge2_rec in (select * from hoge2 where id = i) loop
4 null;
5 end loop;
6 end loop;
7* end;
Enter value for 1: 200000
old 2: for i in 1..&1 loop
new 2: for i in 1..200000 loop

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.94
14:39:36 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

クリーンアウトが行われていれば、データブロックの総数 66,667 ブロックは少なくともクリーンアップの対象になっているはずですが。。。。。
cleanout系の統計値に動きはありませんよね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 21
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 5
sysstat DBWR undo block writes 5
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 366
sysstat commit cleanouts successfully completed 351
sysstat consistent gets 632775
sysstat db block changes 7848
sysstat deferred (CURRENT) block cleanout applications 169
sysstat free buffer requested 76662
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 169
sysstat no work - consistent read gets 21822
sysstat physical reads 69501
sysstat physical reads direct 0
sysstat physical writes 21
sysstat physical writes from cache 21
sysstat physical writes non checkpoint 10
sysstat redo blocks written 5132
sysstat redo size 2536084
sysstat redo writes 28

(PDB) SCOTTのセッション統計

physical reads は、67706 ブロックなのでほ、HOGE2表の索引と表のセグメントサイズほどは物理読み込みされたということになります。ただ、cleanouts only - consistent read gets などのクリーンアウトを示す統計値はほぼ動いていません。ようするに、db file sequential readとなる index unique scan ではブロッククリーンアウトは発生しないということを意味しています。

おおおーーー。だったら、グルグル系の処理で一行ごとに処理するのが最高じゃん! という単純な話ではないので要注意ですからねw

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 4
sesstat commit cleanouts successfully completed 4
sesstat consistent gets 600005
sesstat db block changes 14
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 74540
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 0
sesstat physical reads 67706
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 2152


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(sacattered read)

single block readを行うindex unique scanではクリーンアウトが発生しなかったということは、全表走査させる table full scan かつ scattered readさせれば発生するのではないでしょうか? という確認です。
auto traceで統計情報をみると、なにやら、REDOログが大量に生成されてますよね。。。これは!!!

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.55

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
10 recursive calls
1 db block gets
147157 consistent gets
377 physical reads
5866968 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

14:56:56 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 1121
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 36
sysstat DBWR undo block writes 347
sysstat cleanouts only - consistent read gets 66676
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 806
sysstat commit cleanouts successfully completed 806
sysstat consistent gets 174296
sysstat db block changes 80773
sysstat deferred (CURRENT) block cleanout applications 383
sysstat free buffer requested 2095
sysstat immediate (CR) block cleanout applications 66676
sysstat immediate (CURRENT) block cleanout applications 75
sysstat no work - consistent read gets 27953
sysstat physical reads 1842
sysstat physical reads direct 0
sysstat physical writes 1121
sysstat physical writes from cache 1121
sysstat physical writes non checkpoint 981
sysstat redo blocks written 16550
sysstat redo size 8074260
sysstat redo writes 351

(PDB) SCOTTのセッション統計
HOGE2の表のクリーンアウト対象ブロック数、66,667 きっちり同じ数字が現れました。しかも cleanoutに関わる統計でかつ、大量のREDOログも生成されています。でました〜〜〜w クリーンアウト。全表走査かつ、scattered readで。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 66667
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1
sesstat commit cleanouts successfully completed 1
sesstat consistent gets 153731
sesstat db block changes 66718
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 690
sesstat immediate (CR) block cleanout applications 66667
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 16317
sesstat physical reads 680
sesstat physical reads direct 0
sesstat redo size 5877252




まとめ

index unique scanでグルグルさせつつ、全行アクセスしても発生しないですよね!
single block readになるunique index scanでは、遅延ブロッククリーンアウトは発生しない。(想像通りでよかったw)
で、そのあとで全表走査(scattered read)させてみると、やはり、遅延ブロッククリーンアウトは発生するわけですよ。面白いです。ほんと。



さあ、5月末まで追い込みだw ブログ書かないとw (頑張って、アドベントカレンダー全部俺やっとくほうが実は楽なのかもしれないw)

では、また。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12



| | | コメント (0)

2021年9月25日 (土)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12

Previously on Mac De Oracle
前回、前々回とIASでdirect path writeであるか否かで、そのコミットおよび遅延ブロッククリーンアウトの有無が決まるという動きを確認しました。

今回は、DDLですが、親戚みたいな挙動のCTASではどうなるか確認しておきます。CTASはDDLなのでコミットは不要ですよね。また、 direct path writeが前提になっていることも皆さんご存知の通り。

ということは、IAS + APPENDヒントで、direct path write させた時と同じような挙動になるはず。。。。ですよね。

手順はいつもの図でご確認ください。(DDLなので手順も単純になっています)
Ctas_steps




0) 対象表のdrop
対象表のHOGE2は削除しておきます。CTASで作成することになるので。

SCOTT@orcl> @droppurge_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:02.79


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでCTAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ

SCOTT@orcl> @ctas_from_hoge.sql
1* create table hoge2 as select * from hoge

Table created.

Elapsed: 00:00:07.41


5) CDBのSYSで統計取得(CTAS後)
DDLなのでコミットはありませんが、念の為に確認すると、commit cleanouts、commit cleanouts successfully completed はほんの少しだけ。このテストケースではノイズ程度の量です。
DDL終了時にクリーンアウトは発生していないと読み取れます。(この後の手順で遅延クリーンアウトも発生していなければ。direct path writeではクリーンアウトが必要な状態にはならないということは間違いないと判断するできますよね)

参考程度ですが、physical writes 、physical writes direct、physical writes non checkpoint が同数です。物理書き込みが発生し、かつ、direct path writeでチェックポイントで書き出されたものではないということが確認できます。想定通り、CTASは direct path writeで書き出されているということがわかります。
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは以前確認した通りの値です)

また、physical reads と physical reads direct は、HOGE表のブロック数以上あり、HOGE表は direct path readでお読み込まれていることがわかります。このときのfree buffer requested は非常に低いことは、バッファキャッシュを介さず、ストレージへ書き出されていることを示しています。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 21
sysstat commit cleanouts successfully completed 21
sysstat consistent gets 72654
sysstat db block changes 3898
sysstat deferred (CURRENT) block cleanout applications 7
sysstat free buffer requested 819
sysstat immediate (CURRENT) block cleanout applications 8
sysstat no work - consistent read gets 69569
sysstat physical reads 67150
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 21
sesstat commit cleanouts successfully completed 21
sesstat consistent gets 72366
sesstat db block changes 3898
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 818
sesstat immediate (CURRENT) block cleanout applications 8
sesstat no work - consistent read gets 69415
sesstat physical reads 67149
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

IASで direct path writeさせた場合同様に、direct path writeで書き出された場合、クリーンアウトするブロックは存在しないため、遅延ブロッククリーンアウトも発生しないという状況になります。
REDOは多少生成あれていますが、recursive call 等によるものと考えられ、このテストケースではノイズの類程度です。システム/セッション統計値をみることでその点も確認できます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.66

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
14 recursive calls
12 db block gets
80022 consistent gets
66669 physical reads
2108 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


7) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
deferred (CURRENT) block cleanout applications や、immediate (CURRENT) block cleanout applicationsなど遅延ブロッククリーンアウトで動く統計に極わずかに動きがありますが、数ブロックなので気にする程度ではないです。遅延ブロッククリーンアウトは発生していないと読み取れます。

また、physical reads は該当表のブロック数程度のブロック数となっており、physical reads direct が発生していないことから、 scattered read でバッファキャッシュに載せられたことが確認できます。free buffer requested もほぼ同じ値になっていることからも同様のことが言えます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105725
sysstat db block changes 255
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 67608
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 94138
sysstat physical reads 67567

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86719
sesstat db block changes 43
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66791
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82976
sesstat physical reads 66783




まとめ

CTASはDDLで、 direct path write を伴うため、IASのAPPEND同様、対象データブロックはクリーンアウトが必要な状態にはならず、コミットおよび遅延ブロッククリーンアウトは発生しない!
ことが確認できました。 :)

次回は、一旦中締めにしますか。

つづく。


目がショボショボしてるから、なにか浮遊物の影響を受けてる気がする。ブタクサやヨモギの季節だもんな。(目だけなんだよなー。アレルギーの酷方のように鼻水でたりとかではなくて。疲れ目用の目薬では、効かないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11



| | | コメント (0)

2021年9月24日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11

Previously on Mac De Oracle
前回は、クリーンアウトの後始末をさせられる側ではなく、そもそも、後始末不要な状況もあるという確認でした。
IAS(Insert as Select)でdirect path writeで書き込まれたブロックは、クリーンアウトする必要がない状態なので、コミットクリーンアウトも遅延ブロッククリーンアウトも発生していないことはシステム統計やセッション統計からも明らかでした。

今回は、今一度確認ということで、IASでも非direct path writeだったら、やはりコミットクリーンアウトや遅延ブロッククリーンアウトは発生するよね! というところだけは見ておこうと思います。

手順は前回と同じ。前回と異なる点は、4) の部分。IASで NOAPPEND ヒントを使い direct path writeを抑止している部分のみ。
Iasstep





0) 対象表のdrop/create

SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.67
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.05
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.16


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは 非direct path write でINSERTしたいので NOAPPEND ヒントで direct path write を抑止しています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:16.79


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へ非direct path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは 非direct path write していることが読み取れます。
free buffer requested で読み込みブロック相当のブロックがバッファキャッシュへ載せられているように見えますが、読み込みは direct path read なのでバッファキャッシュには載りません。ようするに、HOGE2表向けのデータと考えることができますよね。このキャッシュされたブロックが本当にHOGE2表向けブロックであるかどうかは、これ以降の操作で物理読み込みが発生しないということで確認することができます。


(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前々回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 19158
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 19158
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 299
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 110098
sysstat db block changes 443304
sysstat deferred (CURRENT) block cleanout applications 5
sysstat free buffer requested 67998
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 66823
sysstat physical reads 66730
sysstat physical reads direct 66709
sysstat physical writes 19158
sysstat physical writes from cache 19158
sysstat physical writes non checkpoint 19158
sysstat transaction tables consistent reads - undo records applied 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 109971
sesstat db block changes 443304
sesstat deferred (CURRENT) block cleanout applications 5
sesstat free buffer requested 67996
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 66781
sesstat physical reads 66730
sesstat physical reads direct 66709


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
APPENDモードのIASと異なり、NOAAPENDモードでは、はやり、コミットクリーンアウトが発生しています。バッファキャッシュの10%-15%の範囲のブロックがコミット時にクリーンアウトされている状況が commit cleanouts successfully completed および、commit cleanouts から確認することができます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 55709
sysstat commit cleanouts successfully completed 55709
sysstat consistent gets 18547
sysstat db block changes 289
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 791
sysstat no work - consistent read gets 10937
sysstat physical reads 758
sysstat transaction tables consistent reads - undo records applied 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

direct path writeと非direct path writeの違いはハッキリでました。REDOログは大量に生成され、遅延ブロッククリーンアウトは発生しているようです。
また、物理読み込みは発生していないので、バッファキャッシュにヒットしているという状況も読み取れます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:05.74

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
19 recursive calls
13 db block gets
91462 consistent gets
2 physical reads
967388 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed
¥
set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
実行統計の示す通り、遅延ブロッククリーンアウトが発生していることは、immediate (CR) block cleanout applications、cleanouts only - consistent read getsのブロック数からもハッキリ確認することができます!(コミットクリーンアウトできなかったブロック数です)

physical readsはほんの少しありますが、発生していないとみなしても良い程度です。このケースはscattered readが発生していない。つまり、非direct path writeでバッファキャッシュに載ったブロックにヒットしていることで、scattered readの必要がなかった! ということを意味しています。もし、この時、バッファキャッシュから該当表のブロックがある程度落ちていれば、physical readsが表のブロック数程度まで増加していたはずです。(多数の同時実行トランザクションが存在する状況であればキャッシュからエージアウトされ、物理読み込みが大量に発生するというケースは珍しくありません。その分処理時間も長くなるわけで、程度とデータサイズサイズしだいで処理時間も延びることが予想できます。場合よっては、処理時間がかかりすぎて、ザワザワしたり。。。w)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 19
sysstat commit cleanouts successfully completed 19
sysstat consistent gets 98619
sysstat db block changes 11065
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 180
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 72340
sysstat physical reads 169

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98015
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 121
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72013
sesstat physical reads 113




まとめ

IASで 非direct path write してINSERTされた場合、ブロッククリーンアウトが通常通り発生する(コミット時でも遅延でも)
direct path write時との統計値の違いからも明らかですね。

前回のIAS(APPEND)時の遅延ブロッククリーンアウトがなかったケースの統計を再掲しておきます。統計値の違いをよーく確認してみてください。(試験に。。。でないですけどw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770

次回は、CTAS ( create table as select ) ではどうなるか確かめます。(まだいくつかの関連統計を動かせていないケースありw)

次回へつづく。


東京は、自転車での移動や買い物のほうが渋滞や混雑のストレスがなくていいな。久々に coutry side側をドライブしてたが、ノーストレレスだった。一転機能は、買い物でドライブするも、いちいち渋滞w
WfHも年単位になると、すでに都心の満員電車や渋滞に耐えられない感じになってる気がしないでもないw (渋滞でスタックしている時間がもったいないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10



| | | コメント (0)

2021年9月22日 (水)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10

Previously on Mac De Oracle
前回は、SELECT文であっても遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログも生成される。ただし、direct path read で読み込まれた場合を除く。
という動きをみました。

今回もdirect pathがらみです。といっても direct path write だったら、コミットクリーンアウトや、遅延ブロッククリーンアウトはどういう扱いになるのだろう。。。と
(このシリーズの初回で紹介したいろいろなブログに答えはあるのですがw)

システム統計やセッション統計の統計値から、それをどう読み取るか。γGTP高いから肝臓あたりに問題があるか、検査前日に酒飲んじゃったでしょ! 的なところを診て、なにがおきているか診察していくシリーズなので、それぞれの統計と、auto traceによる実行計画と実行統計を診ていくわけですが。。w


ということで、今回は、前回利用していた表を元にIASで別表を作成し、コミット前後の状況を診ていきたいとおもいます。IASと言っても direct path write になるケースと従来型のロードがあるのは皆さんご存知だと思いますが、まずは、 direct path writeの方から挙動を診ていくことにします。手順はざっとこんな感じ
Iasstep

そういえば、以前、IASで、direct path write かどうか判別しやすくなったよねーというネタをやってましたね。実行計画だけでも違いがわかりやすくなっているので便利になりました。:)

実行計画は、SQL文のレントゲン写真だ! No.30より、LOAD TABL CONVENTIONAL vs. LOAD AS SELECTの実行計画の違い。
20210213-150833


20210213-150416





0) 対象表のdrop/create
SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.74
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.15

1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは direct path write でINSERTしたいので APPEND ヒントで direct path write を強制しています。 direct path read とは異なり、ヒントで制御できるのは楽w

SCOTT@orcl> @ias_from_hoge.sql
1* insert /*+ append */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:10.42


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へdirect path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは direct path write している状況がはっきりでていてわかりやすい結果を得られました :)
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 72460
sysstat db block changes 3131
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 792
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 69328
sysstat physical reads 67121
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 16
sesstat commit cleanouts successfully completed 16
sesstat consistent gets 72373
sesstat db block changes 3131
sesstat deferred (CURRENT) block cleanout applications 12
sesstat free buffer requested 792
sesstat immediate (CURRENT) block cleanout applications 3
sesstat no work - consistent read gets 69291
sesstat physical reads 67121
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
direct path writeでバッファキャッシュを経由せず書き出されたブロックはコミット時にはクリーンアウトの対象にはなっていないようですね。。。。統計をみる限りノイズ程度ですね。
ということは全てのブロックが遅延ブロッククリーンアウト対象になってしまうのか、または、その逆で、最初からクリーンアウト対象にもなっていないかということになります。次の全表走査の結果でどちらであるか、わかるはずです!!!!(ニヤニヤ、それ、ねらってやってるので、答えは知っているわけですがw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 25
sysstat commit cleanouts successfully completed 25
sysstat consistent gets 12104
sysstat db block changes 1557
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 714
sysstat immediate (CURRENT) block cleanout applications 4
sysstat no work - consistent read gets 7192
sysstat physical reads 705

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 864
sesstat db block changes 1474
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 37
sesstat no work - consistent read gets 351
sesstat physical reads 32


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

REDOログは多少生成されていますが、実際に遅延ブロッククリーンアウトが発生した場合REDOログ量この程度では少なすぎますよね。
今回の検証目的からするとノイズの類ですね。むむむ。これは。。。。
(物理読み込みは発生しているので、direct path read か、scattered readのどちらかということにはなります。期待している動きは、Scattered read 。)

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.84

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
13 recursive calls
12 db block gets
80022 consistent gets
66668 physical reads
2284 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
physical reads は想定通りのブロック以上になっていますが、physical reads direct は発生していないので、scattered readによる全表走査であると読み取ることができます。
ただ、この状態でも、遅延ブロッククリーンアウトの発生を示す統計値はノイズ程度の値です。

つまり、direct path write でINSERTされたデータブロックはクリーンアウトが必要な状態だということになりますね。興味深い動きですよね。メモしておいたほうが良さそうです :)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770




まとめ

IASで direct path write してINSERTされた場合、ブロッククリーンアウトは発生しない!(コミット時でも遅延でも)
そもそもクリーンアウトが必要な状態になっていないというのが正しいのでしょうね。統計値としては全く動いてないに等しいので。

これも試験にでますよ(嘘w

次回は、同じIASもdirect path write ではないケースではどうなるでしょう。。。(ここまでのエントリーを読んでいる方は、結果は想像できそうではありますが)

次回へつづく。


遅めの夏休みですが、まあ、普段とちがうのは、いろいろなタスクの締め切りに追われずに、マイペースな時間の過ごし方になるぐらいだな。この状況下ではw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9



| | | コメント (0)

2021年9月21日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9

Previously on Mac De Oracle
バッファキャッシュから溢れ出る程度のデータを登録しコミットした場合は、コミットクリーンアウトがどうなるか、遅延ブロッククリーンアウトされるブロック数はどの程度になるかという、寄り道でしたw

今回は、寄り道し過ぎて忘れるところだった、前回まで、scattered readを伴うtable full scanで発生していた遅延ブロッククリーンアウトが direct path read だったらどうなるか、というシリーズ :) 
(まだ続くのかーーーっ! はいw いろんなケースがありますから。シンプルなケースであっても。それぞれの基本的な挙動を知っいて損はないとおもいます。 複雑なケースだと脳汁出過ぎるくらい複雑なので考えたくもなくなるのでw)

では、早速再現してみましょう。手順はこれまで行なってきたとおりで、違いは遅延ブロッククリーンアウトを発生させるためのtable full scanでdirect path readさせるという部分のみ。手順はscattered readとの比較も入れるので長くなってしまうので、追加ステップを追記した図を見てもらうと何やっているか、流れは理解しやすいかもしれません。Steps


事前準備
バッファキャッシュのサイズは元のサイズに戻してあります

SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

200,000行登録したデータ(セグメントサイズ農地純粋にデータが乗っているブロック数)が乗っているブロック数はこんなところ。セグメントサイズはこれより多いですよ。行データが載っているブロックだけカウントしているので。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ということで、セグメントサイズも確認。

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS
------------------------------ ----------
HOGE 67584

バッファキャッシュの10-15%程度はコミットクリーンアウトされるので、間をとってこれぐらいはコミットクリーンアウトされる。。。

SCOTT@orcl> select 3472883712 / 8192 * 0.13 from dual;

3472883712/8192*0.13
--------------------
55111.68

残りは遅延ブロッククリーンアウトする。だいだいこんなもん。

SCOTT@orcl> select 66667 - 55112 from dual;

66667-55112
-----------
11555

direct path read狙いの全表走査(シリアル実行で発動させることを意図していますが、言うこときいてくれるかあなぁ)

$ cat table_full_scan_with_dpr.sql
alter session set "_serial_direct_read" = always
.
l
/
alter session set "_very_large_object_threshold" = 512
.
l
/

!echo set autot trace exp stat
set autot trace exp stat

select * from hoge
.
l
/

!echo set autot off
set autot off

Scattered Read狙いの全表走査のスクリプト

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 20400
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




さて、うまく再現できるかどうか。。(今回もやったことをほぼすべて載せているので長いです)


0) 対象表をdrop/create
オブジェクトを作り直し前提合せ

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでINSERT(データ量2倍、コミットなし)
データサイズはバッファキャッシュに載るサイズ、コミットクリーンアウトではクリーンアウト仕切れないサイズで、ある程度の遅延ブロッククリーンアウトが発生するサイズになっているのは以前と同じ。

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)
insertしただけなので、insertしたデータ量に応じたブロック数がバッファキャッシュに確保されたという程度の情報( free buffer requested = 68766 なので事前に確認していた 66667 以上になっています)は確認できます。
(バッファキャッシュに収まるデータ量ですし)ただ、checkpointの発生でいくらか書き出されているのも見えますよね( DBWR checkpoint buffers written )

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22909
sysstat DBWR checkpoints 3
sysstat DBWR thread checkpoint buffers written 22902
sysstat DBWR transaction table writes 6
sysstat DBWR undo block writes 573
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 49682
sysstat db block changes 744727
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 68766
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 82
sysstat physical reads 9
sysstat physical writes 22909
sysstat physical writes from cache 22909
sysstat physical writes non checkpoint 22892

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49561
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat free buffer requested 68766
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 40
sesstat physical reads 9


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
コミットクリーンアウトされているブロック数を見ると、事前に計算していた バッファキャッシュの13%( 55112 blocks )に近い 55700 ブロックがコミットのタイミングでクリーンアウトされていることがわかります。ここまでは想定通りの動きです。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 56077
sysstat commit cleanouts successfully completed 56062
sysstat consistent gets 50494
sysstat db block changes 7894
sysstat deferred (CURRENT) block cleanout applications 158
sysstat free buffer requested 3217
sysstat immediate (CURRENT) block cleanout applications 154
sysstat no work - consistent read gets 32764
sysstat physical reads 2962

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)
全表走査させてコミットクリーンアウトされなかったブロックがクリーンアウトを確認します。ただし、全表走査ではありますが、direct path read で読み込ませるように工夫しています。

さて狙い通りになるかどうか。。。パラレルクエリーでない場合の強制はちょいとむずいのですが、見る限り、REDOログは生成されています。

ただ、以前のscattered read ( db file sequential read )で発生させた遅延ブロッククリーンアウトの検証結果に比べると明らかに少ない。。
なにかが違いますね。。。。むむむ。なんだろう?
scattered readでほぼ同じバッファキャッシュサイズで、遅延ブロッククリーンアウトさせた際、967432 redo size というサイズが生成されていたのを思い出してみてください!!! 明らかに少ないです。。。。

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:10.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
23 recursive calls
13 db block gets
113559 consistent gets
66712 physical reads
2996 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)
direct path read、遅延ブロッククリーンアウトの有無をシステム統計およびセッション統計から読み取ってみます!

物理読み込み( physical writes )とダイレクトパスリードを示す( physical writes from cache )が同じであることから、間違いなく direct path readが発生しています。ブロック数も 66667 ブロックを超えていることは確認できます。
ただ、immediate (CR) block cleanout applications が想定している量の3倍ぐらいあります:)
遅延ブロッククリーンアウトは行われているのは間違いないですが、前述の通りREDOサイズが異常に少ない。どういうことだろう。。(想定通りの結果に、ニヤニヤなわけですがw)

真相を探るため、われわれはアマゾンの奥深くへ入っていくのであった。。。W

少々本題からそれますが、DBWR parallel query checkpoint buffers written で 44793 ブロックほど書き出されています。これが発生するのは direct path readの影響です。direct path read バッファキャッシュを介ず、常にストレージからデータを読み込む必要があります。この検証では、INSERTでバッファキャッシュに載っているデータであるため一旦書き出す必要があります。書き出されたデータを direct path read で読み込むのでこんな動きになっているというわけですね。。。。。

これ、よくよく考えると、コミットクリーンアウトされていないブロックもそのままの状態で書き出されてますよね。。。ここ試験にでますよ(嘘ですw

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 44793
sysstat DBWR checkpoints 2
sysstat DBWR object drop buffers written 2
sysstat DBWR parallel query checkpoint buffers written 44793
sysstat cleanouts only - consistent read gets 33057
sysstat commit cleanouts 18
sysstat commit cleanouts successfully completed 18
sysstat consistent gets 120519
sysstat db block changes 97
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 127
sysstat immediate (CR) block cleanout applications 33057
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 50145
sysstat physical reads 66827
sysstat physical reads direct 66710
sysstat physical writes 44795
sysstat physical writes from cache 44795
sysstat physical writes non checkpoint 44795

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 33057
sesstat commit cleanouts 5
sesstat commit cleanouts successfully completed 5
sesstat consistent gets 120131
sesstat db block changes 48
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 120
sesstat immediate (CR) block cleanout applications 33057
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 49952
sesstat physical reads 66823
sesstat physical reads direct 66710


10) Oracle再起動
Oracleを再起動して、諸々綺麗にした状態で、今一度、direct path readで全表走査させてみましょう。

$ sudo service oracle restart


11) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


12) CDBのSYSで統計取得(再起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


13) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)2回目
お!!! REDOが生成されていないですね。Scattered Readの場合でも、コミット時でも一度クリーンアウトされたブロックはクリーンアウト済みなので、クリーンアウトされるような挙動は発生しませんでしたが、 direct path read でもおなじかなーー。

と。。。。とりあえず、システム統計とセッション統計も確認しておきましょう!

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:03.90

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91159 consistent gets
66938 physical reads
0 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


14) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)2回目
セッション統計のphysical readsとhysical reads direct は同一であることから direct path readになっていることは間違いありません。また、ブロック数も 66667 ブロック以上にはなっているので全ブロック読み込まれているようですね。
ただ、妙な値を示している統計があります。

immediate (CR) block cleanout applications  10959 

遅延ブロッククリーンアウトが行われている時に上がる統計です。しかも、コミットクリーンアウトされたブロック数を差し引いたブロック数にほぼ一致します。(1回目の実行ではこの3倍ぐらいに跳ね上がっていましたが。。一度クリーンアウトされたのでは??)

さらに不思議なことに、REDO生成されないんですね。。。。

ん? ちょっと待ってください。一度、クリーンアウトされたブロックがなぜ、再度クリーンアウトされているのでしょう? scattered readで遅延ブロッククリーンアウトされたケースと動きが違います!!!!!!

Oracleを再起動する前のステップでREDOログが異常に少ないにも関わらず、遅延クリーンアウトされていた統計値が高くなった。Oracleを再起動した後でも、同様に、direct path read で読み込み、遅延ブロッククリーンアウト発生。しかもREDOログはありません。。。これって、クリーンアウト行われているようですが、実際にはメモリー上だけで実祭のブロックはクリーンアウトされずに残っているということですよね。なんども発生しているわけですから。
(ブロックダンプしなくても統計値から状況は見えてきましたよね!!)

direct path readはその名の通り、バッファキャッシュを介さず、常にストレージからデータブロックを読み込み、PGAへ。このケースだとSELECT文なので単純にPGAへ直接読み込み、メモリ上ではクリーンアップは行なっているようですが、クエリーが終了すれば、単に捨てられるのみ。。。なので、クリーンアウトの結果は永続化されない。。。ということになりますよね!

ということは、SELECT文の場合は、scattered read等でバッファキャッシュを経由させないと、遅延ブロッククリーンアウトは、ずーっと先延ばしされる。。。direct path readのSELECT文を2回実行してクリーンアウトさせたわけだが、この後、scattered readで全表走査させれば、遅延ブロッククリーンアウトが発生して、大量のREDOログが生成されるて、完全にクリーンアウトされる。。。。。。はず。。。ですよね。

試してみよう!!!!

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat DBWR object drop buffers written 2
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 36
sysstat commit cleanouts successfully completed 36
sysstat consistent gets 106766
sysstat db block changes 166
sysstat deferred (CURRENT) block cleanout applications 18
sysstat free buffer requested 1130
sysstat immediate (CR) block cleanout applications 10959
sysstat immediate (CURRENT) block cleanout applications 10
sysstat no work - consistent read gets 77392
sysstat physical reads 67827
sysstat physical reads direct 66710
sysstat physical writes 2
sysstat physical writes from cache 2
sysstat physical writes non checkpoint 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98223
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 587
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67289
sesstat physical reads direct 66710


15) Oracle再起動
諸々情報を綺麗にするので再起動!!

$ sudo service oracle restart


16) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


17) CDBのSYSで統計取得(再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


18) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)3回目

キターーーーーーーーーーーーーーっ!。 予想的中!!。(競馬ならいいのにw) 

大量のREDOログが生成され、物理読み込みも初生しています。direct path readで全表走査させた時とは明らかに違う!!!(以前、見た光景!!w

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91170 consistent gets
66720 physical reads
964436 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


19) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)3回目

physical reads が想定ブロック数以上あるため、物理読み込みされ全ブロックが読み込まれていると読み取れます。また、physical reads direct は変化していません。(変化のない統計は記載していません)
つまり direct path read ではなく scattered read で全表走査が行われたことを示しています。

immediate (CR) block cleanout applications                     10959

という統計から、遅延ブロッククリーンアウトが発生し、ほぼ想定していたブロック数であることも確認できます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 98846
sysstat db block changes 10992
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67078
sysstat immediate (CR) block cleanout applications 10959
sysstat no work - consistent read gets 72660
sysstat physical reads 67069

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98234
sesstat db block changes 10988
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67071
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67063


20) Oracle再起動
以前の検証で、scattered readでブロッククリーンアウトされた場合のSELECT文であっても結果は永続化されるので、再度読み込ませた場合はクリーンアウト済みなので再度遅延クリーンアウトが発生しないことは確認確認済みですが、今一度確認しておきましょうw

$ sudo service oracle restart


21) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


22) CDBのSYSで統計取得(再再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


23) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)4回目

想定通り、遅延ブロッククリーンアウトは発生せず、REDOログも生成されていません! めでたしめでたしw

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.32

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
4539159 bytes sent via SQL*Net to clientyoutub
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

24) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)4回目
physical reads はありますが、 physical reads direct は変化していません。これは scattered read で全データを読み込んだと見て良いでしょうね。読み込んだブロックサイズも該当表の想定データブロック数程度です。
また、遅延ブロッククリーンアウトが発生したことを示す統計は変化していないことから、遅延ブロッククリーンアウトは発生していないことも読み取れます。(^^)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 87659
sysstat db block changes 34
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67058
sysstat no work - consistent read gets 83510
sysstat physical reads 67048

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 87049
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67051
sesstat no work - consistent read gets 83175
sesstat physical reads 67042



まとめ

SELECT文であっても、遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログが生成される。ただし、direct path read で読み込まれた場合を除く。
ということのようですね。

ふむふむという興味深い動きですよね。これ。:)

では、次回は direct path に関わる別の動きも確認してみましょうか。。。
このシリーズ、まだまだ引っ張れそうw

ということで次回へつづく。


台風の影響を心配したけどなんとか良い天気の遅い夏休みで。暑くも寒くもないく散歩の気持ちいい秋空 :)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8



| | | コメント (0)

2021年9月14日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8

Previously on Mac De Oracle
前回からのつづき(ちょいと寄り道中)
です。

では、とっとと試してみましょうw

バッファキャッシュから溢れ出る程度のデータ量だったら、どうなるのかなーーーー、という検証です。やりたいことは図の通りです。
Photo_20210911234101

検証方法を考えていたのですが、自動共有メモリー管理になっているのと、sga_max_size/sga_targetを小さくしすぎるとOracle Databaseが起動しないなど諸々引きそうなので、shared_pool_sizeを大きく設定して、バッファキャッシュに回せるメモリーを減らすことで、バッファキャッシュを小さく、バッファから溢れる程度のデータ量も少なくて済むようにして試してみることにします。

準備段階から書いてます。再現させる環境をどうセットアップしたかっていうことも重要だと思うのですよね。少々長くなっちゃいますが。



検証準備

CDBに接続して初期化パラメータを調整!!

SGAコンポーネントの状況
Database Buffersが、3G以上になってます。検証データ量も多くなってしまうので、これを1GB程度まで下げたいですね。検証時間も節約できますし、最小の手数で検証できるほうが良いですから:)

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 3472883712 / 1024 / 1024 AS "MB" from dual;

MB
----------
3312

Elapsed: 00:00:00.00


sga_max_size,sga_min_sizeが4GBですが、ここはそのままにします。あまり小さくしすぎると起動しなくなったり。(^^;;;;

SYS@orclcdb> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 4G
sga_min_size big integer 0
sga_target big integer 4G
unified_audit_sga_queue_size integer 1048576


自動SGA管理なので、Shared Pool Sizeに大きめの値を設定。
自動SGA管理下で自動管理対象メモリーコンポーネントパラメータに値を設定した場合、その値が下限値となり最低でもその値は確保されるという仕組みを利用します!

SYS@orclcdb> show parameter shared_pool

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
shared_pool_reserved_size big integer 39426457
shared_pool_size big integer 0


起動しなくなっても戻しやすいようにspfileをpfileに書き出して退避後に、shared_pool_sizeを3GBへ増やします。これで4Gのうちの3G程度がshared poolに割り当てられ、バッファキャッシュは1GBぐらいになるはず。(VirtualBoxなのでスナップショット取得しておいて戻すのもありですけどw)

SYS@orclcdb> create pfile='pfile20210912.ora' from spfile;

File created.

Elapsed: 00:00:00.00
SYS@orclcdb> alter system set shared_pool_size = 3g scope=both;

System altered.

Elapsed: 00:00:00.77


Database Buffersがいい感じにシュリンクしました。1GBぐらいになりました。これで進めますよー。

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 3238002688 bytes
Database Buffers 1040187392 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 1040187392 / 1024 / 1024 AS "MB" from dual;

MB
----------
992

Elapsed: 00:00:00.01


ざっくりとブロック数を計算

SYS@orclcdb> select 1040187392 / 8192 AS "blocks" from dual;

blocks
---------------
126976

Elapsed: 00:00:00.01


前回のHOGE表に200,000 rowsで、66,667 blocks のデータを生成したので、126,976 blocks を満たすデータ量にしようとすると 400,000 rowsほど必要になりそうですね。。。。少々多めで、バッファキャッシュから溢れる程度の量で、 500,000 rowsのデータを登録することにしましょう!!!!

SYS@orclcdb> select ceil(126976 / 66667) * 200000 AS "rows" from dual;

rows
----------
400000

Elapsed: 00:00:00.00


これまでの検証から 10%-15%程度がCOMMITクリーンアウトされ、残りが遅延されるのは確認できたので、126976 blocks のバッファキャッシュだと、 17,777 blocks ぐらいがコミットクリーンアウトされそうですね。(今回のテストケースではコミットクリーンアウトされないけど。。された場合は最大でこの程度。。。というメモです。はい)

SYS@orclcdb> select ceil(126976 * 0.14) AS "blocks for commit cleanout" from dual;

blocks for commit cleanout
--------------------------
17777

Elapsed: 00:00:00.00

前回作成したデータは、200,000rowsで、66,667 blocksだったので、500,000 rows だと、ざっくり 166,668 blocks ほど。

SCOTT@orcl> select ceil(66667 / 2 * 5) AS "blocks" from dual;

blocks
----------
166668

Elapsed: 00:00:00.00


なので、遅延ブロッククリーンアウトされると想定される(コミットクリーンアウト分を覗くと)ブロック数は、148,891 blocks 程度にはなりそう。

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 17777) AS "blocks" from dual;
blocks
----------
148891

Elapsed: 00:00:00.01


それに加えて、バッファキャッシュに収まらず、コミットする前にバッファキャッシュから落とされ、ストレージへかきだされてしまうブロック数は、これまた、ざっくり計算すると 39,692 blocks ほどですかね。バッファキャッシュのサイズから全てのブロックは乗り切らないので、最初に読み込まれていたブロックから落とされていくことにはなりますね。。
とはいえ、この検証ではキャッシュ落とされるブロック数は特に気にしてなくて、バッファキャッシュ以上のブロック数が生成されていればいいので、落とされそうなのが確認できればOK.

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 126976) AS "blocks" from dual;

blocks
----------
39692

Elapsed: 00:00:00.00


とりあえず、生成するデータ量(行数)の算出とバッファキャッシュサイズの調整はおわり。


次に、実際にデータを生成してブロック数とセグメントサイズを確認して、実行用スクリプトの調整を行なっておきます。



PDBでテストデータの実サイズの確認
SCOTT@orcl> @droppurge_create_hoge.sql
1* drop table hoge purge

Table dropped.

Elapsed: 00:00:00.25
1* create table hoge (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.04
1* select segment_name,blocks from user_segments where segment_name like '%HOGE%'

no rows selected

Elapsed: 00:00:00.11


500,000行登録!!

SCOTT@orcl> @insert_each_rows_5.sql
1* begin for i in 1..500000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:02:37.68


データが登録されているブロック数は、166,667 blocks で、事前に計算していた 166,668 blocks にほぼおなじ。(狙い通り)
セグメントサイズは、約 1344 MB ですね。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
166667

Elapsed: 00:00:05.03
SCOTT@orcl> select segment_name,blocks,bytes/1024/1024 AS "MB" from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS MB
------------------------------ ---------- ----------
HOGE 172032 1344

Elapsed: 00:00:00.17


Scattered read でTable Full Scanできるように少々隠しパラメータを調整しておきますね。念の為。(セッションレベルで調整してます)
セグメントサイズが、1344 MBなので、_very_large_object_threshold は、2048 MBぐらい設定しておけば、Scattered readのまま行けそうですね。

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 2040
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off


準備完了!!!!!





準備長かったけどw やっと本題です!!!w 実行している内容はいままでと同じなのでかなり端折ってポイントだけ記載。

バッファキャッシュから溢れるほどのデータ量で。コミットクリーンアウトはどうなるのだろうか。。。。想定では、ほぼコミットクリーンアウトできないはずではあるのだが。。。。

CDBのSYSで統計取得(コミット後)

commit cleanouts successfully completedはどれぐらいだったのか。。。。。ありません。commit cleanouts successfully completedに差分がなかったので、コミットクリーンアウトしようして失敗、commit cleanout failures: block lostと同数なので、1ブロックもコミットクリーンアウトできない!  commit cleanout failures: block lostがバッファキャッシュに対象ブロックがなかったことを示しています。

つまり、バッファキャッシュに乗り切らなため、最初にINSERTされたブロックはそのままストレージへ物理書き込みされて追い出された結果。。ということになりますね。

登録したブロック全てが遅延クリーンアウト対象になってしまった、ということになります。コミット時にクリーンアウトできてないわけだから!!!!!!!! (イメージ図でざっくり書いたとおりの感じに。。。

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanout failures: block lost 3028
sysstat commit cleanouts 3028
sysstat consistent gets 274
sysstat db block changes 1
sysstat no work - consistent read gets 149
sysstat physical reads 60

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanout failures: block lost 3028
sesstat commit cleanouts 3028
sesstat db block changes 1


次に、Scattered Readが実行される全表走査を行わせ、遅延ブロッククリーンアウトどれだけ発生するか結果確認!

PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)
想定通り、物理読み込み(この時点ではscattered readなのか、direct path readなのかわかりませんが)になっています。また、大量のREDOログが生成されているので遅延ブロッククリーンアウトが発生しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:01:03.08

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
367852 consistent gets
160269 physical reads
14670268 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off


物理読み込みを伴う全表走査でどの程度の遅延ブロッククリーンアウトが発生したか統計を確認!!!!

CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

想定どおり、INSERTした全ブロックが、immediate (CR) block cleanout applications = 166667 で遅延ブロッククリーンアウトされたことがわかります。(冒頭に記載していますが、データが格納されているブロック数は、 166667 blocks でしたよね)
physical readsは意図通り発生していますが、physical reads directが変化していないので、狙い通りScattered Readになったようですね

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 241
sysstat DBWR thread checkpoint buffers written 241
sysstat DBWR undo block writes 1109
sysstat cleanouts only - consistent read gets 166667
sysstat commit cleanouts 55
sysstat commit cleanouts successfully completed 55
sysstat consistent gets 377331
sysstat db block changes 166904
sysstat deferred (CURRENT) block cleanout applications 29
sysstat immediate (CR) block cleanout applications 166667
sysstat immediate (CURRENT) block cleanout applications 15
sysstat no work - consistent read gets 38449
sysstat physical reads 160805
sysstat physical writes 137551
sysstat physical writes from cache 137551
sysstat physical writes non checkpoint 137466

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 166667
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 374538
sesstat db block changes 166710
sesstat deferred (CURRENT) block cleanout applications 3
sesstat immediate (CR) block cleanout applications 166667
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 36779
sesstat physical reads 160513


念の為、今一度、物理読み込みを伴う全表走査を行なって、クリーンアウトされたのか確認してみましょうw(疑い深いw)
もう一度、PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

redo size0 なのでクリーンアウトは発生してない。想定通り

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:00:33.17

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
200399 consistent gets
148198 physical reads
0 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off

同様に、統計でも確認してみます!
CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

クリーンアウトを示す統計値は上昇していません!!!(うんうんw)

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 732
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 200522
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 200403
sysstat physical reads 148200
sysstat physical writes 68570
sysstat physical writes from cache 68570
sysstat physical writes non checkpoint 68331

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 200414
sesstat db block changes 9
sesstat no work - consistent read gets 200366
sesstat physical reads 148198



OK. Done. ということで、まとめ!

バッファキャッシュには収まりきれないデータ量の場合、コミットクリーンアウトしようとしていたブロックも追い出されてしまうので、結果的に、全ブロックが遅延ブロッククリーンアウトになった。というイメージしていた結果の通りでした。
(今回のケースもシンプルケースなので比較的予想しやすい結果ですが、クリーンアウトに関わる統計は以外に多く、複雑な動きになるものもあります。再現するののめんどくさいのでしませんがw)
Photo_20210911234101

寄り道はここまで、次回は、こんどこそ、direct path readと遅延ブロッククリーンアウトの関係をみていきたいと思います。


来週天気いいかなー。遅い夏休みなのに。微妙な気がしてきた。。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7


| | | コメント (0)

2021年9月12日 (日)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7

Previously on Mac De Oracle
前回は、コミットクリーンアウトと遅延クリーンアウト、そして、そこにTable Full ScanでScattered Read (待機イベントだと db file scattered read) を絡めてストレージへ永続化されたクリーンアウトが遅延されてしまったブロックを物理読み込みませつつ遅延ブロッククリーンアウトを再現させてみました。
また、次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。なんてことを言っていましたが、またまた、ちょいと寄り道ですしますw

バッファキャッシュから溢れるぐらいのデータをぐるぐる系INSERTで、しかも1回のコミットにしたら、コミット前にあふれたデータはストレージへ書き出され、かつ、クリーンアウトも遅延されるよなー。という予想を元に、ちょいと遊んでから次に進みたいw と思います。

これまでの流れから、基本的なクリーンアウトおよび遅延ブロッククリーンアウトとしては以下ようなパターンを確認してきました。

バッファキャッシュの上でコミットクリーンアウトおよび、遅延ブロッククリーンアウト(単純なタイプ)が行われているケース
Photo_20210911234001

ここからが想像というか、私が理解している範囲から想像した動き。バッファキャッシュから溢れはしないけど、いっぱいいっぱいな場合は、クリーンアウトされるブロックがキャッシュ上に多くあるだろうな。と.
とは言っても、バッファキャッシュ上ではあるわけです。
Photo_20210911234002


そこで、ちょいと意地悪をして、バッファキャッシュから溢れ出る程度のデータ量だったどうなるのかなーーーーと。冒頭ですでコメントしているわけですけどもw 多分、以下のような動きだよねー、と。
そういえば、以前、DBTSで行なったセッションの「バッファキャッシュ欠乏症」の部分で、似たようなバッファキャッシュから溢れ出したブロックの挙動をなんとかするみたいな資料も今回の動きを想像するにはよいかもしれないですね。
Photo_20210911234101


と、思い、頭の中のイメージを Pagesでざざっと作ったところで、本日はここまで。次回へつづく。


Beat SaberとWalkingの合わせ技で、効果的な減量継続中w



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6



| | | コメント (0)

2021年9月10日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6

Previously on Mac De Oracle
前々回と前回はバッファキャッシュの10%を超えるデータ量のINSERT文の実行とCOMMITの実行で、バッファキャッシュの10%-15%程度は、COMMIT時にクリーンアウトされ、残ったブロックのクリーンアウトは先送りされる。という検証を2つのパターンで確認してみました。

どのような流れで発生するかを各ステップ毎にシステム統計(CDB)とクエリーを実行するセッションのセッション統計(PDB)を取得し、どのように統計値が変化すれば、コミットクリーンアウトや遅延ブロッククリーンアウトが起きているのかを見ながらすすめました。以下2つのエントリーで確認した動きの違いはイメージできたのか少々不安ではありますがw (そこそこ長いエントリーなのでw)

こちら前々回は、クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
で、

前回は、クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

なんです :) 


まだ、イメージつかめない方もいるかもしれないので

超ざっくりした絵が頭の中に浮かばない方向けに、上記検証を行う前に、私の頭の中にうかんだ、ラフイメージをほぼそのまま

(こまけーとこは気にしないでくださいね。ラフイメージですから、こうだろうなーというのを想像している状態そのままのイメージですのでw)

クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
No4


クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
No5

再現させてるケースはシンプルなものなので処理時間云々を比較してはいないですが、複雑なケースになると、本来スマートスキャンさせたいのにシングルブロックリードが多くなったりするケースなど、以前紹介したURLを見ていただければ参考になるかもしれないですね。
クエリーやDMLの処理時間が伸びてビビるぐらいに仕事量が増えてたり、先送りされたことで、もろもろ後処理が複雑化する場合もあるわけで)

ということで、こんな図をイメージしながら、ネタ作ってます。はいw


次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5



| | | コメント (0)

2021年9月 9日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

前回はバッファキャッシュの10%を超えるデータブロックへのINSERT文の実行とCOMMITの実行で、バッファキャッシュの13%-15%程度はCOMMIT時にクリーンアウトされ、残りは遅延ブロッククリーンアウト(先送り)される。
direct path readではないSELECT文による(前回のケースでは scattered read))遅延ブロッククリーンアウトは、1度のみ発生するという状況を確認しました。

ところで、
前回のエントリで、2度、全表走査(前回の検証ではscattered read)を実行しているのですが、物理読み込みは発生させていません。(INSERT→COMMIT→SELECT→SELECTという流れで、十分なバッファキャッシュがあるので、当然ではあるのですがw)
バッファキャッシュに乗ったままのブロックが遅延クリーンアウトされていたわけです。

前回のエントリ:古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

前述の1.2.それぞれの実行で物理読み込みだったなにか変化はあるのだろうか。。。予想では、上記に加えてscattered readに伴う物理読み込みが増えるだけのはずです。その動きを見てみることにします。(こういう動きを見ていると楽しいですよねw)
手順は前回と同じですが、各全表走査の前にインスタンスを再起動してバッファキャッシュを空にしておきます。
(buffer cacheをflushすればいいじゃん。という声も聞こえてきそうですが、今回は再起動でクリアしました。はいw)


前回から多少変更したスクリプトも載せておきます(本文中にもありますが)、細かい解説は後述

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 1056
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




0) 対象表をdrop/create
オブジェクト作り直し

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにインスタンス再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別するため

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(統計差分取得のためのベースラインを取得しているだけ)

SYS$orclcdb> @show_stat scott


4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

未コミットの状態なので特に、気にせず、ふーーーん。ぐらいの感じで眺めていただければいいですね。前回同様に、commit cleanouts, commit cleanouts successfully completed, deferred (CURRENT) block cleanout applications, immediate (CURRENT) block cleanout applicationsといったクリーンアウト関連統計が極わずかありますが、この時点で発生しているのは本題ではないので気にしなくてOK

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 23589
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 22643
sysstat DBWR transaction table writes 50
sysstat DBWR undo block writes 848
sysstat cleanouts and rollbacks - consistent read gets 5
sysstat commit cleanout failures: callback failure 20
sysstat commit cleanouts 1320
sysstat commit cleanouts successfully completed 1300
sysstat consistent gets 124041
sysstat db block changes 757165
sysstat deferred (CURRENT) block cleanout applications 715
sysstat immediate (CR) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 196
sysstat no work - consistent read gets 46398
sysstat physical reads 4063
sysstat physical writes 23589
sysstat physical writes from cache 23589
sysstat physical writes non checkpoint 23494

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49596
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 15


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
前回同様ノイズもなく、綺麗にコミット時のクリーンアウトが発生しています。バッファキャッシュの約14-5%程度なのは前回と変わらずですね。

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) Oracle Databaseを再起動してバッファキャッシュをクリア

ここが前回と違う手順で、クリーンアウトされないブロックはずーーーーーっと残るよね。ということの確認でもあります。(alter system flush buffer_cacheでも同じことができるわけですが、ここでは再起動しています)

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


9) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


10) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したので統計値の差分取得用ベースライン統計の取得)

SYS$orclcdb> @show_stat scott


11) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の1回目

ここでは待機イベントまでは確認できませんが、Table full scanと実行統計よりRedoログがたっぷり生成されていることは確認できます。SELECT文ですが。。。つまり、遅延ブロッククリーンアウトが発生しているということですね。確認は後述の統計で。
狙い通り、物理読み込みも発生しています!

Note)
"_very_large_object_threshold" = 1056 としているのは、direct path readとなる上限セグメントサイズをhoge表が超えているためdirect path readを抑止するためにこの隠しパラメータで上限値を引き上げ、scattered readになるように強制しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.10

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


12) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の1回目

遅延ブロッククリーンアウト関連統計値が上昇しているので、遅延ブロッククリーンアウトの発生が確認できます。ここまでは前回と同じ。(同じじゃないと困りますがw)

違う点は、事前にインスタンスを再起動しているため、physical reads が上昇しています。これは hoge表を scattered readで全表走査しているからです。(phsical read directは発生していない)バッファキャッシュを経由するのでconsistent gets,no work - consistent read gets も上昇しています。狙い通りです。

そして、重量な遅延ブロッククリーンアウトですが、バッファキャッシュでヒットしていた時と同数のブロックで発生しています。(ニッコリ

インスタンスの停止や起動があったとしても、クリーンアウトが先送りされたブロックはアクセスされない限りクリーンアウトされず残っているということを示しています!!!!(ここ試験にでますよー。嘘)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 7
sysstat commit cleanouts successfully completed 7
sysstat consistent gets 98967
sysstat db block changes 11016
sysstat deferred (CURRENT) block cleanout applications 3
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 72543
sysstat physical reads 67403

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98357
sesstat db block changes 11012
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72208
sesstat physical reads 67397


13) Oracle Databaseを再起動してバッファキャッシュをクリア

再度、インスタンスを再起動して、バッファキャッシュをクリアします。後続の全表走査では、遅延ブロッククリーンアウトは発生せず、物理読み込み(この検証では scattered readさせています)を伴うTable full scanが行われるだけのはずです。

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


14) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


15) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したのでベースラインとなる統計を取得)

SYS$orclcdb> @show_stat scott


16) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の2回目

実行統計から、physical readsが、発生しています。Redoは生成されていないことも読み取れるので、遅延ブロッククリーンアウトは発生していないことも確認できます。:) 想定通りですね。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.44

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


17) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の2回目

physical reads は発生していますが、physical reads directではないことが確認できます。Table full scanをscattered readで読み込んでいるという想定通りの結果。 
遅延ブロッククリーンアウトを示deferred (CURRENT) block cleanout applications 、immediate (CURRENT) block cleanout applicationsや、コミットクリーンアウトを示すcommit cleanouts 、commit cleanouts successfully completed という統計が極わずかに変動していますが、今回の検証ではノイズなので気にしたくてOK。

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 11
sysstat commit cleanouts 15
sysstat commit cleanouts successfully completed 15
sysstat consistent gets 87375
sysstat db block changes 79
sysstat deferred (CURRENT) block cleanout applications 12
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 83322
sysstat physical reads 67028
sysstat physical writes 11
sysstat physical writes from cache 11
sysstat physical writes non checkpoint 11

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 86936
sesstat db block changes 29
sesstat deferred (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 83098
sesstat physical reads 67023




まとめ

遅延ブロッククリーンアウトは、インスタンスを停止して残ったままということが確認できました。(クリアされるまで残るのですよねー)
クリーンアウトが遅延されているブロックが物理読み込みされた(アクセスされた)タイミングで遅延ブロッククリーンアウトが発生することも確認できました。(物理読み込みの有無には関係しない)
バッファキャッシュ上の遅延ブロッククリーンアウト同様、クリーンアウトされたブロックでは、再度、クリーンアウト対象になるような更新が発生しなければ、遅延ブロッククリーンアウトは発生しない

久しぶりにシステム統計やセッション統計を見ててワクワクしてきましたよーーーーっw

ところで、
冒頭でも記載しましたが、バッファキャッシュ上の遅延ブロッククリーンアウト(再掲)に加え、物理読み込みを伴う遅延ブロッククリーンアウトのauto traceの結果をまとめて記載しておきます。SELECT文ですが、REDOログが生成されている場合は遅延ブロッククリーンアウトが発生しているということになります。

実行計画

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed


3. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータを物理読み込みし、バッファキャッシュに載せている動きが見えます。

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

4. 3.の直後に再度全表走査した場合。事前にキャッシュをクリアしているため、物理読み込みがありますが、遅延ブロッククリーンアウトは発生していません。(Redoが生成されていないことで確認できます)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

ではでは。
次回は、scattered readのtable full scanではなく、direct path readだったらどうなるのか調べてみましょうか。。(いろいろな再現方法があるわけですが、手間のかからないお手軽な再現方法で確認してみようと思いますw)


洗濯機の修理が終わって一安心w ということで、次回へつづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4


| | | コメント (0)

2021年9月 3日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

Previously on Mac De Oracle
前回はバッファキャッシュの10%未満のデータブロックへのINSERT文の実行とCOMMITの実行で、遅延ブロッククリーンアウトは発生せず、COMMIT時にすべての対象ブロックがクリーンアウトされるということを確認しました。

今回は、そのデータ量を倍にして、バッファキャッシュの10%程度を超えるデータブロックが遅延ブロッククリーンアウトされるのかを見ていくことにします。手順は前回と同じですが、遅延ブロッククリーンアウトさせた後で、もう一度全表走査してクリーンアウトが繰り返されないことも確認しておきます(次回以降に予定している確認への伏線なのですがw)


0) 対象表をdrop/create
オブジェクトを作り直して前提条件を合わせておきます

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat

4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

INSERTしただけです。未コミットなので特に気になる情報は現れていません。この値からコミット後にどのように変化するのか? という部分に注目する必要があるんですよー。
deferred (CURRENT) block cleanout applications と immediate (CURRENT) block cleanout applications が僅かにありますが、この時点では気にする部分ではないです

(値の変化が1以上ある統計のみ表示)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 22756
sysstat DBWR checkpoints 33
sysstat DBWR thread checkpoint buffers written 22756
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 606
sysstat consistent gets 49761
sysstat db block changes 744980
sysstat deferred (CURRENT) block cleanout applications 4
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 192
sysstat physical reads 18
sysstat physical writes 22756
sysstat physical writes from cache 22756
sysstat physical writes non checkpoint 22756

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 49501
sesstat db block changes 744980
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 65
sesstat physical reads 17


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

この結果、ノイズも少なく、綺麗に取れてます!!! w

前々回の事前確認の通り、2倍のデータブロック数は、 66667ブロック で、バッファキャッシュの10%は、ざっくり計算で、42394ブロック。つまり、想定では 42394ブロック ほどが、commit時のブロッククリーンアウトの対象と想定していました。

覚えてますか? みなさん!

実際にcommit時にクリーンアウトされたのはどれぐらいでしょう? 
結果は、55700ブロックとなりました。想定より多いですねw ほぼ合ってはいますが。
実際にはバッファキャッシュの13%〜15%程度が閾値になっているように見えます。とはいえ、commit時にcleanoutされたブロック数は 55700ブロック ですから、残る 10967ブロック のcleanoutは遅延されたということは確実です。commit対象のデータブロック全てをcleanoutするわけではない、ということは確認できたのではないでしょうか?

(差分が1以上ある統計のみ記載)
(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

SCOTT@orcl> sset autot trace exp stat
SCOTT@orcl> salter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

200000 rows selected.

Elapsed: 00:00:05.14

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

遅延ブロッククリーンアウトは、事前の計算通り、 10967ブロック 発生しています。SELECT文では、immediate (CR) block cleanout applications として現れることも確認できます。
また、cleanouts only - consistent read gets として も同数計上されているところが見てます。綺麗に現れています。

commit cleanouts, ommit cleanouts successfully completed がでていますが、ここでは気にしなくてよいですね、極わずかで。SELECT文なので。
immediate (CURRENT) block cleanout applications、deferred (CURRENT) block cleanout applications もでていますが、同じく極わずかで、対象表のものではないと考えられるためここでは気にしなくて良いですね。

しかし、計算通りに発生してくれると確認が楽w (想定外の動きじゃなくてよかったw)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 272
sysstat DBWR thread checkpoint buffers written 272
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 117000
sysstat db block changes 11207
sysstat deferred (CURRENT) block cleanout applications 10
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 83351
sysstat physical reads 939
sysstat physical writes 272
sysstat physical writes from cache 272
sysstat physical writes non checkpoint 260

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98162
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72113
sesstat physical reads 122


10) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

そして、ここからがおまけの確認ステップです

もう一度、同じ表を全表走査してみます。どうなると思いますか? 遅延されていたブロッククリーンアウトも行われたのですから、当然、該当オブジェクトで遅延ブロッククリーンアウトは発生しない。はず! ですよね。

確認してみましょう。(発生してたらどうしようw、もうしそうなったらバグレポートでも上げようかなw)

.......

Redoは生成されてない! (よかった! 想定どおりだ!w)

SCOTT@orcl> @table_full_scan
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:05.43

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

SCOTT@orcl>

11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)2回目

これもノイズが少なく綺麗に取れました。該当セッションでは物理読み込みも発生していないので、キャッシュから全データを読み込んだようです。

そして、想定どおり、該当セッションでは遅延ブロッククリーンアウトは発生していません!

commit cleanouts、commit cleanouts successfully completed、deferred (CURRENT) block cleanout applicationsが1ブロックありますがCDB側の管理情報関連でしょうね。気にする部分ではないですね。

(差分のあった統計のみ記載)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 80150
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 80075

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 80076
sesstat db block changes 9
sesstat no work - consistent read gets 80043




まとめ

おおよそ、バッファキャッシュの10%程度が commit時にcleanout されるという点については、約15%程度と見ておいたほうが良さそうですが、まあ大きな違いはないので、その辺りに閾値があると考えて問題はなさそうです。
また、それを超えるブロックについては、cleanoutが先送りされ、最初に該当ブロックにアクセスしたSQLがその影響を受ける。

この検証ではSELECT文では、immediate (CR) block cleanout applications という形で統計に現れました。UPDATE文やDELETE文の場合は他の統計として現れそうですね。(CURRENT)関連のcleanoutの統計は今回動いていないのでSQL文を変えて同じような検証をしてみると面白い結果をえられそうです。

そして、SELECT文で、遅延ブロッククリーンアウトされてしまえば、その間に更新が発生しなければ、クリーンアウトは発生しない(おまけで検証した部分ですが、別検証では興味深い動きを紹介する予定です。その伏線でもあります)

次回へつづく


5年目を迎えた、パナソニックのドラム洗濯機がH故障した。慌てて近所のコインランドリーを検索w 近所にあってよかったw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3



| | | コメント (0)

2021年9月 2日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3

Previously on Mac De Oracle
前回は準備を終えたところまででした。

今日は、簡単なところから確認していきましょう。

もしも、「遅延ブロッククリーンアウトが起きない程度のブロック更新量だったなら。。。」。結果は遅延ブロッククリーンアウトは起きないはず。 ですよね。

ざっと手順を紹介しておきましょう。下図の 1)〜9)の順で行います
20210901-221016

前述の手順で、各操作後の統計の差分(変化量)を見る。マニュアルの統計の説明ってざっくり過ぎてよくわからないのが多いわけですがw、操作と値の変化を合わせて観察すると、それなりには理解できる程度に値が変化していることに気づきますw :)

1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.


3) CDBのSYSで統計取得(初回)
初回なのですべてリストしていますが、CON_ID=0のCDBのシステム統計([g]v$sysstat)とCON_ID=3のPDBの2)のCLIENT_INFOを設定されたセッションのセッション統計([g]v$sesstat)を取得します。
今回のケースではcleanoutが含まれている統計の差異だけに着目すれば良いのですが。準備運動程度のテストケースなので一応すべて載せておきます :)
(マルチテナントだと、DBRWの動きを見るにはCDBのDBWR関連の統計を見る必要があるため、CDBのシステム統計とPDBの当該セッションのセッション統計を対象にしています)

SYS$orclcdb> @show_stat
SOURCE NAME VALUE CON_ID
------- ------------------------------------------------------------ -------------------- ----------
sysstat DBWR checkpoint buffers written 0 0
sysstat DBWR checkpoints 0 0
sysstat DBWR fusion writes 0 0
sysstat DBWR lru scans 0 0
sysstat DBWR object drop buffers written 0 0
sysstat DBWR parallel query checkpoint buffers written 0 0
sysstat DBWR revisited being-written buffer 0 0
sysstat DBWR tablespace checkpoint buffers written 0 0
sysstat DBWR thread checkpoint buffers written 0 0
sysstat DBWR transaction table writes 0 0
sysstat DBWR undo block writes 0 0
sysstat cleanouts and rollbacks - consistent read gets 6 0
sysstat cleanouts only - consistent read gets 0 0
sysstat commit cleanout failures: block lost 0 0
sysstat commit cleanout failures: buffer being written 0 0
sysstat commit cleanout failures: callback failure 0 0
sysstat commit cleanout failures: cannot pin 0 0
sysstat commit cleanout failures: hot backup in progress 0 0
sysstat commit cleanout failures: write disabled 0 0
sysstat commit cleanouts 570 0
sysstat commit cleanouts successfully completed 570 0
sysstat consistent gets 158,083 0
sysstat db block changes 3,247 0
sysstat deferred (CURRENT) block cleanout applications 316 0
sysstat immediate (CR) block cleanout applications 6 0
sysstat immediate (CURRENT) block cleanout applications 15 0
sysstat no work - consistent read gets 108,516 0
sysstat physical reads 12,551 0
sysstat physical reads direct 0 0
sysstat physical writes 0 0
sysstat physical writes direct 0 0
sysstat physical writes from cache 0 0
sysstat physical writes non checkpoint 0 0
sysstat transaction tables consistent read rollbacks 0 0
sysstat transaction tables consistent reads - undo records applied 0 0
sesstat DBWR checkpoint buffers written 0 3
sesstat DBWR checkpoints 0 3
sesstat DBWR fusion writes 0 3
sesstat DBWR lru scans 0 3
sesstat DBWR object drop buffers written 0 3
sesstat DBWR parallel query checkpoint buffers written 0 3
sesstat DBWR revisited being-written buffer 0 3
sesstat DBWR tablespace checkpoint buffers written 0 3
sesstat DBWR thread checkpoint buffers written 0 3
sesstat DBWR transaction table writes 0 3
sesstat DBWR undo block writes 0 3
sesstat cleanouts and rollbacks - consistent read gets 0 3
sesstat cleanouts only - consistent read gets 0 3
sesstat commit cleanout failures: block lost 0 3
sesstat commit cleanout failures: buffer being written 0 3
sesstat commit cleanout failures: callback failure 0 3
sesstat commit cleanout failures: cannot pin 0 3
sesstat commit cleanout failures: hot backup in progress 0 3
sesstat commit cleanout failures: write disabled 0 3
sesstat commit cleanouts 1 3
sesstat commit cleanouts successfully completed 1 3
sesstat consistent gets 374 3
sesstat db block changes 4 3
sesstat deferred (CURRENT) block cleanout applications 1 3
sesstat immediate (CR) block cleanout applications 0 3
sesstat immediate (CURRENT) block cleanout applications 0 3
sesstat no work - consistent read gets 220 3
sesstat physical reads 28 3
sesstat physical reads direct 0 3
sesstat physical writes 0 3
sesstat physical writes direct 0 3
sesstat physical writes from cache 0 3
sesstat physical writes non checkpoint 0 3
sesstat transaction tables consistent read rollbacks 0 3
sesstat transaction tables consistent reads - undo records applied 0 3


4) PDBのSCOTTユーザーでデータINSERT(コミットなし)
データを1行単位でインサートしています。バルクインサートも使ってないです。綺麗なぐるぐる系ですねw。コミット時の効果を確認しやすいようにコミットは後で実行します!
このインサートで、前回の事前準備の時に確認しておいた、33334ブロックが更新されることになります

SCOTT@orcl> @insert_each_rows
1* begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.


5) CDBのSYSで統計取得(INSERT後、未コミット)

注) 3)の統計との差分のみ記載

未コミットであるこの時点で、commitクリーンアウトが発生(該当セッション統計でも同数発生。commit cleanoutsとcommit cleanouts successfully completed)してますが、これは気にしなくてもよいですね。
実行したトランザクションは、未コミットなので、この実行による直接的な影響ではないので。
また、deferred (CURRENT) block cleanout applicationsやimmediate (CURRENT) block cleanout applications の遅延ブロッククリーンアウトを示す統計も微量ですがこれも同様とみて良いでしょう。

この時点で統計取得理由は、操作毎に変化する統計を追うためなので、ふーーん。ぐらいでの雰囲気でOKです :)
コミット後とその後に該当オブジェクトをアクセスさせた時の遅延ブロッククリーンアウトの有無部分の部分が主役ですので。

参考)
INSERTした行の含まれる全ブロックはバッファキャッシュに載り切るブロック数なので、バッファキャッシュから書き出されているような動きも観測されていないのは確認できると思います。
(physical writes from cache,physical writes non checkpoint,physical writesの統計に変化がないので未記載ですが、それらの統計が動いていないということがその理由です)


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 22884
sysstat db block changes 373785
sysstat deferred (CURRENT) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 59
sysstat physical reads 12


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 22816
sesstat db block changes 373785
sesstat deferred (CURRENT) block cleanout applications 5
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 12


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

注)5)と7)で取得した統計の差分のみ記載

ここが主役ですよー。このケースではバッファキャッシュの10%に満たないブロック数になるようにしたINSERT文(繰り返し実行)で 33334ブロックになるようにしました。これらを1度でcommitした場合、すべてのブロックがcommit時にblock cleanoutされるはずです。

では見てみましょう。

SCOTTのセッション統計より、commit cleanouts および commit cleanouts successfully completed から想定どおり全ブロックがcommit時にcleanoutされていることがわかります!
システム統計はインスタンス全体なのでPDBのそれら統計より大きめにでているのも確認できます。CDB側ではほんの少し deferred (CURRENT) block cleanout applications がありますが、管理情報系の遅延ブロッククリーンアウトでしょうね。 
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1で紹介したとおり 10% 未満では commit 時点で該当ブロックすべてcleanoutされることが確認できました。


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 139
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 55
sysstat commit cleanouts 33343
sysstat commit cleanouts successfully completed 33343
sysstat consistent gets 18111
sysstat db block changes 138
sysstat deferred (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 10853
sysstat physical reads 827
sysstat physical writes 139
sysstat physical writes from cache 139
sysstat physical writes non checkpoint 139


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 33334
sesstat commit cleanouts successfully completed 33334
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

_serial_direct_read = never はdirect path readさせないためのおまじないです。direct path readで読み込まれたケースの動きは別エントリーで見ていく予定なので、direct path readを発生させずtable full scan (ようするにバッファキャッシュに載せる動き)で読み込むよう強制しています。
また、行数が多いのでその時間の短縮のためにautot trace exp statを有効にしてSELECT文を実行させつつ、termout offと同じ効果とauto explainの機能でSELECT文の実行統計からredoの生成有無を確認しています。(redoがあるということはなんらかの更新が行われているわけで、SELECT文の場合は比較的容易に遅延ブロッククリーンアウトの発生を推測できる統計にもなります)
以下のケースでは多少redoが生成されていますが、おそらく recursive callによるもので、HOGE表のオブジェクトそのものに対するもではなさそうです。(このケースではhoge表のオブジェクトに対する遅延ブロッククリーンアウトや
コミット時のブロッククリーンアウトの観察が主題なので、周りのノイズはあまり気にしなくてもOK(追いかけたい場合は別ですがw)


SCOTT@orcl> set autot trace exp stat
SCOTT@orcl> alter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

100000 rows selected.

Elapsed: 00:00:02.59

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 57526 | 55M| 9098 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 57526 | 55M| 9098 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
21 recursive calls
13 db block gets
40465 consistent gets
3 physical reads
2212 redo size
203382760 bytes sent via SQL*Net to client
73706 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orcl> set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

注)7)のコミット時点からとの差異のみ記載。

多少ですが、SELECT文でredoが生成されてはいます。これは、commit cleanouts/commit cleanouts successfully completed (commit時に実施されるblock cleanout)と遅延ブロッククリーンアウトの統計の一つ、deferred (CURRENT) block cleanout applications が現れている影響ですね。コミット時点でHOGE表の全ブロックはcleanout済みなので recursive callによる内部管理情報関連で定常的に現れるものと考えられ、この検証では気にするところではないのでスルーしてください。

また、physical reads は、ほぼないため、この全表走査では、物理読み込み(direct path read含む)は、発生していないことも確認できます。
HOGE表はバッファキャッシュに載ったままという意図通りの状態にはなっているようです。
これ、今後のテストケースでも利用するので、キャッシュからエージアウトされないという点が確認できてると、以降の検証やりやすいんです:)


(CDB) システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 34849
sysstat DBWR transaction table writes 32
sysstat DBWR undo block writes 897
sysstat commit cleanouts 558
sysstat commit cleanouts successfully completed 558
sysstat consistent gets 69973
sysstat db block changes 2862
sysstat deferred (CURRENT) block cleanout applications 311
sysstat immediate (CURRENT) block cleanout applications 28
sysstat no work - consistent read gets 57908
sysstat physical reads 812
sysstat physical writes 34849
sysstat physical writes from cache 34849
sysstat physical writes non checkpoint 34738


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 5
sesstat consistent gets 47037
sesstat db block changes 38
sesstat deferred (CURRENT) block cleanout applications 4
sesstat no work - consistent read gets 43103
sesstat physical reads 117


まとめ

DMLにより更新されたブロック数が、バッファキャッシュの10%未満のブロック数である場合、commit時にすべてcleanoutされ、対象表では遅延ブロッククリーンアウトは発生しない。
(想定通りなのですが、ちょいと安心w 19cでどう動くか確認してなかってしので少々ドキドキしてたw のはナイショ)

というわけで、今回はここまで。

いきなり涼しくなって、なんだこりゃ。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2



| | | コメント (0)

2021年8月31日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2

Previously on Mac De Oracle

前回は、遅延ブロッククリーンアウトに限らず、大きく変わったわけでも、最近実装された機能でもないのに意外に知られてないのか、良いところ悪いところ含め、現場でロストしてしまっているような知識って意外と多いのかもねー。なんて感じたので遅延ブロッククリーンアウトネタのURLリンクをまとめてみた。

続編書くにしても、同じようなことやっても面白くないので、ブロックダンプのような方法は使わず、[g]v$sysstatや[g]v$sesstatなどの統計から、ちょいと血糖値や尿酸値高めだよね的な角度からどのような変化が起きるか見ていくことにした :)

 

環境はVirtualBox上の19cでこれからの主流になるマルチテナントで試してみます。(非マルチテナントでの変化見ててもこれからはあまり役に立たないので)

今日は準備編

VirtualBox
https://www.virtualbox.org/

Pre-Built Developer VMs (for Oracle VM VirtualBox)のDatabase App Development VMとか
https://www.oracle.com/downloads/developer-vm/community-downloads.html

 

 

事前に準備しておくスクリプトは以下のとおり。繰り返し実行するので作っておくと便利ですよ。:)

まず最初に、遅延ブロッククリーンアウトはバッファキャッシュの10%ほどのブロックをコミット時にクリーンアウトして、残りを先送りするという基本的なお約束があるので、上記環境のOracle 19cがどの程度のバッファキャッシュなのかとブロックサイズを確認。これ大切ですよ。スクリプト準備する上でも :)

メモリサイズは大きめですが。。。w うちのは。(^^;;;


$ VBoxManage -v
6.1.26r145957
$
$ VBoxManage showvminfo 'Oracle DB Developer VM 19.3' | grep -E 'Memory|CPUs'
Memory size: 16384MB
Number of CPUs: 4
$

コミット時にブロッククリーンアウトされそうなブロック数をざっくり算出すると 42394 ブロックぐらいになりそう。


SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb>
SYS@orclcdb> show parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192
SYS@orclcdb>
SYS@orclcdb> select ceil( 3472883712 / 8192 * 0.1 ) from dual;

CEIL(3472883712/8192*0.1)
-------------------------
42394

Elapsed: 00:00:00.00

42394ブロックを超える程度のサイズのデータを生成するINSERT文と、その範囲に収まるデータ量生成INSERT文スクリプトを作れば良さそうですね。

想定されるブロックに収まる程度の量。1ブロック 8KB でデフォルトのPCTFREEは10%なのでざっくり6000bytes/rec超えるぐらい。
で、1ブロックに3行ぐらい入るようにすれば面白いかな。。

ということで、

表はなんどもdrop/createするので以下のDDLで。初回は索引を作らず、表のみので影響をみることにする。


$ cat droppurge_create_hoge.sql

drop table hoge purge;
create table hoge (id number, data varchar2(2000));

データ作成(バッファキャッシュの10%未満のデータ登録)
なお、確実に遅延ブロッククリーンアウトの影響を見たいので1行ごとのINSERTを繰り返し、コミット前後の状態の変化も見たいのでcommitも含めていない。(commitは別途実行する)


$ cat insert_rows.sql

begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
33334

データ作成(バッファキャッシュの10%を超えるのデータ登録)
単純にループ回数を倍にして増量。これで事前に算出したバッファキャッシュの10%以上のブロック数は更新される。。。はず。


$ cat insert_rows_2.sql

begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認(20,000ブロックぐらい?は遅延される想定)


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ここまでが遅延ブロッククリーンアウトを意図的に起こすためのデータ作成SQLスクリプト

以降は、遅延ブロッククリーンアウトの発生等を見るための[g]v$sysstatと[g]v$sestatを取得するスクリプトと、[g]v$sesstatから特定のセッションを取得するためのクライアント情報をセットするスクリプト。


$ cat set_client_info.sql
BEGIN
DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
END;
.
l
/

システム統計とセッション統計を取得して差分を見ていく必要があるので各統計のスナップショット取得用スクリプトが必要なわけですが、今回はマルチテナント環境。なので、システム統計はCDB全体から、セッション統計は該当するPDBかつ、前述のスクリプトでClient Infoが設定されているセッションに限定するスクリプトを作る必要があるんですよね。DBWRの動きも含めてみたいときって。少し多めに統計名を取得していますが、実際に重要なのはcleanout系の統計ですね。いくつかのテストケースを実施する上で合わせてみておきたい統計も事前に入れてあります:)


$ cat show_stat.sql
set linesize 400
set tab off
set pagesize 1000
col name for a60
col value for 999,999,999,999,999
SELECT
'sysstat' AS "SOURCE"
, name
, value
, con_id
FROM
gv$sysstat
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
UNION ALL
SELECT
'sesstat' AS "SOURCE"
, name
, value
, vsesstat.con_id
FROM
gv$sesstat vsesstat
inner join gv$statname vstatnam
on
vsesstat.statistic# = vstatnam.statistic#
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
and sid = (
select
sid
from
gv$session
where
username = upper('&1')
and client_info = 'TargetSession'
)
order by
4, 1, 2
;

undefine 1

ちなみに、統計を使って状況を確認する方法って意外に利用されているんですよね。日本だとあまり活用されてないようにも感じることは多いのですが、日々の統計を追っかけてると、どのメトリックが高く跳ね上がるのか把握できるので知ってて損することはないと思います:)

そういえば、Tanel PoderのSnapperもその手のツールではありますね。
Session Snapper
http://tech.e2sn.com/oracle-scripts-and-tools/session-snapper

私の過去のセッションでもElappsed Timeを見せないでチューニング効果を見てもらうネタとしてシステム統計を使ってたりします。
db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニング
https://www.slideshare.net/discus_hamburg/db-tech-showcase-tokyo-2013-a35-sql

少々脱線しますが、
最近、VirtualBox、なつかしー。なんて言う方もいますが、古いバージョンのOracleを残しておけるので、リリース毎の動きの差などを見たい場合は便利なのですよーw(クラウドだと強制アップグレードされちゃうので旧バージョンとの動作比較をネタにしたいときなどには向いてないw)

次回へつづく


東京では救急車のサイレンがまだまだ通常より多く聞こえます。。。。

Stay home, Stay Safe and Stay Hydrated.


古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1


 

| | | コメント (0)

2021年3月31日 (水)

Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0) - including hidden params

Previously on Mac De Oracle
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた


 

ということで、今回はめでたくサポート終了となった11gR2と19cのパラメータ差分を確認してみた(何年振りだろうこれw)。21cがでたらまたやる予定。

 


11gR2 : 11.2.0.4.0
19c : 19.3.0.0.0

 

で比較した結果は以下

 

Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0)

 


どうやればこの差分が作れるかというと、DBリンクでもできるけどそんなことしてません。というかやりたくないw ので、旧リリースでパラメータをcsv出力して(comma区切りではないですがw)、19c側で外部表として参照して差分を出力.

 

パラメータの取り出し。linesizeは適当に調整する必要はあるかも


ORACLE-ORCLCDB@SYS> !cat paramout.sql
set linesize 300
set trimspool on
set head off
set feedback off
set timi off
set termout off
set time off
set timi off
set pagesize 0
spo &1..csv

SELECT
TRIM(a.ksppinm)
||'|'||TRIM(a.ksppdesc)||'|'
FROM
x$ksppi a
/
spo off
undefine 1

 

 

上記スクリプトの実行、結果の例


ORACLE-ORCLCDB@SYS> @paramout 11.2.0.4.0_parameters
ORACLE-ORCLCDB@SYS> !cat 11.2.0.4.0_parameters.csv
_appqos_qt|System Queue time retrieval interval|
_ior_serialize_fault|inject fault in the ior serialize code|
_shutdown_completion_timeout_mins|minutes for shutdown operation to wait for sessions to complete|
_inject_startup_fault|inject fault in the startup code|
_latch_recovery_alignment|align latch recovery structures|
_spin_count|Amount to spin waiting for a latch|
_latch_miss_stat_sid|Sid of process for which to collect latch stats|
_max_sleep_holding_latch|max time to sleep while holding a latch|
_max_exponential_sleep|max sleep during exponential backoff|
_other_wait_threshold|threshold wait percentage for event wait class Other|
_other_wait_event_exclusion|exclude event names from _other_wait_threshold calculations|
・・・略・・・

 

外部表の元ネタであるファイル置き場用ディレクト作製とディレクトリオブジェクトの作製、そして、元ネタの配置は以下の通り


ORACLE-ORCLCDB@SYS> !pwd
/home/oracle
ORACLE-ORCLCDB@SYS> !ls -l exttab
合計 488
-rw-rw-r--. 1 oracle oracle 194319 3月 29 21:26 11.2.0.4_parameters.csv

ORACLE-ORCLCDB@SYS> CREATE DIRECTORY ext_tab AS '/home/oracle/exttab';

ディレクトリが作成されました。

ORACLE-ORCLCDB@SYS> l
1 CREATE TABLE ksppi_11_2_0_4_0 (
2 ksppinm VARCHAR2(80)
3 ,ksppdesc VARCHAR2(255)
4 )
5 ORGANIZATION EXTERNAL (
6 TYPE ORACLE_LOADER
7 DEFAULT DIRECTORY ext_tab
8 ACCESS PARAMETERS (
9 RECORDS DELIMITED BY NEWLINE
10 FIELDS TERMINATED BY '|'
11 (
12 ksppinm
13 ,ksppdesc
14 )
15 )
16 LOCATION (
17 '11.2.0.4_parameters.csv'
18 )
19* )
ORACLE-ORCLCDB@SYS> /

表が作成されました。

ORACLE-ORCLCDB@SYS> SELECT COUNT(1) FROM ksppi_11_2_0_4_0;

COUNT(1)
----------
2915

 

 

以下のスクリプトでcdb$rootのx$ksppiと前述の外部表をfull outer joinして差分をhtml形式で出力すればできあがり


col "11g R2 11.2.0.4.0" for a17
col "19c 19.3.0.0.0" for a17
set pagesize 10000
set timi off
set feed off
set markup html on spool on
spo param-diff.html
SELECT
CASE
WHEN prev.ksppinm = curr.ksppinm
OR (
prev.ksppinm IS NULL
AND curr.ksppinm IS NOT NULL
)
THEN curr.ksppinm
ELSE prev.ksppinm
END AS ksppinm
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL THEN '○'
ELSE 'n/a'
END AS "11g R2 11.2.0.4.0"
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN '○'
ELSE 'n/a'
END AS "19c 19.3.0.0.0"
,CASE
WHEN prev.ksppdesc = curr.ksppdesc THEN curr.ksppdesc
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN curr.ksppdesc
WHEN prev.ksppinm iS NOT NULL AND curr.ksppinm IS NULL
THEN prev.ksppdesc
ELSE prev.ksppdesc
END AS kspdesc
,CASE
WHEN prev.ksppdesc = curr.ksppdesc
OR (prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL)
OR (prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL)
THEN NULL
ELSE curr.ksppdesc
END AS "New description"
FROM
(
SELECT * FROM x$ksppi WHERE con_id = 0
) curr
FULL OUTER JOIN ksppi_11_2_0_4_0 prev
ON
curr.ksppinm = prev.ksppinm
ORDER BY
1
;
spool off
set feed on
set set markup html off

 

 


露天風呂♨️行きたい...

 

 


Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params
Difference of Initialization Parameters between 11g and 12c #2
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた

 

| | | コメント (0)

2021年3月22日 (月)

19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた


ORACLE-ORCLCDB@SYS> select banner_full from v$version;

BANNER_FULL
------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
compute sum label 'Total' of "Num Of Parameters" on report
break on report
WITH cte_params AS
(
SELECT
a.ksppinm
FROM
x$ksppi a JOIN x$ksppcv b
ON a.indx = b.indx
)
SELECT
*
FROM
(
SELECT
'1. Single underscore parameters' AS "CATEGORY"
, COUNT(1) AS "Num Of Parameters"
FROM
cte_params
WHERE
REGEXP_LIKE(ksppinm, '^([_][^_]){1}.*')
UNION
SELECT
'2. Double underscore parameters'
, COUNT(1)
FROM
cte_params
WHERE
REGEXP_LIKE(ksppinm, '^[_]{2}.*')
UNION
SELECT
'3. Non hidden parameters'
, COUNT(1)
FROM
cte_params
WHERE
REGEXP_LIKE(ksppinm, '^[^_].*')
)
ORDER by
category;

カウントした結果は以下のとおり。やはりhidden parameter、かなり増えてますよね。

CATEGORY                        Num Of Parameters
------------------------------- -----------------
1. Single underscore parameters 4934
2. Double underscore parameters 30
3. Non hidden parameters 448
-----------------
Total 5412

経過: 00:00:00.06

20210322-141656

 

ぽかぽか陽気すぎて、海辺でパタパタしたいw

 


Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params
Difference of Initialization Parameters between 11g and 12c #2

 

| | | コメント (0)

2021年2月13日 (土)

実行計画は、SQL文のレントゲン写真だ! No.30

OracleのResource Managerネタを書こう書こうと思いつつ類似ネタが他のブログでも扱われていることに気づきw
ネタを被らないようにしたいなぁとw 考えているうちにすでに2月の半ばw もうすこし考えまするw

ということで、今回はこないだ、実行計画という名のレントゲンネタに絡んだtwitterのやりとりがあったついでなので、INSERTの実行計画をいくつか追加しておきます。

Oracleもバージョンが上がるごとに実行計画の改善やオペレーション名を変えたりするので古いバージョンだと、そんなオペレーション出なーーーい。ということもありますが、そんなオペレーションがでたら、こんな意味なんだぁ。

と理解しておけば裏でどう動いているかイメージしやすいのではないかと思います。それがイメージできていれば、もし、性能問題に絡んでいた時にはどう対処するのが良いか、助けになるとはずです (^^)

DIRECT PATH LOADING登場前のOracle Databaseでは気にする必要はなかったのですが、DIRECT PATHが行われるようになってから実行計画のOperationでDIRECT PATHとの区別がつきやすいように追加されたという微かな記憶があります。
(まちがっていたらコメントいただけますと m(_ _)m

ということで、実行計画の見てみましょう。INSERT文なので単純ですw INSERTのoperationが(裏で)どう行われているかの違いです。
(以下の実行計画ではIASを利用して500MBほどの元表から同一定義の別表へ全データをINSERTしています)

LOAD TABLE CONVENTIONALというoperationが該当部分。 覚えておくと何かの役にはたつと思います!

20210213-150833

この実行計画になるように以下のようにNOAPPENDヒントでdirect path writeを抑止しています。ヒントの使い方も覚えておくとなにかのときにや役立ちますよ:)
NO_GATHER_OPTIMIZER_STATISTICSヒントは今回の実行計画には直接関係ないリアルタイム統計の取得を抑止するヒントです。
19cのおそらく後半のリリースから従来型のDMLでもリアルタイム統計が取得されるようになったようです。この例ではじゃまなので抑止。

従来型のDMLでもリアルタイム統計が取得される https://docs.oracle.com/cd/F19136_01/tgsql/optimizer-statistics-concepts.html#GUID-769E609D-0312-43A7-9581-3F3EACF10BA9

Real-Time Statistics https://docs.oracle.com/en/database/oracle/oracle-database/19/dblic/Licensing-Information.html#GUID-0F9EB85D-4610-4EDF-89C2-4916A0E7AC87

INSERT
/*+
MONITOR
NOAPPEND
NO_GATHER_OPTIMIZER_STATISTICS
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

では、つづいて、direct path writeが動作した場合は該当部分のoperationはどうなるでしょうか? :)

LOAD AS SELECT に変わったoperationが該当部分です! operation は LOAD AS SELECT ですが、待機イベントは direct path write です。
どのようなoperationがどのような待機イベントに繋がるのかっていうのもおぼえておくと何かの役に立ちますよ ;)

20210213-150416

実行したINSERT文のヒントがNOAPPPENDからAPPENDに変えてあることに注目。この例ではdirect path writeをヒントで強制しています。

INSERT
/*+
MONITOR
APPEND
NO_GATHER_OPTIMIZER_STATISTICS
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

最後に、NO_GATHER_OPTIMIZER_STATISTICS ヒントを外してリアルタイム統計取得が動いた場合にはどのようなoperationになるかみてみましょう。

20210213-153502

OPTIMIZER STATISTICS GATHERING というoperationが現れました。これは19cの後半で追加された従来型DMLでのリアルタイム統計取得が動作した場合も現れるとマニュアルに記載されているoperationとも同一です。
direct path writeなのかによらず、リアルタイム統計取得の動作有無を確認するためにはこのoperationの有無をチェックする必要があります!

INSERT
/*+
MONITOR
APPEND
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

なかなかよい、レントゲンコレクションが撮れたな :)


Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan

 

| | | コメント (0)

2021年1月15日 (金)

SQL Macros / ほぼ理解した、つもり

SQL Macrosの使い道、ほぼ理解した:)

現在提供されている Live SQL 20.4.2(19c 19.8.0.0.0)で実行可能であることを確認<

Oracle DatabaseのSQLマクロを検証する
https://qiita.com/nakaie/items/75358c3138328dd985c4

SQL Macros - Creating parameterised views
https://livesql.oracle.com/apex/livesql/file/tutorial_KQNYERE8ZF07EZMRR6KJ0RNIR.html<

他のRDBMSからの移行では効果ありそうな気はするが、DWH系かなぁ。MySQLやPostgreSQLから移行してくるのは多くはなさそうだし。
その逆となると色々考え混むわけだが、新機能なのでそこまで影響なさそうな気もする。

昔関わったプロジェクトで、PL/SQL禁止されていた記憶はある。そのようなプロジェクトだと即、禁止されそうな機能ではあるが、単純にSQL文が内部展開されるのだとすると書き換えは容易にかもしれない。内部的にどうなるのか現物が出てきてたら詳しく見ておきたいところ。

個人的に、こいつの使い道は、既存UDFの高速化などがメインになるのかなと、遠ーくをみて妄想しているところ:)
特にデータ量が膨大になればなるほど此の手のが効いてくるわけで。

20210115-110329

| | | コメント (0)

2019年12月30日 (月)

実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 おまけ#3

この実行計画がなにを行っているか見抜けたかたは, 前日のエントリーを読んだか, RDFVIEWを利用したRDF Graphを試したことがある方ぐらいだと思います.
RDF Semantic Graph「RDF 超入門」

RDFトリプルをRelational表にマップしてダイレクトにアクセスする方式なのですが, ご存じない方には何のこっちゃというのも仕方ないと思います.
3,4年前ぐらいに, なんとなーく流れ的に関わることになって, 寝る暇も惜しんでRDFトリプルストア (実態はオブジェクト表だったはず. 違ったらコメント頂けますと幸いです) とこのRDFVIEWのSQLチューニング方法を調べていたもんですw

そのころは余裕なさすぎてブログにも書けずじまいでしたが, 良い機会なのでRDFVIEWを問い合わせた実行計画をレントゲン写真として載せておきます.
詳細は前日の準備段階のエントリーを読んでいただけると良いかと思いますが, 知らない用語とかいきなりでてきているので, なんとなーくそんなもんかなーぐらいの理解よいと思います.

RDFVIEWのを問い合わせるSQL文は, SEM_MATCH()を利用するのが特徴で, RDFVIEW最大の特徴であるRelational表を直接よみながら, 最終的に RDFトリプルを返すという点にあります.
R2RML: RDB to RDF Mapping Language / W3Cあたりを読んでもらうと想像できるのではないかと思います. 読んで最初にきづくのは,それに関わっているのは Oracle社の方だったりしてるので納得感があります.

ということで、
SQLは以下のとおり. 関わったことがない方はこれまた見たこともない SEM_MATCH()関数が大量のパラメータを取って呼び出されていることが見えるぐらいですね。
裏では, SEM_MODEL()で指定したRDF Graphのモデルに対応したRelational表のEMPLOYEES表からEMPLOYEE_IDを元に, FIRST_NAME, LAST_NAME, MANAGER_IDの3列を, それぞれをトリプルとして返すようなマッピングになっています.

SELECT
s
, p
, o
FROM
TABLE (
SEM_MATCH (
'{?s ?p ?o}'
, SEM_MODELS('TEST_MODEL1')
, null
, null
, null
, null
, ' '
, null
, null
, 'RDFUSER'
, 'LOCALNET'
)
)
ORDER BY
s
,p;
RDFUSER@orcl> desc employees
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)

RDFUSER@orcl> break on index_name skip 1
RDFUSER@orcl> select index_name,column_name from user_ind_columns where table_name='EMPLOYEES' order by 1,2;

INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------
EMP_DEPARTMENT_IX DEPARTMENT_ID

EMP_EMAIL_UK EMAIL

EMP_EMP_ID_PK EMPLOYEE_ID

EMP_JOB_IX JOB_ID

EMP_MANAGER_IX MANAGER_ID

EMP_NAME_IX FIRST_NAME
LAST_NAME

では, 実行計画を読み解いてみましょう.

EMPLOYEES表からEMPLOYEE_IDを元に, FIRST_NAME, LAST_NAME, MANAGER_IDの3列と説明しましたが, それぞれの列に索引が存在しているため, Index Only Scanで索引のみをアクセスし,
Id=7,8,9でEMPLOYEE_IDをEMP_EMP_ID_PK索引から, MANAGER_IDをEMP_MANAGER_IX索引から EMPLOYEE_IDとMANAGER_IDの2列からなら行を結合で作り出しています.
Id=11,12,13で同じく, EMPLOYEE_IDとEMP_NAME_IX索引から, EMPLOYEE_IDとLAST_NAMEの2列からなる行を結合で作り出してます.
Id=15,16,17でも, EMPLOYEE_IDとEM_NAME_IX索引から, EMPLOYEE_IDとFAST_NAMEの2列からなる行を結合でつくりだしています.

これらの動きから, トリプルを作成するために, EMPLOYEE_ID列, MANAGER_ID, FAST_NAME, FIRST_NAME列ぞれぞれを取得するために複数回索引にアクセスしています.
もし索引がなかったら, それぞれの列を取得するために, 複数回全表走査を行うだろうということは容易に想像できます. これらの動きは、RDFVIEWの特性の一つになっています. Relational表に格納されたデータを即刻RDFトリプルとして参照したいという目的のために性能にはある程度目をつぶっている姿が見えてきます.
列持ちのデータをトリプルという, ある意味, 行持ちのデータへ縦横変換しているわけですから仕方ない動作ではあります.

データの新鮮さよりも性能を, というケースでは, ロードという作業は必要になりますがトリプルストアを利用したほうが有利にはなります. トリプルストアをアクセスする実行計画は気が向いたら載せるかもしれません.
覚えておいてほしいことは, Id=19やPredicate InformationにRDF_RRのようなオブジェクトとUNION ALLのVIEWが1~3個登場したらRDFVIEWだろうということです. 治療が必要になった場合などには役にたつかもしれません.

Rdfview

-----
それでは, みなさま, 良いお年をお迎えください.



previously on Mac De Oracle
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)

| | | コメント (0)

2019年12月29日 (日)

RDFナレッジ・グラフ / 備忘録 19c

5年ぐらい前に触ったことがある程度で、すっかり忘れてしまった。しばらくぶりで思い出してパタパタしてみたら、Apache Jenaも含めていろいろ変わっていたので備忘録

RDFナレッジ・グラフ開発者ガイド RDFナレッジ・グラフの概要

R2RML: RDB to RDF Mapping Language

A Direct Mapping of Relational Data to RDF

いろいろ思い出しながら....

$ sqlplus sys@orclcdb as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Dec 28 00:51:52 2019
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle. All rights reserved.

Enter password:

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> col namespace for a30
SQL> col value for a40
SQL> col description for a50
SQL> set linesize 400
SQL> set tab off
SYS@orclcdb> select * from MDSYS.RDF_PARAMETER;

NAMESPACE ATTRIBUTE VALUE DESCRIPTION
------------------- ----------------- ------------- --------------------------------------------------
COMPONENT RDFCTX INSTALLED Semantic (Text) Search component
COMPONENT RDFOLS INSTALLED RDF Optional component for OLS support
MDSYS SEM_VERSION 19.1.0.0.0 VALID

SYS@orclcdb> r
1 SELECT namespace, attribute, value FROM mdsys.rdf_parameter
2 WHERE namespace='MDSYS'
3 AND attribute IN ('FLOAT_DOUBLE_DECIMAL',
4 'XSD_TIME', 'XSD_BOOLEAN',
5* 'DATA_CONVERSION_CHECK')

no rows selected

SYS@orclcdb> conn system@orcl
Enter password:
Connected.
SYSTEM@orcl> create tablespace rdf_users datafile 'rds_users01.dbf' size 128m reuse autoextend on next 64m maxsize unlimited segment space management auto;

Tablespace created.

SYSTEM@orcl> create user rdfuser identified by hogehoge default tablespace rdf_users temporary tablespace temp;

User created.

SYSTEM@orcl> grant create view to rdfuser;

Grant succeeded.

SYSTEM@orcl> grant unlimited tablespace to rdfuser;

Grant succeeded.

SYSTEM@orcl> grant unlimited tablespace to mdsys;

Grant succeeded.

SYSTEM@orcl> grant select any dictionary to rdfuser;

Grant succeeded.

SYSTEM@orcl> grant connect, resource to rdfuser;

Grant succeeded.

SYSTEM@orcl> execute sem_apis.create_sem_network('RDF_USERS', network_owner=>'RDFUSER', network_name=>'LOCALNET');

PL/SQL procedure successfully completed.


SYSTEM@orcl> conn rdfuser@orcl
Enter password:
Connected.
RDFUSER@orcl>

RDFUSER@orcl> l
1 create table r2rmlview_nt_staging_tab (
2 rdf$stc_sub varchar2(4000) not null
3 ,rdf$stc_pred varchar2(4000) not null
4 ,rdf$stc_obj varchar2(4000) not null
5 )
6* nologging
RDFUSER@orcl> /

Table created.

RDFUSER@orcl> l
1 create table rdfview_export_tab (
2 rdf$stc_sub varchar2(4000) not null
3 ,rdf$stc_pred varchar2(4000) not null
4 ,rdf$stc_obj varchar2(4000) not null
5 )
6* nologging
RDFUSER@orcl> /

Table created.

RDFでいろいろやるには sga_targetやsga_max_sizeはそれなりに必要なので適当に調整

そして、Apache Jenaのrdfcatもdeprecatedになってて、これまたしばし時代に追いつく作業を....

RDFUSER@orcl> !rdfcat --help
------------------------------------------------------------------
DEPRECATED: Please use 'riot' instead.
http://jena.apache.org/documentation/io/#command-line-tools
------------------------------------------------------------------

------------------------------------
DEPRECATED: Please use riot instead.
------------------------------------

Usage: java jena.rdfcat (option|input)*
Concatenates the contents of zero or more input RDF documents.
Options: -out N3 | N-TRIPLE | RDF/XML | RDF/XML-ABBREV
-n expect subsequent inputs in N3 syntax
-x expect subsequent inputs in RDF/XML syntax
-t expect subsequent inputs in N-TRIPLE syntax
-[no]include include rdfs:seeAlso and owl:imports
input can be filename, URL, or - for stdin
Recognised aliases for -n are: -n3 -ttl or -N3
Recognised aliases for -x are: -xml -rdf or -rdfxml
Recognised aliases for -t are: -ntriple
Output format aliases: x, xml or rdf for RDF/XML, n, n3 or ttl for N3, t or ntriple for N-TRIPLE
See the Javadoc for jena.rdfcat for additional details.

rdfcatに変えて、riot ってやつをつかわなきゃいけなくなったっぽい。

RDFUSER@orcl> !riot -version
Jena: VERSION: 3.13.1
Jena: BUILD_DATE: 2019-10-06T18:57:39+0000
RIOT: VERSION: 3.13.1
RIOT: BUILD_DATE: 2019-10-06T18:57:39+0000

ちなみに、SHユーザのEMPLOYEES表や索引をRDFUSERユーザへコピーした上で、以下のような、RDFVIEWとRelational表と列のマッピングをTurtle (Terse RDF Triple Language)で定義した。

RDFUSER@orcl> !cat test_real_rdf_r2rml.ttl
@prefix rr: .
@prefix xsd: .
@prefix ex: .

ex:TriplesMap_Employees
rr:logicalTable [ rr:tableName "EMPLOYEES" ];
rr:subjectMap [
rr:template "http://r2rml.com/employees/{EMPLOYEE_ID}";
rr:class ex:Employees;
];

rr:predicateObjectMap [
rr:predicate ex:FirstName;
rr:objectMap [ rr:column "FIRST_NAME" ];
];

rr:predicateObjectMap [
rr:predicate ex:LastName;
rr:objectMap [ rr:column "LAST_NAME" ];
];

rr:predicateObjectMap [
rr:predicate ex:ManagerId;
rr:objectMap [ rr:column "MANAGER_ID" ];
].

5年ぐらい前の記憶ではrdfcatを利用して変換していたが、今は、 riot というコマンドを利用してTurtle(Terse RDF Triple Language)N-Triplesへ変換するみたいね。

RDFUSER@orcl> !riot --out=N-TRIPLE test_real_rdf_r2rml.ttl > test_real_rdf_r2rml_use_riot.nt

RDFUSER@orcl> !cat test_real_rdf_r2rml_use_riot.nt
_:Bf70c7f0d1b418dc63ad89dbcea313cd1 "EMPLOYEES" .
_:Bf70c7f0d1b418dc63ad89dbcea313cd1 .
_:Bdd4cf5eb9ecb0b12212d342518513827 "http://r2rml.com/employees/{EMPLOYEE_ID}" .
_:Bdd4cf5eb9ecb0b12212d342518513827 .
_:Bdd4cf5eb9ecb0b12212d342518513827 .
_:B310abfde7d61aac8f303fbd1f4ba5db8 .
_:Bfe48b8372e4da59d0ce99e0e24b894ad "FIRST_NAME" .
_:B310abfde7d61aac8f303fbd1f4ba5db8 _:Bfe48b8372e4da59d0ce99e0e24b894ad .
_:B310abfde7d61aac8f303fbd1f4ba5db8 .
_:B11c76919901c01b44cb0e2507a997c28 .
_:B5474fc7a1b7c7302521175dac3c58028 "LAST_NAME" .
_:B11c76919901c01b44cb0e2507a997c28 _:B5474fc7a1b7c7302521175dac3c58028 .
_:B11c76919901c01b44cb0e2507a997c28 .
_:Be7008cecff6b130ea15b4ba060cee0fb .
_:B0f41936e13c4655082ee47f866dc9f61 "MANAGER_ID" .
_:Be7008cecff6b130ea15b4ba060cee0fb _:B0f41936e13c4655082ee47f866dc9f61 .
_:Be7008cecff6b130ea15b4ba060cee0fb .


SQL*Loaderを使用したステージング表へのN-Triple形式のデータのロードを参考

RDFUSER@orcl> !cat test_real_rdf_r2rml_nt_load.ctl
UNRECOVERABLE
LOAD DATA
TRUNCATE
into table r2rmlview_nt_staging_tab
when (1) <> '#'
(
RDF$STC_sub CHAR(4000) terminated by whitespace
"(
CASE
WHEN substr(:RDF$STC_sub,1,1)='<' AND substr(:RDF$STC_sub,-1,1)='>' AND
length(:RDF$STC_sub)>2
THEN :RDF$STC_sub
WHEN substr(:RDF$STC_sub,1,2)='_:' AND
REGEXP_LIKE(:RDF$STC_sub,'^(_:)[[:alpha:]][[:alnum:]]*$')
THEN :RDF$STC_sub
WHEN substr(:RDF$STC_sub,1,1) NOT IN ('\"','<','#') AND
substr(:RDF$STC_sub,-1,1) NOT IN ('\"','>')
THEN ('<' || SDO_RDF.replace_rdf_prefix(:RDF$STC_sub) || '>')
WHEN substr(:RDF$STC_sub,1,1)='#'
THEN SDO_RDF.raise_parse_error(
'Ignored Comment Line starting with ', :RDF$STC_sub)
ELSE SDO_RDF.raise_parse_error('Invalid Subject', :RDF$STC_sub)
END
)",
RDF$STC_pred CHAR(4000) terminated by whitespace
"(
CASE
WHEN substr(:RDF$STC_pred,1,1)='<' AND substr(:RDF$STC_pred,-1,1)='>' AND
length(:RDF$STC_pred)>2
THEN :RDF$STC_pred
WHEN substr(:RDF$STC_pred,1,2) != '_:' AND
substr(:RDF$STC_pred,1,1) NOT IN ('\"','<') AND
substr(:RDF$STC_pred,-1,1) NOT IN ('\"','>')
THEN ('<' || SDO_RDF.replace_rdf_prefix(:RDF$STC_pred) || '>')
ELSE SDO_RDF.raise_parse_error('Invalid Predicate', :RDF$STC_pred)
END
)",
--
-- right-trimming of WHITESPACEs is reqd for "RDF$STC_obj"
-- (due to absence of "TERMINATED BY WHITESPACE")
--
-- For ease of editing below replace
-- "rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))" with ":xy".
-- and then replace back
--
RDF$STC_obj CHAR(4000)
"(
CASE
WHEN substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,1)='<' AND
substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),-1,1)='>' AND
length(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)))>2
THEN rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))
WHEN substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,1)='\"' AND
substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),-1,1)='\"' AND
length(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)))>1
THEN rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))
WHEN substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,2)='_:' AND
REGEXP_LIKE(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),
'^(_:)[[:alpha:]][[:alnum:]]*$')
THEN rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))
WHEN substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,1)
NOT IN ('\"','<') AND
substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),-1,1)
NOT IN ('\"','>')
THEN ('<' ||
SDO_RDF.replace_rdf_prefix(
rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))) ||
'>')
WHEN substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,1)='\"' AND
substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),-1,1)
NOT IN ('\"','>') AND
instr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),'\"\@',-1)>1 AND
REGEXP_LIKE(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),
'^\"[[:print:]]*\"\@[[:alpha:]]+(-[[:alnum:]]+)*$')
THEN rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13))
WHEN (substr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),1,1)='\"' AND
instr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),'\"^^',-1)>1 AND
(length(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)))-
(instr(rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)),'\"^^',-1)+4)
)>0)
THEN SDO_RDF.pov_typed_literal(
rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)))
ELSE SDO_RDF.raise_parse_error(
'Invalid Object', rtrim(:RDF$STC_obj,'. '||CHR(9)||CHR(10)||CHR(13)))
END
)"
)

RDFUSER@orcl> exit


$ sqlldr userid=rdfuser@orcl control=test_real_rdf_r2rml_nt_load.ctl data=test_real_rdf_r2rml_use_riot.nt direct=true skip=0 load=1000000 discardmax=0 bad=test_real_rdf_r2rml_nt_load.bad discard=test_real_rdf_r2rml_nt_load.rej log=test_real_rdf_r2rml_nt_load.log errors=1000000
Password:

SQL*Loader: Release 19.0.0.0.0 - Production on Sat Dec 28 02:05:44 2019
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.

Path used: Direct, LOAD=1000000

Load completed - logical record count 17.

Table R2RMLVIEW_NT_STAGING_TAB:
17 Rows successfully loaded.

Check the log file:
test_real_rdf_r2rml_nt_load.log
for more information about the load.

そして、SEM_MATCH()関数を利用してモデルと問い合わせると....

RDFUSER@orcl> @test_real_rdf_cre_rdfview.sql

PL/SQL procedure successfully completed.

RDFUSER@orcl> l
1 SELECT
2 s
3 , p
4 , o
5 FROM
6 TABLE (
7 SEM_MATCH (
8 '{?s ?p ?o}'
9 , SEM_MODELS('TEST_MODEL1')
10 , null
11 , null
12 , null
13 , null
14 , ' '
15 , null
16 , null
17 , 'RDFUSER'
18 , 'LOCALNET'
19 )
20 )
21 ORDER BY
22 s
23 ,p
24*
RDFUSER@orcl>
RDFUSER@orcl> @test_query_rdfview.sql

S P O
---------------------------------------- -------------------------------------------------- ----------------------------------------
http://r2rml.com/employees/100 http://r2rml.com/ns#FirstName Steven
http://r2rml.com/employees/100 http://r2rml.com/ns#LastName King
http://r2rml.com/employees/100 http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://r2rml.com/ns#Employees
http://r2rml.com/employees/101 http://r2rml.com/ns#FirstName Neena
http://r2rml.com/employees/101 http://r2rml.com/ns#LastName Kochhar
http://r2rml.com/employees/101 http://r2rml.com/ns#ManagerId 100
http://r2rml.com/employees/101 http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://r2rml.com/ns#Employees
http://r2rml.com/employees/102 http://r2rml.com/ns#FirstName Lex
http://r2rml.com/employees/102 http://r2rml.com/ns#LastName De Haan
http://r2rml.com/employees/102 http://r2rml.com/ns#ManagerId 100

・・・中略・・・

http://r2rml.com/employees/205 http://r2rml.com/ns#FirstName Shelley
http://r2rml.com/employees/205 http://r2rml.com/ns#LastName Higgins
http://r2rml.com/employees/205 http://r2rml.com/ns#ManagerId 101
http://r2rml.com/employees/205 http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://r2rml.com/ns#Employees
http://r2rml.com/employees/206 http://r2rml.com/ns#FirstName William
http://r2rml.com/employees/206 http://r2rml.com/ns#LastName Gietz
http://r2rml.com/employees/206 http://r2rml.com/ns#ManagerId 205
http://r2rml.com/employees/206 http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://r2rml.com/ns#Employees

| | | コメント (0)

2019年9月30日 (月)

なぜ、そこに、LONG型があるんだ / FAQ

all/dba/user_tab_columns

https://docs.oracle.com/cd/E82638_01/refrn/ALL_TAB_COLUMNS.html#GUID-F218205C-7D76-4A83-8691-BFD2AD372B63

これらのビューは、列の属性関連の情報を持つビューです。
たまに、便利なびゅーではあるのですが、これらのビューをアクセスする使うスクリプトというかPL/SQLでコード書くこともあるのですが、一箇所だけ、使いにくいところがあります。

 

どこかわかります?

下位互換のためだろうと思われるのですが、一般には推奨されていない LONG型の列 が残っています。

ご存知だとは思いますが、一般的なガイドだと、CLOBの利用が推奨されています。
下位互換のためだから仕方ないのだとは思うのですが。

LONG型といえば、とにかく制約が多くて、文字列操作を行うにもめんどくさいわけで、実際に利用したい状況になると、うううううっとなることしばしば。

LONG型

で、普段どうやって、その面倒くさいところを回避しているかといえば、CLOBに変換してしまうことがが多いです。
CLOBにしてしまえば、沢山の制約から解放されますしね :)

以下のような感じで。


SCOTT> l
1 CREATE TABLE my_dba_tab_columns
2 AS
3 SELECT
4 owner
5 ,table_name
6 ,column_name
7 ,data_type
8 ,data_type_mod
9 ,data_type_owner
10 ,data_length
11 ,data_precision
12 ,data_scale
13 ,nullable
14 ,column_id
15 ,TO_CLOB(default_length) AS default_length
16 ,num_distinct
17 ,low_value
18 ,high_value
19 ,density
20 ,num_nulls
21 ,num_buckets
22 ,last_analyzed
23 ,sample_size
24 ,character_set_name
25 ,char_col_decl_length
26 ,global_stats
27 ,user_stats
28 ,avg_col_len
29 ,char_length
30 ,char_used
31 ,v80_fmt_image
32 ,data_upgraded
33 ,histogram
34 ,default_on_null
35 ,identity_column
36 ,sensitive_column
37 ,evaluation_edition
38 ,unusable_before
39 ,unusable_beginning
40 ,collation
41 FROM
42* dba_tab_columns
SCOTT> /

Table created.

SCOTT> desc dba_tab_columns
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
TABLE_NAME NOT NULL VARCHAR2(128)
COLUMN_NAME NOT NULL VARCHAR2(128)
DATA_TYPE VARCHAR2(128)
DATA_TYPE_MOD VARCHAR2(3)
DATA_TYPE_OWNER VARCHAR2(128)
DATA_LENGTH NOT NULL NUMBER
DATA_PRECISION NUMBER
DATA_SCALE NUMBER
NULLABLE VARCHAR2(1)
COLUMN_ID NUMBER
DEFAULT_LENGTH NUMBER
DATA_DEFAULT LONG
NUM_DISTINCT NUMBER
LOW_VALUE RAW(2000)
HIGH_VALUE RAW(2000)
DENSITY NUMBER
NUM_NULLS NUMBER
NUM_BUCKETS NUMBER
LAST_ANALYZED DATE
SAMPLE_SIZE NUMBER
CHARACTER_SET_NAME VARCHAR2(44)
CHAR_COL_DECL_LENGTH NUMBER
GLOBAL_STATS VARCHAR2(3)
USER_STATS VARCHAR2(3)
AVG_COL_LEN NUMBER
CHAR_LENGTH NUMBER
CHAR_USED VARCHAR2(1)
V80_FMT_IMAGE VARCHAR2(3)
DATA_UPGRADED VARCHAR2(3)
HISTOGRAM VARCHAR2(15)
DEFAULT_ON_NULL VARCHAR2(3)
IDENTITY_COLUMN VARCHAR2(3)
SENSITIVE_COLUMN VARCHAR2(3)
EVALUATION_EDITION VARCHAR2(128)
UNUSABLE_BEFORE VARCHAR2(128)
UNUSABLE_BEGINNING VARCHAR2(128)
COLLATION VARCHAR2(100)

SCOTT> desc my_dba_tab_columns
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
TABLE_NAME NOT NULL VARCHAR2(128)
COLUMN_NAME NOT NULL VARCHAR2(128)
DATA_TYPE VARCHAR2(128)
DATA_TYPE_MOD VARCHAR2(3)
DATA_TYPE_OWNER VARCHAR2(128)
DATA_LENGTH NOT NULL NUMBER
DATA_PRECISION NUMBER
DATA_SCALE NUMBER
NULLABLE VARCHAR2(1)
COLUMN_ID NUMBER
DEFAULT_LENGTH CLOB
NUM_DISTINCT NUMBER
LOW_VALUE RAW(2000)
HIGH_VALUE RAW(2000)
DENSITY NUMBER
NUM_NULLS NUMBER
NUM_BUCKETS NUMBER
LAST_ANALYZED DATE
SAMPLE_SIZE NUMBER
CHARACTER_SET_NAME VARCHAR2(44)
CHAR_COL_DECL_LENGTH NUMBER
GLOBAL_STATS VARCHAR2(3)
USER_STATS VARCHAR2(3)
AVG_COL_LEN NUMBER
CHAR_LENGTH NUMBER
CHAR_USED VARCHAR2(1)
V80_FMT_IMAGE VARCHAR2(3)
DATA_UPGRADED VARCHAR2(3)
HISTOGRAM VARCHAR2(15)
DEFAULT_ON_NULL VARCHAR2(3)
IDENTITY_COLUMN VARCHAR2(3)
SENSITIVE_COLUMN VARCHAR2(3)
EVALUATION_EDITION VARCHAR2(128)
UNUSABLE_BEFORE VARCHAR2(128)
UNUSABLE_BEGINNING VARCHAR2(128)
COLLATION VARCHAR2(100)

 


db tech showcase 2019もおわり、今年も残すところ 3ヶ月あまり。一年早い. そして。
来週は、開催時期を秋に変更してから2回目の多摩川花火大会。天気がよいといいのですが:)

ではまた。

| | | コメント (0)

2019年8月25日 (日)

FAQ / PL/SQL PACKAGEでパプリックスコープを持つ定数をSQL文中で利用するには...

かなーり、ご無沙汰しておりました。(本業でいっぱいいっぱいで、という言い訳はこれぐらいにしてw) 偶に聞かれることがあるので、FAQネタから。 パッケージでパブリックなスコープを持つ定数は無名PL/SQLブロックやパッケージ、プロシージャ、ファンクションでしか参照できないんですよねー 例えば、DBMS_CRYPTOパッケージでHASHファンクションを利用してSH-256を作成したいなーと思って、

39.4 DBMS_CRYPTOのアルゴリズム
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_CRYPTO.html#GUID-CE3CF17D-E781-47CB-AEE7-19A9B2BCD3EC
DBMS_CRYPTO.HASH()は関数なのでSQL文から呼びたーい、と以下のような使い方をすると...

SQL> SELECT DBMS_CRYPTO.HASH(TO_CLOB('hoge'), DBMS_CRYPTO.HASH_SH2569) AS "SH-256" FROM dual;
SELECT DBMS_CRYPTO.HASH(TO_CLOB('hoge'), DBMS_CRYPTO.HASH_SH2569) AS "SH-256" FROM dual
*
ERROR at line 1:
ORA-00904: "DBMS_CRYPTO"."HASH_SH2569": invalid identifier


SQL>
SQL> select DBMS_CRYPTO.HASH_SH256 from dual;
select DBMS_CRYPTO.HASH_SH256 from dual
*
ERROR at line 1:
ORA-06553: PLS-221: 'HASH_SH256' is not a procedure or is undefined
見事にエラーとなるわけです。 DBMS_CRYPTO.HASH_SH256は、パッケージファンクションではないので...利用可能なのはPL/SQLでのみ。
SQL> set serveroutput on
SQL>
¥SQL>
SQL> begin
2 dbms_output.put_line('DBMS_CRYPTO.HASH_SH256 : ' || DBMS_CRYPTO.HASH_SH256);
3 end;
4 /
DBMS_CRYPTO.HASH_SH256 : 4

PL/SQL procedure successfully completed.
SQL文で活用する為には、ファンクションでラップする必要があります。 以下のように。
SQL> l
1 CREATE OR REPLACE FUNCTION get_hash_sh256_type
2 RETURN NUMBER
3 AS
4 BEGIN
5 RETURN DBMS_CRYPTO.HASH_SH256;
6* END;
SQL> /

Function created.

SQL>
冒頭でエラーとなっていたSQL文をDBMS_CRYPTO.HASH_SH256を返すファンクションを使うように書き換えると、 はい、できました。
SQL> l
1 SELECT
2 DBMS_CRYPTO.HASH(
3 TO_CLOB('hoge')
4 , get_hash_sh256_type()
5 ) AS "SH-256"
6 FROM
7* dual
SQL> /

SH-256
--------------------------------------------------------------------------------
ECB666D778725EC97307044D642BF4D160AABB76F56C0069C71EA25B1E926825

SQL>


露店の焼きそばと焼き鳥を食べつつ、晩夏の夏祭りと、涼しい朝晩の気温で熟睡可能な山形より。 では、では。

| | | コメント (0)