年を跨いで, ”実行計画は, 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 | . | | | ======================================================================================================================================================
|
最近のコメント