実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination Tweet
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
コメント