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年9月 1日 (日)

なぜ、主キー制約の追加時間に違いがでるのでしょうか? (東京都 ITエンジニア 男性)/ FAQ のおまけ

Previously on Mac De Oracle
前回は、なぜ、主キー制約の追加時間に違いがでるのでしょうか? (東京都 ITエンジニア 男性)/ FAQでした。
今日は、おまけとして、前回意図的に書いていなかったツールを利用した場合にも意図せず発症してしまう一例を紹介したいと思います。

 

前回のエントリーで以下のようなことを書いていたのですがおぼえているでしょうか?。
回避方法は、主キー列のNOT NULL制約(チェック制約)が落ちてしまうのを避ければよいだけなので大した内容ではないのですが、Data Pumpっていう癖の多いツールが絡む場合の例なので書いておいたほうが良いかなぁと。ww

 

それ以外のケースでは、各種ツール(Oracle database純正のもを含む)の使い方次第では意図せず遭遇してしまうこともあります。(ex. impdp利用時のオブジェクトの扱いを制御するパラメータの使い方によるもの、とか)”

 

では、早速準備から。
impdpするので、expdpしておかないといけません。それより前にData Pump用のディレクトリオブジェクトを作っておきましょう。ホスト側に実際のディレクトリを用意して権限なども忘れずに設定しておきましょうね。
CREATE DIRECTORY文ではそこまで検証してくれないので、impdp/expdp実行時に、”あ”〜っ” みたいなエラーに遭遇しないようにしておきますw(けっこうやらかしがちなんですけどね。私もwwww)

 

[oracle@localhost work]$ sqlplus scott@orclpdb1

...中略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> CREATE DIRECTORY mydump_dir AS '/work';

ディレクトリが作成されました。

経過: 00:00:00.57
SCOTT@orclpdb1> ! ls -l /work
合計 0

SCOTT@orclpdb1>

 

次に、前回も作成したTABLE_2だけを用意します。主キー列にはNOT NULL制約があり、主キー制約も作成しておきます。(主キー制約追加で遅延が発生しなかった状態)

SCOTT@orclpdb1> @additional_example.sql

表が削除されました。

経過: 00:00:00.54
1 CREATE TABLE table_2
2 (
3 id1 NUMBER NOT NULL
4 ,id2 NUMBER NOT NULL
5 ,text VARCHAR2(10)
6* )

表が作成されました。

経過: 00:00:00.11
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

1 BEGIN
2 FOR i in 1..15000000 LOOP
3 INSERT INTO table_2 VALUES(i,i,i);
4 IF MOD(i,1000) = 0
5 THEN
6 COMMIT;
7 END IF;
8 END LOOP;
9* END;

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

経過: 00:07:06.40
1* ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX

表が変更されました。

経過: 00:00:35.23

 

expdpで表モードでエクスポートしておきます。TABLE_2だけで十分。
正常にエクスポートできたら準備完了!

[oracle@localhost ~]$ expdp scott@orclpdb1 tables=scott.table_2 directory=mydump_dir dumpfile=scott_table_2.dmp

Export: Release 21.0.0.0.0 - Production on 土 8月 31 15:36:56 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle and/or its affiliates. All rights reserved.
パスワード:

接続先: Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
"SCOTT"."SYS_EXPORT_TABLE_01"を起動しています: scott/********@orclpdb1 tables=scott.table_2 directory=mydump_dir dumpfile=scott_table_2.dmp
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
. . "SCOTT"."TABLE_2" 344.8 MB 15000000行がエクスポートされました
マスター表"SCOTT"."SYS_EXPORT_TABLE_01"は正常にロード/アンロードされました
******************************************************************************
SCOTT.SYS_EXPORT_TABLE_01に設定されたダンプ・ファイルは次のとおりです:
/work/scott_table_2.dmp
ジョブ"SCOTT"."SYS_EXPORT_TABLE_01"が土 8月 31 15:38:00 2024 elapsed 0 00:00:50で正常に完了しました

 

 

では、主キー制約の追加で時間を要してしまう例の紹介から。

 

excludeオプションで、CONSTRAINTを除外してしまっています。後述しますが、これ方法で除外してしまうと全ての制約が除外されてしまいます。 NOT NULLもCHECK制約とい制約の一つなので、副作用を考慮せず除外してしまうと、NULLBALEな状態でインポートされていまうことを意味します。
つまり、今回話題にしている症状が発症してしまう典型的な状況が作り出されてしまうということですよね。そろそろ気づき始めましたかね? 

 

以下のインポートのログには、CONSTRAINTオブジェクトがインポートされたとは記録されません。exclude しちゃってますからね!(実は、制約名で一部の制約だけにしても同様のログになるのでわかりにくいのですけどね! Data Pumpの癖の一つw)

[oracle@localhost ~]$ impdp scott@orclpdb1 tables=scott.table_2 exclude=constraint table_exists_action=replace directory=mydump_dir dumpfile=scott_table_2.dmp

Import: Release 21.0.0.0.0 - Production on 土 8月 31 16:15:43 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle and/or its affiliates. All rights reserved.
パスワード:

接続先: Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
マスター表"SCOTT"."SYS_IMPORT_TABLE_01"は正常にロード/アンロードされました
"SCOTT"."SYS_IMPORT_TABLE_01"を起動しています: scott/********@orclpdb1 tables=scott.table_2 exclude=constraint table_exists_action=replace directory=mydump_dir
dumpfile=scott_table_2.dmp
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
. . "SCOTT"."TABLE_2" 344.8 MB 15000000行がインポートされました
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
ジョブ"SCOTT"."SYS_IMPORT_TABLE_01"が土 8月 31 16:17:02 2024 elapsed 0 00:01:05で正常に完了しました

 

 

 

インポート完了後、desc でみると ID1, ID2ともにNULLABLEになっています。まずい状態ですよね
制約は全く存在しません。(exclude=constraintにしてしまったので当然ではあるわけですがw)

[oracle@localhost ~]$ sqlplus scott@orclpdb1

...中略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
1SCOTT@orclpdb1> set line 80
1SCOTT@orclpdb1> desc table_2
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NUMBER
ID2 NUMBER
TEXT VARCHAR2(10)

SCOTT@orclpdb1> SELECT table_name,constraint_name,constraint_type,search_condition FROM user_constraints WHERE table_name = 'TABLE_2';

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

経過: 00:00:04.90
SCOTT@orclpdb1> SELECT COUNT(1) FROM table_2;

COUNT(1)
----------
15000000

経過: 00:00:01.03

 

 

主キー制約追加で例の症状を発症させてみましょう! 主キー制約追加に、10秒もかかってますね。

SCOTT@orclpdb1> CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2);

索引が作成されました。

経過: 00:00:30.11
SCOTT@orclpdb1> ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2;

表が変更されました。

経過: 00:00:10.60
SCOTT@orclpdb1> desc table_2
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

SCOTT@orclpdb1> set line 400
SCOTT@orclpdb1> SELECT table_name,constraint_name,constraint_type,search_condition FROM user_constraints WHERE table_name = 'TABLE_2'

TABLE_NAME CONSTRAINT_NAME CON SEARCH_CONDITION
------------------------------ ------------------------------ --- ------------------------------
TABLE_2 PK_TABLE_2 P

経過: 00:00:00.02

 

 

再確認のため、ID1, ID2をNOT NULLに変更した上で、主キー制約を追加してましょう。
見事に瞬殺で完了しているのがわかります!

SCOTT@orclpdb1> ALTER TABLE table_2 DROP PRIMARY KEY DROP INDEX;

表が変更されました。

経過: 00:00:00.39
SCOTT@orclpdb1> ALTER TABLE table_2 MODIFY (id1 NUMBER NOT NULL, id2 NUMBER NOT NULL);

表が変更されました。

経過: 00:00:03.02
SCOTT@orclpdb1> CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2);

索引が作成されました。

経過: 00:00:27.11
SCOTT@orclpdb1> ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2;

表が変更されました。

経過: 00:00:00.01
SCOTT@orclpdb1> set line 80
SCOTT@orclpdb1> desc table_2
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

SCOTT@orclpdb1> set line 400
SCOTT@orclpdb1> SELECT table_name,constraint_name,constraint_type,search_condition FROM user_constraints WHERE table_name = 'TABLE_2';

TABLE_NAME CONSTRAINT_NAME CON SEARCH_CONDITION
------------------------------ ------------------------------ --- ------------------------------
TABLE_2 SYS_C0010546 C "ID1" IS NOT NULL
TABLE_2 SYS_C0010547 C "ID2" IS NOT NULL
TABLE_2 PK_TABLE_2 P

経過: 00:00:00.03

 

 

 

ということで、Data Pumpのマニュアルの excludeオプションの仕様を確認!!!

 

Oracle Database / Release 23 / ユーティリティ / 2 Oracle Data Pumpエクスポート / 2.4.23 EXCLUDE https://docs.oracle.com/cd/F82042_01/sutil/oracle-data-pump-export-utility.html#SUTIL-GUID-64249296-2AFF-40EA-AA44-BC0A1B5A1E7C

 

"制約の除外
次の制約は明示的に除外できません。
表の作成とロードを正常に行うために必要な制約。たとえば、索引構成表の主キー制約、REF列を持つ表のREF SCOPEおよびWITH ROWID制約など
たとえば、次のEXCLUDE文は、次のように解釈されます。
EXCLUDE=CONSTRAINTでは、表の正常な作成およびロードに必要な制約を除く、すべての制約を除外します。
EXCLUDE=REF_CONSTRAINTは、参照整合性(外部キー)制約を除外します。
"

 

と記されています。

 

SCHEMA_EXPORT_OBJECTSで確認しておきましょう. Oracle Data Pumpと仲良くなるために忘れちゃいけないのがこのビューですよ。(覚えておきましょう)
TABLE/CONSTRAINT , TABLE/CONSTRAINT/REF_CONSTRAINT という制約に関連するオブジェクトパスがありますが、ここがポイント。マニュアル通り、exclude/includeで制御できるのはこのレベルまで
参照整合性制約と、すべての制約のいずれかのパスしか選べません。つまりこれより細かく、例えば、主キー制約だけexcludeしたいという場合には、ピンポイントでオブジェクト名でフィルタする必要があるということを意味します。

 

つまり、 impdpのexcludeオプションで constraint としてインポートしてしまったため、主キー制約だけでなく、今回話題にしている主キー列のNOT NULL制約(CHECK制約で検証されることは前回も記載したとおり)も丸っと削除されてしまったため、今回話題にしている主キー制約の追加で時間を要してしまう症状が発症してしまった!! ということ。 前回少しだけ触れていた、ツールの使い方によっては影響を受ける可能性があるということの一例です。

SCOTT@orclpdb1> SELECT object_path,comments FROM schema_export_objects WHERE object_path LIKE '%TABLE%' ORDER BY object_path;

OBJECT_PATH COMMENTS
------------------------------------------------------------ ----------------------------------------------------

...中略...

SCHEMA_EXPORT/TABLE/COMMENT Table and column comments on the selected tables
SCHEMA_EXPORT/TABLE/CONSTRAINT Constraints (including referential constraints)
SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT Referential constraints
SCHEMA_EXPORT/TABLE/FGA_POLICY Fine-grained auditing policies

...中略...

TABLE/COMMENT Table and column comments on the selected tables
TABLE/CONSTRAINT Constraints (including referential constraints)
TABLE/CONSTRAINT/REF_CONSTRAINT Referential constraints
TABLE/FGA_POLICY Fine-grained auditing policies
TABLE/GRANT Object grants on the selected tables

...中略...

TABLE/RLS_POLICY/RLS_POLICY Fine-grained access control policies
TABLE/TRIGGER Triggers
TABLESPACE_QUOTA Tablespace quotas granted to users associated with the selected schemas

52行が選択されました。

経過: 00:00:00.40

 

 

対処方法は細かくフィルタして、NOT NULL制約は残したまま、主キー制約だけexceludeしましょう。簡単ですよね。Data Pumpに慣れている皆さんなら:)
マニュアルには書かれていないですが、コマンドラインの場合、"(ダブルクォート)や、'(シングルクォート)を利用する場合はバックスラッシュ(Windowsだと¥だったかな。使わないのでド忘れしたので間違ってたらツッコミよろしゅう)でエスケープしておく必要があります。ここでも、Data Pumpの癖が炸裂してますねw ()
今回は、PK_TABLE_2という主キー制約だけを除外しています。(インポート後にNOT NULL制約はインポートされていることを確認します)

[oracle@localhost ~]$ impdp scott@orclpdb1 tables=scott.table_2 exclude=constraint:\"=\'PK_TABLE_2\'\" table_exists_action=replace directory=mydump_dir dumpfile=scott_table_2.dmp

Import: Release 21.0.0.0.0 - Production on 土 8月 31 16:43:11 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle and/or its affiliates. All rights reserved.
パスワード:

接続先: Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
マスター表"SCOTT"."SYS_IMPORT_TABLE_01"は正常にロード/アンロードされました
"SCOTT"."SYS_IMPORT_TABLE_01"を起動しています: scott/********@orclpdb1 tables=scott.table_2 exclude=constraint:"='PK_TABLE_2'" table_exists_action=replace directory=mydump_dir dumpfile=scott_table_2.dmp
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
. . "SCOTT"."TABLE_2" 344.8 MB 15000000行がインポートされました
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
ジョブ"SCOTT"."SYS_IMPORT_TABLE_01"が土 8月 31 16:44:22 2024 elapsed 0 00:01:02で正常に完了しました

 

 

正常にインポートできました! 制約関連が想定通りにインポートされているか確認!
(excludeオプションでCONSTRAINTを除外してしまうと、オブジェクト名でフィルタリングしていたとしてもCONSTRAINTがインポートされた(一部)とは記録もされません。この癖も改善して惜しい癖なんだけどなかなか改善してくれないですね→オラクルさん)

 

ID1, ID2列に NOT NULL制約が付いています。また、主キー制約は存在していません。
descコマンドや、dba/user_tab_clumnsでは、主キー制約によるNOT NULLの制約であっても、NOT NULLと表示されるため確認は、必ず、dba/user_constraintsで該当列のconstraint_typeが'C'でsearch_conditionに xxxx IS NOT NULLというチェック制約でNOT NULLが保証されていることを確認する必要あるので見落とさないようにしてくださいね。(ここ重要!!)

[oracle@localhost ~]$ sqlplus scott@orclpdb1

...中略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> set line 80
SCOTT@orclpdb1> desc table_2
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

SCOTT@orclpdb1> set line 400
SCOTT@orclpdb1> col constraint_name for a30
SCOTT@orclpdb1> SELECT table_name,constraint_name,constraint_type,search_condition FROM user_constraints WHERE table_name = 'TABLE_2';

TABLE_NAME CONSTRAINT_NAME CON SEARCH_CONDITION
------------------------------ ------------------------------ --- --------------------------------------------------------------------------------
TABLE_2 SYS_C0010597 C "ID1" IS NOT NULL
TABLE_2 SYS_C0010598 C "ID2" IS NOT NULL

経過: 00:00:00.02
SCOTT@orclpdb1> SELECT table_name,index_name FROM user_indexes WHERE table_name = 'TABLE_2';

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

経過: 00:00:00.08

 

 

 

では、主キー制約の追加が瞬殺で終わることを確認して、今回のネタはおしまい! :)

SCOTT@orclpdb1> CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2);

索引が作成されました。

経過: 00:00:29.75
SCOTT@orclpdb1> ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2;

表が変更されました。

経過: 00:00:00.07

SCOTT@orclpdb1> set line 80
SCOTT@orclpdb1> desc table_2
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

SCOTT@orclpdb1> set line 400
SCOTT@orclpdb1> SELECT table_name,constraint_name,constraint_type,search_condition FROM user_constraints WHERE table_name = 'TABLE_2';

TABLE_NAME CONSTRAINT_NAME CON SEARCH_CONDITION
------------------------------ ------------------------------ --- --------------------------------------------------------------------------------
TABLE_2 SYS_C0010597 C "ID1" IS NOT NULL
TABLE_2 SYS_C0010598 C "ID2" IS NOT NULL
TABLE_2 PK_TABLE_2 P

経過: 00:00:00.02
SCOTT@orclpdb1> SELECT table_name,index_name FROM user_indexes WHERE table_name = 'TABLE_2';

TABLE_NAME INDEX_NAME
------------------------------ ------------------------------
TABLE_2 PK_TABLE_2

経過: 00:00:00.01

 

ノロノロ台風?みたいなやつ、まじではなく、どこかへいってほしいっすねw (天候がめんどくさいことになってて勘弁してくれという感じの東京より

 

ではまた。

 


参考)準備に利用したスクリプト

[oracle@localhost ~]$ cat additional_example.sql
DROP TABLE table_2;

CREATE TABLE table_2
(
id1 NUMBER NOT NULL
,id2 NUMBER NOT NULL
,text VARCHAR2(10)
)
.
l
/
set line 80
desc table_2
set line 400

BEGIN
FOR i in 1..15000000 LOOP
INSERT INTO table_2 VALUES(i,i,i);
IF MOD(i,1000) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
.
l
/

ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX
.
l
/

 



関連エントリー
Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
ata Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する

Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...)


RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp
RDS Oracle 雑多なメモ#19 FAQ / DBMS_DATAPUMPパッケージ de ジョブの停止


なぜ、主キー制約の追加時間に違いがでるのでしょうか? (東京都 ITエンジニア 男性)/ FAQ

 

 

 

| | | コメント (0)

2024年8月30日 (金)

なぜ、主キー制約の追加時間に違いがでるのでしょうか? (東京都 ITエンジニア 男性)/ FAQ


え~〜っ、
十年ひと昔てぇなぁこと申しますけれど、十年経つと元へ戻るんですってねぇ。
あの問題でハマった〜ってぇ話題をおもいだしやして、ググってみたんでさぁ、ありましたよ、ありましたよ!
十年数年前。

落語調で入ってみましたww

 

さて、今日の患者さん、
何にハマってたのかぁってぇと、主キー制約を追加するのに〜、偉い時間がかかって、頭を抱えて相談に。
(いい加減、落語っぽい言葉遣いヤメぇ!w


この話題、10年以上前のOracle Forumでも話題になった有名な、FAQだよねー。
元々そういうリスクのあるテーブル定義だったり、各種ツールの使い方によっては遭遇することもあるので、この症状を覚えておくと対処もしやすいですよ。というお話をしたいと思います。

まずは、当時話題になったスレ。
Oracle Forum - SQL & PL/SQL "ALTER TABLE xxx add constraint primary key" takes long long time!

 

この症状、ご存知の方も多いと思うのですが、Forum外で言及しているエントリーって見たこともあまりないので、実はあまり知られてなかったりして。。w という心配もあり、メモがわりにブログに書いておきますね:)

ただし、この症状が顕著に現れるのは大規模な表の場合だけ(数十TBとか)なので、少量のデータだと気づかずというか影響していないことも多いので注意が必要なんです。(データが増加してから初めて気づいたりしてザワザワしたりするので)
巨大なデータを扱う場合、物凄い遅延に繋がってしまったりするので、舐めちゃいけません。


症状と原因をサクッとまとめると、

きっかけとなる主キー制約には以下の特徴があります。
・主キー制約では、キー値(複合キーの場合はキー全体で)が一意であり、NULL が含まれないでないことが保証されます。
NULLでないことが保証されるという点が今回のポイント。NULLと聞いてガタッとしたあなた!。いいリアクションです!w

 

主キー制約追加時には、主キーに含まれる列の値は NULL ではないことが検証されます。

主キー制約追加時には、主キーに含まれる列の値は NULL ではないことが検証されます。

主キー制約追加時には、主キーに含まれる列の値は NULL ではないことが検証されます。

(ここが大切なので3度書いておきましたw 太字でw

 

この NULL ではないことを保証するために、追加のIOと NULL でないことのチェックが塵も積もで、処理時間が伸びてしまう症状につながります。これは対象のデータ量に比例して伸びるので、データ量が少ないとほぼ気づきませんw
ただでさえ処理時間を要する大量データの場合のみ顕著な影響が現れます!!!!!

ただ、一般的には、主キー列として利用される列は NOT NULL となっている多いはずですが、
稀に、どうせ主キー制約でNOT NULLが保証されるから、それぞれの列には NOT NULL制約 (Oracle DatabaseではCHECK制約として実装されます) を付加しない! という方も見かけますが、それ手を抜かない方がいいですよ。
そんな横着していると、今回お見せするような症状を発症してしまうことがあります。
それ以外のケースでは、各種ツール(Oracle database純正のもを含む)の使い方次第では意図せず遭遇してしまうこともあります。(ex. impdp利用時のオブジェクトの扱いを制御するパラメータの使い方によるもの、とか)

症状と原因が理解できれば対処方法は簡単。そのような状態を避ければ良いだけですね! w
そんな大した話ではなくて、
・主キー制約に含める列は列定義レベルで、NOT NULL制約を付加しておきましょう!
・各種ツールの利用方法による副作用で、NULLABLEになってしまうようなケースでは、その状況を回避する利用手順なり、オプションを選ぶようにすることです。


これ覚えておくと、妙なところで時間溶かさなくて済むので、頭の片隅に置いておくと良いと思いますよ:)

 

 

 

では、簡単な例で、遅延の発生と、裏で何が起こっているのか10046トレースで、サクッと軽めに確認しておきましょう!

スクリプトの内容は本記事の後半に載せますので参考にしてみてください。

table_1/tabl_2を作成して、それぞれ、400MBほどになるようにデータを登録。(違いは、id2の列がNULLABLEかNOT NULLかだけです。この列は複合主キーに含まれます)


SCOTT@orclpdb1> @why_do_you_think_it_is_slow_adding_the_primary_constraint.sql

表が削除されました。

経過: 00:00:01.25

表が削除されました。

経過: 00:00:00.07
1 CREATE TABLE table_1
2 (
3 id1 NUMBER NOT NULL
4 ,id2 NUMBER
5 ,text VARCHAR2(10)
6* )

表が作成されました。

経過: 00:00:00.18
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NUMBER
TEXT VARCHAR2(10)

1 CREATE TABLE table_2
2 (
3 id1 NUMBER NOT NULL
4 ,id2 NUMBER NOT NULL
5 ,text VARCHAR2(10)
6* )

表が作成されました。

経過: 00:00:00.02
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

1 BEGIN
2 FOR i in 1..15000000 LOOP
3 INSERT ALL
4 INTO table_1 VALUES(i,i,i)
5 INTO table_2 VALUES(i,i,i)
6 SELECT null FROM dual
7 ;
8 IF MOD(i,1000) = 0
9 THEN
10 COMMIT;
11 END IF;
12 END LOOP;
13* END;

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

経過: 00:11:42.62

 

ALTER TABLE ADD CONSTRAINT PRIMARY KEYで id1とid2の複合キーで主キー制約を追加。同時に索引も作成させる
今回の程度データ量だとびっくりするほどの差は見えずらいですが、id2がnullableであるtable_1の方が、約11秒ほど処理時間が長くなっています!
ちょっとわかりにくくなる原因は、制約追加と同時に索引も作成しているため索引作成の時間に埋もれやすく、索引作成の時間だと勘違いする方が多い影響もありますが、データ量が多くなればなるほどこの差は大きくなります。

  1* ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX

表が変更されました。

経過: 00:00:48.31
1* ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX

表が変更されました。

経過: 00:00:36.92

 

最初の例は、索引の作成時間も含まれいるため制約の追加のオーバーヘッドがわかりにくいので他の手順で確認してみると。。。。
ALTER TABLE ADD CONSTRAINT PRIMARY KEY USING [事前に作成しておいた一意索引名]部分(赤字)に注目。
制約の追加で、id2がNULLABLEかNOT NULLという部分だけの違いで、272倍の差になっています。table_2の制約追加時間はデータ量が増加してもほぼ一定なので、データ量が増加した場合の影響はどれぐらいかは想像してみてくださいね。
今回は、2列からなる複合主キーの1列だけですが、異なる条件では差は広がる可能性はあります。(環境の差も含め)

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

経過: 00:00:00.93
1* ALTER TABLE table_2 DROP PRIMARY KEY DROP INDEX

表が変更されました。

経過: 00:00:00.10
1* CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)

索引が作成されました。

経過: 00:00:33.84
1* CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2)

索引が作成されました。

経過: 00:00:36.13
1* ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1

表が変更されました。

経過: 00:00:05.44
1* ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2

表が変更されました。

経過: 00:00:00.02

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

経過: 00:00:18.53

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

経過: 00:00:15.50

SEGMENT_NAME MB
------------------------------ ----------
PK_TABLE_1 368
PK_TABLE_2 368
TABLE_1 416
TABLE_2 416

経過: 00:00:00.26

 

最後にNOT NULLにしておけば回避できることも確認
table_1のid2列をNOT NULLに変更して。。。

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

経過: 00:00:00.41

名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NUMBER
TEXT VARCHAR2(10)

1* ALTER TABLE table_1 MODIFY id2 NUMBER NOT NULL

表が変更されました。

経過: 00:00:03.14

 

変更後のtabl_1 (table_2と同一定義になっています!)表の id2はNULLABLE から NOT NULL へ変更

 名前                                    NULL?    型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

 

table_2同様の処理時間となり大きく改善! 11秒のオーバーヘッドが消えてますね!

  1* ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX

表が変更されました。

経過: 00:00:34.65

 

主キー制約の追加部分に絞って改善を確認してみましょう!
table_2同様の処理時間となり大きく改善! 0.02秒で完了 

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

経過: 00:00:00.13
1* CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)

索引が作成されました。

経過: 00:00:32.22
1* ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1

表が変更されました。

経過: 00:00:00.02

 

最後に、10046トレースで違い見る!
一目瞭然ですよね。 10046トレースでALTER文を拾ってみると、ID2列がNULLABLEdであるケースではexecuteで大量のqueryが発生していて、db file scattered readやdb file sequential readでオブジェクトを読みにに行っていることがわかります。これようするに該当列値に NULLが存在しないことを保証しなければならない主キー制約の宿命なわけですが、この作業を軽くしてくれているので、table_2のid2列にあるようなNOT NULL制約です。これがあればメタ情報をみるだけで NULL は存在しないことを確認できちゃいますからね。処理時間の差はそこだけです!

SCOTT@orclpdb1> @dive_in
DROP TABLE table_1 PURGE
*
行1でエラーが発生しました。:
ORA-00942: 表またはビューが存在しません。

経過: 00:00:00.01
DROP TABLE table_2 PURGE
*
行1でエラーが発生しました。:
ORA-00942: 表またはビューが存在しません。

経過: 00:00:00.01
1 CREATE TABLE table_1
2 (
3 id1 NUMBER NOT NULL
4 ,id2 NUMBER
5 ,text VARCHAR2(10)
6* )

表が作成されました。

名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NUMBER
TEXT VARCHAR2(10)

1 CREATE TABLE table_2
2 (
3 id1 NUMBER NOT NULL
4 ,id2 NUMBER NOT NULL
5 ,text VARCHAR2(10)v 6* )

表が作成されました。

名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID1 NOT NULL NUMBER
ID2 NOT NULL NUMBER
TEXT VARCHAR2(10)

1 BEGIN
2 FOR i in 1..15000000 LOOP
3 INSERT ALL
4 INTO table_1 VALUES(i,i,i)
5 INTO table_2 VALUES(i,i,i)
6 SELECT null FROM dual
7 ;
8 IF MOD(i,1000) = 0
9 THEN
10 COMMIT;
11 END IF;
12 END LOOP;
13* END;

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

1* CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)

索引が作成されました。

1* CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2)

索引が作成されました。

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
接続されました。

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

...略...

経過: 00:00:00.02
1* ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1

表が変更されました。

経過: 00:00:07.12

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

経過: 00:00:00.01
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
接続されました。

...略...

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

経過: 00:00:00.01
1* ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2

表が変更されました。

経過: 00:00:00.05

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

経過: 00:00:00.00
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
接続されました。

表が削除されました。

表が削除されました。

[oracle@localhost ~]$ ls -lrt $ORACLE_BASE/diag/rdbms/orclcdb/ORCLCDB/trace/*table_1*
-rw-r-----. 1 oracle oinstall 48367 8月 28 20:11 /opt/oracle/diag/rdbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7254_10046_table_1_slow.trm
-rw-r-----. 1 oracle oinstall 348949 8月 28 20:11 /opt/oracle/diag/rdbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7254_10046_table_1_slow.trc
[oracle@localhost ~]$ tkprof /opt/oracle/diag20dbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7254_10046_table_1_slow.trc ORCLCDB_ora_7254_10046_table_1_slow.txt sys=yes waits=yes

TKPROF: Release 21.0.0.0.0 - Development on 水 8月 28 20:14:16 2024

[oracle@localhost ~]$ view ORCLCDB_ora_7254_10046_table_1_slow.txt

...略...

SQL ID: 48v4hxnmpykdy Plan Hash: 0

ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING
INDEX pk_table_1

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.02 2 4 0 0
Execute 1 4.15 6.85 48874 52229 2 0
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 4.16 6.88 48876 52233 2 0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 109

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Disk file operations I/O 1 0.00 0.00
db file scattered read 1425 0.01 2.03
db file sequential read 54 0.00 0.04
Compression analysis 9 0.00 0.00
log file sync 1 0.00 0.00
PGA memory operation 1 0.00 0.00
SQL*Net message to client 1 0.00 0.00
SQL*Net message from client 1 0.00 0.00
********************************************************************************

...略...


[oracle@localhost ~]$ ls -lrt $ORACLE_BASE/diag/rdbms/orclcdb/ORCLCDB/trace/*table_2*
-rw-r-----. 1 oracle oinstall 15431 8月 28 20:11 /opt/oracle/diag/rdbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7256_10046_table_2_fast.trm
-rw-r-----. 1 oracle oinstall 67970 8月 28 20:11 /opt/oracle/diag/rdbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7256_10046_table_2_fast.trc
[oracle@localhost ~]$
[oracle@localhost ~]$ tkprof /opt/oracle/diag/rdbms/orclcdb/ORCLCDB/trace/ORCLCDB_ora_7256_10046_table_2_fast.trc ORCLCDB_ora_7256_10046_table_2_fast.txt sys=yes waits=yes

TKPROF: Release 21.0.0.0.0 - Development on 水 8月 28 20:17:33 2024

[oracle@localhost ~]$ view ORCLCDB_ora_7256_10046_table_2_fast.txt

...略...

SQL ID: 7x7hqgxcpkyky Plan Hash: 0

ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING
INDEX pk_table_2

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.01 2 4 0 0
Execute 1 0.00 0.00 3 41 2 0
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 0.01 0.01 5 45 2 0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 109

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Compression analysis 9 0.00 0.00
SQL*Net message to client 1 0.00 0.00
SQL*Net message from client 1 0.00 0.00
********************************************************************************

 

 

(参考)利用したスクリプト

[oracle@localhost ~]$ cat why_do_you_think_it_is_slow_adding_the_primary_constraint.sql
set line 80
DROP TABLE table_1 PURGE;
DROP TABLE table_2 PURGE;

CREATE TABLE table_1
(
id1 NUMBER NOT NULL
,id2 NUMBER
,text VARCHAR2(10)
)
.
l
/
desc table_1

CREATE TABLE table_2
(
id1 NUMBER NOT NULL
,id2 NUMBER NOT NULL
,text VARCHAR2(10)
)
.
l
/
desc table_2

set line 400

BEGIN
FOR i in 1..15000000 LOOP
INSERT ALL
INTO table_1 VALUES(i,i,i)
INTO table_2 VALUES(i,i,i)
SELECT null FROM dual
;
IF MOD(i,1000) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
.
l
/

ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX
.
l
/

ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX
.
l
/

ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX
.
l
/
ALTER TABLE table_2 DROP PRIMARY KEY DROP INDEX
.
l
/

CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)
.
l
/
CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2)
.
l
/
ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1
.
l
/
ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2
.
l
/

@gather_tab_stats table_1
@gather_tab_stats table_2

col segment_name for a30
SELECT
SEGMENT_NAME
,BLOCKS * 8 / 1024 AS "MB"
FROM
USER_SEGMENTS
WHERE
SEGMENT_NAME IN (
'TABLE_1'
,'TABLE_2'
,'PK_TABLE_1'
,'PK_TABLE_2'
)
ORDER BY
SEGMENT_NAME
;

PROMPT **************** table_1のid2列をNOT NULLに変更 ******************
ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX
.
l
/

set line 80
PROMPT 変更前のtable_1
desc table_1
ALTER TABLE table_1 MODIFY id2 NUMBER NOT NULL
.
l
/
PROMPT 変更後のtabl_1 (table_2と同一定義になっています!) id2はNULLABLE から NOT NULL へ変更されました
desc table_1
set line 400

PROMPT ****** table_2同様の処理時間となり大きく改善! *******
ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX
.
l
/

PROMPT ***** 主キー制約の追加部分に絞って改善を確認してみましょう!
ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX
.
l
/
CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)
.
l
/

PROMPT ****** table_2同様の処理時間となり大きく改善! *****
ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1
.
l
/

 

(参考)内部で利用している統計情報取得スクリプト

[oracle@localhost ~]$ cat gather_tab_stats.sql
set verify on
eXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>upper('&1'),cascade=>true,no_invalidate=>false);
set verify off
undefine 1

 

(参考)10046トレース状況を覗くためのスクリプト

[oracle@localhost ~]$ cat dive_in.sql
set line 80
DROP TABLE table_1 PURGE;
DROP TABLE table_2 PURGE;

CREATE TABLE table_1
(
id1 NUMBER NOT NULL
,id2 NUMBER
,text VARCHAR2(10)
)
.
l
/
desc table_1

CREATE TABLE table_2
(
id1 NUMBER NOT NULL
,id2 NUMBER NOT NULL
,text VARCHAR2(10)
)
.
l
/
desc table_2

set line 400
BEGIN
FOR i in 1..15000000 LOOP
INSERT ALL
INTO table_1 VALUES(i,i,i)
INTO table_2 VALUES(i,i,i)
SELECT null FROM dual
;
IF MOD(i,1000) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
.
l
/

CREATE UNIQUE INDEX pk_table_1 ON table_1(id1, id2)
.
l
/
CREATE UNIQUE INDEX pk_table_2 ON table_2(id1, id2)
.
l
/

disconnect

connect scott/[パスワード]@orclpdb1
ALTER SESSION SET tracefile_identifier='10046_table_1_slow';
ALTER SESSION SET timed_statistics = true;
ALTER SESSION SET statistics_level=all;
ALTER SESSION SET max_dump_file_size = unlimited;
ALTER SESSION SET events '10046 trace name context forever,level 12';
ALTER TABLE table_1 ADD CONSTRAINT pk_table_1 PRIMARY KEY (id1, id2) USING INDEX pk_table_1
.
l
/
ALTER SESSION SET events '10046 trace name context off';
disconnect

connect scott/[パスワード]@orclpdb1
ALTER SESSION SET tracefile_identifier='10046_table_2_fast';
ALTER SESSION SET timed_statistics = true;
ALTER SESSION SET statistics_level=all;
ALTER SESSION SET max_dump_file_size = unlimited;
ALTER SESSION SET events '10046 trace name context forever,level 12';
ALTER TABLE table_2 ADD CONSTRAINT pk_table_2 PRIMARY KEY (id1, id2) USING INDEX pk_table_2
.
l
/
ALTER SESSION SET events '10046 trace name context off';
disconnect

connect scott/[パスワード]@orclpdb1
DROP TABLE table_1 PURGE;
DROP TABLE table_2 PURGE;

 


酷暑じゃなければ、台風起因の大雨の東京より。
みなさん、安全最優先で!

ではまた。

 

| | | コメント (0)

2024年8月21日 (水)

実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)

色々な患者さんが来るわけですが、年に一度ぐらい、私のJOINが無いんです! みたいなこともあります。

Sql_20240821102901
ということで、似たアトモスフィアを感じる

患者「先生、私のLEFT OUTER JOINが無いんです!!」
というところから、今回の物語は始まりますw

 

私「どれどれ」(触診というか問診というかw) SQL文を診る。。
私「あ!w これは!!!!w 」

私「大丈夫ですよ!、りっぱな、 INNER JOIN です。栄養状態も良さそうですねwwww」
 「念の為、レントゲン(実行計画)も撮っておきましょう」

患者 (キョトン!)

私「ほら〜〜」

(以下、Oracle Databaseです)

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> @where_did_my_left_outer_join_go.sql
1 SELECT
2 e.empno
3 FROM
4 emp e
5 LEFT OUTER JOIN dept d
6 ON
7 e.deptno = d.deptno
8 WHERE
9* d.dname = 'ACCOUNTING'

EMPNO
----------
7782
7839

解析されました。

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------
Plan hash value: 2904284165

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 80 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 4 | 80 | 3 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 4 | 80 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| DEPT | 1 | 13 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_DEPT_NAME | 1 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IX_DEPT | 4 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | EMP | 4 | 28 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

4 - access("D"."DNAME"='ACCOUNTING')
5 - access("E"."DEPTNO"="D"."DEPTNO")

19行が選択されました。

 


 

って、コントはこれぐらいにして、本題。

 

最近、なんだか、なんでもかんでも LEFT OUTER JOINで書いちゃう病気多くないですかね。。:(

 

可読性も悪いし、本人は INNER JOINしてる意識があるのやら、ないのやら。 オプティマイザはしっかり反応して内部で書き換えてくれちゃってるので、 LEFT OUTER JOIN なんて書かれてても、無駄だからそれ! ということでINNER JOINにしてくれています。
Nested Loopが利用される場合、本当に、OUTER JOINなのであれば、外部表(駆動表)になる表は構文上から簡単に特定できますが、INNER JOINだとHASH JOIN同様に結合順を見極める作業も必要になるので要注意です。
(なお、Hash Joinでは外部表と内部表はオプティマイザヒントで入れ替え可能ですが、MySQLのHash Joinでは現状入れ替えることはできないので注意が必要です / 帰ってきた! 標準はあるにはあるが癖の多いSQL #8、Hash Joinさせるにも癖が出る)

 

治療の難易度を上げてしまうだけで良いこともないので、OUTER/INNER は適切に使い分けてほしいものですよね。

 

単純な症例を一つ。MySQL/PostgreSQLでも同じ。

環境の準備 Oracle Databaseのemp/dept表を元に、制約は主キー制約、索引は以下のようになっています。(記載は略しますが、MySQL/PostgreSQLともに同様の表と索引を用意してあります)

SCOTT@orclpdb1> break on table_name on index_name skip page
SCOTT@orclpdb1> r
1 select table_name,index_name,column_name
2 from user_ind_columns
3 where table_name in ('EMP','DEPT')
4* order by table_name,index_name,column_position

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
DEPT IX_DEPT_NAME DNAME

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
DEPT PK_DEPT DEPTNO

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
EMP IX_DEPT DEPTNO

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
EMP PK_EMP EMPNO

SCOTT@orclpdb1>
SCOTT@orclpdb1> select * from emp order by 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

12行が選択されました。

SCOTT@orclpdb1> select * from dept order by deptno;

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

SCOTT@orclpdb1> r
1 select table_name,constraint_name,constraint_type
2 from user_constraints
3 where table_name in ('EMP','DEPT')
4* order by table_name,constraint_type

TABLE_NAME CONSTRAINT_NAME CON
------------------------------ ------------------------------ ---
DEPT PK_DEPT P
EMP PK_EMP P

 

PostgreSQL データ量が少ない影響だと思いますが、HASH JOINになってますね。とはいえ、LEFT OUTER JOINではなく、Oracle Database同様に、INNER JOINに書き換えられています。
当然ですよね。INNER JOINなんですもの。
pg_hint_planのヒントを使ってNLJにした実行計画でも確認しておきました:)

perftestdb=> select version();
version
---------------------------------------------------------------------------------------------------------
PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-22), 64-bit
(1 行)

perftestdb=> \! cat where_did_my_left_outer_join_go.sql
SELECT
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;

explain
SELECT
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;

SELECT
/*+
Leading(d e)
NestLoop(d e)
IndexScan(d ix_dname)
IndexScan(e ix_deptno)
*/
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;

explain
SELECT
/*+
Leading(d e)
NestLoop(d e)
IndexScan(d ix_dname)
IndexScan(e ix_deptno)
*/
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;
perftestdb=> \i where_did_my_left_outer_join_go.sql
empno
-------
7782
7839
(2 行)

QUERY PLAN
------------------------------------------------------------------
Hash Join (cost=1.06..2.27 rows=3 width=5)
Hash Cond: (e.deptno = d.deptno)
-> Seq Scan on emp e (cost=0.00..1.14 rows=14 width=10)
-> Hash (cost=1.05..1.05 rows=1 width=5)
-> Seq Scan on dept d (cost=0.00..1.05 rows=1 width=5)
Filter: ((dname)::text = 'ACCOUNTING'::text)
(6 行)

empno
-------
7782
7839
(2 行)

QUERY PLAN
------------------------------------------------------------------------------
Nested Loop (cost=0.27..16.39 rows=3 width=5)
-> Index Scan using ix_dname on dept d (cost=0.13..8.15 rows=1 width=5)
Index Cond: ((dname)::text = 'ACCOUNTING'::text)
-> Index Scan using ix_deptno on emp e (cost=0.14..8.21 rows=4 width=10)
Index Cond: (deptno = d.deptno)
(5 行)

perftestdb=>

 

MySQL MySQLもInner Joinに書き換えてますよね。だって、どこからどうみても、INNER JOINですもの。

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.00 sec)

mysql>
mysql> \! cat where_did_my_left_outer_join_go.sql
SELECT
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;

explain format=tree
SELECT
e.empno
FROM
emp e
LEFT OUTER JOIN dept d
ON
e.deptno = d.deptno
WHERE
d.dname = 'ACCOUNTING'
;
mysql> \. where_did_my_left_outer_join_go.sql
+-------+
| empno |
+-------+
| 7782 |
| 7839 |
+-------+
2 rows in set (0.08 sec)

+-------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=0.95 rows=3.5)
-> Filter: (d.deptno is not null) (cost=0.35 rows=1)
-> Covering index lookup on d using ix_dname (dname='ACCOUNTING') (cost=0.35 rows=1)
-> Covering index lookup on e using emp_deptno (deptno=d.deptno) (cost=0.6 rows=3.5)
|
+-------------------------------------------------------------------------------------------+
1 row in set (0.07 sec)

 

では、また。

いつまで、この酷暑が続くのやら。。。。


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のおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3

 

| | | コメント (0)

2024年8月16日 (金)

VirtualBox TestBuild 7.1.0_BETA1r164387 (2024-08-15T17:27:33Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

VirtualBox 7.1.0_BETA1r164378が8/15に公開されていましたが、今回もGuestVM起動できず、計測不能でした。

20240816-33746

ちなみに、VirtualBox Extension PackもVersionが古く(7.0.97.163425なのでかなり古いですね)インストールできないミスマッチの状態となっているので、そのあたりも影響しているのかもしれません。TestBuildsですからね。長ーい目で見守りましょう。 7.1になってから二回目の更新ですし:)

20240816-40340

20240816-34235

 

20240816-40543

インストールされたVirtualBoxのリリースは、7.1.0_BETA1r164378

20240816-35226

ということで、強烈な台風が近づいている東京より。

みなさん、安全を最優先に:)

ではまた。

 


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の起動、停止時間の記録

 



| | | コメント (0)

2024年8月 8日 (木)

VirtualBox TestBuild 7.1.0_BETA1r164292 (2024-08-07T18:27:07Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

VirtualBox TestBuild 7.1.0_BETA1r164292 (2024-08-07T18:27:07Z) が公開されていました。

7.1になり、アイコンやスプラッシュも変更されたようですが、、、久しぶりに計測できず! (次回に期待したいと思います!)

 

20240808-50049


20240808-50147


20240808-50318

以下、M1/M2いずれもGuest VM起動できませんでした!

20240808-51648

 

M1

*** 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_BETA1r164292

 

M2

*** mac info. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** macOS ver. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** VirtualBox ver. ***
7.1.0_BETA1r164292

7.1になった影響だろうか。。。
なお、今回は旧リリースのVirtualBox TestBuild 7.0.97r163779 (2024-07-04T18:53:02Z) をアップデートしただけ(本来それで良いはずだが)なので、VMのインポートし直した場合も同様の結果、No Supported 。どうなってるんだろう。どうなるかまでは未確認です。
(検証後、本エントリーへ追加する予定)


しかし、酷暑と夕方のCloud 9もくもく後のゲリラ豪雨が、夏のニューノーマルになってしまうと夏のイベントは秋にでもしないと無理かもしれないですねー。花笠祭りも影響受けたようですし。
ちなみに、多摩川花火大会は、何年か前のゲリラ豪雨以後、10月開催に切り替えた経緯がありますよね。。

ではまた。

 



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.0.97r163779 (2024-07-04T18:53:02Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

 

| | | コメント (0)

2024年7月24日 (水)

実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3

Previously on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2では、限定的なようですが、PostgreSQL 16では Join Elimination が行われることを確認しました。(全く行わないとわけではないようですね。PostgreSQL。)

 

今日は、Join Elimination (再び) の最後に、もう一つだけ、めちゃめちゃシンプルな Join Elimination の挙動を追ってみることにします。

 

前回り作成した、foo/barの2表を少々作り変えて foo2 / bar2 の2表を作成します。
Oracle Database同様の表をMySQL/PostgreSQLにも作成して検証。(今回もデータの有無は影響しないので、データは未登録です)
(なおデータ型は、MySQL/PostgreSQLに合わせて変更しています。e.g. NUMBER->INTEGER, VARCHAR2 -> VARCHAR。データは未登録では統計情報は PostgreSQL/MySQLでも取得します)

COTT@orclpdb1> l
1 CREATE TABLE foo2 (
2 id NUMBER
3 , note VARCHAR2(100)
4 , PRIMARY KEY (id)
5* )
SCOTT@orclpdb1> /

表が作成されました。

SCOTT@orclpdb1> l
1 CREATE TABLE bar2 (
2 id NUMBER
3 , sq NUMBER NOT NULL
4 , memo VARCHAR2(100)
5 , PRIMARY KEY (id, sq)
6* )
SCOTT@orclpdb1> /

表が作成されました。

SCOTT@orclpdb1> @gather_tab_stats foo2

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

SCOTT@orclpdb1> @gather_tab_stats bar2

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

 

上記表を作成後、以下のSQL文を実行します。これもOracle Databaseでは Join Elimination される構文にしています。2つ目の例に類似していますが、 foo2.id = bar2.id は、 1 : 0..* の関係にあります。
したがって foo2.id = bar2.id という結合条件では Join Elimination できませんが、代わりにインラインビュー内で小細工しています。:)
bar2 WHERE 1=0 として、インライビューの結果は、常に0行となり、そもそも結合不要なので結合自体を除外してしまえ! というシンプルな Join Elimination の確認です。
この手の文、トリッキーでわかりにくいので個人的には好みではありませんが、ERPなどでは結構、見るような気がしますw

SELECT
foo2.id
, foo2.note
FROM
foo2
LEFT OUTER JOIN
(
SELECT * FROM bar2 WHERE 1=0
) bar2
ON
foo2.id = bar2.id;

 

 

Oracle Database (21c) Oracle Databaseの場合、無駄な結合を除外しているのことを確認できますよね。
この挙動を知っているからこそ、動的SQLの代替としてこのような記述をする傾向があることも理解はしていますが、元の文は読みにくくなるし、ヒント文も埋め込みにくいので、素直に動的SQLにしてくれたらいいのに。。と思ったことがなんとかありますw (Oracle DatabaseのオプティマイザーはSQL文を書き換えて最適化することも多く、この手の構文とヒントによるチューニングは相性的に悪いことが多く、チューニング難易度があがることもあり、個人的には嫌いだ!! というのもあるのですけどもw)

SCOTT@orclpdb1> l
1 EXPLAIN PLAN FOR
2 SELECT
3 foo2.id
4 , foo2.note
5 FROM
6 foo2
7 LEFT OUTER JOIN
8 (
9 SELECT * FROM bar2 WHERE 1=0
10 ) bar2
11 ON
12* foo2.id = bar2.id
SCOTT@orclpdb1> /

解析されました。

経過: 00:00:00.01
SCOTT@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
Plan hash value: 2844017661

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 65 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| FOO2 | 1 | 65 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------

8行が選択されました。

 

PostgreSQL (16.3) ちょっと微妙な感じですが、 Join elimination ではないですね。Nested Loop Left Joinが残ってますし。
とはいえ、 bar2にはアクセスしていないことは確認できるので似たような挙動にはなるのかも。。。
興味深いですね。。。

perftestdb=> EXPLAIN verbose
perftestdb-> SELECT
perftestdb-> foo2.id
perftestdb-> , foo2.note
perftestdb-> FROM
perftestdb-> foo2
perftestdb-> LEFT OUTER JOIN
perftestdb-> (
perftestdb(> SELECT * FROM bar2 WHERE 1=0
perftestdb(> ) bar2
perftestdb-> ON
perftestdb-> foo2.id = bar2.id;
QUERY PLAN
-------------------------------------------------------------------
Nested Loop Left Join (cost=0.00..0.01 rows=1 width=222)
Output: foo2.id, foo2.note
Join Filter: (false AND (foo2.id = id))
-> Seq Scan on public.foo2 (cost=0.00..0.00 rows=1 width=222)
Output: foo2.id, foo2.note
-> Result (cost=0.00..0.00 rows=0 width=4)
Output: id
One-Time Filter: false
(8 行)

 

MySQL (8.0.36) MySQLもPostgreSQLと類似しています。Left Hash Joinは残っているので、Join eliminationではないと考えて良いと思います。

mysql> EXPLAIN format=tree
-> SELECT
-> foo2.id
-> , foo2.note
-> FROM
-> foo2
-> LEFT OUTER JOIN
-> (
-> SELECT * FROM bar2 WHERE 1=0
-> ) bar2
-> ON
-> foo2.id = bar2.id;
+-----------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------+
| -> Left hash join (no condition) (cost=0.25 rows=0)
-> Table scan on foo2 (cost=0.35 rows=1)
-> Hash
-> Zero rows (Impossible filter) (cost=0..0 rows=0)
|
+-----------------------------------------------------------------------------------+
1 row in set (0.01 sec)

 

今回のレントゲン。いや、実行計画の比較。たまたま閃いたので試してみたのですが想像の斜め上をいく面白さでした。

まとめると、

Oracle Database
Join eliminationによる結合の最適化を行う

 

PostgreSQL (16以前では未確認)

特定のケースでは、Join elimination できるようだ。(全く行っていないわけではない)

 

MySQL

現時点8.0までは、Join eliminationは実装されていないようだ。

 

では、また。

Enjoy SQL! and Execution Plan!

 



実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1で検証に利用した表の作成ログです。
Oracle Databaseのscottユーザで作成したcustomers/orders表をcsv形式でエクスポート後、Oracle Databaseのcustomers/ordersと同じ表をPostgreSQL/MySQLそれぞれで作成、最後にcsvファイルからデータをロードしています。
試してみたい方は参考にしてみてください。

 

ーーーcustomers/orders表作成ログーーー

 

Oracle Database サンプルスキーマ OE で scottユーザへ customers/orders表へのSELECT権限を付与後、scottユーザ側で、customers/orders表をCTAS。その後、csv形式で、それぞれのデータをexportしています。

 

oeユーザで。

OE@orclpdb1> grant select on customers to scott;

権限付与が成功しました。

OE@orclpdb1> grant select on orders to scott;

権限付与が成功しました。

 

 

以降、scottユーザで。

SCOTT@orclpdb1> l
1 CREATE TABLE customers AS
2 SELECT
3 cust.customer_id
4 , cust.cust_first_name AS first_name
5 , cust.cust_last_name AS last_name
6 , cust.address
7 , cust.phone# AS phone_number
8 FROM
9 (
10 SELECT
11 c.customer_id
12 , c.cust_first_name
13 , c.cust_last_name
14 , c.cust_address.street_address AS address
15 , cr.COLUMN_VALUE AS phone#
16 , ROW_NUMBER()
17 OVER (
18 PARTITION BY c.customer_id
19 ORDER BY c.customer_id
20 ) AS phone_count
21 FROM
22 oe.customers c
23 , TABLE(c.phone_numbers) cr
24 ORDER BY
25 c.customer_id
26 , phone_count
27 ) cust
28 WHERE
29* cust.phone_count = 1
SCOTT@orclpdb1> /

表が作成されました。

SCOTT@orclpdb1> alter table customers add constraint pk_customers primary key (customer_id) using index;

表が変更されました。

SCOTT@orclpdb1> create table orders as select * from oe.orders;

表が作成されました。

SCOTT@orclpdb1> desc orders
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ORDER_ID NUMBER(12)
ORDER_DATE NOT NULL TIMESTAMP(6) WITH LOCAL TIME ZONE
ORDER_MODE VARCHAR2(8)
CUSTOMER_ID NOT NULL NUMBER(6)
ORDER_STATUS NUMBER(2)
ORDER_TOTAL NUMBER(8,2)
SALES_REP_ID NUMBER(6)
PROMOTION_ID NUMBER(6)

SCOTT@orclpdb1> alter table orders add constraint pk_orders primary key (order_id) using index;

表が変更されました。

SCOTT@orclpdb1> alter table orders add constraint fk_orders_customers foreign key (customer_id) references customers;

表が変更されました。

SCOTT@orclpdb1> desc customers
名前 NULL? 型
----------------------------------------- -------- ----------------------------
CUSTOMER_ID NUMBER(6)
FIRST_NAME NOT NULL VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(20)
ADDRESS VARCHAR2(40)
PHONE_NUMBER VARCHAR2(25)

SCOTT@orclpdb1> create index fk_orders_customers on orders(customer_id);

索引が作成されました。

SCOTT@orclpdb1> l
1 select
2 table_name
3 , index_name
4 , column_name
5 from
6 user_ind_columns
7 where
8 table_name in ('CUSTOMERS', 'ORDERS')
9 order by
10 table_name
11 , index_name
12* , column_position
SCOTT@orclpdb1> /

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
CUSTOMERS PK_CUSTOMERS CUSTOMER_ID
ORDERS FK_ORDERS_CUSTOMERS CUSTOMER_ID
ORDERS PK_ORDERS ORDER_ID

SCOTT@orclpdb1> r
1 select
2 table_name
3 , constraint_name
4 , constraint_type
5 , r_owner
6 , r_constraint_name
7 from
8 user_constraints
9 where
10 table_name in ('CUSTOMERS','ORDERS')
11* and constraint_type in ('P','R')

TABLE_NAME CONSTRAINT_NAME CON R_OWNER R_CONSTRAINT_NAME
------------------------------ -------------------- --- -------------------- --------------------
ORDERS FK_ORDERS_CUSTOMERS R SCOTT PK_CUSTOMERS
CUSTOMERS PK_CUSTOMERS P
ORDERS PK_ORDERS P

SCOTT@orclpdb1> select count(1) from customers;

COUNT(1)
----------
319

SCOTT@orclpdb1> select count(1) from orders;

COUNT(1)
----------
105

SCOTT@orclpdb1>
SCOTT@orclpdb1> !cat gather_tab_stats.sql
set verify on
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>upper('&1'),cascade=>true,no_invalidate=>false);
set verify off
undefine 1

SCOTT@orclpdb1> @gather_tab_stats.sql orders

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

SCOTT@orclpdb1> @gather_tab_stats.sql customers

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

SCOTT@orclpdb1>
SCOTT@orclpdb1> !cat makecsv.sql
--
-- parameter 1 : table name
--
set feed off
set timi off
set head off
set termout off
set veri off
set markup csv on
spool loaddata_&1..csv
select * from &1;
spo off
set markup csv off
set termout on
set head on
set feed on
set veri on
undefine 1

SCOTT@orclpdb1> @makecsv customers
SCOTT@orclpdb1> @makecsv orders
SCOTT@orclpdb1> !ls -l loaddata*.csv
-rw-r--r--. 1 oracle oinstall 19021 7月 13 19:42 loaddata_customers.csv
-rw-r--r--. 1 oracle oinstall 6064 7月 13 19:42 loaddata_orders.csv

 

 

 

PostgreSQL (16.3) customers/orders表を作成(主キー制約や、参照整合性制約含む)、外部キー列に索引を作成したあと、copyコマンドでcsvファイルからデータをロードして統計情報取得という内容です。

perftestdb=> 
perftestdb=> CREATE TABLE customers
perftestdb-> (
perftestdb(> customer_id INTEGER NOT NULL
perftestdb(> , first_name VARCHAR(20) NOT NULL
perftestdb(> , last_name VARCHAR(20) NOT NULL
perftestdb(> , address VARCHAR(40)
perftestdb(> , phone_number VARCHAR(25)
perftestdb(> , CONSTRAINT pk_customers PRIMARY KEY (customer_id)
perftestdb(> );
CREATE TABLE
perftestdb=> CREATE TABLE orders
perftestdb-> (
perftestdb(> order_id INTEGER NOT NULL
perftestdb(> , order_date TIMESTAMP WITH TIME ZONE NOT NULL
perftestdb(> , order_mode VARCHAR(8)
perftestdb(> , customer_id INTEGER NOT NULL
perftestdb(> , order_status SMALLINT
perftestdb(> , order_total NUMERIC(8,2)vperftestdb(> , sales_rep_id INTEGER
perftestdb(> , promotion_id INTEGER
perftestdb(> , CONSTRAINT pk_orders PRIMARY KEY (order_id)
perftestdb(> , CONSTRAINT fk_orders_customers foreign key (customer_id) references customers
perftestdb(> );
CREATE TABLE
perftestdb=>
perftestdb=> CREATE INDEX fk_orders_customers ON orders(customer_id);
CREATE INDEX
perftestdb=>
perftestdb=> \copy customers(customer_id,first_name,last_name,address,phone_number) from 'loaddata_customers.csv' csv
COPY 319
perftestdb=> \copy orders(order_id,order_date,order_mode,customer_id,order_status,order_total,sales_rep_id,promotion_id) from 'loaddata_orders.csv' csv
COPY 105
perftestdb=> vacuum analyze customers;
VACUUM
perftestdb=> vacuum analyze orders;
VACUUM
perftestdb=>
count
-------
319
(1 行)

perftestdb=> select count(1) from orders;
count
-------
105
(1 行)

 

 

 

MySQL (8.0.36) customers/orders表を作成(主キー制約や、参照整合性制約含む)、外部キー列に索引を作成後、loadコマンドでcsvファイルからデータをロードして、統計情報取得という流れになっています
(loadコマンドでワーニングでてたりしますが、今回のテストでは影響ないので気にしないでください。。(^^;;;

mysql> CREATE TABLE customers
-> (
-> customer_id INTEGER NOT NULL
-> , first_name VARCHAR(20) NOT NULL
-> , last_name VARCHAR(20) NOT NULL
-> , address VARCHAR(40)
-> , phone_number VARCHAR(25)
-> , CONSTRAINT pk_customers PRIMARY KEY (customer_id)
-> );
Query OK, 0 rows affected (0.18 sec)

mysql> CREATE TABLE orders
-> (
-> order_id INTEGER NOT NULL
-> , order_date TIMESTAMP NOT NULL
-> , order_mode VARCHAR(8)
-> , customer_id INTEGER NOT NULL
-> , order_status SMALLINT
-> , order_total NUMERIC(8,2)
-> , sales_rep_id INTEGER
-> , promotion_id INTEGER
-> , CONSTRAINT pk_orders PRIMARY KEY (order_id)
-> , CONSTRAINT fk_orders_customers foreign key (customer_id) references customers (customer_id)
-> );
Query OK, 0 rows affected (0.07 sec)
mysql> CREATE INDEX fk_orders_customers ON orders(customer_id);
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
[master@localhost ~]$ mysql -u root -D perftestdb -p --local-infile=1
Enter password:

....中略....

mysql>
mysql> \! ls -l load*
-rw-rw-r--. 1 master master 19021 7月 14 19:26 loaddata_customers.csv
-rw-rw-r--. 1 master master 6064 7月 14 19:27 loaddata_orders.csv

mysql> load data local infile "./loaddata_customers.csv" into table perftestdb.customers fields terminated by ',' optionally enclosed by '"';
Query OK, 319 rows affected (0.10 sec)
Records: 319 Deleted: 0 Skipped: 0 Warnings: 0

mysql> load data local infile "./loaddata_orders.csv" into table perftestdb.orders fields terminated by ',' optionally enclosed by '"';
Query OK, 105 rows affected, 140 warnings (0.05 sec)
Records: 105 Deleted: 0 Skipped: 0 Warnings: 140

mysql> analyze table perftestdb.customers;
+----------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------------+---------+----------+----------+
| perftestdb.customers | analyze | status | OK |
+----------------------+---------+----------+----------+
1 row in set (0.05 sec)

mysql> analyze table perftestdb.orders;
+-------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------------------+---------+----------+----------+
| perftestdb.orders | analyze | status | OK |
+-------------------+---------+----------+----------+
1 row in set (0.02 sec)

mysql> select count(1) from perftestdb.customers;
+----------+
| count(1) |
+----------+
| 319 |
+----------+
1 row in set (0.02 sec)

mysql> select count(1) from perftestdb.orders;
+----------+
| count(1) |
+----------+
| 105 |
+----------+
1 row in set (0.07 sec)

 

 

 

 



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のおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2

| | | コメント (0)

2024年7月23日 (火)

実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2

Previously on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1では、参照整合性制約を利用した Join Elimination の挙動を確認しました。

PostgreSQL、意外にも(知ってたくせに〜w)行われませんでしたね MySQLは事前の想定通りでしたが:)

では、次の Join Elimination のテストケースを確認してみましょう。

シンプルな例で試しています。Oracle Database / PostgreSQL / MySQL それぞれに以下の2表を作成しておきます。
どちらの表も id 列が主キーですが、前回のケースのような参照整合性制約はありません。

Oracle Database同様の表をMySQL/PostgreSQLにも作成して検証。(データはあってもなくても結合の除外には影響しないためデータは登録していません)
(なおデータ型は、MySQL/PostgreSQLに合わせて変更しています。e.g. NUMBER->INTEGER, VARCHAR2 -> VARCHAR。 また、MySQL/PostgreSQLそれぞれで統計情報も取得しておきます)

SCOTT@orclpdb1> CREATE TABLE foo (id NUMBER PRIMARY KEY, note VARCHAR2(100));

表が作成されました。

SCOTT@orclpdb1> CREATE TABLE bar (id NUMBER PRIMARY KEY, memo VARCHAR2(100));

表が作成されました。

SCOTT@orclpdb1> !cat gather_tab_stats.sql
set verify on
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>upper('&1'),cascade=>true,no_invalidate=>false);
set verify off
undefine 1

SCOTT@orclpdb1> @gather_tab_stats foo

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

SCOTT@orclpdb1> @gather_tab_stats bar

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

前述の表を使い、以下のSQL文を実行します!
このSQL文では、bar表を外部結合していますが、SELECTリストでは bar表 を参照していません。
また、foo.id = bar.id は 1 : 0..1 であるため、bar表の対象行の結合されるかどうかは問合せ結果に影響しないようにしてあります。つまり、bar表は結合しなくてもよい問合せにしてあります。。。
(さあ、どうなるでしょうね。楽しくなってきました)

SELECT
foo.id
, foo.note
FROM
foo
LEFT OUTER JOIN bar
ON
foo.id = bar.id;


Oracle Database (21c)
すばらしい。無駄な結合を見つけ、Join Elimination していることを確認できます。(分かってましたけどw)

SCOTT@orclpdb1> l
1 EXPLAIN PLAN FOR
2 SELECT
3 foo.id
4 , foo.note
5 FROM
6 foo
7 LEFT OUTER JOIN bar
8 ON
9* foo.id = bar.id
SCOTT@orclpdb1> /

解析されました。

経過: 00:00:00.01
SCOTT@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
Plan hash value: 1245013993

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 65 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| FOO | 1 | 65 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------

8行が選択されました。


PostgreSQL (16.3)
おおおおおおおーーーーーーーっ。このケースでは、PostgreSQLも Join Elimination しています!!!!!
bar表が結合されていません! これまた新しい気づき。メモメモw

PostgreSQLのプランナー。 Join Elimination を全く実装していないのかと思いましたが、限定的なようですが、 Join Elimination が実装されているように見えますよね。
興味深い。発展途上というところか。。(次回のPostgreSQL アンカンファレンスで聞いてみようかな)

perftestdb=> EXPLAIN verbose
perftestdb-> SELECT
perftestdb-> foo.id
perftestdb-> , foo.note
perftestdb-> FROM
perftestdb-> foo
perftestdb-> LEFT OUTER JOIN bar
perftestdb-> ON
perftestdb-> foo.id = bar.id;

QUERY PLAN
------------------------------------------------------------
Seq Scan on public.foo (cost=0.00..0.00 rows=1 width=222)
Output: foo.id, foo.note
(2 行)

MySQL (8.0.36)
んーーーーっ。MySQLのオプティマイザーは、Join Elimination は考慮していないように見えますよ。軽量なオプティマイザーが売りだからだろうか。。

mysql> EXPLAIN format=tree
-> SELECT
-> foo.id
-> , foo.note
-> FROM
-> foo
-> LEFT OUTER JOIN bar
-> ON
-> foo.id = bar.id;
+-----------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------+
| -> Nested loop left join (cost=0.7 rows=1)
-> Table scan on foo (cost=0.35 rows=1)
-> Single-row covering index lookup on bar using PRIMARY (id=foo.id) (cost=0.35 rows=1)
|
+-----------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)


海外のブログでは、実装されてない! と言い切られているのもありましたが、PostgreSQL 16 では限定的ですが行われるようですね。(PostgreSQL 16より前のリリースってどうなんだろう。。13のままにしてたほうがおもしろかったかな。。。むむむ


ということで、次回へつづく。




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のおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1

| | | コメント (0)

2024年7月22日 (月)

実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1

Previously on Mac De Oracle
前回はコレクション型をアクセスした場合の実行計画がどうなるのかを確認しました。

今回は少し嗜好を変えて。。

先日、Oracle Databaseの Join Elimination が行われている実行計画を、ぼーっと眺めていたのですが、、そういえば、PostgreSQL / MySQL ってどうなんだっけ? と。気になりまして。はい。
ちょいとぐぐると、海外のブログ等では、Join Elimination - Advanced SQL tuningなど含め、PostgreSQL / MySQL 共に実装されてない。ということが書かれているのが多かったのですが、とにかく自分の目で確かめてみるか。。。ということに。。

Oracle Databaseの実行計画の話ではないですが、本「実行計画は, SQL文のレントゲン写真だ!」シリーズの番外編的な位置付けで、今回含め3回に分けた現時点の動きを確認してみます。

まずは、Oracle Databaseでの Join elimination の復習 - 無駄に結合してないですよね?


Join Elimination(結合の排除)と 参照整合性制約 / FAQ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
join elimination(結合の排除)のバリエーション / FAQ


Oracle Databaseの主要な Join Elimination 思い出しましたか? 復讐できましたよね!? 
ということで、PostgreSQL / MySQL 含め確認していきますよ〜っ!

 

今回は以下のバージョンのOracle Database/PostgreSQL/MySQLを利用。(PostgreSQL、やっと16にした! w

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

perftestdb=> select version();
version
---------------------------------------------------------------------------------------------------------
PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-22), 64-bit
(1 行)

mysql>
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.00 sec)

 

この検証では、 Oracle Database のサンプルスキーマの一つである OE スキーマから、cusotomersの一部の列、および orders表を元に scottスキーマへ複製し、参照整合性制約を追加( orders表のcustomer_idからcustomers表の主キーを参照 )します。データはあってもなくても構わないのですが、customers/orders表に関しては別ネタで検証する際に利用することも兼ねてデータもロードしています。
(表や参照整合性など利用したオブジェクト、ロード等含めたログは、最後 ( 後日公開予定 / 実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3 ) に記載しています)

 

さっそく、結果から見ていきましよう(面白いですよー、そうなの!!! いう感じではありました。Oraclerからするとw

まずは、参照整合性制約で保証されていることで、結合不要と判断される Join Elimination から。 ( db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニングの「参照整合性制約アレルギー」でも紹介していたので、この挙動については知っているかたは多いと思います。参照整合性制約を使ってないとお目にかかることはないタイプの Join Elimination ではあるのですけどもw )

Oracle Database / PostgreSQL / MySQL それぞれに以下のような表と主キー制約、および、参照整合性制約 (orders.customer_id -> customers.customer_id)を作成します。

Oracle Databaseでの定義内容 (なおデータ型は、MySQL/PostgreSQLに合わせて変更しています。e.g. NUMBER(n)->INTEGER or SMALLINT, VARCHAR2-> VARCHAR, TIMESTAMP WITH LOCAL TIME ZONE -> TIMESTAMP WITH TIME ZONE, TIMESTAMP)

SCOTT@orclpdb1> desc customers
名前 NULL? 型
----------------------------------------- -------- ----------------------------
CUSTOMER_ID NUMBER(6)
FIRST_NAME NOT NULL VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(20)
ADDRESS VARCHAR2(40)
PHONE_NUMBER VARCHAR2(25)

SCOTT@orclpdb1> desc orders
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ORDER_ID NUMBER(12)
ORDER_DATE NOT NULL TIMESTAMP(6) WITH LOCAL TIME
ZONE
ORDER_MODE VARCHAR2(8)
CUSTOMER_ID NOT NULL NUMBER(6)
ORDER_STATUS NUMBER(2)
ORDER_TOTAL NUMBER(8,2)
SALES_REP_ID NUMBER(6)
PROMOTION_ID NUMBER(6)

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
CUSTOMERS PK_CUSTOMERS CUSTOMER_ID
ORDERS FK_ORDERS_CUSTOMERS CUSTOMER_ID
ORDERS PK_ORDERS ORDER_ID

TABLE_NAME CONSTRAINT_NAME CON R_OWNER R_CONSTRAINT_NAME
------------------------------ -------------------- --- -------------------- --------------------
ORDERS FK_ORDERS_CUSTOMERS R SCOTT PK_CUSTOMERS
CUSTOMERS PK_CUSTOMERS P
ORDERS PK_ORDERS P

 

このケースで実行するSQL文はそれぞれ共通で以下を使います。

SELECT
DISTINCT
order_id
FROM
orders o
INNER JOIN customers c
ON o.customer_id = c.customer_id
WHERE
order_id < 2400;

 

Oracle Database (21c) customers表は結合されず、join elimination されていることがわかります。inner join で保証しようとしている orders 表に存在している order_idだcustomer表に存在している顧客の注文であるということが参照整合性制約で保証されているため、結合は不要と判断されたわけです。
参照整合性制約アレルギーのみなさんには耳の痛い話ではありますが、この制約のメリットの一つは、Join Eliminationだったりします。
話は少し脱線しますが、発症すると一生ものの参照整合性制約アレルギーなのでw うまく付き合っていきたいものですよね。使いたい!と思えなくなってしまうものなので、Pros/Consをよーーーーーく考えて上で判断したい仕組みですよね。

SCOTT@orclpdb1> l
1 EXPLAIN PLAN FOR
2 SELECT
3 DISTINCT
4 order_id
5 FROM
6 orders o
7 INNER JOIN customers c
8 ON o.customer_id = c.customer_id
9 WHERE
10* order_id < 2400
SCOTT@orclpdb1> /

解析されました。

経過: 00:00:00.01
SCOTT@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------
Plan hash value: 2834288864

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| PK_ORDERS | 46 | 184 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------

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

1 - access("ORDER_ID"<2400)

13行が選択されました。

 

念のため、参照整合性制約が無い場合はどうなるか確認しておきましょう。
Join Elimination されず、customers表が結合されている状況が確認できますよね!!

SCOTT@orclpdb1> alter table orders disable constraint fk_orders_customers;

表が変更されました。

経過: 00:00:00.31
SCOTT@orclpdb1> l
1 EXPLAIN PLAN FOR
2 SELECT
3 DISTINCT
4 order_id
5 FROM
6 orders o
7 INNER JOIN customers c
8 ON o.customer_id = c.customer_id
9 WHERE
10* order_id < 2400
SCOTT@orclpdb1> /

解析されました。

経過: 00:00:00.01
SCOTT@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------
Plan hash value: 572428435

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 552 | 3 (34)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT | | 46 | 552 | 3 (34)| 00:00:01 |
| 2 | NESTED LOOPS SEMI | | 46 | 552 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| ORDERS | 46 | 368 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | PK_ORDERS | 46 | | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 4 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

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

4 - access("ORDER_ID"<2400)
5 - access("O"."CUSTOMER_ID"="C"."CUSTOMER_ID")

18行が選択されました。

 

PostgreSQL (16.3) なんとーーー。PostgreSQLの場合は、Join elimination しないのか。(初めて知った!!!) 脳のシワが一つ増えた!

perftestdb=> explain verbose
perftestdb-> SELECT
perftestdb-> DISTINCT
perftestdb-> order_id
perftestdb-> FROM
perftestdb-> orders o
perftestdb-> INNER JOIN customers c
perftestdb-> on o.customer_id = c.customer_id
perftestdb-> WHERE
perftestdb-> order_id < 2400;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
HashAggregate (cost=7.52..7.98 rows=46 width=4)
Output: o.order_id
Group Key: o.order_id
-> Merge Join (cost=3.73..7.40 rows=46 width=4)
Output: o.order_id
Merge Cond: (c.customer_id = o.customer_id)
-> Index Only Scan using pk_customers on public.customers c (cost=0.15..12.93 rows=319 width=4)
Output: c.customer_id
-> Sort (cost=3.58..3.70 rows=46 width=8)
Output: o.order_id, o.customer_id
Sort Key: o.customer_id
-> Seq Scan on public.orders o (cost=0.00..2.31 rows=46 width=8)
Output: o.order_id, o.customer_id
Filter: (o.order_id < 2400)
(14 行)

Join Eliminationされていないので参照整合性制約の有無が影響しないのは自明ですが、念の為w 参照整合性制約を削除して実行計画を確認してみます。
(PostgreSQLでは参照整合性制約を無効化/有効化することができないため、dropすることで無効化しています)

一目瞭然、影響していないことがわかります。(そうなのかーーーー。まじで知らなかったこれ)

perftestdb=> alter table orders drop constraint fk_orders_customers;
ALTER TABLE
perftestdb=*> commit;
COMMIT
perftestdb=> explain verbose
perftestdb-> SELECT
perftestdb-> DISTINCT
perftestdb-> order_id
perftestdb-> FROM
perftestdb-> orders o
perftestdb-> INNER JOIN customers c
perftestdb-> ON o.customer_id = c.customer_id
perftestdb-> WHERE
perftestdb-> order_id < 2400;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
HashAggregate (cost=7.52..7.98 rows=46 width=4)
Output: o.order_id
Group Key: o.order_id
-> Merge Join (cost=3.73..7.40 rows=46 width=4)
Output: o.order_id
Merge Cond: (c.customer_id = o.customer_id)
-> Index Only Scan using pk_customers on public.customers c (cost=0.15..12.93 rows=319 width=4)
Output: c.customer_id
-> Sort (cost=3.58..3.70 rows=46 width=8)
Output: o.order_id, o.customer_id
Sort Key: o.customer_id
-> Seq Scan on public.orders o (cost=0.00..2.31 rows=46 width=8)
Output: o.order_id, o.customer_id
Filter: (o.order_id < 2400)
(14 行)

 

 

MySQL (8.0.36) MySQLもPostgreSQL同様に、参照整合性制約があったとしても customers表を結合しており、Join elimination は行われていません。
(海外の記事の通り、MySQL/PostgreSQLでは Join Elimination による結合の最適化は実装されていないように見えますね。これまでのところは。)

mysql> explain format=tree
-> SELECT
-> DISTINCT
-> order_id
-> FROM
-> orders o
-> INNER JOIN customers c
-> ON o.customer_id = c.customer_id
-> WHERE
-> order_id < 2400;
+---------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=30.2..33.3 rows=46)
-> Temporary table with deduplication (cost=30.2..30.2 rows=46)
-> Nested loop inner join (cost=25.6 rows=46)
-> Filter: (o.order_id < 2400) (cost=9.48 rows=46)
-> Index range scan on o using PRIMARY over (order_id < 2400) (cost=9.48 rows=46)
-> Limit: 1 row(s) (cost=0.252 rows=1)
-> Single-row covering index lookup on c using PRIMARY (customer_id=o.customer_id) (cost=0.252 rows=1)
|
+---------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

 

MySQLでも同様に、参照整合性制約を無効化します。
(こちらも、有効/無効だけを制御することはできず、参照整合性制約を削除して無効化する必要があります。戻すどきめんどくさいのだけどもw FOREIGN_KEY_CHECKSでチェックしないという方法はあるらしい)

こちらも参照整合性制約の有無は影響していないことは明らかですね。

mysql> alter table orders drop foreign key fk_orders_customers;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> explain format=tree
-> SELECT
-> DISTINCT
-> order_id
-> FROM
-> orders o
-> INNER JOIN customers c
-> ON o.customer_id = c.customer_id
-> WHERE
-> order_id < 2400;
+---------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=30.2..33.3 rows=46)
-> Temporary table with deduplication (cost=30.2..30.2 rows=46)
-> Nested loop inner join (cost=25.6 rows=46)
-> Filter: (o.order_id < 2400) (cost=9.48 rows=46)
-> Index range scan on o using PRIMARY over (order_id < 2400) (cost=9.48 rows=46)
-> Limit: 1 row(s) (cost=0.252 rows=1)
-> Single-row covering index lookup on c using PRIMARY (customer_id=o.customer_id) (cost=0.252 rows=1)
|
+---------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

いきなり違いが見えて、楽しいーーーーぞっ。 :)

海側も無茶苦茶暑いのだろうか、海風吹いてそうでもないのだろうか。。と湘南方面を見ながら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
・実行計画は, 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のおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?

| | | コメント (0)

2024年7月12日 (金)

実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?

前回の実行計画は, SQL文のレントゲン写真だ!は2023/1だったので、Long time no seeな感じではありますが、このシリーズもネタストックが多いのでまだまだ続けていく予定です:)

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




さて、今日のレントゲンからはどのような状況が見えるのでしょうか。。。

一から準備するのは大変なのでサンプルスキーマである、OEの customers表を利用します。
Oracle Database Release 18 / Database Sample Schemas / 4.5 OEサンプル・スキーマの表の説明

オブジェクト型の列が複数ありますね。ニコニコ(よいサンプルだw)
(今回のエントリーでは、CUST_ADDRESS、CUST_ADDRESS列を利用します)

OE@orclpdb1> desc customers
名前 NULL? 型
----------------------------------------- -------- ----------------------------
CUSTOMER_ID NOT NULL NUMBER(6)
CUST_FIRST_NAME NOT NULL VARCHAR2(20)
CUST_LAST_NAME NOT NULL VARCHAR2(20)
CUST_ADDRESS CUST_ADDRESS_TYP
PHONE_NUMBERS PHONE_LIST_TYP
NLS_LANGUAGE VARCHAR2(3)
NLS_TERRITORY VARCHAR2(30)
CREDIT_LIMIT NUMBER(9,2)
CUST_EMAIL VARCHAR2(40)
ACCOUNT_MGR_ID NUMBER(6)
CUST_GEO_LOCATION MDSYS.SDO_GEOMETRY
DATE_OF_BIRTH DATE
MARITAL_STATUS VARCHAR2(20)
GENDER VARCHAR2(1)
INCOME_LEVEL VARCHAR2(20)


利用する列に絞ってデータを覗いてみます。
(オブジェクト型を利用している2列を含む5列に絞ってあります)

OE@orclpdb1> r
1 SELECT
2 c.customer_id
3 , c.cust_first_name
4 , c.cust_last_name
5 , c.cust_address AS address
6 , c.phone_numbers AS phones
7 FROM
8 customers c
9 WHERE
10* customer_id = 348

CUSTOMER_ID CUST_FIRST_NAME CUST_LAST_NAME ADDRESS(STREET_ADDRESS, POSTAL_CODE, CITY, STATE_P PHONES
----------- -------------------- -------------------- -------------------------------------------------- ----------------------------------------------------
348 Kelly Lange CUST_ADDRESS_TYP('Piazza Del Congresso 22', '36121 PHONE_LIST_TYP('+39 49 012 4373', '+39 49 083 4373')
9', 'San Giminiano', NULL, 'IT')

ひとつめは、CUST_ADDRESS_TYP型、住所情報ですね。複数ある属性から、STREET_ADDRESS だけをアクセスすることにします

OE@orclpdb1> set linesize 80
OE@orclpdb1> desc CUST_ADDRESS_TYP
名前 NULL? 型
----------------------------------------- -------- ----------------------------
STREET_ADDRESS VARCHAR2(40)
POSTAL_CODE VARCHAR2(10)
CITY VARCHAR2(30)
STATE_PROVINCE VARCHAR2(10)
COUNTRY_ID CHAR(2)

ふたつめは、PHONE_LIST_TYP型、複数の電話を持つことを前提としたモデルですが、最大で5個までですね。それ以上の人はどうするのでしょう?(という余計なことはここでは気にしないでw
電話番号は2つは不要なので、最初の電話番号だけを利用することします。 1顧客1電話番号(主番号)という問い合わせにすると面白そうですね。すこし難易度を上げたSELECT文のほうが面白いですし:)

OE@orclpdb1> desc PHONE_LIST_TYP
PHONE_LIST_TYP VARRAY(5) OF VARCHAR2(25)


ところで、
オブジェクト型に出くわすと、Oracle Databaseって色々飲み込んだというか取り込んだORDBMSでもあることを思い出させてくれますw
それと同時に、ああああ〜〜っ。構文どうだっけーーーーと。(サクッと出てこないw
(ちなみに、Oracle Database 8の頃にORDBMSの機能が取り込まれた。と言う、ちょっと曖昧な記憶がありますが、おそらくその頃なので、1997年ぐらいですよねw)


ということで、オブジェクト型にアクセスしつつ、今日の実行計画というレントゲン写真を診ながら動きを確認してみましょう。
CUST_ADDRESS_TYP型の属性は単純なので修飾してあげればよいですよね。

 OE@orclpdb1> r
1 SELECT
2 customer_id
3 , cust_first_name
4 , cust_last_name
5 , cust_address.street_address
6 FROM
7 customers
8 WHERE
9* customer_id = 348
, cust_address.street_address
*
行5でエラーが発生しました。:
ORA-00904: "CUST_ADDRESS"."STREET_ADDRESS": 無効な識別子です。


あ”〜やっちまった。オブジェクト型を扱うときは、表エイリアスが必要だったはず!

OE@orclpdb1> r
1 SELECT
2 c.customer_id
3 , c.cust_first_name
4 , c.cust_last_name
5 , c.cust_address.street_address
6 FROM
7 customers c
8 WHERE
9* customer_id = 348

CUSTOMER_ID CUST_FIRST_NAME CUST_LAST_NAME CUST_ADDRESS.STREET_ADDRESS
----------- -------------------- -------------------- ------------------------------
348 Kelly Lange Piazza Del Congresso 22

では、cusomters表に含まれる、CUST_ADDRESS_TYP型を含むクエリーの実行計画はどうなるか...

よく見る index unique scan + table access by index rowid、ユニークキーまたは主キーによる1行だけのアクセスですね。ふむふむ。

OE@orclpdb1> r
1 explain plan for SELECT
2 c.customer_id
3 , c.cust_first_name
4 , c.cust_last_name
5 , c.cust_address.street_address
6 FROM
7 customers c
8 WHERE
9* customer_id = 348

解析されました。

経過: 00:00:00.00
OE@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------
Plan hash value: 4238351645

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 1 | 37 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | CUSTOMERS_PK | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

2 - access("CUSTOMER_ID"=348)

次に、customers表に含まれるコレクションオブジェクPHONE_LIST_TYP型をアクセスしてみます。
少しずつオブジェクト型を思い出してきましたw

コレクション型は、表として扱う必要があるので、TABLEファンクションを使う必要がありますよね(思い出してきましたw パイプラインファンクションと同じ考え方)
PHONE_LIST_TYP型はコレクション型で属性名のないVARCHAR2のVARRAY型です。TABLEファンクションを利用した場合、単一列の仮想表として返されるので、列名には、COLUMN_VALUE疑似列を使います。XML/JSONでも応用できる知識なので覚えておくと便利です。
Oracle Database Release 19 / SQL言語リファレンス / COLUMN_VALUE疑似列
c.cust_address.street_addresとして、CUST_ADDRESS_TYP型のstreet_addres属性まで指定することで通常の列のように扱えます。それほどトリッキーな構文ではないですよね。



コレクション型のアクセス方法は独特なので、いざという時に慌てないよう、日頃からSQLパズルなどで遊んでいると良いかもしれません。
ある程度使えるようになっていないと、道に迷って時間を溶かすことになるので。。。

customers表のPHONE_LIST_TYP型コレクションには複数の電話番号が含まれています(よくありますよね。固定電話番号、携帯とか複数登録させるユーザー登録画面など)
このSELECT文では、PHONE_LIST_TYP型コレクションから最初の電話番号を主電話番号として取り出し(ROW_NUMBERウィンドウ関数を利用している箇所)、1顧客N電話番号ではなく、1顧客1電話番号(主番号のみ)でリストしています。

表エイリアスは必須(前述の通り)になりますが、もう一つ、コレクション型を仮想表にするTABLEファンクションを利用しています。ここがポイント。
結合しているイメージでOK。中に抱えているコレクションを仮想表として取り出し結合していると思えばイメージしやすいはず:) 
(実行計画では、それをそのままおこなっている部分があります。実行計画の赤字部分の操作を、よーく確認しておいてください)

OE@orclpdb1> r
1 SELECT
2 cust.customer_id
3 , cust.phone# AS primary_phone#
4 FROM
5 (
6 SELECT
7 c.customer_id
8 , cr.COLUMN_VALUE AS phone#
9 , ROW_NUMBER()
10 OVER (
11 PARTITION BY c.customer_id
12 ORDER BY c.customer_id
13 ) AS phone_count
14 FROM
15 customers c
16 , TABLE(c.phone_numbers) cr
17 ORDER BY
18 c.customer_id
19 , phone_count
20 ) cust
21 WHERE
22* cust.phone_count = 1

CUSTOMER_ID PRIMARY_PHONE#
----------- ---------------------------------------------------------------------------
101 +1 317 123 4104
102 +1 317 123 4111

....中略....

980 +91 80 012 3837
981 +86 10 012 3839


このコレクション型から配列の属性を取り出すSELECT文の実行計画は以下の通り。コレクション自体は表の列として保持されているのでcustomers表以外へのオブジェクトにはアクセスしませんが、TABLEファンクションで作り出した仮想表から属性を取り出す際の操作として、COLLECTION ITERATOR PICKLER FETCH が行われています。
この COLLECTION ITERATOR PICKLER FETCH って以前、 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCHで紹介したことがあるので覚えている方も多いと思います。テーブルファンクション特有の操作です。

また、ウィンドウ関数で必要となるソートをCUSTOMERS表の主キーをINDEX FULL SCANすることで回避していのも面白い最適化です。
ソートするよりソート済みの主キーを使ってアクセスしたほうが効率的と判断した結果、INDEX FULL SCAN -> WINDOW NOSORT という操作が行われています。
データ量が多くなるケースだとソートを避ける最適化が多く見られるものOracle Databaseのオプティマイザの特徴ですね。わかりやすくて好きです:) 
悪くない実行計画ではないでしょうか。

OE@orclpdb1> r
1 EXPLAIN PLAN FOR SELECT
2 cust.customer_id
3 , cust.phone# AS primary_phone#
4 FROM
5 (
6 SELECT
7 c.customer_id
8 , cr.COLUMN_VALUE AS phone#
9 , ROW_NUMBER()
10 OVER (
11 PARTITION BY c.customer_id
12 ORDER BY c.customer_id
13 ) AS phone_count
14 FROM
15 customers c
16 , TABLE(c.phone_numbers) cr
17 ORDER BY
18 c.customer_id
19 , phone_count
20 ) cust
21 WHERE
22* cust.phone_count = 1

解析されました。

経過: 00:00:00.17
OE@orclpdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2349769165

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 319 | 12760 | 22981 (2)| 00:00:01 |
|* 1 | VIEW | | 319 | 12760 | 22981 (2)| 00:00:01 |
|* 2 | WINDOW NOSORT | | 2605K| 201M| 22981 (2)| 00:00:01 |
| 3 | NESTED LOOPS | | 2605K| 201M| 22981 (2)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS | 319 | 25201 | 10 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | CUSTOMERS_PK | 319 | | 1 (0)| 00:00:01 |
| 6 | COLLECTION ITERATOR PICKLER FETCH| | 8168 | 16336 | 72 (2)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

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

1 - filter("CUST"."PHONE_COUNT"=1)
2 - filter(ROW_NUMBER() OVER ( PARTITION BY "C"."CUSTOMER_ID" ORDER BY NULL )<=1)


ということで、実行計画は読めてなんぼ、という感じなので、読む練習は怠らないようにしておきたいものですね。いろいろな機能が追加されてくるので。。。RDBMSっぽくないやつがRDBMSっぽい世界でどのように最適化され、実行されていくのか。。。最近はAI というかVector?もあるし

雨ばかりの東京より。雨が降らなきゃ猛暑だし。

ではまた。



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

| | | コメント (0)

2024年7月11日 (木)

帰ってきた! 標準はあるにはあるが癖の多いSQL #16 - FROM句のインラインビューのエイリアスにもクセがある(必須だったり、任意だったり)

Previously on Mac De Oracle
前回の癖は、SELECTリストに記述したスカラー副問合せが実行計画上どうみせられるかにも癖がでるというお話でした。

 

今日は副問合せは副問合せでもインラインビューの話題です。
FROM句でインラインビューを使う場合、表エイリアスを記述しなくてもエラーにはならいってのは、Oraclerを長年やってると不思議なことではないというか、そういうものだと思ってたりします。
とはいえ、可読性含や不思議な結果になったりする不具合などに遭遇するリスクを回避する意味でも表エイリアスを記述するのが手癖になっているかたも多いのではないでしょうか。私もそうですw(コーディング規約にも記載されていることは多いと思いますし)

 

ということで、今日の癖を見てみることにしますw

 

 

Oracle Database (23ai) インラインビューで表エイリアスを記述しなくても文法エラーにはなりません。
Oracle Database / Release 19 / SQL言語リファレンス / SELECT / table_reference::=のダイアグラムにあるように表エイリアスを省略可能です。(前述したように省略しないことの方が多いですけども、可読性向上のためにも)

SCOTT@localhost:1521/freepdb1> @inlineview_alias_name.sql
1 SELECT
2 empno
3 ,ename
4 FROM
5 (
6 SELECT
7 empno
8 ,ename
9 ,mgr
10 FROM
11 emp
12 )
13 WHERE
14* mgr IS NULL

EMPNO ENAME
---------- ----------
7839 KING


1 SELECT
2 empno
3 ,ename
4 FROM
5 (
6 SELECT
7 /*+ NO_MERGE */
8 empno
9 ,ename
10 ,mgr
11 FROM
12 emp
13 )
14 WHERE
15* mgr IS NULL

EMPNO ENAME
---------- ----------
7839 KING


1 SELECT
2 empno
3 ,ename
4 FROM
5 (
6 SELECT
7 empno
8 ,ename
9 ,mgr
10 FROM
11 emp
12 ) iv_emp
13 WHERE
14* mgr IS NULL

EMPNO ENAME
---------- ----------
7839 KING

 

 

MySQL (8.0.36) インラインビューに表エイリアスを記述しないと見事にエラーになります。コーディング規約で書くこと!なんて明記しなくても、書かないとシンタックスエラーなので書き忘れて、あ”〜っということはないわけすw
MySQL 8.0 リファレンスマニュアル / SQL ステートメント / データ操作ステートメント / SELECT ステートメント / 13.2.10.2 JOIN 句 / table_subqueryに、”table_subquery は、FROM 句では導出テーブルまたはサブクエリーとも呼ばれます。 セクション13.2.11.8「導出テーブル」を参照してください。 このようなサブクエリーには、サブクエリーの結果にテーブル名を指定するエイリアスを含める必要があります。”と記載されています。

mysql> SELECT
-> empno
-> ,ename
-> FROM
-> (
-> SELECT
-> empno
-> ,ename
-> ,mgr
-> FROM
-> emp
-> )
-> WHERE
-> mgr IS NULL
-> ;
ERROR 1248 (42000): Every derived table must have its own alias


mysql> SELECT
-> empno
-> ,ename
-> FROM
-> (
-> SELECT
-> empno
-> ,ename
-> ,mgr
-> FROM
-> emp
-> ) iv_emp
-> WHERE
-> mgr IS NULL
-> ;
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.00 sec)


mysql> SELECT
-> /*+ NO_MERGE(iv_emp) */
-> empno
-> ,ename
-> FROM
-> (
-> SELECT
-> empno
-> ,ename
-> ,mgr
-> FROM
-> emp
-> ) iv_emp
-> WHERE
-> mgr IS NULL
-> ;
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.02 sec)

 

 

PostgreSQL (13.14) PostgreSQLもMySQLと同じ挙動ですね。
PostgreSQL 13.1文書 / 第7章 問い合わせ / 7.2. テーブル式 / 7.2.1. FROM句 / 7.2.1.3. 副問い合わせに、"7.2.1.3. 副問い合わせ 派生テーブルを指定する副問い合わせは括弧で囲む必要があります。 また、(7.2.1.2にあるように)必ずテーブル別名が割り当てられている必要があります。 例を示します。"とあります。

perftestdb=> SELECT
perftestdb-> empno
perftestdb-> ,ename
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> ,ename
perftestdb(> ,mgr
perftestdb(> FROM
perftestdb(> emp
perftestdb(> )
perftestdb-> WHERE
perftestdb-> mgr IS NULL
perftestdb-> ;
ERROR: subquery in FROM must have an alias
行 5: (
^
HINT: For example, FROM (SELECT ...) [AS] foo.

perftestdb=> SELECT
perftestdb-> empno
perftestdb-> ,ename
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> ,ename
perftestdb(> ,mgr
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) iv_emp
perftestdb-> WHERE
perftestdb-> mgr IS NULL
perftestdb-> ;
empno | ename
-------+-------
7839 | KING
(1 行)

 

実際、この手の癖に遭遇したOracle Databaseなど表エイリアス不要の世界の方々は、「おまえは、なにをいっているんだぁ〜」という感じで一瞬固まったあとに、あ〜〜〜〜っ! 、なるとは思いますけどw

 

ところで、最近のdown pour半端ない感じがします。私の子供の頃でも台風以外でみたことのないような集中的な豪雨が頻発しています。用心しても来るものはきてしまうわけですが、正しい情報を入手して早めに避難するなりしましょうね。と。実家近くの川の水位が落ち着いたのをみて安堵する。。。

 

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

 

| | | コメント (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年7月 8日 (月)

VirtualBox TestBuild 7.0.97r163779 (2024-07-04T18:53:02Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

2024-07-04T18:53:02Z に最新のTestBuild ( development revision 163779 ) が公開されていました。


20240708-101041

恒例のOracle Database 21c on VirtualBox TestBuild for macOS/ARM64 の起動時間の記録です。

今回は、M1/M2とも前回とほぼ同じぐらい。改善は次回に期待 :) :) :) :) :)
とはいえ、そろそろ大詰め? な感じもしなくもない。。。。

M1

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** 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.0.97r163779

起動1回目 : 168 sec
停止1回目 : 77 sec
起動2回目 : 156 sec
停止2回目 : 48 sec

M2

oracle@angelfish ~ % ./print_env.sh

*** mac info. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** macOS ver. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** VirtualBox ver. ***
7.0.97r163779

起動1回目 :  93 sec
停止1回目 : 33 sec
起動2回目 : 95 sec
停止2回目 : 55 sec


VMのOSバージョンなどは過去のエントリーを見ていただくとして、M1/M2 それぞれ以下のPostgreSQL/MySQL/Oracle Databaseが起動することを確認。ルーティーン :)

MySQL

[master@localhost ~]$ mysql -u scott -D perftestdb -p -h localhost -e 'select version();'
Enter password:
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+

PostgreSQL

[master@localhost ~]$ sudo su - postgres -c 'psql -d perftestdb -U discus -p 5432 -W -h localhost -c "select version()"'
パスワード:
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

Oracle Database (21c)

[oracle@localhost ~]$ sqlplus / as sysdba @version

....中略....

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

Oracle Database (23ai)

[oracle@localhost ~]$ sqlplus hr/oracle@localhost:1521/freepdb1 @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


では、次回の起動停止時間ログをお楽しみに:)
最近のペースだと、7月の後半にありそうですよね.





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の起動、停止時間の記録

| | | コメント (0)

2024年7月 1日 (月)

帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)

Previously on Mac De Oracle
前回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖があるでした。
今回は、続編w とは言ってもMySQLが主役ではありませんし、DBMSのエンジンの癖でもなく、SQL Clentの癖のお話です。

 

前回利用したSQL文(SQLスクリプト化してありますが、スクリプト化せずコピペしても同じ)に以下のような位置に一行コメントを書いてみました。
MySQLの制限も考慮して、(--)の後には半角スペースを忘れずに!

 

MySQL (8.0.26) ふむふむ、という感じですよね。

[master@localhost ~]$ mysql -u scott -D perftestdb -p 
Enter password:

....略...

mysql> \! cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.
mysql>
mysql>
mysql> \. one_more_thing.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.01 sec)

mysql>

 

PostgreSQL (13.14) PostgreSQLもふむふむという感じ。

psql -d perftestdb -U discus -p 5432 -W -h localhost
パスワード:
psql (13.14)
"help"でヘルプを表示します。

....略...

perftestdb=> \! cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.
perftestdb=>
perftestdb=>
perftestdb=> \i one_more_thing.sql
empno | ename
-------+-------
7839 | KING
(1 行)

perftestdb=>

 

 

 

 

Oracle Database (23ai)

さて、お待ちかね、今回の癖の持ち主も実はOracle。といっても、SQL clientのSQL*Plusです。挙動を見てみましょう。
おやおや〜〜〜〜。結果が返らずスクリプトが終了しちゃいました。どういうこと?

[oracle@localhost ~]$ sqlplus -version

SQL*Plus: Release 23.0.0.0.0 - Production
Version 23.4.0.24.05

[oracle@localhost ~]$ sqlplus scott/tiger@localhost:1521/freepdb1

....略...

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.


SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

 

SQL文はしっかりSQL*Plusのバッファにあるに、どういうことでしょう。 r または / で再実行させると、、

え、え、エラ〜だぁ! 。。。。
いったい、おまえは、何をいっているんだぁ(ジョジョ風)

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 empno
3 ,ename
4 FROM
5 emp
6 WHERE
7* mgr IS NULL; -- It means president of the company.
SCOTT@localhost:1521/freepdb1> /
mgr IS NULL; -- It means president of the company.
*
ERROR at line 7:
ORA-03048: SQL reserved word ';' is not syntactically valid following '...FROM
emp
WHERE
mgr IS NULL'
Help: https://docs.oracle.com/error-help/db/ora-03048/

 

これ、一行コメントの位置がまずいんです。 SQL*PLusの癖せいです。(昔からの癖ですので。。。
(;)セミコロンの後に記述されている一行コメントを該当行の上において、セミコロンより前になるように修正してみましょう!

こんどは正しい結果が返ってきました! wwww SQL*Plusの癖もわかりにくいですね。癖の存在をしらないと。。。(マニュアルにも記載されているので参考にしてください)

SCOTT@localhost:1521/freepdb1> edit one_more_thing.sql

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
-- It means president of the company.
mgr IS NULL;

SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

EMPNO ENAME
---------- ----------
7839 KING

 

参考)

Oracle Database / Release 19 / ユーザーズ・ガイドおよびリファレンス / 5 SQL*Plusでのスクリプトの使用 / 文の終了記号(ピリオド、セミコロンまたはスラッシュ)の後に、コメントを挿入しないでください。

 

では、最後に、Oracle純正のSQL client、実は、最近はもう一つあるんです。 Oraclerならみなさんご存知の、SQLcl です。
こちら、SQL*Plusと異なり、前述した癖がありません。MySQL/PostgreSQL純正のSQL clientと同じ位置に一行コメントを書いても怒られることはありません。なかなか難しいですね。Oracle純正でも挙動が異なるのでご注意くださいね。。。(苦笑いw

この挙動の違い、私もついさっき気づいたんですけどね。wwwww このエントリー書きながらwwwww (本当ですw)

[oracle@localhost ~]$ sql -version

SQLcl: リリース24.1.0.0 Production ビルド: 24.1.0.087.0929

[oracle@localhost ~]$ sql scott/tiger@localhost:1521/freepdb1

....略...

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.

SCOTT@localhost:1521/freepdb1>
SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

EMPNO ENAME
________ ________
7839 KING

 

SQL Client特有の癖もあるので本体以外でも癖には注意しましょうね。

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 - コメント書くにも癖がある

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある

人間、なくて七癖。とか申しまして、わしゃ癖がない。といぅ人でも、七つぐらいは癖がある。と言ぅんですなぁ。
SQLも同じで、標準に対応しています。と言う割には、びっくりするぐらい癖があります。
(なぜか落語風w)


ということで、本日の癖、SQLにコメントを書くときは、どのタイプがお好きですかね? みなさん。
複数行コメントと単一行コメント併用していますが、知ってる人しかしらない(そりゃそうだ)w
コメントを書くにも、ちょっとした癖の持ち主もいたりします。

 

Oracle Database (23ai)

SCOTT@localhost:1521/freepdb1> !cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;

SCOTT@localhost:1521/freepdb1> @sqlcomment.sql

EMPNO ENAME
---------- ----------
7900 JAMES

 

 

PostgreSQL (13.14) Oracle Databaseと同じルールなのでそのまま使えます

perftestdb=> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;
perftestdb=> \i sqlcomment.sql
empno | ename
-------+-------
7900 | JAMES
(1 行)

 

MySQL (8.0.36) MySQLでは一行コメント(--)の後に半角スペースが必須となっているので注意なのね。

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;
mysql>
mysql> \. sqlcomment.sql
ERROR 1054 (42S22): Unknown column 'foobar' in 'field list'
mysql>

 

なかなか、いい癖もってますよね。MySQL

 

MySQLの場合、一行コメントとして、(#)もサポートされていて、こちらは、(#)の半角スペースは必須ではないと!!

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename #foobar
FROM
emp
WHERE
empno = 7900;
mysql> \. sqlcomment.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7900 | JAMES |
+-------+-------+
1 row in set (0.04 sec)

 

最後に、MySQLで有効なコメント記述を確認しておこう。(/* */)や(-- )という記述にしておけば、他のRDBMSで使う時にも楽そうではありますよね。(--)のスペースが必須なのは注意するとして。。

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno -- Do not forget "white space" following "--"
,ename #foobar
FROM
emp
WHERE
empno = 7900;
mysql>
mysql>
mysql> \. sqlcomment.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7900 | JAMES |
+-------+-------+
1 row in set (0.01 sec)

 

おあとがよろしいようで。。。。

一行コメント(--)の後に空白を置くか置かないかってとこまで気を使う必要があるみたいなので、MySQLへ乗り換えたり、MySQLでも実行できるSQL文やスクリプトを用意する場合には注意したい部分ですすねー。ハマりそうなきもするけどw(一行コメントの部分もケアされているSQLコーディング標準がああって厳格に管理されていれば問題ないと思いますけども)

 

参考)

Oracle Database / Release 19 / SQL言語リファレンス / コメント
PostgreSQL 13.1文書 / 第4章 SQLの構文 / 4.1.5. コメント
MySQL 8.0 リファレンスマニュアル / 言語構造 / コメント


 

梅雨らしい雨の降る、ちがさき、、いや、東京よりw。
ではまた。

 



関連エントリー
標準はあるにはあるが癖の多い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のソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!

| | | コメント (0)

2024年6月29日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!

Previously on Mac De Oracle
前回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)でした、

引用符にも多少の方言が存在すること、NULLのソート方法にも同様に方言が存在することを確認しました。
今回はその後編です。

引用符で囲んだ識別子(列名、表名、列エイリアス、表エイリアス)を英語だと、Quoted Identifier と記載されシノニムはあまり見かけません。(SQL-1992などでは、delimited identifier と記されている程度ですかね)日本語のマニュアル等では翻訳影響だと思いますが、多少揺れていたりします。

Oraclerのみなさんだと、引用識別子 のほうが馴染み深い日本語訳だと思いますが、他のRDBMSの日本語マニュアルでは、引用符付き識別子 と記載されていたりします。とうのは前回にも書いてますが、Oracle Database/PostgreSQL/MySQLの間でも多少表現は違ったりします。

参考)
Database Oracle / Release 19 / SQL言語リファレンス / データベース・オブジェクト名および修飾子 / データベース・オブジェクトのネーミング規則
MySQL 8.0 リファレンスマニュアル / 言語構造 / スキーマオブジェクト名
PostgreSQL 13.1文書 / 第4章 SQLの構文 / 4.1. 字句の構造


という前置きはこれぐらいで。本日のお題。列エイリアスの扱いの癖!

今日のお題は、列エイリアスの扱いの癖 を見てみます(気づかないと意外にハマりますよーーw)

今回も前回同様、お馴染みの、emp表を、Oracle Database 23ai/PostgreSQL 13.14/MySQL 8.0.36 それぞれに作成してあります。(以下はOracle Database)

SCOTT@localhost:1521/freepdb1> desc emp
Name Null? Type
----------------------------------------- -------- ----------------------------
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@localhost:1521/freepdb1> select * from emp order by 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
7788 SCOTT ANALYST 7566 87-04-19 3000 20
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7876 ADAMS CLERK 7788 87-05-23 1100 20
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

14 rows selected.


では、前回のおさらいから、MySQLだけ癖が強い結果となりましたが、問い合わせたい結果はどれも正しいですよね!(標準はあるにはあるが、癖の多いSQLらしい。素晴らしい結果ですよねw)

Oracle Database 23ai

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS "Boss's emp no."
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "Boss's emp no."
8 ORDER BY
9* "Boss's emp no." NULLS FIRST

Boss's emp no. HEAD_COUNTS
-------------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.

PostgreSQL 13.14

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST
;
perftestdb=> \i quoted_identification.sql
Boss's emp no. | head_counts
----------------+-------------
[null] | 1
7566 | 2
7698 | 5
7782 | 1
7788 | 1
7839 | 3
7902 | 1
(7 行)


MySQL 8.0.36

mysql> \! cat quoted_identification.sql
SELECT
mgr AS `Boss's emp no.`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`Boss's emp no.`
ORDER BY
`Boss's emp no.` IS NULL DESC
,`Boss's emp no.` ASC
;
mysql>
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.01 sec)


今日は、上記のクエリー列エイリアスにちょっと意地の悪い変更を行なって、その挙動の違いを見てみたいと思います。(おもしろいよ、それぞれの個性が出てて)
オリジナルでは、mgr AS "Boss's emp no." としていた列エイリアスですが、まずは、 mgr AS empno と、非識別引用子にして、かつ、emp表の列名である empno と同じ名称にしてあります。。。 AS 列エイリアス にはなっているので文法的には正しいです。。。よね。ちょっと嫌な予感はしますがw

(MySQLの場合、ORDER BY句の構文が異なります。以下、Oracle Database 23ai/PostgreSQL向け)

Before

SELECT
mgr AS Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST;


After

SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno NULLS FIRST;

では、早速、MySQLから実験してみましょう!

mysql> desc emp;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| empno | smallint | NO | PRI | NULL | |
| ename | varchar(10) | YES | | NULL | |
| job | varchar(10) | YES | | NULL | |
| mgr | smallint | YES | | NULL | |
| hiredate | date | YES | | NULL | |
| sal | decimal(7,2) | YES | | NULL | |
| comm | decimal(7,2) | YES | | NULL | |
| deptno | smallint | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
8 rows in set (0.05 sec)

mysql> \! cat quoted_identification2.sql
SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno IS NULL DESC
,empno ASC
;


おおおおおおおおおーーーーーーー。想定外の結果がががががー(いや、予想してましたよw)。

mysql> \. quoted_identification2.sql
+-------+-------------+
| empno | head_counts |
+-------+-------------+
| NULL | 1 |
| 7566 | 1 |
| 7566 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7902 | 1 |
+-------+-------------+
14 rows in set, 1 warning (0.02 sec)


ワーニングがでてますね。覗いてみると、どうやら、empno が曖昧だけど、実行しておいたから。。。。と。emp表のempnoと mgr列に対する列エイリアス、どちらか曖昧だけど、とりあえず、emp表のempno列の方でやっといたでーーー。ということみたいですね。まじか。。。 AS 列エイリアス とSQLで書いてるから列エイリアスとしてみてくれんの???(ちょっとわざとらしいセリフを入れてみましたw)

mysql> show warnings;
+---------+------+------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------+
| Warning | 1052 | Column 'empno' in group statement is ambiguous |
+---------+------+------------------------------------------------+
1 row in set (0.00 sec)


そうか、列エイリアスと表の列名が被ってて、曖昧だと。 では、 引用符を使って、これは、列エイリアスだーーーーーーとわかるように書けば良いのでは??  
ということで試してみる。

MySQLのデフォルトの引用符(`)バッククォートで囲った結果。。。だめだ。。。。引用識別子にしても、表の列側としてハンドリングされている。。。。。

mysql> \! cat quoted_identification2.sql
SELECT
mgr AS `empno`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`empno`
ORDER BY
`empno` IS NULL DESC
,`empno` ASC
;
mysql> \. quoted_identification2.sql
+-------+-------------+
| empno | head_counts |
+-------+-------------+
| NULL | 1 |
| 7566 | 1 |
| 7566 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7902 | 1 |
+-------+-------------+
14 rows in set, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------+
| Warning | 1052 | Column 'empno' in group statement is ambiguous |
+---------+------+------------------------------------------------+
1 row in set (0.00 sec)

mysql>


気を取り直して、PostgreSQL ではどうなのでしょう?

perftestdb=> \d+ emp
テーブル"public.emp"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
----------+-----------------------+----------+---------------+------------+------------+----------+------
empno | numeric(4,0) | | not null | | main | |
ename | character varying(10) | | | | extended | |
job | character varying(9) | | | | extended | |
mgr | numeric(4,0) | | | | main | |
hiredate | date | | | | plain | |
sal | numeric(7,2) | | | | main | |
comm | numeric(7,2) | | | | main | |
deptno | numeric(2,0) | | | | main | |
インデックス:
"pk_emp" PRIMARY KEY, btree (empno)
"ix_deptno" btree (deptno)
外部キー制約:
"fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno)
アクセスメソッド: heap

perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno NULLS FIRST
;

ギョギョ! サカナくんみたいな声がでますね!w MySQLと同じ挙動です!!!! なーーーーーーーんーーーーーーだってーーーーーーーっ。 MySQLとは異なりワーニングもなく、結果が想定結果を違うんだーー。というところから列エイリアスが列エイリアスと認識されていないことに気づいてあげないとならないですね。。。これだと。 難易度が上がったw
MySQL、意外と親切かも。ワーニングで教えてくれるなんて。。。

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \i quoted_identification.sql
empno | head_counts
--------+-------------
[null] | 1
7566 | 1
7566 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7782 | 1
7788 | 1
7839 | 1
7839 | 1
7839 | 1
7902 | 1
(14 行)

では、引用識別子に書き換えてみましょう。。。。。あ、まじで、PostgreSQLもMySQLと同じ挙動ですね。 列エイリアスだよーーーーーと明示しても、表の列としてのハンドリングを優先しています。。。むむむ。

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification2.sql
SELECT
mgr AS "empno"
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"empno"
ORDER BY
"empno" NULLS FIRST
;
perftestdb=> \i quoted_identification2.sql
empno | head_counts
--------+-------------
[null] | 1
7566 | 1
7566 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7782 | 1
7788 | 1
7839 | 1
7839 | 1
7839 | 1
7902 | 1
(14 行)

perftestdb=>


では、ここまでくると、わかりますよね。 前回のエントリーで、MySQL の立ち位置にいるのは、Oracle Database 23ai でーーーす。
なお、23aiからGROUP BYに列エイリアスが書けるようになってます。

結果を見てみましょうw

おおおおおーーーーーー。まじでーーーー。
MySQL/PostgreSQLと異なる挙動。列エイリアス(非引用識別子)と表の列名が被ってる影響で列エイリアスがエイリアスとして認識されていないためエラーとして扱っていますね。興味深い。いや、実はミスに気づきやすいのかも。。。

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> edit quoted_identification.sql

SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS empno
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 empno
8 ORDER BY
9* empno NULLS FIRST
mgr AS empno
*
ERROR at line 2:
ORA-00979: "MGR": must appear in the GROUP BY clause or be used in an aggregate function
Help: https://docs.oracle.com/error-help/db/ora-00979/


非識別引用子のままだと、表の列名である、empno と区別できない(AS empno と記載しても)ので、引用識別子にしてみます。PostgreSQL/Oracle Databaseの引用符は(")ダブルクォートですよね。

実行してみると。。。。。。あーーーーら不思議、MySQL/PostgreSQLとは異なり、引用識別子にしてあげることで、emp表のempno列とはことなる、列エイリアスと認識して、正しく処理しています。。。なんと。。。というか、この実装のほうが引用識別子の意味としては理解しやすくないですかね。。どうなんだろう。

SCOTT@localhost:1521/freepdb1> !cp quoted_identification.sql quoted_identification2.sql

SCOTT@localhost:1521/freepdb1> edit quoted_identification2.sql

SCOTT@localhost:1521/freepdb1> @quoted_identification2.sql
1 SELECT
2 mgr AS "empno"
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "empno"
8 ORDER BY
9* "empno" NULLS FIRST

empno HEAD_COUNTS
---------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.

Elapsed: 00:00:00.01
SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc emp
Name Null? Type
----------------------------------------- -------- ----------------------------
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@localhost:1521/freepdb1>


引用識別子/非引用識別子が既存の列名と同じ場合の評価の仕方は皆バラバラですねw。思いっきり実装依存の部分なので、列、表名に対するエイリアスを指定する場合には注意しましょう。
(個人的な感覚でしかないですがw

Oracle Databaseの実装のほうが直感的には理解しやすいきがします)

MySQLの場合、PostgreSQL同様に実行されますが、面白い特徴は、ワーニングという形で、"Warning 1052 | Column 'empno' in group statement is ambiguous" を示してくれることですね。MySQLでは warningのカウントに注意!!! 忘れないようにしたいですね。重要なコメントが書かれてたりしますw

この3者の中で、もっとも問題に気づきにくいのは、PostgreSQLでしょうか。。。
挙動がMySQL側に似ているので、MySQLのようにちょっと曖昧だけど、実行だけしておいたよ! 的なワーニングでも返してくれると、
MySQLのように気づきやすくなるかもしれませんね。> 各位


これだから、癖のあるSQLとの付き合いはやめられないwww


Enjoy SQL! そして、癖! もw

ではまた。





関連エントリー
標準はあるにはあるが癖の多い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のソート構文にも癖がある!(前編)

| | | コメント (0)

2024年6月23日 (日)

VirtualBox TestBuild 7.0.97r163606 (2024-06-21T11:55:16Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

2024-06-21に最新のTestBuildが公開されていました。


20240623-112941

恒例のOracle Database 21c on VirtualBox TestBuild for macOS/ARM64 の起動時間の記録です。

 

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** 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.0.97r163606

 

M1で停止時間が2桁秒台になったほか、起動時間も改善してますね!

起動1回目 : 130 sec
停止1回目 : 66 sec
起動2回目 : 145 sec
停止2回目 : 45 sec

 

 

oracle@angelfish ~ % ./print_env.sh

*** mac info. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** macOS ver. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** VirtualBox ver. ***
7.0.97r163606

 

 

起動停止時間の記録をとり始めてから、M2でも最短記録ですね!!!! 

起動1回目 : 108 sec
停止1回目 : 34 sec
起動2回目 : 91 sec
停止2回目 : 49 sec

 

今回は、起動停止時間ともこれまでの最速値です。(^^) (^^) (^^)

次回のリリースが楽しみですね。。。

 

VMのOSバージョンなどは過去のエントリーを見ていただくとして、M1/M2 それぞれ以下のPostgreSQL/MySQL/Oracle Databaseが起動することを確認。

 

PostgreSQL

[master@localhost ~]$ sudo su - postgres -c 'psql -d perftestdb -U discus -p 5432 -W -h localhost -c "select version()"'

version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

 

MySQL

[master@localhost ~]$ mysql -u scott -D perftestdb -p -h localhost -e 'select version();'

+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+

 

Oracle Database 21c

[oracle@localhost ~]$ sqlplus / as sysdba @version

...略...

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

 

Oracle Database 23ai

[oracle@localhost ~]$ sql hr/oracle@localhost:1521/freepdb1 @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

 

VirtualBox Team がんばれ〜〜〜〜〜 :)

では、次回の起動停止時間ログをお楽しみに:)

 

 



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の起動、停止時間の記録

 

 

| | | コメント (0)

2024年6月22日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)

Oracle ACE Program的に新年度に切り替わり。今期もOracle ACE Proに認定されました。:)

前置きはそれぐらいにして、今日の本題。

column expressionのaliasや、 table, view, subqueryなどのaliasを指定する際に利用することがある引用符、通常は (")ダブルクォートで囲むわけですが、そんな引用符にも癖があるというお話。
SQL-1992のドラフトではありますが以下のドキュメントを delimited identifier で検索すると見つけることができます。
( (Second Informal Review Draft) ISO/IEC 9075:1992, Database Language SQL- July 30, 1992 )

ついでに、世間ではいろいろ言われて肩身の狭い?想いをしているかもしれない NULL. そのNULLのソートが必要になってしまった場合にも、ソートの構文に癖がある!!

ほんと、みんな、癖多すぎますよね!(w
いい感じに差し支えない単語にすると、個性 があるというか... これだからSQLは楽しいって話もありますけども。人によるかなw


Oracle Database 23ai / PostgreSQL 13.14 / MySQL 8.0.36 のそれぞれで、どうなるか見てみましょう。

それぞれのデータベースに以下のような emp表を事前に作成しておきます。Oraclerにはお馴染みの表です:)

SCOTT@localhost:1521/freepdb1> select * from emp order by 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
7788 SCOTT ANALYST 7566 87-04-19 3000 20
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7876 ADAMS CLERK 7788 87-05-23 1100 20
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

14 rows selected.

まず、Oracle Database 23ai
なぜ最新にしたかと言うと、GROUP BYで alias が利用可能になった最初のリリースだからなのですw (例で利用するクエリで利用する必要があるので)
GROUP BY列の別名または位置の指定が可能に! / 23ai〜 / SQL / FAQ

SCOTT@localhost:1521/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


この例では、別名に空白などを含めているため引用符が必要になります。
Oracle Databaseの場合は昔から (") ダブルクォートですね。
一般的には引用符を必要としない記述にすることが多いのではないでしょうか。プログラムで扱うには面倒ですからね。(印字するだけの目的なら別ですが)
とはいえ、引用符の利用が必須のケースや、コーディング規約次第というところはあります。
SQL言語リファレンス/ データベース・オブジェクト名および修飾子/ データベース・オブジェクトのネーミング規則

NULLの位置が最初に来るようにソートするには、NULLS FIRSTですよね。みなさんもご存知のはず。

Oraclerのみなさんには GROUP BY でいきなりaliasを使う構文で目新しいですよね。すっきり書けるようになって嬉しい:)

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS "Boss's emp no."
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "Boss's emp no."
8 ORDER BY
9* "Boss's emp no." NULLS FIRST

Boss's emp no. HEAD_COUNTS
-------------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.


次は、PostgreSQL 
Oracle Database同様、引用符が必要な別名は、ダブルクォートを利用します。( PostgreSQL 16.0 / 4.1.1. 識別子とキーワード )

NULLS FIRSTでNULLをいい感じにソートする方法も同じですね。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

perftestdb=>
perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST;
;
perftestdb=> \i quoted_identification.sql
Boss's emp no. | head_counts
----------------+-------------
[null] | 1
7566 | 2
7698 | 5
7782 | 1
7788 | 1
7839 | 3
7902 | 1
(7 行)

さて、最後は、MySQLです。

気付いたかと思いますが、本日の癖の主役ですw

MySQLのデフォルトの引用符は、なんと、(`) バッククォートです。手癖で、ダブルクォートをタイプして、え!と一瞬固まる、Oraclerが。。> 俺だよw
( MySQL 8.0 リファレンスマニュアル / 言語構造 / スキーマオブジェクト名 )

また、NULLのソートも可能ですが、見たこともない構文でソートします。私まだよくわかってないですが、これで良いらしい。この癖に慣れる必要もありそう。。

+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS `Boss's emp no.`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`Boss's emp no.`
ORDER BY
`Boss's emp no.` IS NULL DESC
,`Boss's emp no.` ASC
;
mysql>
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.01 sec)

ところで、MySQLでも、(")ダブルクォートを引用符にすることができます。
( MySQL 8.0 リファレンスマニュアル / 5.1.11 サーバー SQL モード / ANSI_QUOTES 参照のこと )

sql_modeに ANSI_QUOTESを設定することで使えるようになります。。。。あ〜っ、スッキリ。NULLのソート構文以外はw

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> set session sql_mode = concat(@@sql_mode,',ANSI_QUOTES');
Query OK, 0 rows affected (0.01 sec)

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------------------+
| ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." IS NULL DESC
,"Boss's emp no." ASC
;
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.00 sec)

mysql>


ANSI_QUOTESを無効にすると、GROUP BY で指定した alias 無効となり、デフォルトで有効化されているONLY_FULL_GROUP_BYのため、ERROR 1055 となります。なんとなく理解した!

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------------------+
| ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> set session sql_mode = replace(@@sql_mode,'ANSI_QUOTES,','');
Query OK, 0 rows affected (0.00 sec)

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." IS NULL DESC
,"Boss's emp no." ASC
;
mysql> \. quoted_identification.sql
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'perftestdb.emp.mgr' which is not functionally dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by


そういえば、
英語だと、大抵は、quoted identifier と書かれていますが、日本語表記だと 引用符付き識別子とか、引用識別子、 各社のドキュメントで微妙に違いがあったりして難しいなぁ。と思ったり。
quoted identifier ってカタカタにしたら長くてタイプするの面倒、結局、Oraclerなので、引用識別子/非引用識別子 で通りしゃったりしますけど。


ではでは。
次回へつづく。






関連エントリー
標準はあるにはあるが癖の多い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〜)

| | | コメント (0)

2024年6月15日 (土)

VirtualBox TestBuild 7.0.97r163425 (2024-06-05T13:13:46Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

さて、恒例のOracle Database 21c on VirtualBox TestBuild for macOS/ARM64 の起動時間の記録です。
2024-06-05T13:13:46Zに公開されていた。起動自体は非常に安定してきましたね。細かいところは色々あるようですが、正式リリースが待ち遠しい。

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** 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.0.97r163425


Oracle Database 21c / Mac Studio M1
微妙な差なので変化なしという感じですね。

起動1回目 : 202 sec
停止1回目 : 86 sec
起動2回目 : 163 sec
停止2回目 : 143 sec


oracle@angelfish ~ % ./print_env.sh

*** mac info. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** macOS ver. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** VirtualBox ver. ***
7.0.97r163425


Oracle Database 21c / MacBook Air M2
若干、前回公開されたTestBuildより遅くなってる気がしますよね。(たった二回しか起動停止させてないので、参考記録程度なわけですけども)

起動1回目 : 244 sec
停止1回目 : 136 sec
起動2回目 : 201 sec
停止2回目 : 101 sec


Virtualbox-20240605-7097r163425

その他、いつもの起動確認。
PostgreSQL 13.14 / MySQL 8.0.36 / Oracle Database 21c 及び、 23ai (M1/M2それぞれ起動)
GuestOSのバージョンなどはこれまでと同じなので省略して、それぞれのバージョン確認ログで起動確認。

[master@localhost ~]$ mysql -u scott -D perftestdb -p -h localhost -e 'select version();'
Enter password:
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+

[master@localhost ~]$ sudo su - postgres -c 'psql -d perftestdb -U discus -p 5432 -W -h localhost -c "select version()"'
パスワード:
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

[oracle@localhost ~]$ sqlplus scott@orclpdb1 @version

...略...

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


[oracle@localhost ~]$ sql hr@192.168.1.138:1521/freepdb1 @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
[oracle@localhost ~]$ cat version.sql
select banner_full from v$version;
exit


これ、ルーティーンになりそうw

ではまた。




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の起動、停止時間の記録


| | | コメント (0)

2024年5月31日 (金)

VirtualBox TestBuild 7.0.97r163376 (2024-05-28T15:08:56Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

VirtualBox TestBuild 7.0.97r163376 (2024-05-28T15:08:56Z) / macOS/ARM64 Dev Previewがリリースされていたので、いつもの、起動確認と、Oracle Database 21cの起動時間比較を。
前回の記録は、VirtualBox TestBuild for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 / 7.0.97r162957(2024/4/26) / 7.0.97r163029(2024/5/3)を見てもらうとして、結果的には大きくな変化なし!。

頑張って! :) 

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** 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.0.97r163376

Oracle Database 21c Mac Studio M1

起動1回目 : 217sec
停止1回目 : 153sec
起動2回目 : 186sec
停止2回目 : 119sec


oracle@angelfish ~ % ./print_env.sh 

*** mac info. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** macOS ver. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** VirtualBox ver. ***
7.0.97r163376

Oracle Database 21c MacBook Air M2

起動1回目 : 138sec
停止1回目 : 108sec
起動2回目 : 128sec
停止2回目 : 83sec



20240531-81713

その他、いつもの起動確認。
PostgreSQL 13.14 / MySQL 8.0.36 / Oracle Database 21c 及び、 23ai 、それぞれ、VirtualBox TestBuild 7.0.97r163376 (024-05-28T15:08:56Z)でも起動した! (このあたりはマジで安定してきた)

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79

*** 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.0.97r163376

[master@localhost ~]$ cat /etc/*release*
Oracle Linux Server release 8.5
NAME="Oracle Linux Server"
VERSION="8.5"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.5"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.5"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:5:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://bugzilla.oracle.com/"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.5
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.5
Red Hat Enterprise Linux release 8.5 (Ootpa)
Oracle Linux Server release 8.5
cpe:/o:oracle:linux:8:5:server
[master@localhost ~]$


PostgreSQL 13.14 - 起動した!

[master@localhost ~]$ sudo su - postgres
[postgres@localhost ~]$ psql -d perftestdb -U discus -p 5432 -W -h localhost
パスワード:
psql (13.14)
"help"でヘルプを表示します。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

perftestdb=> exit


MySQL 8.0.36 - 起動した!

[master@localhost ~]$ mysql -u scott -D perftestdb -p -h 192.168.1.125
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.36 Source distribution

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.01 sec)

mysql> exit
Bye


Oracle Database 21c - 起動した!

[oracle@localhost ~]$ cat /etc/*release*
Oracle Linux Server release 8.4
NAME="Oracle Linux Server"
VERSION="8.4"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.4"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.4"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:4:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://bugzilla.oracle.com/"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.4
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.4
Red Hat Enterprise Linux release 8.4 (Ootpa)
Oracle Linux Server release 8.4
cpe:/o:oracle:linux:8:4:server
[oracle@localhost ~]$
[oracle@localhost ~]$ sqlplus / as sysdba

SYS@ORCLCDB> 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

SYS@ORCLCDB> exit


Oracle Database 23ai - 起動した!

[oracle@localhost ~]$ cat /etc/*release*
Oracle Linux Server release 8.9
NAME="Oracle Linux Server"
VERSION="8.9"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.9"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.9"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:9:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://github.com/oracle/oracle-linux"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.9
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.9
Red Hat Enterprise Linux release 8.9 (Ootpa)
Oracle Linux Server release 8.9
cpe:/o:oracle:linux:8:9:server
[oracle@localhost ~]$ sql scott/@192.168.1.138:1521/freepdb1


SQL> 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

SQL>


これが、本当の5月最後の投稿ということでw

では、また。





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)

| | | コメント (0)

2024年5月29日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)

ちょっと意地悪してみました。

 

前回のエントリで、以下のようにindex only scanにできるBOOLEAN型の単列索引を作成したのを覚えていますか?

 

SCOTT@freepdb1> create index ix_bool_example2 on example2(b1);

Index created.

SCOTT@freepdb1> set autot trace exp stat
SCOTT@freepdb1> select count(1) from example2 where b1;

Execution Plan
----------------------------------------------------------
Plan hash value: 1159143718

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 1 | | |
|* 2 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

2 - access("B1"=TRUE)

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

 

この状態で、述語のBOOLEAN列(索引列)に暗黙型変換が発生する意地悪をしてみました。
BOOLEAN列を数値として計算した後に、falseと比較しているので、TO_NUMBER() の後に、TO_BOOLEAN()されています。当然、索引は使えなくなるので index only scan から table access fullに変わっています! (狙い通りですね。こんなことしないと思いますけどもw

 

で、みなさん、SQL*Plusのautoraceに見慣れない情報が出力されているのに気づきませんか!?

 

そう、Oracle Database 23ai free developerでは、SQL*PlusのautotraceでSQL Analysis reportが表示され、index range scan できるよう、述語の書き換えをご検討くさい! とレコメンドされています!(赤字部分)

 

このメッセージカスタマイズできたりしたらw 「チッ、少しは考えたSQL書けよな〜。」と上から目線のメッセージに変えてみたいw(無理でしょうけどもw) 

ということで、おまけでした!

 

SCOTT@freepdb1> select b1 from example2 where (b1 - 1) = false;

18 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1894430233

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EXAMPLE2 | 1 | 1 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------

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

1 - filter(TO_BOOLEAN(TO_NUMBER("B1")-1)=FALSE)

SQL Analysis Report (identified by operation id/Query Block Name/Object Alias):
-------------------------------------------------------------------------------

1 - SEL$1 / "EXAMPLE2"@"SEL$1"
- The following columns have predicates which preclude their
use as keys in index range scan. Consider rewriting the
predicates.
"B1"

Statistics
----------------------------------------------------------
9 recursive calls
6 db block gets
11 consistent gets
0 physical reads
1012 redo size
914 bytes sent via SQL*Net to client
133 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
18 rows processed

 

この例だと的確なレコメンドしてるんだけど、もっとムズイやつだとどうなるんだろうね。誰か本番で使って結果公開して欲しい :)

 

Enjoy SQL!

 

 



関連エントリー
標準はあるにはあるが癖の多い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型にも癖が出る(後編)

 

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)

Previously on Mac De Oracle
前回は、BOOLEAN型に使える値の差異について、Oracle Database 23ai/PostgreSQL/MySQLで比較してみました。 なかなか癖がありますよね。それぞれw Oracle Database 23aiは、Db2やSnowflakeの仕様に類似していそうでしたよね。(未検証ですが)
ということで、5月もラストスパートwで、BOOLEANネタ後編で締めたいと思いますw

 

BOOLEAN/BOOL型どちらでもOKということなので、一応確認しておきます。

 

Oracle Database


SCOTT@freepdb1> create table sample3 (b1 boolean, b2 bool);

Table created.

SCOTT@freepdb1> desc sample3
Name Null? Type
----------------------------------------- -------- ----------------------------
B1 BOOLEAN
B2 BOOLEAN

SCOTT@freepdb1> drop table sample3 purge;Table dropped.

 

 

PostgreSQL


perftestdb=> create table sample3 (b1 boolean, b2 bool);
CREATE TABLE
perftestdb=> \d sample3
テーブル"public.sample3"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト
----+---------+----------+---------------+------------
b1 | boolean | | |
b2 | boolean | | |

perftestdb=> drop table sample3;
DROP TABLE
perftestdb=>

 

MySQL


mysql> create table sample3 (b1 boolean, b2 bool);
Query OK, 0 rows affected (0.27 sec)

mysql> desc sample3;
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| b1 | tinyint(1) | YES | | NULL | |
| b2 | tinyint(1) | YES | | NULL | |
+-------+------------+------+-----+---------+-------+
2 rows in set (0.04 sec)

mysql> drop table sample3;
Query OK, 0 rows affected (0.10 sec)

 

 

次は、データサイズ。
MySQL/PostgreSQLそれぞれマニュアルにて、1バイトと記載されていますが、Oracle Databaseでは発見できす(仕方ないので直接確認してみますw)
PostgreSQL - https://www.postgresql.jp/document/13/html/datatype-boolean.html MySQL BOOLEAN = TINYINT - https://dev.mysql.com/doc/refman/8.0/ja/other-vendor-data-types.html MySQL TINYINTのサイズ - https://dev.mysql.com/doc/refman/8.0/ja/integer-types.html

 

Oracleも1バイトのようですね!

 


SCOTT@freepdb1> set null [null]
SCOTT@freepdb1> col dump(b1) for a30
SCOTT@freepdb1> col vsize(b1) for 9990
SCOTT@freepdb1> select dump(b1),vsize(b1) from example2

DUMP(B1) VSIZE(B1)
------------------------------ ---------
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1

...中略...

Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
NULL [null]

33 rows selected.

 

 

BOOLEAN型を文字、数値へキャストするとどうなるでしょう?

 

Oracle Database BOOLEAN型の数値へのキャストは、1 (true) / 0 (false)、文字列へのキャストは、'TRUE' / 'FALSE' になるようですね。CHAR型へのキャストの場合、空白埋めされるようですね(マニュアルより)。

 


SCOTT@freepdb1> select id,b1,to_number(b1),memo from example2 order by id;

ID B1 TO_NUMBER(B1) MEMO
---------- ----------- ------------- ----------
1 TRUE 1 TRUE
2 TRUE 1 true
3 TRUE 1 True
4 TRUE 1 ON
5 TRUE 1 on
6 TRUE 1 On
7 TRUE 1 YES
8 TRUE 1 yes
9 TRUE 1 Yes
10 TRUE 1 T
11 TRUE 1 t
12 TRUE 1 Y
13 TRUE 1 y
14 TRUE 1 1
15 TRUE 1 0.01
16 TRUE 1 -0.01
17 TRUE 1 10
18 TRUE 1 -12
19 FALSE 0 FALSE
20 FALSE 0 false
21 FALSE 0 False
22 FALSE 0 OFF
23 FALSE 0 off
24 FALSE 0 Off
25 FALSE 0 NO
26 FALSE 0 no
27 FALSE 0 No
28 FALSE 0 F
29 FALSE 0 f
30 FALSE 0 N
31 FALSE 0 n
32 FALSE 0 0
33 [null] [null] null

33 rows selected.

SCOTT@freepdb1> select id,b1,to_char(b1),memo from example2 order by id;

ID B1 TO_CHA MEMO
---------- ----------- ------ ----------
1 TRUE TRUE TRUE
2 TRUE TRUE true
3 TRUE TRUE True
4 TRUE TRUE ON
5 TRUE TRUE on
6 TRUE TRUE On
7 TRUE TRUE YES
8 TRUE TRUE yes
9 TRUE TRUE Yes
10 TRUE TRUE T
11 TRUE TRUE t
12 TRUE TRUE Y
13 TRUE TRUE y
14 TRUE TRUE 1
15 TRUE TRUE 0.01
16 TRUE TRUE -0.01
17 TRUE TRUE 10
18 TRUE TRUE -12
19 FALSE FALSE FALSE
20 FALSE FALSE false
21 FALSE FALSE False
22 FALSE FALSE OFF
23 FALSE FALSE off
24 FALSE FALSE Off
25 FALSE FALSE NO
26 FALSE FALSE no
27 FALSE FALSE No
28 FALSE FALSE F
29 FALSE FALSE f
30 FALSE FALSE N
31 FALSE FALSE n
32 FALSE FALSE 0
33 [null] [null] null

33 rows selected.

 

PostgreSQL PostgreSQLは想定の範囲内ですね。'true'/'false', 1/0 になるようですね


perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=>
perftestdb=> select id,b1,b1::varchar,memo from example2 order by id;
id | b1 | b1 | memo
----+--------+--------+-------
1 | t | true | TRUE
2 | t | true | true
3 | t | true | True
4 | t | true | ON
5 | t | true | on
6 | t | true | On
7 | t | true | YES
8 | t | true | yes
9 | t | true | Yes
10 | t | true | T
11 | t | true | t
12 | t | true | Y
13 | t | true | y
14 | t | true | 1
17 | t | true | 10
19 | f | false | FALSE
20 | f | false | false
21 | f | false | False
22 | f | false | OFF
23 | f | false | off
24 | f | false | Off
25 | f | false | NO
26 | f | false | no
27 | f | false | No
28 | f | false | F
29 | f | false | f
30 | f | false | N
31 | f | false | n
32 | f | false | 0
33 | [null] | [null] | null
(30 行)

perftestdb=> select id,b1,b1::integer,memo from example2 order by id;
id | b1 | b1 | memo
----+--------+--------+-------
1 | t | 1 | TRUE
2 | t | 1 | true
3 | t | 1 | True
4 | t | 1 | ON
5 | t | 1 | on
6 | t | 1 | On
7 | t | 1 | YES
8 | t | 1 | yes
9 | t | 1 | Yes
10 | t | 1 | T
11 | t | 1 | t
12 | t | 1 | Y
13 | t | 1 | y
14 | t | 1 | 1
17 | t | 1 | 10
19 | f | 0 | FALSE
20 | f | 0 | false
21 | f | 0 | False
22 | f | 0 | OFF
23 | f | 0 | off
24 | f | 0 | Off
25 | f | 0 | NO
26 | f | 0 | no
27 | f | 0 | No
28 | f | 0 | F
29 | f | 0 | f
30 | f | 0 | N
31 | f | 0 | n
32 | f | 0 | 0
33 | [null] | [null] | null
(30 行)

 

MySQL TINYINTのまま文字列に変換されちゃいますね。これはこれで癖が強いといえば強い

 


mysql> select id,b1,cast(b1 as char),memo from example2 order by id;
+----+------+------------------+-------+
| id | b1 | cast(b1 as char) | memo |
+----+------+------------------+-------+
| 1 | 1 | 1 | TRUE |
| 2 | 1 | 1 | true |
| 3 | 1 | 1 | True |
| 14 | 1 | 1 | 1 |
| 15 | 0 | 0 | 0.01 |
| 16 | 0 | 0 | -0.01 |
| 19 | 0 | 0 | FALSE |
| 20 | 0 | 0 | false |
| 21 | 0 | 0 | False |
| 32 | 0 | 0 | 0 |
| 33 | NULL | NULL | null |
+----+------+------------------+-------+

 

 

 

Boolean型は索引にも利用できるので、念の為、Boolean型の検索でリテラルとして使える記述がどう評価されるか確認しておきましょうね。
暗黙のキャストやらなんやらあるのかないのか。。。索引に含めた場合、キャストされて索引として利用できないないケースなど把握しておくと便利ですよー。:)

 

Oracle Database 23ai Oracleの場合、簡易な方法として、SQL*Plusのオートトレースを利用し、 Predicate Information(述語情報)を見て filterの際に、関数が適用され内部的にキャストされてしまうかどうかで判断していくことにします。
ついでに、boolean列に非ユニーク索引(単一列索引)を作成しておき、索引が利用されることも見ておきます。

 

全てのケースは実施していませんが、大文字小文字は無視される前提で、小文字で検証しています。
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0以外の数値は全て、TRUEと解釈される。e.g. -1,10,0.1,-1.5 など) TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の数値

 

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される) FALSE, 'OFF', 'NO', 'F', 'N', 0

 

以上が、OracleのBOOELAN型で利用できるリテラル値なので、

 

true, 'on', 'yes', 't', 'y', 1, 0以外の数値(10, 0.1, -2, -0.1)、全てにおいて、内部的にTRUEとしてハンドリングされていることがわかります。
(キャストされているわけではなく、TRUEとして評価されていることが、predicate information及び、index only scanになっているところでも判断できます)

 

ますます、 true/false, nullだけ使えばいいじゃんって感じがしますね。

 

以下、検証ログ


SCOTT@freepdb1> create index ix_bool_example2 on example2(b1);

Index created.

SCOTT@freepdb1> select b1 from example2 where b1 = true;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
7 recursive calls
4 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 't';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'y';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'yes';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'on';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 10;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 0.1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

05:53:05 SCOTT@192.168.1.23:1521/freepdb1> select b1 from example2 where b1 = -2;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = -0.1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

 

 

PostgreSQL

 

TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、正の整数のみTRUEと解釈され、負の整数や少数はエラーとなる。 -1, 0.1, -1.5などはエラー) TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の正の整数のみ(ただし、booleanへのキャストが必要)

 

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される) FALSE, 'OFF', 'NO', 'F', 'N', 0

 

true, 'on', 'yes', 't', 'y', 1, 0以外の正の整数のみ(ただし、booleanへのキャスト必須)(10) 全てにおいて、内部的にTRUEとしてハンドリングされていることがわかります。
(キャストされているわけではなく、Index Cond:及び、、hintで矯正していますが、Index Scan uging..になっているところでも判断できます)

 

0以外の正の整数以外(0.1, -2, -0.1)は、エラーになっていることが確認できます。

 


perftestdb=> create index ix_bool_example2 on example2(b1);
CREATE INDEX
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = true;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.093..0.098 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 13.020 ms
Execution Time: 8.653 ms
(5 行)
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'on';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.038..0.046 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.259 ms
Execution Time: 0.142 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'yes';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.021..0.026 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.168 ms
Execution Time: 0.087 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 't';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.022..0.030 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.173 ms
Execution Time: 0.117 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'y';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.017..0.021 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.119 ms
Execution Time: 0.066 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 1;
ERROR: operator does not exist: boolean = integer
行 1: ...example2 ix_bool_example2) */ b1 from example2 where b1 = 1;
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 1::boolean;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.010..0.015 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.125 ms
Execution Time: 0.067 ms
(5 行)
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 0.1::boolean;
ERROR: cannot cast type numeric to boolean
行 1: ..._bool_example2) */ b1 from example2 where b1 = 0.1::boolean;
^
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = -2::boolean;
ERROR: operator does not exist: - boolean
行 1: ... ix_bool_example2) */ b1 from example2 where b1 = -2::boolea...
^
HINT: No operator matches the given name and argument type. You might need to add an explicit type cast.
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = -0.1::boolean;
ERROR: cannot cast type numeric to boolean
行 1: ...bool_example2) */ b1 from example2 where b1 = -0.1::boolean;


暗黙型変換ではないですが、暗黙変換同様に関数が利用された場合は、索引が利用できなくなることが確認できますよね。

 


perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where (b1::integer - 1)::boolean = false;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Seq Scan on public.example2 (cost=10000000000.00..10000000001.52 rows=20 width=1) (actual time=0.027..0.041 rows=15 loops=1)
Output: b1
Filter: (NOT (((example2.b1)::integer - 1))::boolean)
Rows Removed by Filter: 15
Planning Time: 0.171 ms
Execution Time: 2.737 ms
(6 行)

 

 

 

MySQL

 

TRUEとして解釈されるリテラル値
TRUE, 1のみ。(エラーにはならないが、0以外の数字は数字として整数として登録さえるので要注意)

 

FALSEとして解釈されるリテラル値
FALSE, 0のみ

 

なので、true,1 は 内部的に  true と扱われていることがわかります。
'yes'はやなりエラーですね。

 


mysql> create index ix_bool_example2 on example2(b1);
Query OK, 0 rows affected (1.28 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> explain analyze select b1 from example2 where b1 = true;
+---------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=true) (cost=0.651 rows=4) (actual time=0.04..0.0441 rows=4 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)

mysql> explain analyze select b1 from example2 where b1 = 1;
+--------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=1) (cost=0.651 rows=4) (actual time=0.0165..0.0203 rows=4 loops=1)
|
+--------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

 

文字列だとエラーになるかと思いきや。ワーニング扱いですね。文字列がトランケートされた結果、述語が b1 = 0 に変更されてしまっています。これはダメですね。


mysql> explain analyze select b1 from example2 where b1 = 'yes';
+--------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=0) (cost=0.851 rows=6) (actual time=0.0179..0.0222 rows=6 loops=1)
|
+--------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
mysql> show warnings;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'yes' |
+---------+------+-----------------------------------------+
1 row in set (0.01 sec)

 

整数の正の値、負の値。予想通りそのまま、数値として比較されています。エラーにならないのでこれは避けた方が良さそう


mysql> explain analyze select b1 from example2 where b1 = 100;
+---------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=100) (cost=0.35 rows=1) (actual time=0.0214..0.0214 rows=0 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> explain analyze select b1 from example2 where b1 = -100;
+------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=-(100)) (cost=0.35 rows=1) (actual time=0.0147..0.0147 rows=0 loops=1)
|
+------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

 

少数の場合もエラーにはなりません。述語は (Impossible WHERE) なことになってますね。これらも避けた方が良さそう。


mysql> explain analyze select b1 from example2 where b1 = 0.1;
+--------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------+
| -> Zero rows (Impossible WHERE) (cost=0..0 rows=0) (actual time=281e-6..281e-6 rows=0 loops=1)
|
+--------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> explain analyze select b1 from example2 where b1 = -0.1;
+--------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------+
| -> Zero rows (Impossible WHERE) (cost=0..0 rows=0) (actual time=176e-6..176e-6 rows=0 loops=1)
|
+--------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

最後に、意地悪な検証を。

 

こんなことやらないと思いますけども、MySQL独特な挙動ですね。index only scanのまま、filterしてますね。TINYINTである影響なのでしょうね。面白いですが真似しないようにw。 この手の作り込んだら探すの大変そうだし。(データ積んでしっかり検証すると炙り出せるとは思うけどw)


mysql> explain analyze select b1 from example2 where (b1 - 1) = false;
+-------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: ((example2.b1 - 1) = false) (cost=1.35 rows=11) (actual time=1.78..1.8 rows=4 loops=1)
-> Covering index scan on example2 using ix_bool_example2 (cost=1.35 rows=11) (actual time=0.234..0.251 rows=11 loops=1)
|
+-------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)

 

 


BOOLEAN型の癖、総まとめ

 

BOOLEAN/BOOLどちらでも定義可能 (Oracle/PostgreSQL/MySQL)
(MySQLは内部的には、TINYINT型なのでそれ由来の癖がある)

 

データサイズは、1バイト (Oracle/PostgreSQL/MySQL)

 

利用可能なリテラル値(INSERT/UPDATEでセットする場合) TRUEとして扱われる値 TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 少数以下を切り捨てた整数として扱われるのでCHECK制約などで保護した方が安全なのではないだろうか。内部的にTINYINTである影響だろう)

 

 

FALSEとして扱われる値 FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 


利用可能なリテラル値(述語で利用する場合) TRUEとして扱われる値 TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'ON' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'YES' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'T' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い 。 sql_mode挙動が変わったりするのかは未確認)

 

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'Y' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 内部的にTINYINTである影響だろうか、エラーとはならず、数値として扱われたり、切り捨てられたりする。避けた方が良い。 e.g. WHERE boolean列 = 100や、WHERE boolean列 = 0.1 など。 sql_mode挙動が変わったりするのかは未確認)

 

FALSEとして扱われる値 FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'OFF' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'NO' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'F' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'N' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

全体的に、true/false (null) を使うように規約で制限したり、CHECK制約で保護しておいた方が混乱を避けられるのではないだろうか。。

 

 


癖ありすぎ! w w

 

では、また。

 



関連エントリー
標準はあるにはあるが癖の多い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型にも癖が出る

 

 

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る

PostgreSQL/MySQLには実装済みだった Boolean型がやっとOracle Database 23aiで追加されました。長ーい間実装されなかったデータ型なので、number型を使ったりして代替されていたわけですが、23ai以降は普通に使えるってことですね。
ただ、タイトルの通り、標準はあるものの、それぞれの実装には癖があります!!!!w


どのような癖があるのか、知っておきましょう。 (兼、自分用メモ)
Oracle/PostgreSQL/MySQLで違いを見てみます。

Oracle Database 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

PostgreSQL
例によってw こちらの都合により、13.14を使っています。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

MySQL

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.02 sec)



データ登録時、どの値がBOOLEANとして扱われるのかマニュアルから拾った値を元に確認しておきたいと思います。キャストが必要だったりする場合には、暗黙型変換とか気にしておいた方が良いですからね。索引にも使えるデータ型ですし。。。

Oracle Database 23ai
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0以外の数値は全て、TRUEと解釈される。e.g. -1,10,0.1,-1.5 など)
TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の数値

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される)
FALSE, 'OFF', 'NO', 'F', 'N', 0

この仕様、Db2SnowflakeのBOOLEAN型と同じように見えますね。(試してないですけどw)

マニュアルに記載されている通りの挙動。。。数値を指定した時の解釈もマニュアル通りなのですが、こんな使い方したくないですよねw 挙動確認なので試してはいますが。。。。

SCOTT@freepdb1> @boolean_literals.sql

Table dropped.
Table created.
1 row created.
1 row created.

...中略...

1 row created.
1 row created.
Commit complete.

ID B1 MEMO
---------- ----------- ----------
1 TRUE TRUE
2 TRUE true
3 TRUE True
4 TRUE ON
5 TRUE on
6 TRUE On
7 TRUE YES
8 TRUE yes
9 TRUE Yes
10 TRUE T
11 TRUE t
12 TRUE Y
13 TRUE y
14 TRUE 1
15 TRUE 0.01
16 TRUE -0.01
17 TRUE 10
18 TRUE -12
19 FALSE FALSE
20 FALSE false
21 FALSE False
22 FALSE OFF
23 FALSE off
24 FALSE Off
25 FALSE NO
26 FALSE no
27 FALSE No
28 FALSE F
29 FALSE f
30 FALSE N
31 FALSE n
32 FALSE 0
33 [null] null

33 rows selected.




PostgreSQL 13.14
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、正の整数のみTRUEと解釈され、負の整数や少数はエラーとなる。 -1, 0.1, -1.5などはエラー)
TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の正の整数のみ(ただし、booleanへのキャストが必要)

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される)
FALSE, 'OFF', 'NO', 'F', 'N', 0

Oracleに類似していますが、数値の扱いが微妙に違いますね。

perftestdb=> \i boolean_literals.sql
DROP TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1

...中略...

INSERT 0 1
INSERT 0 1
INSERT 0 1
psql:boolean_literals.sql:19: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(14, 1, '1');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
psql:boolean_literals.sql:21: ERROR: column "b1" is of type boolean but expression is of type numeric
行 1: insert into example2 values(15, 0.01, '0.01');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:22: ERROR: cannot cast type numeric to boolean
行 1: insert into example2 values(15, 0.01::boolean, '0.01');v ^
psql:boolean_literals.sql:23: ERROR: column "b1" is of type boolean but expression is of type numeric
行 1: insert into example2 values(16, -0.1, '-0.01');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:24: ERROR: cannot cast type numeric to boolean
行 1: insert into example2 values(16, -0.1::boolean, '-0.01');
^
psql:boolean_literals.sql:25: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(17, 10, '10');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
psql:boolean_literals.sql:27: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(18, -12, '-12');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:28: ERROR: operator does not exist: - boolean
行 1: insert into example2 values(18, -12::boolean, '-12');
^
HINT: No operator matches the given name and argument type. You might need to add an explicit type cast.
INSERT 0 1
INSERT 0 1

...中略...

INSERT 0 1
INSERT 0 1
psql:boolean_literals.sql:44: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(32, 0, '0');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
INSERT 0 1
Null表示は"[null]"です。
id | b1 | memo
----+--------+-------
1 | t | TRUE
2 | t | true
3 | t | True
4 | t | ON
5 | t | on
6 | t | On
7 | t | YES
8 | t | yes
9 | t | Yes
10 | t | T
11 | t | t
12 | t | Y
13 | t | y
14 | t | 1
17 | t | 10
19 | f | FALSE
20 | f | false
21 | f | False
22 | f | OFF
23 | f | off
24 | f | Off
25 | f | NO
26 | f | no
27 | f | No
28 | f | F
29 | f | f
30 | f | N
31 | f | n
32 | f | 0
33 | [null] | null
(30 行)

perftestdb=>



MySQL 8.0.32
実データ型がTINYINTであるためですが、
数値の場合0,1以外ではエラーは出ないようで注意が必要ですよね。CHECK制約で保護するとかだろうか。。。

TRUEとして解釈されるリテラル値
TRUE, 1のみ。(エラーにはならないが、0以外の数字は数字として整数として登録さえるので要注意)

FALSEとして解釈されるリテラル値
FALSE, 0のみ

Oracle/PostgreSQLとも違い、最低限に絞ってるって感じですが、数値を指定した時の挙動には要注意ですね。内部の型はTINYINTなので。。。文字列を全て受け付けないのは、これもTINYINTである影響ですかね?

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> \. boolean_literals.sql
Query OK, 0 rows affected (0.05 sec)

...中略...

Query OK, 1 row affected (0.01 sec)
ERROR 1366 (HY000): Incorrect integer value: 'ON' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'on' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'On' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'YES' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'yes' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Yes' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'T' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 't' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Y' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'y' for column 'b1' at row 1
Query OK, 1 row affected (0.01 sec)

...中略...

Query OK, 1 row affected (0.01 sec)
ERROR 1366 (HY000): Incorrect integer value: 'OFF' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'off' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Off' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'NO' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'no' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'No' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'F' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'f' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'N' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'n' for column 'b1' at row 1
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)

+----+------+-------+
| id | b1 | memo |
+----+------+-------+
| 1 | 1 | TRUE |
| 2 | 1 | true |
| 3 | 1 | True |
| 14 | 1 | 1 |
| 15 | 0 | 0.01 |
| 16 | 0 | -0.01 |
| 17 | 10 | 10 |
| 18 | -12 | -12 |
| 19 | 0 | FALSE |
| 20 | 0 | false |
| 21 | 0 | False |
| 32 | 0 | 0 |
| 33 | NULL | null |
+----+------+-------+
13 rows in set (0.00 sec)



長くなりそうなので、次回へ続く!w 

今回のまとめ

Oracle > PostgreSQL > MySQLのような感じで使える値の種類はサブセットになってる感じですね。 なので、true/false , nullだけ使ってればどこに行っても問題はなさそう。。ではあります。

Oracle Database 23ai/MySQL/PostgreSQL共通
BOOLEAN/BOOL型として定義可能。
(ただし、MySQLでは実態はTINYINTなので、TINYINT由来の癖があるので注意

TRUEとして扱われる値
TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 少数以下を切り捨てた整数として扱われるのでCHECK制約などで保護した方が安全なのではないだろうか。内部的にTINYINTである影響だろう)


FALSEとして扱われる値
FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

全体的に、true/false (null) を使うように規約等、場合によってはCHECK制約で保護しておいた方が、何かと混乱を避けられるのではないだろうか。。



補足)

この検証に利用したスクリプト
Oracle Database 23ai

SCOTT/freepdb1> !cat boolean_literals.sql
drop table if exists example2;
create table example2 (id number primary key, b1 boolean, memo varchar2(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(18, -12, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');

-- NULL
insert into example2 values(33, null, 'null');

commit;

-- check
set NULL [null]
select * from example2 order by id;


PostgreSQL 13.14

erftestdb=> \! cat boolean_literals.sql
drop table if exists example2;
create table example2 (id integer primary key, b1 boolean, memo varchar(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(14, 1::boolean, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(15, 0.01::boolean, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(16, -0.1::boolean, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(17, 10::boolean, '10');
insert into example2 values(18, -12, '-12');
insert into example2 values(18, -12::boolean, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');
insert into example2 values(32, 0::boolean, '0');

-- NULL
insert into example2 values(33, null, 'null');

-- check
\pset null [null]
select * from example2 order by id;


MySQL 8.0.32

mysql> \! cat boolean_literals.sql
drop table if exists example2;
create table example2 (id integer primary key, b1 boolean, memo varchar(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(18, -12, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');

-- NULL
insert into example2 values(33, null, 'null');

-- check
select * from example2 order by id;


ここまで見た感じ、true/false, 必要があれば null を使う側のルールとしておくと、みんな幸せになれる気がするよね。BOOLEAN型って。


参考資料)
Oracle Database 23 / 開発者ガイド / 外部データ型
"https://docs.oracle.com/cd/F82042_01/lnoci/data-types.html#GUID-D69455D9-CE01-44CC-B5A9-E541C7774805

Oracle Database 23 / SQL言語リファレンス / ブールデータ型
https://docs.oracle.com/cd/F82042_01/sqlrf/Data-Types.html#GUID-285FFCA8-390D-4FA9-9A51-47B84EF5F83A

Oracle Database 23 / SQL言語リファレンス / Oracleの組込みデータ型
https://docs.oracle.com/cd/F82042_01/sqlrf/Data-Types.html#GUID-7B72E154-677A-4342-A1EA-C74C1EA928E6

PostgreSQL / データ型 / 論理値データ型
https://www.postgresql.jp/document/13/html/datatype-boolean.html

マニュアルによると、PostgreSQLのBOOLEAN型もサイズは1バイト
PostgreSQL 13 / 8.6. 論理値データ型
https://www.postgresql.jp/document/13/html/datatype-boolean.html

MySQL その他のデータベースエンジンのデータ型の使用  - BOOLEAN/BOOL
https://dev.mysql.com/doc/refman/8.0/ja/other-vendor-data-types.html

MySQL 数値型の記憶域要件 - TINYINT
https://dev.mysql.com/doc/refman/8.0/ja/storage-requirements.html#data-types-storage-reqs-numeric

MySQLのBOOLEANはTINYINT型で1バイトということですね。Boolean型のサイズはMySQL/Oracle/PostgreSQLどれも1バイト。
MySQL 8.0 / 11.1.6 Boolean Literals
https://dev.mysql.com/doc/refman/8.0/en/boolean-literals.html



長くなりそうなので、次回へ続くw

こういうことで、この手の SQLの癖w なかなか厳しいなw  Enjoy SQL!






関連エントリー
標準はあるにはあるが癖の多い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させるにも癖が出る

| | | コメント (0)

2024年5月19日 (日)

構文図で見る、SELECT文の構文拡張の歴史 w

2015年にイベント開催のコメントおまけで、Oracle DatabaseのSQLからSELECT文の構文拡張の歴史をOracle Database 7.3と12.1版のマニュアルに記載されている構文図の長さを使って可視化したことがあったのですが、覚えているでしょうか?w (多分、忘れてますよねw)

 

JPOUG> SET EVENTS 20151017 を開催します!

 

Oracle Database 23aiがリリースされてSQLシンタックスの拡張をマニュアルをつらつら読んでて思ったのですが、色々拡張されてますよね!
ということで、Oracle Database 7.3 / 8i 8.1.6 / 12cR1 12.1 / 23ai それぞれのSELECT文の拡張の歴史を構文図の長さを使って、今一度、可視化して残しておこうと思います。

 

みなさん、SQLの進化というか拡張に、追いつけていますよね。。。ね。。。。ね!? (大変ですけどもw)

 

各バージョンのSELECT文の構文図のソースは以下です。みなさんもマニュアルのページ数の増加や構文図の拡張に着目しつつ追ってみるのも楽しいかもしれません。  

 

Oracle7 Server SQL Reference Manual - SELECT
Oracle8i SQL Reference Release 2 (8.1.6) - SELECT and Subqueries
Database SQL Language Reference 12c 12.1 - SELECT
SQL Language Reference - Oracle Database 23ai - SELECT

 

 

 

Oracle Database 9i, 10g, 11gのダイアグラムは端折ってますが、これぐらい差分があったほうが、インパクトがあっていいかなぁ。と思いあえて載せていません :)

 

12cR1以降長すぎてこれぐらい小さくしないと収まりませんw 

 

オチも何もないですが、現場からは以上です! (なお、実際のリリース年は多少前後しているかもしれません)
Select78i12cr123ai

 

Enjoy SQL!

 

ではまた。

| | | コメント (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)

2024年5月 9日 (木)

VirtualBox TestBuild for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 #2 / 7.0.97r163068 ( 2024-5-07T20:38:44Z )

2025-05-03にTestBuildが更新されていたわけですが、なんと、2024-5-07T20:38:44Zに更新されていました。(テストビルドって、月に何回更新されてるのだろう?...)

Oracle Database 23ai Free Developer、Oracle Database 21c EE 、MySQL 8.0.36 そして PostgreSQL 13.14
は問題なく起動(最近安定して起動するのでログは省略)。

ということで、Oracle Database 21cの起動、停止時間の記録だけメモしておきます。

起動停止時間は大きく変わってないように思います(ブレはある程度あるにしても)が、若干速くなってる気もしますね。特に、M1で。次のアップデートが楽しみですね。

Mac Studio M1

** macOS ver. ***
ProductName: macOS
ProductVersion: 14.4.1
BuildVersion: 23E224

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

*** VirtualBox ver. ***
7.0.97r163068

VirtualBox TestBuild 7.0.97r163068 ( 2024-5-07T20:38:44Z ) / Mac Studio M1

起動1回目 : 211sec
停止1回目 : 90sec
起動2回目 : 159sec
停止2回目 : 104sec

MacBook Air M2

*** macOS ver. ***
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB

*** mac info. ***
ProductName: macOS
ProductVersion: 14.4.1
BuildVersion: 23E224

*** VirtualBox ver. ***
7.0.97r163068

VirtualBox TestBuild 7.0.97r163068 ( 2024-5-07T20:38:44Z ) / MacBook Air M2

起動1回目 : 133sec
停止1回目 : 20sec
起動2回目 : 152sec
停止2回目 : 106sec

ではまた



Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA r160167
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_BETA r160702
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_BETA r161342
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_BETA r161709
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)

| | | コメント (0)

2024年5月 4日 (土)

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

Oracle Database 23c 改め、23ai となったのと、4/26日版 VirtualBox Test builds Development snapshotsがリリースされていた(だが、5/3版がすでに公開されていたw)
https://www.virtualbox.org/wiki/Testbuilds


20240503-95842


20240503-85117

M2 MBAでの確認情報だけで、M1はどうよ?とこともあるので、今回は、M1側で検証してみた。(本当はこちらでバシバシ使いたいわけですけども、まだまだ遅いので確認だけw)

*** mac info. ***
ProductName: macOS
ProductVersion: 14.4.1
BuildVersion: 23E224

*** 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.0.97r162957
(VirtualBox 4/26 Test-Builds Developer Snapshot)


公開されたばかりの、Oracle Database 23ai Free DeveloperのVirtualBox pre-Build VMでの確認。
https://www.oracle.com/database/technologies/databaseappdev-vm.html

Oracle Database 23ai Free Developer (VirtualBox pre-build VM) / Oracle Linux 8.9

相変わらずもっさり感は強いもののなんとか起動します! 

[oracle@localhost ~]$ cat /etc/*release
Oracle Linux Server release 8.9
NAME="Oracle Linux Server"
VERSION="8.9"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.9"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.9"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:9:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://github.com/oracle/oracle-linux"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.9
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.9
Red Hat Enterprise Linux release 8.9 (Ootpa)
Oracle Linux Server release 8.9

[oracle@localhost ~]$ sql hr/oracle@192.168.1.138:1521/freepdb1

SQLcl: 土 5月 04 01:14:44 2024のリリース24.1 Production

Copyright (c) 1982, 2024, Oracle. All rights reserved.

接続先:
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05

SQL> 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

SQL> exit
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05から切断されました
[oracle@localhost ~]$ sudo service oracle status
[sudo] oracle のパスワード:

LSNRCTL for Linux: Version 23.0.0.0.0 - Production on 04-MAY-2024 01:26:55

Copyright (c) 1991, 2024, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))
STATUS of the LISTENER
------------------------
Alias LISTENER
Version TNSLSNR for Linux: Version 23.0.0.0.0 - Production
Start Date 04-MAY-2024 00:49:21
Uptime 0 days 0 hr. 37 min. 34 sec
Trace Level off
Security ON: Local OS Authentication
SNMP OFF
Default Service FREE
Listener Parameter File /opt/oracle/product/23ai/dbhomeFree/network/admin/listener.ora
Listener Log File /opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xml
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
Services Summary...
Service "176858bcadf91ba6e0630100007f7de0" has 1 instance(s).
Instance "FREE", status READY, has 1 handler(s) for this service...
Service "FREE" has 2 instance(s).
Instance "FREE", status UNKNOWN, has 1 handler(s) for this service...
Instance "FREE", status READY, has 1 handler(s) for this service...
Service "FREEXDB" has 1 instance(s).
Instance "FREE", status READY, has 1 handler(s) for this service...
Service "freepdb1" has 1 instance(s).
Instance "FREE", status READY, has 1 handler(s) for this service...
The command completed successfully
[oracle@localhost ~]$ sudo service oracle stop
Stopping oracle (via systemctl): [ OK ]


続いて、いつもの 21c。 こちらは、 Oracle Linux 8.4に載せています

Oracle Database 21c EE / Oracle Linux 8.4

Oracle Database 21c EE / Oracle Linux 8.4
[master@localhost ~]$ cat /etc/*release
Oracle Linux Server release 8.4
NAME="Oracle Linux Server"
VERSION="8.4"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.4"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.4"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:4:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://bugzilla.oracle.com/"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.4
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.4
Red Hat Enterprise Linux release 8.4 (Ootpa)
Oracle Linux Server release 8.4

[master@localhost ~]$ sudo su - oracle
[oracle@localhost ~]$ lsnrctl start

LSNRCTL for Linux: Version 21.0.0.0.0 - Production on 04-5月 -2024 08:54:58

Copyright (c) 1991, 2021, Oracle. All rights reserved.

/opt/oracle/product/21c/dbhome_1/bin/tnslsnrを起動しています。お待ちください...

TNSLSNR for Linux: Version 21.0.0.0.0 - Production
システム・パラメータ・ファイルは/opt/oracle/homes/OraDBHome21cEE/network/admin/listener.oraです。
ログ・メッセージを/opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xmlに書き込みました。
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 21.0.0.0.0 - Production
開始日 04-5月 -2024 08:55:00
稼働時間 0 日 0 時間 0 分 0 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/oracle/homes/OraDBHome21cEE/network/admin/listener.ora
ログ・ファイル /opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
リスナーはサービスをサポートしていません。
コマンドは正常に終了しました。
[oracle@localhost ~]$
[oracle@localhost ~]$
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 土 5月 4 08:55:08 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

08:55:10 SYS@ORCLCDB> startup
ORACLEインスタンスが起動しました。

Total System Global Area 1073740720 bytes
Fixed Size 9694128 bytes
Variable Size 910163968 bytes
Database Buffers 41943040 bytes
Redo Buffers 7081984 bytes
In-Memory Area 104857600 bytes
データベースがマウントされました。
データベースがオープンされました。
08:58:23 SYS@ORCLCDB> 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


経過: 00:00:00.32
08:58:36 SYS@ORCLCDB> exit
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 土 5月 4 08:58:46 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

08:58:55 SYS@ORCLCDB> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
09:00:51 SYS@ORCLCDB>


最後に、安定して起動しているMySQL/PostgreSQL
MySQL 8.0.36 and PostgreSQL 13.14 / Oracle Linux 8.5

[master@localhost ~]$ cat /etc/*release
Oracle Linux Server release 8.5
NAME="Oracle Linux Server"
VERSION="8.5"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.5"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.5"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:5:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://bugzilla.oracle.com/"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.5
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.5
Red Hat Enterprise Linux release 8.5 (Ootpa)
Oracle Linux Server release 8.5

MySQL 8.0.36

Redirecting to /bin/systemctl status mysqld.service
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2024-05-03 05:34:51 EDT; 53min ago
Main PID: 1119 (mysqld)
Status: "Server is operational"
Tasks: 38 (limit: 22947)
Memory: 34.5M
CGroup: /system.slice/mysqld.service
└─1119 /usr/libexec/mysqld --basedir=/usr

5月 03 05:34:04 localhost.localdomain systemd[1]: Starting MySQL 8.0 database server...
5月 03 05:34:07 localhost.localdomain mysql-check-socket[1034]: Socket file /var/lib/mysql/mysql.sock exists.
5月 03 05:34:08 localhost.localdomain mysql-check-socket[1034]: No process is using /var/lib/mysql/mysql.sock, which means it is a garbage, so it will be removed automatically.
5月 03 05:34:51 localhost.localdomain systemd[1]: Started MySQL 8.0 database server.
[master@localhost ~]$
[master@localhost ~]$
5月 03 05:34:47 localhost.localdomain systemd[1]: Started PostgreSQL 13 database server.

[master@localhost ~]$ mysql -u scott -D perftestdb -p -h 192.168.1.125
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.36 Source distribution

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.02 sec)

mysql> exit
Bye
[master@localhost ~]$ sudo service mysqld stop
Redirecting to /bin/systemctl stop mysqld.service
[master@localhost ~]$ sudo service mysqld status
Redirecting to /bin/systemctl status mysqld.service
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: inactive (dead) since Fri 2024-05-03 06:30:03 EDT; 8s ago
Process: 7704 ExecStopPost=/usr/libexec/mysql-wait-stop (code=exited, status=0/SUCCESS)
Process: 1119 ExecStart=/usr/libexec/mysqld --basedir=/usr (code=exited, status=0/SUCCESS)
Main PID: 1119 (code=exited, status=0/SUCCESS)
Status: "Server shutdown complete"

5月 03 05:34:04 localhost.localdomain systemd[1]: Starting MySQL 8.0 database server...
5月 03 05:34:07 localhost.localdomain mysql-check-socket[1034]: Socket file /var/lib/mysql/mysql.sock exists.
5月 03 05:34:08 localhost.localdomain mysql-check-socket[1034]: No process is using /var/lib/mysql/mysql.sock, which means it is a garbage, so it will be removed automatically.
5月 03 05:34:51 localhost.localdomain systemd[1]: Started MySQL 8.0 database server.
5月 03 06:29:56 localhost.localdomain systemd[1]: Stopping MySQL 8.0 database server...
5月 03 06:30:03 localhost.localdomain systemd[1]: mysqld.service: Succeeded.
5月 03 06:30:03 localhost.localdomain systemd[1]: Stopped MySQL 8.0 database server.

PostgreSQL 13.14

[master@localhost ~]$ sudo service postgresql-13 status
Redirecting to /bin/systemctl status postgresql-13.service
● postgresql-13.service - PostgreSQL 13 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-13.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/postgresql-13.service.d
└─local.conf
Active: active (running) since Fri 2024-05-03 05:34:47 EDT; 54min ago
Docs: https://www.postgresql.org/docs/13/static/
Main PID: 1343 (postmaster)
Tasks: 9 (limit: 22947)
Memory: 19.7M
CGroup: /system.slice/postgresql-13.service
├─1343 /usr/pgsql-13/bin/postmaster -D /pg/pgdata/data
├─1411 postgres: logger
├─1591 postgres: checkpointer
├─1592 postgres: background writer
├─1593 postgres: walwriter
├─1594 postgres: autovacuum launcher
├─1596 postgres: archiver
├─1597 postgres: stats collector
└─1598 postgres: logical replication launcher

5月 03 05:34:25 localhost.localdomain systemd[1]: Starting PostgreSQL 13 database server...
5月 03 05:34:33 localhost.localdomain postmaster[1343]: 2024-05-03 05:34:33.304 EDT [1343] LOG: redirecting log output to logging collector process
5月 03 05:34:33 localhost.localdomain postmaster[1343]: 2024-05-03 05:34:33.304 EDT [1343] HINT: Future log output will appear in directory "log".

[postgres@localhost ~]$ psql -d perftestdb -U discus -p 5432 -W -h localhost
パスワード:
psql (13.14)
"help"でヘルプを表示します。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

perftestdb=> exit
[postgres@localhost ~]$ exit
ログアウト
[master@localhost ~]$ sudo service postgresql-13 stop
Redirecting to /bin/systemctl stop postgresql-13.service
[master@localhost ~]$ sudo service postgresql-13 status
Redirecting to /bin/systemctl status postgresql-13.service
● postgresql-13.service - PostgreSQL 13 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-13.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/postgresql-13.service.d
└─local.conf
Active: inactive (dead) since Fri 2024-05-03 06:31:04 EDT; 6s ago
Docs: https://www.postgresql.org/docs/13/static/
Process: 1343 ExecStart=/usr/pgsql-13/bin/postmaster -D ${PGDATA} (code=exited, status=0/SUCCESS)
Main PID: 1343 (code=exited, status=0/SUCCESS)

5月 03 05:34:25 localhost.localdomain systemd[1]: Starting PostgreSQL 13 database server...
5月 03 05:34:33 localhost.localdomain postmaster[1343]: 2024-05-03 05:34:33.304 EDT [1343] LOG: redirecting log output to logging collector process
5月 03 05:34:33 localhost.localdomain postmaster[1343]: 2024-05-03 05:34:33.304 EDT [1343] HINT: Future log output will appear in directory "log".
5月 03 05:34:47 localhost.localdomain systemd[1]: Started PostgreSQL 13 database server.
5月 03 06:31:03 localhost.localdomain systemd[1]: Stopping PostgreSQL 13 database server...
5月 03 06:31:04 localhost.localdomain systemd[1]: postgresql-13.service: Killing process 1411 (postmaster) with signal SIGKILL.
5月 03 06:31:04 localhost.localdomain systemd[1]: postgresql-13.service: Succeeded.
5月 03 06:31:04 localhost.localdomain systemd[1]: Stopped PostgreSQL 13 database server.

残る課題は処理速度改善だろうとは思うのですが、頑張って欲しいですね:)

そして、ゴールデンウィークなのに、真夏みたいな気温とか、異常すぎる。。。
体調管理に気を遣いつつも、こんな気候だと、まじで今年の夏はどうなることやら。。。。

ではまた。



Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA r160167
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_BETA r160702
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_BETA r161342
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_BETA r161709

| | | コメント (0)

2024年3月25日 (月)

Oracleのマニュアルで Bushy Join Tree と説明されてる図、Zigzag Join Treeだよね?、いわゆる、Bushy Join Tree は無いのか? いいえ、あります!

久々の技術ネタの投稿です。
昨年末のエントリーに軽くスルーしていた面白いネタが隠れていたのですが、気付いた方はどれぐいたでしょうか? 多分、ぼぼ居ないだろうとは思いますがw

ということで、本日のお題は、軽くスルーしていた面白いネタとして、

Oracleでも、一般的に Bushy Join って言われているJoin Treeを生成することもできるのだ!

。。。というお話をしたいと思います。

 

Oracleのマニュアル( SQL Tuning Guide / Join - Oracle Database 23c )では、一応、Bushy Join Tree という記述をされているのですが、実際には、Zigzag Join Tree なんですよね。結合ツリーの図も実際の実行計画も。

一方、PostgreSQLでは、見間違えようながない Bushy Join Tree が生成されているのがわかります。
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる

Oracleさんのマニュアルのリンク貼っても、リンク切れしちゃうので、いずれリンク切れしちゃう想定でマニュアルの図を手書きでw (といっても、 Pagesで書いたのですが)を貼っておきますね。
なお、本エントリーの後半に参考になりそうなリンクを貼っておきました。(Oracleさんのマニュアルリンクよりはリンク切れし難いと信じてw)

 


20231208195525
20231208195537

 

 

Oracleでは Zigzag も Bushy Join Tree のように扱われてるかのうように見えちゃいますが、実は、Bushy Join Tree に分類される結合ツリーは別に存在していたりします。
最近のOracleだとあまり目にする機会は無いように思いますが。。。特に、11g以降は見た記憶はないです。。。初期のOracleだと比較的目にしていたような気もしますが。(思い出せない! それぐらい昔ではないかと。。。)

では、さっそく、冒頭で紹介した帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでるの該当部分を再掲して確認してみましょう!

 

再掲

Oracle Database


SCOTT@orclpdb1> @ora_sql_hj.sql
1 SELECT
2 /*+
3 MONITOR
4 USE_HASH(t1 t2 t3 t4)
5 */
6 t1.id
7 , t1.t1_c1
8 , t2.s_id
9 , t2.t2_c1
10 , t3.b_id
11 , t3.t3_c1
12 , t4.a_id
13 , t4.c_id
14 , t4.t4_c1
15 FROM
16 t1
17 INNER JOIN t2
18 ON
19 t1.id = t2.id
20 INNER JOIN t3
21 ON
22 t1.id = t3.id
23 INNER JOIN t4
24 ON
25* t1.id = t4.id

...略...

SQL Plan Monitoring Details (Plan Hash Value=122725940)
===================================================================================================================================================
| 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 | | | | 1 | +0 | 1 | 25000 | | | . | | |
| 1 | HASH JOIN | | 25000 | 12 | 1 | +0 | 1 | 25000 | | | 1MB | | |
| 2 | TABLE ACCESS FULL | T4 | 500 | 3 | 1 | +0 | 1 | 500 | 2 | 49152 | . | | |
| 3 | HASH JOIN | | 500 | 9 | 1 | +0 | 1 | 500 | | | 1MB | | |
| 4 | HASH JOIN | | 50 | 6 | 1 | +0 | 1 | 50 | | | 1MB | | |
| 5 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | 2 | 49152 | . | | |
| 6 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | 2 | 49152 | . | | |
| 7 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | 2 | 49152 | . | | |
===================================================================================================================================================

 


20231208195525

 

ご覧のとおり、T1からT3までの結合はLeft Deep Join で、T4をRight Deep Joinで結合している、Zigzag Join Treeになっています(Oracleのマニュアルだと、Bushy Join Treeと記載されている結合ツリー)

 

では、PostgreSQLの Bushy Join Tree を再確認してみましょう。

PostgreSQL


perftestdb=> \! cat pg_sql_hj.sql
explain (analyze)
SELECT
/*+
HashJoin(t1 t2 t3 t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;
perftestdb=> \i pg_sql_hj.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Id=1 Hash Join (cost=17.48..309.84 rows=25000 width=36) (actual time=3.405..10.466 rows=25000 loops=1)
Hash Cond: (t1.id = t2.id)
Id=2 -> Hash Join (cost=1.23..11.09 rows=500 width=24) (actual time=1.696..1.989 rows=500 loops=1)
Hash Cond: (t4.id = t1.id)
Id=3 -> Seq Scan on t4 (cost=0.00..8.00 rows=500 width=16) (actual time=0.907..0.993 rows=500 loops=1)
Id=4 -> Hash (cost=1.10..1.10 rows=10 width=8) (actual time=0.721..0.722 rows=10 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Id=5 -> Seq Scan on t1 (cost=0.00..1.10 rows=10 width=8) (actual time=0.705..0.707 rows=10 loops=1)
Id=6 -> Hash (cost=10.00..10.00 rows=500 width=24) (actual time=1.687..1.687 rows=500 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 36kB
Id=7 -> Hash Join (cost=2.12..10.00 rows=500 width=24) (actual time=1.400..1.554 rows=500 loops=1)
Hash Cond: (t3.id = t2.id))
Id=8 -> Seq Scan on t3 (cost=0.00..2.00 rows=100 width=12) (actual time=0.701..0.712 rows=100 loops=1)
Id=9 -> Hash (cost=1.50..1.50 rows=50 width=12) (actual time=0.679..0.680 rows=50 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
Id=10 -> Seq Scan on t2 (cost=0.00..1.50 rows=50 width=12) (actual time=0.648..0.656 rows=50 loops=1)

 


20231208195537

 

上記はどちらも、HASH JOINをヒントで強制しているだけですが、それでも、オプティマイザの癖というか特徴は現れています。
ZigzagとBushyの違いと一口に言っちゃうと、簡単過ぎますが。

 

以下、SQL文の結合条件部分を抜粋してみました。赤字部分を注意深く見てください。


FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id

 

PostgreSQLのActual Planに見やすいよう、Idを振ってみました。
Id=7でINNER JOINしていますが、何が気づきませんか? 特に、PostgreSQLの Bushy Join Tree で起きている変化に。。。(前述したSQLの赤字部分に注目

私が実行したSQL文の結合条件と異なっている部分があります! (内部でオプティマイザというかプランナが最適化のために書き換えた部分です) 
結合条件を書き換え t1とt3ではなく、t2とt3を結合し、Bushy Join Tree で Hash Join されていますよね!!!!!(同意なのが自明なので書き換えているわけです。 Bushy Join Tree にするために)

一方、OracleもPostgreSQLとおなじタイプの Bushy Join Tree になることもあるのですが、このような書き換えは起こらなかったはず。。。

念の為、Oracleの結合条件が変化していないことをこの時点で確認しておきましょう。
PostgreSQL同様に、赤字部分に注目してください。SQLに記述されている結合条件のまま。


  1  EXPLAIN PLAN FOR
2 SELECT
3 /*+
4 MONITOR
5 USE_HASH(t1 t2 t3 t4)
6 */
7 t1.id
8 , t1.t1_c1
9 , t2.s_id
10 , t2.t2_c1
11 , t3.b_id
12 , t3.t3_c1
13 , t4.a_id
14 , t4.c_id
15 , t4.t4_c1
16 FROM
17 t1
18 INNER JOIN t2
19 ON
20 t1.id = t2.id
21 INNER JOIN t3
22 ON
23 t1.id = t3.id
24 INNER JOIN t4
25 ON
26* t1.id = t4.id

解析されました。

経過: 00:00:00.02

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 122725940

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25000 | 878K| 12 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 25000 | 878K| 12 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | T4 | 500 | 6000 | 3 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 500 | 12000 | 9 (0)| 00:00:01 |
|* 4 | HASH JOIN | | 50 | 750 | 6 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL| T1 | 10 | 60 | 3 (0)| 00:00:01 |
| 6 | TABLE ACCESS FULL| T2 | 50 | 450 | 3 (0)| 00:00:01 |
| 7 | TABLE ACCESS FULL | T3 | 100 | 900 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

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

1 - access("T1"."ID"="T4"."ID")
3 - access("T1"."ID"="T3"."ID")
4 - access("T1"."ID"="T2"."ID")

では、Oracleには、PostgreSQLで見られる Bushy Join Tree は存在しないのか? 

冒頭でも書きましたが、存在します!

BUSHY_JOINヒントまでありますw

 

ほぼ、お目にかかることは無くなった気がしますが。。。最近は。。

PostgreSQLとおなじ実行計画になるように、ゴニョゴニョしてみましたw (何が起こるでしょう。。。w)

なお、LEADINGヒントで、オプティマイザが内部的に生成する Bushy Join 向けのインラインビュー( VW_BUSHY_0C91E486 )を指定していますが、このインラインビュー名は事前に確認することはできないので、一度、インラインビューを生成させインラインビュー名を確認した後に指定しています。
(内部的に生成されるインラインビュー名称なので、手順としてはそれしかありません!)

BUSHY_JOINヒント、なんとなく、pg_hint_plan で使うような構文に似てますよねw Oracleで ZigZag Join Tree (Oracleのマニュアルだと Zigzag な Tree だけど、Bushy Join となっているので注意)になっている状態を BUSHY_JOINヒントで あえて、Bushy Join に書き換えることは無いと思いますが、使い方を理解していると、何かの役になる、、、かも。(なるとは言ってないw)


SCOTT@orclpdb1> @ora_sql_hj_bushy_join
1 SELECT
2 /*+
3 MONITOR
4 LEADING(VW_BUSHY_0C91E486)
5 USE_HASH(t2 t3)
6 USE_HASH(t1 t4)
7 BUSHY_JOIN((t2 t3) (t1 t4))
8 */
9 t1.id
10 , t1.t1_c1
11 , t2.s_id
12 , t2.t2_c1
13 , t3.b_id
14 , t3.t3_c1
15 , t4.a_id
16 , t4.c_id
17 , t4.t4_c1
18 FROM
19 t1
20 INNER JOIN t2
21 ON
22 t1.id = t2.id
23 INNER JOIN t3
24 ON
25 t1.id = t3.id
26 INNER JOIN t4
27 ON
28* t1.id = t4.id

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

...略...

SQL Plan Monitoring Details (Plan Hash Value=3887185)
===================================================================================================================================================
| 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 | 25000 | . | | |
| 1 | HASH JOIN | | 250K | 80 | 3 | +0 | 1 | 25000 | 2MB | | |
| 2 | VIEW | VW_BUSHY_0C91E486 | 5000 | 72 | 1 | +0 | 1 | 5000 | . | | |
| 3 | MERGE JOIN CARTESIAN | | 5000 | 72 | 1 | +0 | 1 | 5000 | . | | |
| 4 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | . | | |
| 5 | BUFFER SORT | | 100 | 69 | 1 | +0 | 50 | 5000 | 6144 | | |
| 6 | TABLE ACCESS FULL | T3 | 100 | 1 | 1 | +0 | 1 | 100 | . | | |
| 7 | VIEW | VW_BUSHY_CF941F82 | 500 | 6 | 3 | +0 | 1 | 500 | . | | |
| 8 | HASH JOIN | | 500 | 6 | 3 | +0 | 1 | 500 | 1MB | | |
| 9 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | . | | |
| 10 | TABLE ACCESS FULL | T4 | 500 | 3 | 3 | +0 | 1 | 500 | . | | |
===================================================================================================================================================

 

PostgreSQLのように結合条件を書き換えてくれるまでは行わないようですね。Oracleのオプティマイザでは。
MERGE JOIN CARTESIAN となって、直積が発生してしまいました。PostgreSQLのように結合条件を書き換えていない結果として、結合条件の無い結合の強制となってしまった結果、直積が発生したわけです。

であれば、オリジナルの結合条件を書き換えて、それっぽくなるようにしてみましょう!
PostgreSQLのプランナのように結合条件を書き換えて再実行!!。

おお!
思い通りの実行計画になりました!

 

このような箇所でもオプティマイザの特徴は現れるので注意したいですね。Oracleでこの形にしたくなることは無いとは思いますけども。

 

ちなみに、Oracleのオプティマイザが内部的に、インラインビューを作る場合、どうやらネーミングルールがあるようで(マニュアルには記載されていないですけどもw)
VW_BUSHY_xxxxxxx となっている場合は、Bushy Join を行うためのインラインビュー名であるということは知られています
(知らない方もおおいかもしれませんが。Internal Views / Oracle Scratchpad by Jonathan Lewisあたりでそこそこまとめられていますが、Bushy Joinのためのはリストされてないですが、知らなくても困らないとは思います。OracleのBusy Join自体が現状はかなりレアな存在なので)


  1  SELECT
2 /*+
3 MONITOR
4 LEADING(VW_BUSHY_0C91E486)
5 USE_HASH(t2 t3)
6 USE_HASH(t1 t4)
7 BUSHY_JOIN((t2 t3) (t1 t4))
8 */
9 t1.id
10 , t1.t1_c1
11 , t2.s_id
12 , t2.t2_c1
13 , t3.b_id
14 , t3.t3_c1
15 , t4.a_id
16 , t4.c_id
17 , t4.t4_c1
18 FROM
19 t1
20 INNER JOIN t2
21 ON
22 t1.id = t2.id
23 INNER JOIN t3
24 ON
25 t2.id = t3.id
26 INNER JOIN t4
27 ON
28* t1.id = t4.id

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

SQL Text

...略...


SQL Plan Monitoring Details (Plan Hash Value=400336061)
=================================================================================================================================================
| 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 | 25000 | . | | |
| 1 | HASH JOIN | | 25000 | 12 | 3 | +0 | 1 | 25000 | 1MB | | |
| 2 | VIEW | VW_BUSHY_0C91E486 | 500 | 6 | 1 | +0 | 1 | 500 | . | | |
| 3 | HASH JOIN | | 500 | 6 | 1 | +0 | 1 | 500 | 1MB | | |
| 4 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | . | | |
| 5 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | . | | |
| 6 | VIEW | VW_BUSHY_CF941F82 | 500 | 6 | 3 | +0 | 1 | 500 | . | | |
| 7 | HASH JOIN | | 500 | 6 | 3 | +0 | 1 | 500 | 1MB | | |
| 8 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | . | | |
| 9 | TABLE ACCESS FULL | T4 | 500 | 3 | 3 | +0 | 1 | 500 | . | | |
=================================================================================================================================================

 

Oracleでも、一般的に言われている Bushy Join Tree へ持っていくことはできるよ。あえて使わないけど、というかこれに持っていきたい時って想像できないわけですけどもね。Zigzag Join Treeで対応しちゃうだろうから。。。

ということで、Oracleでかなり久々に見た(無理やりですけどもw)、Bushy Join Tree もあるんだよ! の巻。完。。

 

今回使ったスクリプト

Oracle(PostgreSQLと同じ実行計画になるよう結合条件を書き換え,内部生成されるインラインビュー名称を確認した上で、LEADINGで結合順を聖書したもの)


SCOTT@orclpdb1> ! cat ora_sql_hj_bushy_join.sql

SELECT
/*+
MONITOR
LEADING(VW_BUSHY_0C91E486)
USE_HASH(t2 t3)
USE_HASH(t1 t4)
BUSHY_JOIN((t2 t3) (t1 t4))
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t2.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
.
l
set tab off
set termout off
/
set termout on

@show_realtime_sql

 

 

PostgreSQL


perftestdb=> \! cat pg_sql_hj.sql
explain (analyze)
SELECT
/*+
HashJoin(t1 t2 t3 t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;

 

なお、テーブル、索引、登録したデータなどは、過去のエントリーを参照ください。
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる

 


参考)
202311232038251

 

Trees

https://resources.mpi-inf.mpg.de/departments/d5/teaching/ss09/queryoptimization/lecture4.pdf

 

Classification of Join Ordering Problems

https://resources.mpi-inf.mpg.de/departments/d5/teaching/ss09/queryoptimization/lecture5.pdf

 

Specialized Course "Query Optimization"

https://resources.mpi-inf.mpg.de/departments/d5/teaching/ss09/queryoptimization/

 

 

では、また、面白そうなネタを見つけたら書こうと思います。:)

 

| | | コメント (0)

2024年2月21日 (水)

Top N Queryの弱点と対策

久々にSQLチューニングネタです。しかも、真面目な感じのタイトルw

11年前に、rownum使って満足しちゃってると.....おまけのおまけ FETCH FIRST N ROWS ONLY編 という、rownumや、fetch first N rows only でTOP N rows を取得する際に、忘れがちな問題として、TOP N rowsの行数未満(空振りを含む)の場合、全行読み込んでしまうので、絞り込みしにくいような検索で、rownumやfetch first N rowsで全表走査を回避したつもりになっていると、痛い目にあうよ!

というネタを書いていました。

この大切な癖、忘れちゃってませんか? 大変なことになりますよ。。ということで、再びこのネタを書くことにしました。

Oracle Database 21cを使っていますが11年も昔から変わったところはないので、塩漬けしている古〜いオラクルでも楽しめる内容にしてありますw

 

まず、今日の準備から

SCOTT@orclpdb1> @fullscanfulness

表が削除されました。

経過: 00:00:01.31
1 create table fullscanfulness
2 (
3 id number not null
4 ,dummy_text varchar2(4000)
5 ,hoge_flg number(1) not null
6 ,constraint pk_fullscanfulness primary key(id)
7* )

表が作成されました。

経過: 00:00:00.29
1 begin
2 for i in 1..100000 loop
3 insert into fullscanfulness values(i,lpad('*',3500,'*'),0);
4 if mod(i,100)=0 then commit; end if;
5 end loop;
6* end;

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

経過: 00:00:22.45
1* insert into fullscanfulness values(100001,lpad('*',3500,'*'),1)

1行が作成されました。

経過: 00:00:00.01

コミットが完了しました。

経過: 00:00:00.01

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

経過: 00:00:03.53
1* select count(1) from fullscanfulness

COUNT(1)
----------
100001

経過: 00:00:00.01

SEGMENT_NAME BLOCKS
------------------------------ ----------
FULLSCANFULNESS 51200
PK_FULLSCANFULNESS 256

経過: 00:00:00.19

 

と、クセのありそうな、なんとなく、見る機会の多そうなフラグ列のある表に主キーだけがある状態です。
なお、最後のデータ id=100001だけフラグ列が 1 になっています。最後の行なので、物理的にも最後尾にしてあります(意図的に)

 

フラグ=1になっている行が表どの位置にあるかイメージ図で書くと以下のような感じ

20240221-200436

 

表セグメントのブロック数は上記のとおりですが、まずは、実際にtable full scan させてみましょう physical reads/ consistent gets からもわかるように綺麗にw 読み込まれてます!

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> @fullscanfulness2
1 SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5* fullscanfulness

100001行が選択されました。

経過: 00:00:03.34

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

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 334M| 13807 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| FULLSCANFULNESS | 100K| 334M| 13807 (1)| 00:00:01 |
-------------------------------------------------------------------------------------


統計
----------------------------------------------------------
137 recursive calls
0 db block gets
53724 consistent gets
50133 physical reads
0 redo size
2322822 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
27 sorts (memory)
0 sorts (disk)
100001 rows processed

 

では、早速、本題ですw

rownumでも良いのですが、fetch first N rows onlyで WHERE句なし、つまり、全表走査上等な状態ですが、10行取得だけ。つまり、table full scanではありますが、10行取得したところまでで終了させます。
WINDOW NOSORT STOPKEYというオペレーションが増加しています。rownumだと、STOPKEYだけですよね。ここがrownumとは違うところですが、動作としては行数をカウントして、制限値に達したところで走査終了とするためのオペレーションが追加されています。
これで、table full scanであっても、全データブロックを読み取ることはないですよね。みなさんご存知の通りです。 consistent gets = 8 なので、 8 ブロックしか読みこんでません。

ここまではいいですよね。

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.00

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

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

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
800 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)
10 rows processed

 

では、次の例

WHERE句で条件を指定しておきます。なかなか渋い検索条件ですよね。一般的に索引は作成しにくいですw。 
TOP N クエリーなので一つ前の例のように table full scanは回避できるは。。。。ですよね。

想定通り、必要な行数取得後に、table full scanは止まって必要最小限の表データブロックだけアクセスしています。 いいじゃないですか。。これで。

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 0
8* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.01

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 5 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 5 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 10 | 35080 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | FULLSCANFULNESS | 10 | 35080 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
800 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)
10 rows processed

 

 

では、Top N クエリーをやめて、WHERE条件だけ指定します。 hoge_flg = 1 である、表データブロックの最後のブロック格納されている行を1行取得してみましょう。

hoge_flg列には索引はないので、table full scanしかできません。当然、全データブロックを読み込み(物理読み込み)して1行取得しています。これも想定通りです。Top N クエリーで制限していないので最後まで読み込んでしまいますよね。

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7* hoge_flg = 1

経過: 00:00:00.19

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

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50001 | 167M| 13807 (1)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| FULLSCANFULNESS | 50001 | 167M| 13807 (1)| 00:00:01 |
-------------------------------------------------------------------------------------

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

1 - filter("HOGE_FLG"=1)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
50144 consistent gets
50133 physical reads
0 redo size
673 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

 

さて、次が問題です。

一つ前の例と同じWHERE条件で、FETCH FIRST 10 ROWS ONLYでTop N クエリーしてみましょう。どうなったかわかりますか?

実行計画上は、これまでと同じく、 table full scanで、STOPKEYによる途中停止オペレーションも含まれていますが、、、、全データブロックを読み込んでしまいました。

 

なぜでしょう?

 

理由は、Top N クエリーの条件に指定された行数に満たない行数しか存在しなかったから、ですね!。 
これが rownum / fetch first N rows onlyの弱点なのです。条件に満たないことが確定するのは、table full scanで全データブロックを読み込み終わるまで確定しません。
最後まで読み切ってしまうんです。途中で止まらないのです。。。

大問題ですね!これw(ワロてますが)

僕たちの Top N クエリー、ダメじゃん。みたいな。(そんなことはないですが、考慮が漏れているだけなのでw)

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 1
8* FETCH FIRST 10 ROWS ONLY

経過: 00:00:00.34

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 5 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 5 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 10 | 35080 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | FULLSCANFULNESS | 10 | 35080 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
50153 consistent gets
50082 physical reads
0 redo size
673 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

 

でも、もう一つ、残念なお知らせ。というか例を。

 

FETCH FIRST N ROWSの条件に満たなかったから、全データブロックを読んでしまったわけだから、条件を満たせば、絶対、俺たちの Top N クエリーは、table full scanの途中で止まってくれるはずだ!

そんなことはないですねw

以下の例では、FETCH FISRT N ROWSの条件を満たせてはいますが、ヒットした1行のデータは、運の悪いことに、全表データブロック中、最も最後のブロックに存在しています。
したがって、索引のないこの表では、最後のデータブロックを読み込むまで、FETCH FIRST N ROWSの条件は満たせないため、全データブロックを読み込むしかなくなっています。残念!
(対象行を最後のデータブロックになるような小細工をしていたのはこれを見せたかったわけです)

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 1
8* FETCH FIRST 1 ROWS ONLY

経過: 00:00:00.33

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

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

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
50124 consistent gets
50053 physical reads
0 redo size
673 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

 

さらに、Top N クエリーの弱点を再確認してみましょう。

 

今度は、空振りする検索条件です。対象データは0件です。そうでう。ここまでついて来れた皆さんなら、もうお気づきだと思いますが。空振りする検索条件だと、0件であることが確定するのはどういう状態になった時でしょうか?

そうです。 変なマウントおじさんが登場してきた時(違w

ではなくて、全データブロックを読み終えた時ですね。 table full scan は Top N クエリーだけでは止まれないケースが存在するんですよ。みなさん!

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 9
8* FETCH FIRST 1 ROWS ONLY

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

経過: 00:00:00.20

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

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

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
50145 consistent gets
50133 physical reads
0 redo size
453 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

 

では、追加の確認、Top N クエリーの条件が早期に満たされれば、table full scanは回避できますよね。という念の為の確認です。
以下では、表の先頭ブロックに Top N クエリーの条件を満足させるためのデータを用意しました。


20240221-200459

 

この状態であれば、table full scanは止められるはずです。(常にこんな状態になることは稀なわけですけども)

結果は見ての通り、table full scanは途中で止まりました。当然と言えば当然ですが。

  1* update fullscanfulness set hoge_flg = 1 where id between 1 and 9

9行が更新されました。

経過: 00:00:00.02

コミットが完了しました。

経過: 00:00:00.00
1* select id from fullscanfulness where hoge_flg=1

ID
----------
3
4
5
6
7
8
9
1
2
100001

10行が選択されました。

経過: 00:00:00.21
1 SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 1
8* FETCH FIRST 1 ROWS ONLY

経過: 00:00:00.01

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

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

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
671 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

 

では、事前準備で、表の先頭ブロックには9行だけ WHERE条件に該当するデータを置きました。ただし、 Top N クエリーの条件は満たせません。
Top N クエリーの条件を満たすための最後のピースは、表データの最後尾のブロックに置いてあります。

20240221-200422

 

結果は、またまた、 Top N クエリーの効果で、table full scanが途中で止まることはできず、全データブロックを読み込んでしまいました。辛いですね(この状況)

  1  SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 1
8* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.34

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 5 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 5 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 10 | 35080 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | FULLSCANFULNESS | 10 | 35080 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
50125 consistent gets
50053 physical reads
0 redo size
802 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)
10 rows processed

 

次の例では、表データの最後尾のデータブロックに置いたデータのフラグを0にして、WHERE条件を満たす行を 9行にしました。
これだと、表データの先頭にある数ブロックだけでは、 Top N クエリーの条件を満たせません。

 

結果は、見るまでもなく(見てますがw)、表の全データブロックを読み込むまで終了できません。

  1* update fullscanfulness set hoge_flg = 0 where id = 100001

1行が更新されました。

経過: 00:00:00.00

コミットが完了しました。

経過: 00:00:00.01
1 SELECT
2 id
3 ,substr(dummy_text,1,10) as dummy
4 FROM
5 fullscanfulness
6 WHERE
7 hoge_flg = 1
8* FETCH FIRST 10 ROWS ONLY

9行が選択されました。

経過: 00:00:00.34

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 5 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 5 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 10 | 35080 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | FULLSCANFULNESS | 10 | 35080 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

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


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
50154 consistent gets
50082 physical reads
0 redo size
792 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)
9 rows processed

 

では、いよいよ。Top N クエリーの弱点に対する対策には何が効くのかみていきましょう。あれしかないですけどもw

 

一般的な状況だと、ほぼ、作成したくない索引を作ってみます。この状況では仕方ないですw(意図的ですがw)

  1* create index ix_fullscanfulness on fullscanfulness(hoge_flg)

索引が作成されました。

経過: 00:00:00.44

 

念の為、索引を強制するヒントを追加しています。

 

条件は一つ前の例と同じです。違いはフラグ列に作成した単一列索引だけです。

 

結果は、ご覧の通り。 狙い通り、 Top N クエリーの条件は満たせていませんが、WHERE条件に一致する行だけを索引経由で取得することで、 table full scanを完全に回避しています。 これが Top N クエリーの弱点回避への最後の希望です :)
状況次第ですが、かなり、何この索引と突っ込まれる系の索引を作成しなければいけなくなることも多いです。ですが、これしかないのです。

  1  SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 1
11* FETCH FIRST 10 ROWS ONLY

9行が選択されました。

経過: 00:00:00.01

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

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 7 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 7 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 10 | 35080 | 7 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 10 | 35080 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 50001 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=10)
4 - access("HOGE_FLG"=1)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
9 consistent gets
1 physical reads
0 redo size
792 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)
9 rows processed

 

さらに、 Top N クエリーの弱点回避の効果を確認してみましょう。

次は、Top N クエリーの条件は満たせていますが、WHERE条件を満たせる行が、表データブロックの先頭と最後尾に存在しているケースです。

表の先頭ブロックに9行の該当データがあるので、最後尾のデータブロックにある行を更新して置きます。索引が存在しない状態では、table full scanを途中で止めることはできませんでしたが、どうなりましか?

 

止まっていますよね!

  1* update fullscanfulness set hoge_flg = 1 where id = 100001

1行が更新されました。

経過: 00:00:00.00

コミットが完了しました。

経過: 00:00:00.01
1 SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 1
11* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.00

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

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 7 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 7 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 10 | 35080 | 7 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 10 | 35080 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 50001 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=10)
4 - access("HOGE_FLG"=1)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
802 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)
10 rows processed

 

WHERE条件を変更しました。表データブロックの先頭ブロックに該当行全てが含まれるようにしてみました。もともと、table full scanが途中で止まるケースですが、索引を利用させても遜色ない結果を得られます。唯一の違いは索引の存在ですね。DMLなどには性能的にマイナスの影響はありますが索引の数次第のところもあるので、どちらが大切かをよく検討、検証して決める必要はあります

   1* update fullscanfulness set hoge_flg = 0 where id between 1 and 9

9行が更新されました。

経過: 00:00:00.00

コミットが完了しました。

経過: 00:00:00.00
1 SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 0
11* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.00

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

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 7 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 7 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 10 | 35080 | 7 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 10 | 35080 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 50001 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=10)
4 - access("HOGE_FLG"=0)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
800 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)
10 rows processed

 

もう一つ、意地悪な確認をしてみましょう。

表データの中間付近に、WHERE条件を満たす行を配置して、索引を利用できないよう NO_INDEXヒントに変更します。


20240221-200451

 

結果は、想定通り、 Top N クエリーは、表データの1/2ほどまで table full scanを行って停止することがわかります。

  1* update fullscanfulness set hoge_flg = 0 where id between 1 and 9 or id = 100001

10行が更新されました。

経過: 00:00:00.00
1* update fullscanfulness set hoge_flg = 1 where id between 50000 and 50010

11行が更新されました。

経過: 00:00:00.01

コミットが完了しました。

経過: 00:00:00.00
1 SELECT
2 /*+
3 NO_INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 1
11* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.24

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 5 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 5 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY| | 10 | 35080 | 5 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | FULLSCANFULNESS | 10 | 35080 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

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


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
24739 consistent gets
24672 physical reads
0 redo size
818 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)
10 rows processed

 

同じ状態で、ヒントを元に戻し、索引を利用できるようにします。

当然ですが、必要最小限のデータブロックを索引使って取得するようになり、 table full scan は回避できます。

  1  SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 1
11* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.01

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

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 7 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 7 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 10 | 35080 | 7 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 10 | 35080 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 50001 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=10)
4 - access("HOGE_FLG"=1)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
818 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)
10 rows processed

 

次の例は、 空振りのケースでも索引アクセスなら瞬時に空振りが判定できますよね!! という確認です。

WHERE条件に対応した索引を用意するだけで、索引を見たたけで対象データが存在しないことが判定できます。空振りして、table full scanして、全データブロックを総ナメなんて無駄ですよね。

  1  SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 9
11* FETCH FIRST 10 ROWS ONLY

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

経過: 00:00:00.01

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

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 480 | 2 (0)| 00:00:01 |
|* 1 | VIEW | | 10 | 480 | 2 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 1 | 3508 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 1 | 3508 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=10)
4 - access("HOGE_FLG"=9)


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

 

そんな索引でも索引に含まれていない列でソートされたりすると困ることもあります。という例です。

次の例は、索引でtable full scanを回避できていたTop N クエリーが仕様変更され、索引に含まれないid列のソートが追加されてしまいました。
その結果。。。索引エントリーが読み込まれ、かつ、ソートまでされる結果となってしまいました。ありゃありゃw

  1  SELECT
2 /*+
3 INDEX(fullscanfulness ix_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 0
11 ORDER BY
12 id DESC
13* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.56

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

--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 610 | | 61653 (1)| 00:00:03 |
|* 1 | VIEW | | 10 | 610 | | 61653 (1)| 00:00:03 |
|* 2 | WINDOW SORT PUSHED RANK | | 50001 | 167M| 195M| 61653 (1)| 00:00:03 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| FULLSCANFULNESS | 50001 | 167M| | 25103 (1)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_FULLSCANFULNESS | 50001 | | | 92 (2)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ID") DESC )<=10)
4 - access("HOGE_FLG"=0)


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

 

前述の例は、索引の効果がなくなってしまったことが原因なので、仕様変更に合わせ、索引も変更しなければなりません!

仕様変更に対応した索引を作成します。新規作成していますが、作り直しでも良いですね。列が増加しただけで他に影響がない索引であれば。

  1* create index ix2_fullscanfulness on fullscanfulness(hoge_flg, id desc)

索引が作成されました。

経過: 00:00:00.51
1 SELECT
2 /*+
3 INDEX(fullscanfulness ix2_fullscanfulness)
4 */
5 id
6 ,substr(dummy_text,1,10) as dummy
7 FROM
8 fullscanfulness
9 WHERE
10 hoge_flg = 0
11 ORDER BY
12 id DESC
13* FETCH FIRST 10 ROWS ONLY

10行が選択されました。

経過: 00:00:00.01

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

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 610 | 9 (12)| 00:00:01 |
| 1 | SORT ORDER BY | | 10 | 610 | 9 (12)| 00:00:01 |
|* 2 | VIEW | | 10 | 610 | 8 (0)| 00:00:01 |
|* 3 | WINDOW NOSORT STOPKEY | | 10 | 35080 | 8 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| FULLSCANFULNESS | 50001 | 167M| 8 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IX2_FULLSCANFULNESS | 10 | | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

2 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
3 - filter(ROW_NUMBER() OVER ( ORDER BY SYS_OP_DESCEND("ID"))<=10)
5 - access("HOGE_FLG"=0)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
8 consistent gets
1 physical reads
0 redo size
818 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)
10 rows processed

 

フルスキャンそんなにしたいのでしょうか? 無駄なエネルギーを消費させて。。謎は深まる、 

 

フルスキャンフルネスな感じなのだろうか。。

 

では、また。

 


rownum使って満足しちゃってると.....おまけ
rownum使って満足しちゃってると.....おまけのおまけ FETCH FIRST N ROWS ONLY編

 

 

| | | コメント (0)

2024年1月27日 (土)

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_BETA5r161342

2024年最初のエントリーは、
VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r161342がアップされていたので、いつもの定点観測です。

結果から言うと、OS起動から、MySQL, PostgreSQLまでは起動する状態にはなっています。最近は、OS毎クラッシュしたかと思うと次のテストビルドでは起動するようになったり、なかなか状況は安定していません。また、Oracleですが、今の所一度も起動せずでした。

20240127-111838
20240127-112626

グラフィックコントローラーの3Dアクセラレーションを有効化は、OFFにしておいたほうが現状は良さそうです!
20240130-203635
20240130-203646

久々なので環境情報から
MBA M2とVirtualBox Test Buildは以下の通り

oracle@catfish ~ % sw_vers 
ProductName: macOS
ProductVersion: 14.3
BuildVersion: 23D56
oracle@catfish ~ % /usr/sbin/system_profiler SPHardwareDataType | grep -E '(Processor|Cores|Memory|Chip|Model Name)'
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB
oracle@catfish ~ % VBoxManage -v
7.0.97_BETA5r161342

[master@localhost etc]$ cat *release*
Oracle Linux Server release 8.5
NAME="Oracle Linux Server"
VERSION="8.5"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.5"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.5"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:5:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://bugzilla.oracle.com/"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.5
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.5
Red Hat Enterprise Linux release 8.5 (Ootpa)
Oracle Linux Server release 8.5
cpe:/o:oracle:linux:8:5:server
[master@localhost etc]$ uname -a
Linux localhost.localdomain 5.4.17-2136.304.4.1.el8uek.x86_64 #2 SMP Tue Feb 8 11:54:24 PST 2022 x86_64 x86_64 x86_64 GNU/Linux

MySQLは以下の通り起動しました。

[master@localhost ~]$ sudo service mysqld status
Redirecting to /bin/systemctl status mysqld.service
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2024-01-26 21:25:04 EST; 8min ago
Process: 1699 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)
Process: 1079 ExecStartPre=/usr/libexec/mysql-prepare-db-dir mysqld.service (code=exited, status=0/SUCCESS)
Process: 1027 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)
Main PID: 1123 (mysqld)
Status: "Server is operational"
Tasks: 38 (limit: 22947)
Memory: 402.6M
CGroup: /system.slice/mysqld.service
└─1123 /usr/libexec/mysqld --basedir=/usr

...略...

[master@localhost ~]$ mysql -u scott -D perftestdb -p
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 Source distribution

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)

mysql> exit
Bye
[master@localhost ~]$ sudo service mysqld stop
Redirecting to /bin/systemctl stop mysqld.service
[master@localhost ~]$ sudo service mysqld status
Redirecting to /bin/systemctl status mysqld.service
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: inactive (dead) since Fri 2024-01-26 21:34:39 EST; 5s ago
Process: 2914 ExecStopPost=/usr/libexec/mysql-wait-stop (code=exited, status=0/SUCCESS)
Process: 1699 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)
Process: 1123 ExecStart=/usr/libexec/mysqld --basedir=/usr (code=exited, status=0/SUCCESS)
Process: 1079 ExecStartPre=/usr/libexec/mysql-prepare-db-dir mysqld.service (code=exited, status=0/SUCCESS)
Process: 1027 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)
Main PID: 1123 (code=exited, status=0/SUCCESS)
Status: "Server shutdown complete"

...略...

PostgreSQLも以下の通り起動した

[master@localhost ~]$ sudo service postgresql-13 status
Redirecting to /bin/systemctl status postgresql-13.service
● postgresql-13.service - PostgreSQL 13 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-13.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/postgresql-13.service.d
└─local.conf
Active: active (running) since Fri 2024-01-26 21:24:01 EST; 12min ago
Docs: https://www.postgresql.org/docs/13/static/
Process: 1033 ExecStartPre=/usr/pgsql-13/bin/postgresql-13-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
Main PID: 1060 (postmaster)
Tasks: 9 (limit: 22947)
Memory: 23.3M
CGroup: /system.slice/postgresql-13.service
├─1060 /usr/pgsql-13/bin/postmaster -D /pg/pgdata/data
├─1080 postgres: logger
├─1127 postgres: checkpointer
├─1128 postgres: background writer
├─1129 postgres: walwriter
├─1130 postgres: autovacuum launcher
├─1131 postgres: archiver
├─1132 postgres: stats collector
└─1134 postgres: logical replication launcher

...略...

[master@localhost ~]$ sudo su - postgres
[postgres@localhost ~]$ psql -d perftestdb -U discus -p 5432 -W -h localhost
パスワード:
psql (13.6)
"help"でヘルプを表示します。

perftestdb=> select version();
version
--------------------------------------------------------------------------------------------------------
PostgreSQL 13.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4), 64-bit
(1 行)

perftestdb=> exit
[postgres@localhost ~]$ exit
ログアウト
[master@localhost ~]$ sudo service postgresql-13 stop
Redirecting to /bin/systemctl stop postgresql-13.service
[master@localhost ~]$ sudo service postgresql-13 status
Redirecting to /bin/systemctl status postgresql-13.service
● postgresql-13.service - PostgreSQL 13 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-13.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/postgresql-13.service.d
└─local.conf
Active: inactive (dead) since Fri 2024-01-26 21:37:12 EST; 5s ago
Docs: https://www.postgresql.org/docs/13/static/
Process: 1060 ExecStart=/usr/pgsql-13/bin/postmaster -D ${PGDATA} (code=exited, status=0/SUCCESS)
Process: 1033 ExecStartPre=/usr/pgsql-13/bin/postgresql-13-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
Main PID: 1060 (code=exited, status=0/SUCCESS)
...略...

残念ながら、今回のテストビルトでも、Oracleは起動せず。リスナーはいつも通り起動しますが。

[oracle@localhost ~]$ lsnrctl start

LSNRCTL for Linux: Version 21.0.0.0.0 - Production on 27-1月 -2024 12:01:04

Copyright (c) 1991, 2021, Oracle. All rights reserved.

/opt/oracle/product/21c/dbhome_1/bin/tnslsnrを起動しています。お待ちください...

TNSLSNR for Linux: Version 21.0.0.0.0 - Production
システム・パラメータ・ファイルは/opt/oracle/homes/OraDBHome21cEE/network/admin/listener.oraです。
ログ・メッセージを/opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xmlに書き込みました。
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 21.0.0.0.0 - Production
開始日 27-1月 -2024 12:01:04
稼働時間 0 日 0 時間 0 分 1 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/oracle/homes/OraDBHome21cEE/network/admin/listener.ora
ログ・ファイル /opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
リスナーはサービスをサポートしていません。
コマンドは正常に終了しました。
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 土 1月 27 12:01:09 2024
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SYS@ORCLCDB> startup
ORA-03113: 通信チャネルでend-of-fileが検出されました


もう一息なのかどうかわかりませんが、Oracleも起動してくれることを、期待しつつ、定点観測は続く :)
今年中に起動するようになってくれると嬉しいですよね。

ではまた:)



Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160167
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

| | | コメント (0)

2023年12月17日 (日)

帰ってきた! 標準はあるにはあるが癖の多いSQL #8、Hash Joinさせるにも癖が出る



本エントリーは、
MySQL Advent Calendar 2023 シリーズ1
PostgreSQL Advent Calendar 2023 シリーズ1
JPOUG Advent Calendar 2023

の Day 17 向けエントリーです。

また、本エントリー向け予習エントリーを投稿していますので、一読していただくと私が何と戦っていたのかw 理解しやすいのではないかと思います

予習 その1は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い

予習 その2は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる

予習 その3は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画の見せ方にも癖がでる


ということで、これまでの予習で学んだことを利用して、もしも、こんなデータ(予習 その2 を参照のこと)で、以下のようなSQLがあった場合、Hash Joinの特性上いい感じの実行計画にさせるにはどうHintingするのが良いのか考えてみたいと思います。
(答えが見えている方は多いとは思いますけどもw)

以下のSQL文を使い、後述のような実行計画に制御することを目指してみます。

SELECT
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
, t0.m_id
, t0.t0_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
INNER JOIN t0
ON
t1.id = t0.id
;


いざというときに慌てずに、どうするかを判断、チューニングできるようになっておくと良いですよねー。
そう言う、いざ、という場面には付き合わされない人も、雑学的になんとなーーーく理解しているだけでも良いのではないかと思います。


Oracleの実行計画風ですが、こんな実行計画にオプティマイザーヒントだけで制御してみたいなぁ。というのがお題。右側にコメントしているBuild/Probeの関係を覚えておいてください。この形にするのが目的です。
(予習に目を通した方は、あ”〜っ。と何かに気づいちゃったと思いますけどもw)

ポイントになるのは、t0の結合です。 t1,t2,t3,t4と結合した結果が、t0の行数より多くなります。(そうなるようにデータを用意したので)Hash Joinを想定しているので、最後にt0を結合する場合には、t0をBuildにしたいですよね。。。
(Hash Joinの結合順で理想なのは、常にBuildの方が小さくなる結合順なので、以下の形が理想的だとのがわかりやすいようにJoin cardinarityも記載してあります)

HJ (join card = 2,500,000)
-> t0 (rows = 1,000) -- Build
-> HJ (join card = 250,000) -- Probe
-> HJ (join card = 500) -- Build
-> HJ (join card = 50) -- Build
-> t1 (rows = 10) -- Build
-> t2 (rows = 50) -- Probe
-> t3 (rows = 100) -- Probe
-> t4 (rows = 500) -- Probe

なお、これまでの予習で、MySQLは、Hash Join はできるけど、Build/Probeの制御ができない。 Left Deep Join Treeにしかならないのもどうしようもない。 
オプティマイザヒントでは無理だろうと想像はできるわけですがw 折角なので、できるところまで試して、挙動をみておきましょう:)

再掲
テーブル定義とデータ量は以下、予習 その2 参照のこと。
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる




まず、Oracleからですかねぇ。やはり。豊富なオプティマイザヒントと最適化が行えることが強みの一つだと思います。

Oracle Database 21c

ちょっと暴れてたのでw NO_SWAP_JOIN_INPUTSを使ってますが、実行計画としては狙ったところに持っていけますよね。Oraclerの方なら、ふむふむ。というところだと思います。
t1, t2, t3, t4, t0の順で結合しますが、最後の、t0の結合時は、Buildを t0 にしてね! というHinging。実践でも結構使う場面はありますよね。
(実行計画の右側に確認しやすくするため、どちらが (Build) / (Probe) なのかをコメントしてあります)

SCOTT@ORCLCDB> 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> @advent_sql_ora
1 SELECT
2 /*+
3 MONITOR
4 LEADING(t1 t2 t3 t4 t0)
5 USE_HASH(t1 t2 t3 t4 t0)
6 NO_SWAP_JOIN_INPUTS(t4)
7 SWAP_JOIN_INPUTS(t0)
8 */
9 t1.id
10 , t1.t1_c1
11 , t2.s_id
12 , t2.t2_c1
13 , t3.b_id
14 , t3.t3_c1
15 , t4.a_id
16 , t4.c_id
17 , t4.t4_c1
18 , t0.m_id
19 , t0.t0_c1
20 FROM
21 t1
22 INNER JOIN t2
23 ON
24 t1.id = t2.id
25 INNER JOIN t3
26 ON
27 t1.id = t3.id
28 INNER JOIN t4
29 ON
30 t1.id = t4.id
31 INNER JOIN t0
32 ON
33* t1.id = t0.id

...略...

SQL Plan Monitoring Details (Plan Hash Value=3728371915)
====================================================================================================================================================
| 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 | | | | 106 | +0 | 1 | 3M | | | . | | |
| 1 | HASH JOIN | | 3M | 29 | 106 | +0 | 1 | 3M | | | 1MB | | |
| 2 | TABLE ACCESS FULL | T0 | 1000 | 3 | 1 | +0 | 1 | 1000 | 2 | 49152 | . | | | (Build)
| 3 | HASH JOIN | | 25000 | 12 | 106 | +0 | 1 | 25000 | | | 1MB | | | (Probe)
| 4 | HASH JOIN | | 500 | 9 | 1 | +0 | 1 | 500 | | | 1MB | | | (Build)
| 5 | HASH JOIN | | 50 | 6 | 1 | +0 | 1 | 50 | | | 1MB | | | (Build)
| 6 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | 2 | 49152 | . | | | (Build)
| 7 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | 2 | 49152 | . | | | (Probe)
| 8 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | 2 | 49152 | . | | | (Probe)
| 9 | TABLE ACCESS FULL | T4 | 500 | 3 | 106 | +0 | 1 | 500 | 3 | 81920 | . | | | (Probe)
====================================================================================================================================================



簡単に解説すると、t1からt4まではLeft Deep Join Tree、t0は、術式反転w で Right Deep Join Treeになるように、SWAP_JOIN_INPUTSしてます。

Id=5で、Id=6のt1(Build)と、Id=7のt2(Probe)をHash Join
Id=4で、Id=5の結果(Build)と、Id=8のt3(Probe)をHash Join
Id=3で、Id=4の結果(Build)と、Id=9のt4(Probe)をHash Join
Id=2で、SWAP_JOIN_INPUTSヒントで入れ替えた、Id=2のt0(Build)と、Id=3の結果(Probe)をHash Join



狙った実行計画に制御できました。ニッコリ。

参考)使ったSQLスクリプトは以下の通り。
SCOTT@ORCLCDB> !cat show_realtime_sqlplan.sql
set linesize 1000
set long 1000000
set longchunksize 1000000
select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual;

SCOTT@ORCLCDB> !cat advent_sql_ora.sql
SELECT
/*+
MONITOR
LEADING(t1 t2 t3 t4 t0)
USE_HASH(t1 t2 t3 t4 t0)
NO_SWAP_JOIN_INPUTS(t4)
SWAP_JOIN_INPUTS(t0)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
, t0.m_id
, t0.t0_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
INNER JOIN t0
ON
t1.id = t0.id
.
l
set tab off
set termout off
/
set termout on

@show_realtime_sqlplan


PostgreSQL 13.4 with pg_hint_plan 1.3.9
なんで、最新のPostgreSQLじゃないのか? 単にアップデートサボってるだなので、気にししないでw

perftestdb=# select version();
version
--------------------------------------------------------------------------------------------------------
PostgreSQL 13.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1), 64-bit

perftestdb=# select * from pg_extension where extname='pg_hint_plan';
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
--------+--------------+----------+--------------+----------------+------------+-----------------+--------------
131364 | pg_hint_plan | 10 | 131363 | f | 1.3.9 | {131367,131365} | {"",""}


perftestdb=> show shared_preload_libraries;
shared_preload_libraries
-----------------------------------------------
pg_hint_plan


pg_hint_planのLeadingヒントの使い方慣れるまで結構迷子になってましたw。空気感でなんとなーく使い方を理解できたかなぁ。という感じ。
pg_hint_planのLeading(pair)の書き方と、順序の指定順って、Oraclerの感覚的には逆なんですよね。Hash Joinの時だけは。。。というところ気づきました? みなさん。(主に、Oracle方面の)

perftestdb=> \! cat advent_sql_pg.sql
explain (analyze)
SELECT
/*+
Leading(((t4 (t3 (t2 t1))) t0))
HashJoin(t0 t1 t2 t3 t4)
SeqScan(t0)
SeqScan(t1)
SeqScan(t2)
SeqScan(t3)
SeqScan(t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
, t0.m_id
, t0.t0_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
INNER JOIN t0
ON
t1.id = t0.id
;

perftestdb=> \i advent_sql_pg.sql
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Id=1 Hash Join (cost=46.66..28524.66 rows=2500000 width=44) (actual time=0.875..574.957 rows=2500000 loops=1)
Hash Cond: (t1.id = t0.id)
Id=2 (Probe) -> Hash Join (cost=18.16..308.66 rows=25000 width=48) (actual time=0.429..11.123 rows=25000 loops=1)
Hash Cond: (t4.id = t1.id)
Id=3 (Probe) -> Seq Scan on t4 (cost=0.00..8.00 rows=500 width=16) (actual time=0.006..0.776 rows=500 loops=1)
Id=4 (Build) -> Hash (cost=11.91..11.91 rows=500 width=32) (actual time=0.418..0.423 rows=500 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 40kB
Id=5 -> Hash Join (cost=4.04..11.91 rows=500 width=32) (actual time=0.072..0.270 rows=500 loops=1)
Hash Cond: (t3.id = t1.id)
Id=6 (Probe) -> Seq Scan on t3 (cost=0.00..2.00 rows=100 width=12) (actual time=0.004..0.016 rows=100 loops=1)
Id=7 (Build) -> Hash (cost=3.41..3.41 rows=50 width=20) (actual time=0.064..0.067 rows=50 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
Id=8 -> Hash Join (cost=1.23..3.41 rows=50 width=20) (actual time=0.018..0.053 rows=50 loops=1)
Hash Cond: (t2.id = t1.id)
Id=9 (Probe) -> Seq Scan on t2 (cost=0.00..1.50 rows=50 width=12) (actual time=0.004..0.011 rows=50 loops=1)
Id=10 (Build) -> Hash (cost=1.10..1.10 rows=10 width=8) (actual time=0.008..0.009 rows=10 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Id=11 -> Seq Scan on t1 (cost=0.00..1.10 rows=10 width=8) (actual time=0.003..0.005 rows=10 loops=1)
Id=12 (Build) -> Hash (cost=16.00..16.00 rows=1000 width=12) (actual time=0.437..0.437 rows=1000 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 51kB
Id=13 -> Seq Scan on t0 (cost=0.00..16.00 rows=1000 width=12) (actual time=0.022..0.219 rows=1000 loops=1)

解説のために、Id=noと、Build/Probeを見やすくするためにコメント (Build) / (Probe) を追加しています

Oracleと同じように制御できているか、確認しましょう。

Id=8で、Id=11のt1をSeq ScanしてId=10でハッシュ表作成(Build)と、Id=9のt2をSec Scan(Probe)をHash Join
Id=5で、Id=8の結果を元に、Id=7でハッシュ表作成(Build)と、Id=6のt3 Seq Scan(Probe)をHash Join
Id=2で、Id=5の結果を元に、Id=4でハッシュ表作成(Build)と、Id=3のt4 Seq Scan(Probe)をHash Join
Id=1で、Id=13のt0をSeq ScanしてId=12でハッシュ表作成(Build)と、Id=2の結果(Probe)をHash Join

狙い通りの実行計画になっています!、Yahooooo!!

今一度、Oracleの実行計画と比較してみると、Hash JoinのBuild/Probeの表現が逆になっていることがよくわかりますよね。(前回の予習の通りです)

               =======================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) |
=======================================================================================================================
| 0 | SELECT STATEMENT | | | | 106 | +0 | 1 | 3M | | | . |
| 1 | HASH JOIN | | 3M | 29 | 106 | +0 | 1 | 3M | | | 1MB |
(Build) | 2 | TABLE ACCESS FULL | T0 | 1000 | 3 | 1 | +0 | 1 | 1000 | 2 | 49152 | . |
(Probe) | 3 | HASH JOIN | | 25000 | 12 | 106 | +0 | 1 | 25000 | | | 1MB |
(Build) | 4 | HASH JOIN | | 500 | 9 | 1 | +0 | 1 | 500 | | | 1MB |
(Build) | 5 | HASH JOIN | | 50 | 6 | 1 | +0 | 1 | 50 | | | 1MB |
(Build) | 6 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | 2 | 49152 | . |
(Probe) | 7 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | 2 | 49152 | . |
(Probe) | 8 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | 2 | 49152 | . |
(Probe) | 9 | TABLE ACCESS FULL | T4 | 500 | 3 | 106 | +0 | 1 | 500 | 3 | 81920 | . |
=======================================================================================================================


最後に、
MySQL 8.0.32
これも現時点のリリースよりちょっと前のですが、こちらの都合なので、気にしないでくださいw

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.01 sec)

mysql>


無理やり、Hash Joinにはしていますが、PostgreSQLのpg_hint_planのように結合優先順位をペアで制御する方法も、Oracleのように、SWAP_JOIN_INPUTSヒントを使うこともできません。Left Deep Join Treeにしかならないということなので、それが理由ではありますが。とりあえず、こんな感じにしかなりません。JOIN_ORDERヒントの結合順は、OracleのLEADINGヒントに近い考え方で支えているのがわかります。要するに、Buhy Join Treeが可能であれば、これは解決できるわけですが、それは無理なので、t0をBuildにすることができず、これが限界といことになりますよね。(ちょっと思いついたことがあるので、このあと、もうひとあがきしてみますがw)

あと、ヒントで面白いのは、主キー索引を利用させないためのヒント。primaryって指定が必要なんので覚えておくと便利ですよね。
マニュアルにも記載されていますが、インデックスヒントが非推奨になる前に、オプティマイザヒントに慣れておいたほうがよさそうですし。

MySQL 8.0 / 8.9.4 インデックスヒント

”注記
MySQL 8.0.20 の時点で、サーバーは、インデックスオプティマイザヒント JOIN_INDEX, GROUP_INDEX, ORDER_INDEX、および INDEX(NO_JOIN_INDEX, NO_GROUP_INDEX, NO_ORDER_INDEX および FORCE INDEX オプティマイザヒントに相当およびを置き替える)、および NO_INDEX オプティマイザヒント (IGNORE INDEX インデックスヒントに相当し、それらを置き換える) をサポートします。 したがって、USE INDEX、FORCE INDEX および IGNORE INDEX は、MySQL の将来のリリースで非推奨になり、後で完全に削除される予定です。 詳細は、インデックスレベルのオプティマイザヒントを参照してください。”

mysql> \! cat advent_sql_my.sql
explain analyze
SELECT
/*+
JOIN_ORDER(t1,t2,t3,t4,t0)
NO_JOIN_INDEX(t1 primary)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
NO_JOIN_INDEX(t0 primary)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
, t0.m_id
, t0.t0_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
INNER JOIN t0
ON
t1.id = t0.id
;

mysql> \. advent_sql_my.sql
+---------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------+
Id=1 | -> Inner hash join (t0.id = t1.id) (cost=2525558.24 rows=2500000) (actual time=23.139..356.660 rows=2500000 loops=1)
Id=2 (Probe) -> Table scan on t0 (cost=0.00 rows=1000) (actual time=0.083..0.512 rows=1000 loops=1)
Id=3 (Build) -> Hash
Id=4 -> Inner hash join (t4.id = t1.id) (cost=25552.55 rows=25000) (actual time=0.679..4.613 rows=25000 loops=1)
Id=5 (Probe) -> Table scan on t4 (cost=0.01 rows=500) (actual time=0.057..0.330 rows=500 loops=1)
Id=6 (Build) -> Hash
Id=7 -> Inner hash join (t3.id = t1.id) (cost=551.75 rows=500) (actual time=0.223..0.412 rows=500 loops=1)
Id=8 (Probe) -> Table scan on t3 (cost=0.03 rows=100) (actual time=0.041..0.069 rows=100 loops=1)
Id=9 (Build) -> Hash
Id=10 -> Inner hash join (t2.id = t1.id) (cost=51.50 rows=50) (actual time=0.106..0.146 rows=50 loops=1)
Id=11 (Probe) -> Table scan on t2 (cost=0.08 rows=50) (actual time=0.025..0.040 rows=50 loops=1)
Id=12 (Build) -> Hash
Id=13 -> Table scan on t1 (cost=1.25 rows=10) (actual time=0.040..0.048 rows=10 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------+
(確認し訳するため、Id=nと(Build) / (Probe)を示すコメントを追記してあります)

Id=10で、Id=13のt1をTable Scanてから、Id=12でハッシュ表作成(Build)と、Id=11で、t2をTable Scan(Probe)をHash Join
Id=7で、Id=10の結果を元に、Id=9でハッシュ表作成(Build)と、Id=8のt3 Table Scan(Probe)をHash Join
Id=4で、Id=7の結果を元に、Id=6でハッシュ表作成(Build)と、Id=5のt4 Table Scan(Probe)をHash Join
Id=1で、Id=4の結果を元に、Id=3でハッシュ表作成(Build)と、Id=2のt0 Table Scan(Prove)をHash Hoin

最後のt0をBuildにしたいわけですけども、Build表制御することができないので。こんな感じですね。

と、一つだけ、思いついたので、SQL魔改造でなんとかならんか、試してみます。
(ヒントだけでなんとかするという趣旨からはズレるのですけどもw)

ダメ元で試してみますw

おおおおお、一瞬、できた! か?  と思いましたが、やはり無理です。CTEでサブクエリーとして先に一時表としてマテリアライズしたら行くかなぁ。
と思いましたが、MySQLは、一時表でも、自動的に内部的な主キーが作成されるという、... clustered indexなのか。。。一時表も。という気づき。
この方法では、CTEを使っていますが、インラインビューにして、NO_MERGEヒントにしても同様に、一時表としてマテリアライズされるようで、同様の結果でした。。。。

記載はしていませんが、 マテリアライズされた一時表の  という内部的な主キーですが、現状、 NO_JOIN_INDEXヒントで止められないので、t0の結合はNLJにしかできませんでした。。。

現時点のMySQLのオプティマイザの気持ちが、少しだけ、理解できるようになった気がします :)

mysql> \! cat advent_sql2_my.sql
explain analyze
WITH
t1234 AS
(
SELECT
/*+
QB_NAME(ilv1)
JOIN_ORDER(t1,t2,t3,t4)
NO_JOIN_INDEX(t1 primary)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
),
t00 AS
(
SELECT
/*+
QB_NAME(ilv2)
NO_INDEX(t0 primary)
*/
t0.id
, t0.m_id
, t0.t0_c1
FROM
t0
)
SELECT
/*+
JOIN_ORDER(@ilv2)
NO_MERGE(t00)
*/
t1234.id
, t1234.t1_c1
, t1234.s_id
, t1234.t2_c1
, t1234.b_id
, t1234.t3_c1
, t1234.a_id
, t1234.c_id
, t1234.t4_c1
, t00.m_id
, t00.t0_c1
FROM
t1234
INNER JOIN t00
ON
t1234.id = t00.id
;

mysql> \. advent_sql2_my.sql
+---------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=2588052.66 rows=25000001) (actual time=2.449..1233.983 rows=2500000 loops=1)
-> Inner hash join (t4.id = t1.id) (cost=25552.55 rows=25000) (actual time=0.626..11.339 rows=25000 loops=1)
-> Table scan on t4 (cost=0.01 rows=500) (actual time=0.031..2.330 rows=500 loops=1)
-> Hash
-> Inner hash join (t3.id = t1.id) (cost=551.75 rows=500) (actual time=0.403..0.483 rows=500 loops=1)
-> Table scan on t3 (cost=0.03 rows=100) (actual time=0.024..0.037 rows=100 loops=1)
-> Hash
-> Inner hash join (t2.id = t1.id) (cost=51.50 rows=50) (actual time=0.329..0.350 rows=50 loops=1)
-> Table scan on t2 (cost=0.08 rows=50) (actual time=0.021..0.028 rows=50 loops=1)
-> Hash
-> Table scan on t1 (cost=1.25 rows=10) (actual time=0.190..0.199 rows=10 loops=1)
-> Index lookup on t00 using (id=t1.id) (cost=201.25..203.50 rows=10) (actual time=0.002..0.036 rows=100 loops=25000)
-> Materialize CTE t00 (cost=201.00..201.00 rows=1000) (actual time=1.803..1.803 rows=1000 loops=1)
-> Table scan on t0 (cost=101.00 rows=1000) (actual time=0.024..0.514 rows=1000 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------+
1 row in set (2.77 sec)

(Warningも出てないので、オプティマイザヒントの指定方法としては良さげだが、狙い通りにはなりませんw)




では、最後に、このポストへ至るまでの予習ポストも含め、リンクを再掲しておきます

予習 その1

帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い

予習 その2

帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる

予習 その3

帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる

皆様、メリークリスマス & 良いお年をお迎えください。 :) / Oracle database 23c with pipe line function / SQL de Christmas Tree 、4K対応で作り直しましたw
恒例?の、Oracle Pipelined Table Function で Christmas ASCII Art








関連エントリー

標準はあるにはあるが癖の多い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の実行計画にも癖がでる

| | | コメント (0)

2023年12月13日 (水)

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



予定外ですが、Advent Calendarのシーズンついでなので、空いていた以下のアドベントカレンダー向けエントリーとなります。
Kernel/VM Advent Calendar 2023 / Day 13



macOS / ARM64 Beta development revision 160702
https://www.virtualbox.org/wiki/Testbuilds

以前、Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160167で、MySQL 8.0.32が起動した!と言うことを書きましたが、その後のBETAリリースではOS起動中にクラッシュしていました。
で、今回は、development revision 160702をダメもとで試してみました。

結果から書くと、色々もっさりしているのは仕方ないかないところですが、このrevisionでは、MySQLに加えて、PostgreSQLも起動しました!!!

VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160702
GuestOS : Oracle Linux Server release 8.5
MySQL 8.0.32 - 起動した!
PostgreSQL 13.4 - 起動した!
Oracle database 21c - 起動せず!


試した内容は前回と同じですが、Oracleについては変えています。
1) Oracle Linux 8.5 に、PostgreSQL 13.4 と MySQL 8.0.32 を構成したVMをエクスポートして ova 作成
2) 1)のOracle Linux 8.5 に、Oracle Database 21cを構成したものを ova としてエクスポート。
それぞれをVirtualBoxへインポートして検証しました。

ちなみに、
Oracle Linux 7.6のPre-Built Developer VMs (for Oracle VM VirtualBox) および、Oracle Linux 8.6に構築したOracle 23c Freeは、OS起動にクラッシュしていたので、うまく起動した1)を利用して、OS起動後にOracleの挙動を確認する方法にしましたが、Oracleがコケたと言う結果となりました。

20231213-121328

20231213-120924

20231213-121535

20231213-121631

20231213-155006

Test Buildは、結構進んできているようで、今後が楽しみです ;)

では、また。



関連エントリー
Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160167

| | | コメント (0)

2023年12月10日 (日)

帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画の見せ方にも癖がでる



本エントリーは、MySQL Advent Calendar 2023 シリーズ2 / Day 10PostgreSQL Advent Calendar 2023 シリーズ2 / Day 10へのクロスポスト、および、JPOUG Advent Calendar 2023 / Day 10 の裏番組 (ADVENTARはシリーズ増やせないので) エントリーで、12/17日向けの 予習 その3 という位置付けのエントリーです。

予習 その1は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い

予習 その2は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる


MySQLは、Left Deep Join Treeにしかならない、というところまで理解できたのが前回までの予習でした。

今日は、もう少し、理解を深めておこうと言うことで、MySQLでは無理だけど、 PostgreSQL と Oracle で結合ツリーの種別(Oracleのマニュアルに記載されている分類の範囲)で、Right Deep Join、Left Deep Join、Bushy Joinでは、どのような実行計画として見えるのかを確認しておきましょう。


まず初めに、MySQLはできない Right Deep Join Tree で Hash Join させると、どのような実行計画に見えるか? の比較から。

Oracle Database 21c
Oraclerなら見慣れたSWAP_JOIN_INPUTSヒント利用の典型的な例です。Id=6でt1(build)がtable full scanされ、ID=7のt2(Probe)と結合されています。t3, t4はそれぞれ、Buildとなっていることが読み取れます。

Oracle Database 21c / Right Deep Join Tree / Hash Join
(実行計画の右端に (Build) なのか、 (Probe) を確認しやすいようにコメントを付加しています)

SCOTT@orclpdb1> @ora_sql_rightdj.sql
1 SELECT
2 /*+
3 MONITOR
4 LEADING(t1 t2 t3 t4)
5 USE_HASH(t1 t2 t3 t4)
6 SWAP_JOIN_INPUTS(t3)
7 SWAP_JOIN_INPUTS(t4)
8 */
9 t1.id
10 , t1.t1_c1
11 , t2.s_id
12 , t2.t2_c1
13 , t3.b_id
14 , t3.t3_c1
15 , t4.a_id
16 , t4.c_id
17 , t4.t4_c1
18 FROM
19 t1
20 INNER JOIN t2
21 ON
22 t1.id = t2.id
23 INNER JOIN t3
24 ON
25 t1.id = t3.id
26 INNER JOIN t4
27 ON
28* t1.id = t4.id

...略...

SQL Plan Monitoring Details (Plan Hash Value=3835853103)
===================================================================================================================================================
| 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 | | | | 2 | +0 | 1 | 25000 | | | . | | |
| 1 | HASH JOIN | | 25000 | 12 | 2 | +0 | 1 | 25000 | | | 1MB | | |
| 2 | TABLE ACCESS FULL | T4 | 500 | 3 | 1 | +0 | 1 | 500 | 2 | 49152 | . | | | (Build)
| 3 | HASH JOIN | | 500 | 9 | 2 | +0 | 1 | 500 | | | 1MB | | | (Probe)
| 4 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | 2 | 49152 | . | | | (Build)
| 5 | HASH JOIN | | 50 | 6 | 2 | +0 | 1 | 50 | | | 1MB | | | (Probe)
| 6 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | 2 | 49152 | . | | | (Build)
| 7 | TABLE ACCESS FULL | T2 | 50 | 3 | 2 | +0 | 1 | 50 | 2 | 49152 | . | | | (Probe)
===================================================================================================================================================


PostgreSQL 13.4 / pg_hint_plan 1.3.9 / Right Deep Join Tree / Hash Join

OracleとはBuild表の位置が逆なので、OracleのLeft deep join treeの実行計画に読み間違えそうですがw PostgreSQLのRight Deep Join Treeだとこのような実行計画として表示されます。
Oracleでは、Id=6でT1がBuild表になり、Id=7のt2(Probe)と結合するように表現されますが、
PostgreSQLでは、 ★ で示した行で、t1が、Seq ScansされてHash表が作成されている。つまり、Build表になっており、t2(Probe)と結合されています。Oracleの感覚で読んでしまうと、あれ? となるところだと思います。
t3, t4から、それぞれ、Hash表が作成されているので、Buildになっていることがわかります:) 
同じ実行計画になるようにしてみましたが、実行計画の見せ方では、Oracle/PostgreSQLでは差異がありますよね。ただ、MySQLのTree表示もPostgreSQLに類似しているので、PostgreSQLのに慣れてると、MySQLのTree formatの実行計画は読みやすいかもしれません)

Orableのように、SWAP_JOIN_INPUTSヒントでBuild/Probeを制御するヒントが存在しないので、代わりに、LeadingヒントのLeading()構文を利用して制御しています。OracleのLEADING + SWAP_JOIN_JOINPSと比べると可読性は劣化する(個人の感想です)の使い方が難しいく感じますね。慣れなのかなぁ。これ。

(実行計画の左端に (Build) なのか、 (Probe) を確認しやすいようにコメントを付加しています)

perftestdb=> \! cat pg_sql_rightdj.sql
explain (analyze)
SELECT
/*+
Leading((((t2 t1) t3) t4))
HashJoin(t1 t2 t3 t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;

perftestdb=> \i pg_sql_rightdj.sql
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Hash Join (cost=18.73..308.66 rows=25000 width=36) (actual time=3.352..8.740 rows=25000 loops=1)
Hash Cond: (t1.id = t4.id)
(Probe) -> Hash Join (cost=4.47..11.91 rows=500 width=32) (actual time=2.316..2.460 rows=500 loops=1)
Hash Cond: (t1.id = t3.id)
(Probe) -> Hash Join (cost=1.23..2.91 rows=50 width=20) (actual time=1.534..1.562 rows=50 loops=1)
Hash Cond: (t2.id = t1.id)
(Probe) -> Seq Scan on t2 (cost=0.00..1.50 rows=50 width=12) (actual time=0.744..0.750 rows=50 loops=1)
(Build) -> Hash (cost=1.10..1.10 rows=10 width=8) (actual time=0.723..0.724 rows=10 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
★ -> Seq Scan on t1 (cost=0.00..1.10 rows=10 width=8) (actual time=0.705..0.707 rows=10 loops=1)
(Build) -> Hash (cost=2.00..2.00 rows=100 width=12) (actual time=0.760..0.761 rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 13kB
-> Seq Scan on t3 (cost=0.00..2.00 rows=100 width=12) (actual time=0.715..0.732 rows=100 loops=1)
(Build) -> Hash (cost=8.00..8.00 rows=500 width=16) (actual time=1.012..1.013 rows=500 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 32kB
-> Seq Scan on t4 (cost=0.00..8.00 rows=500 width=16) (actual time=0.792..0.905 rows=500 loops=1)


MySQL 8.0.32 / Right Deep Join Tree / Hash Join
できないので、なし。


次、みんさんが、見慣れている(?)、Left Deep Join Tree にすると Hash Join の実行計画は、それぞれどのように見えるのでしょうか? 比較してみましょう。

Oraclerには、わかりやすいですよね(慣れの問題かもしれませんけどもw) 。Id=4で、t1がBuild表となってt2(Probe)と結合。。。以下、t3, t4がそれぞれ、ProbeとしてHash Joinされています。
Oracle 21 / Left Deep Join Tree / Hash Join

Id=4でt1がtable full scan(Build)で、ID=5 のt2 (Probe) と結合されています。t3, t4は それぞれのProbeとなっていることが読み取れます。;)

(実行計画の右端に (Build) なのか、 (Probe) を確認しやすいようにコメントを付加しています)

SCOTT@orclpdb1> @ora_sql_leftdj.sql
1 SELECT
2 /*+
3 MONITOR
4 LEADING(t1 t2 t3 t4)
5 USE_HASH(t1 t2 t3 t4)
6 NO_SWAP_JOIN_INPUTS(t4)
7 */
8 t1.id
9 , t1.t1_c1
10 , t2.s_id
11 , t2.t2_c1
12 , t3.b_id
13 , t3.t3_c1
14 , t4.a_id
15 , t4.c_id
16 , t4.t4_c1
17 FROM
18 t1
19 INNER JOIN t2
20 ON
21 t1.id = t2.idv 22 INNER JOIN t3
23 ON
24 t1.id = t3.id
25 INNER JOIN t4
26 ON
27* t1.id = t4.id

...略...

SQL Plan Monitoring Details (Plan Hash Value=894925296)
====================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
====================================================================================================================================
| 0 | SELECT STATEMENT | | | | 2 | +0 | 1 | 25000 | . | | |
| 1 | HASH JOIN | | 25000 | 12 | 2 | +0 | 1 | 25000 | 1MB | | |
| 2 | HASH JOIN | | 500 | 9 | 1 | +0 | 1 | 500 | 1MB | | | (Build)
| 3 | HASH JOIN | | 50 | 6 | 1 | +0 | 1 | 50 | 1MB | | | (Build)
| 4 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | . | | | (Build)
| 5 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | . | | | (Probe)
| 6 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | . | | | (Probe)
| 7 | TABLE ACCESS FULL | T4 | 500 | 3 | 2 | +0 | 1 | 500 | . | | | (Probe)
====================================================================================================================================


PostgreSQL 13.4 / pg_hint_plan 1.3.9 / Left Deep Join Tree / Hash Join
★ のある行で、t1がSeq Scanされ、その上位の行で、Hashが作成されているので、Buildとなっていることがわかります。次に、同一階層で t2がSeq ScanされHash Joinされています。
t3, t4はそれぞれProbeになっていることが読み取れます。ぱっと見は、Oracleの ight Deep Join Tree で見られる実行計画の形に似てますが。。。(^^;;;;

(実行計画の左端に (Build) なのか、 (Probe) を確認しやすいようにコメントを付加しています)

perftestdb=> \! cat pg_sql_leftdj.sql
explain (analyze)
SELECT
/*+
Leading((t4 (t3 (t2 t1))))
HashJoin(t2 t1 t3 t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;
perftestdb=>
perftestdb=> \i pg_sql_leftdj.sql
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=17.66..308.16 rows=25000 width=36) (actual time=0.522..8.362 rows=25000 loops=1)
Hash Cond: (t4.id = t1.id)
(Probe) -> Seq Scan on t4 (cost=0.00..8.00 rows=500 width=16) (actual time=0.012..0.101 rows=500 loops=1)
(Build) -> Hash (cost=11.41..11.41 rows=500 width=32) (actual time=0.462..0.467 rows=500 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 40kB
-> Hash Join (cost=3.54..11.41 rows=500 width=32) (actual time=0.122..0.328 rows=500 loops=1)
Hash Cond: (t3.id = t1.id)
(Probe) -> Seq Scan on t3 (cost=0.00..2.00 rows=100 width=12) (actual time=0.004..0.017 rows=100 loops=1)
(Build) -> Hash (cost=2.91..2.91 rows=50 width=20) (actual time=0.098..0.102 rows=50 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
-> Hash Join (cost=1.23..2.91 rows=50 width=20) (actual time=0.039..0.068 rows=50 loops=1)
Hash Cond: (t2.id = t1.id)
(Probe) -> Seq Scan on t2 (cost=0.00..1.50 rows=50 width=12) (actual time=0.005..0.011 rows=50 loops=1)
(Build) -> Hash (cost=1.10..1.10 rows=10 width=8) (actual time=0.016..0.018 rows=10 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
★ -> Seq Scan on t1 (cost=0.00..1.10 rows=10 width=8) (actual time=0.003..0.004 rows=10 loops=1)


MySQL 8.0.32 / Left Deep Join Tree / Hash Join
これは、前回のエントリーでも紹介した無理やり Hash Join にした例なので、特に解説はしませんが、PostgreSQLに類似した実行計画になっていますよね。

現状、MySQLは、HASH JOIN を強制するヒント、Build/Probeを制御するヒント、PostgreSQLのように結合順の優先順位を細かく指定するヒントもないため、Nested Loop Join にならないようヒントで制御するしか Hash Join を強制する方法はなさそうです。
Left Deep Join Tree にしかならないのでこともあり、Hash Join で、 Bushy Join Treeや、Right Deep Join Treeのようなチューニングはできないですね。

実際、関わった案件で、Hash Join が選択されたのは良いと思うけど、Build/Probeを入れ替えられたら、もっとサクッと終わるはずなのに、と言う状況になって初めて、この事実を知りました。(なので、このエントリー書いているのですけどもw)
できないのかよ。。とw

以下実行計画の ★ を付与した行で、t1がTable Scanされ、その上位の行で、Hashが作成されているので、Buildとなっていることがわかります。次に、同一階層で t2がTable Scan後に、Hash Joinされています。
t3, t4は、Probeになっていることも読み取れます。PostgreSQLの実行計画の表示方法に類似してますよね。

(実行計画の左端に (Build) なのか、 (Probe) を確認しやすいようにコメントを付加しています)

mysql> \! cat my_sql_leftdj.sql
explain analyze
SELECT
/*+
JOIN_ORDER(t1,t2,t3,t4)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;

mysql> \. my_sql_leftdj.sql
+-----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t4.id = t1.id) (cost=25552.55 rows=25000) (actual time=0.643..5.045 rows=25000 loops=1)
(Probe) -> Table scan on t4 (cost=0.01 rows=500) (actual time=0.052..0.346 rows=500 loops=1)
(Build) -> Hash
-> Inner hash join (t3.id = t1.id) (cost=551.75 rows=500) (actual time=0.216..0.380 rows=500 loops=1)
(Probe) -> Table scan on t3 (cost=0.03 rows=100) (actual time=0.041..0.069 rows=100 loops=1)
(Build) -> Hash
-> Inner hash join (t2.id = t1.id) (cost=51.50 rows=50) (actual time=0.100..0.141 rows=50 loops=1)
(Probe) -> Table scan on t2 (cost=0.08 rows=50) (actual time=0.026..0.041 rows=50 loops=1)
(Build) -> Hash
★ -> Table scan on t1 (cost=1.25 rows=10) (actual time=0.039..0.047 rows=10 loops=1)
|
+-----------------------------------------------------------------------------------------------------------------------+


Bushy Joinのも書こうと思ってましたが、前回ので十分そうなので、省略。(Left/Right Join Treeのミックスですし)MySQLは、Right Deep Join Treeにはならないので、結果なしです。


と言うことで、
Advent Calendar 2023 / Day 17向けの準備運動的な予習(その3)はここまで。

では、また。






関連エントリー

標準はあるにはあるが癖の多い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の結合ツリーにも癖がでる

| | | コメント (0)

2023年12月 9日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる



本エントリーは、MySQL Advent Calendar 2023 シリーズ2 / Day 9PostgreSQL Advent Calendar 2023 シリーズ2 / Day 9へのクロスポスト、および、JPOUG Advent Calendar 2023 / Day 9 の裏番組 (ADVENTARはシリーズ増やせないので) エントリーで、12/17日向けの 予習 その2 という位置付けのエントリーです。

予習 その1は、以下。
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い



少し前に、MySQLでもHash Joinになるんだね。といいうエントリーを書きました。覚えてますか? 
MySQL 8.0.32では NLJに使えるINDEXが存在していても、Hash Joinをヒントで強制することができる!(オプティマイザが選択することもある!)

その時点では、

MySQLのHash Joinを強制するオプティマイザーヒントも廃止されているようだし、Build/Probeの制御もできないよね? ヒントないし。
とか、
オプティマイザーヒントで強制できないけど、オプティマイザーが勝手に選んじゃうこともあるのか〜、NLJに利用できる索引あっても。。
勝手に選ばれることはあってもヒントではもろもろ制御できない。最初に戻る。

という状況ループ状態でしたw

そこで、閃いたのが、MySQLで、Hash Joinが自動発動する条件。その条件に合う状況にすればいいじゃん。と!
Hash Joinを強制するヒントはなくても、Nested Loop Joinに使ってる主索引(MySQLの場合は主キーはクラスター索引)を、オプティマイザーヒントで無効化してしまえば、勝手に、Hash Joinするよね!? 

という確認ばかりに目が行ってしまって、気づきませんでした!!!!


MySQLのHash Joinの実行計画ツリーを見て、Oracle/PostgreSQLと違う点、何か気づきませんか? 
みなさん! これ試験に出ますよw(嘘
答え合わせは後半でw

それでは、環境づくりから(少々長めですw)
それぞれの DB( Oracle Database 21c / PostgreSQL 13.4 with pg_hint_plan 1.3.9 / MySQL 8.0.32 )で、そのまま実行できるDDLにしてあります。
表と索引作成(各DB共通)、データ登録、そして、統計取得まで行っています。

Oracle/PostgreSQL/MySQL共通DDL

cre_advent_tabs.sql

-- 10 rows
CREATE TABLE t1
(
id INT NOT NULL
, t1_c1 INT NOT NULL
, CONSTRAINT t1_pk PRIMARY KEY (id)
);

-- 50 rows
CREATE TABLE t2
(
id INT NOT NULL
, s_id INT NOT NULL
, t2_c1 INT NOT NULL
, CONSTRAINT t2_pk PRIMARY KEY (id, s_id)
);

-- 100 rows
CREATE TABLE t3
(
id INT NOT NULL
, b_id INT NOT NULL
, t3_c1 INT NOT NULL
, CONSTRAINT t3_pk PRIMARY KEY (id, b_id)
);

-- 500 rows
CREATE TABLE t4
(
id INT NOT NULL
, a_id INT NOT NULL
, c_id INT NOT NULL
, t4_c1 INT NOT NULL
, CONSTRAINT t4_pk PRIMARY KEY (id, a_id, c_id)
);

-- 1000 rows
CREATE TABLE t0
(
id INT NOT NULL
, m_id INT NOT NULL
, t0_c1 INT NOT NULL
, CONSTRAINT t0_pk PRIMARY KEY (id, m_id)
);

Oracle 21c向けデータ登録と統計取得からデータ件数確認までのスクリプトと実行例

make_data4oracle.sql

-- for t1
BEGIN
FOR i IN 1..10 LOOP
INSERT INTO t1 VALUES(i,i);
END LOOP;
COMMIT;
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'T1', cascade=>true, no_invalidate=>false);
END;
/
SELECT COUNT(1) FROM t1;

-- for t2
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..5 LOOP
INSERT INTO t2 VALUES(i,j,i+j);
END LOOP;
END LOOP;
COMMIT;v DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'T2', cascade=>true, no_invalidate=>false);
END;
/
SELECT COUNT(1) FROM t2;

-- for t3
BEGINv FOR i IN 1..10 LOOP
FOR j IN 1..10 LOOP
INSERT INTO t3 VALUES(i,j,i+j);
END LOOP;
END LOOP;
COMMIT;
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'T3', cascade=>true, no_invalidate=>false);
END;
/
SELECT COUNT(1) FROM t3;

-- for t4
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..10 LOOP
FOR k IN 1..5 LOOP
INSERT INTO t4 VALUES(i,j,k,i+j);
END LOOP;
END LOOP;
COMMIT;
END LOOP;
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'T4', cascade=>true, no_invalidate=>false);
END;
/
SELECT COUNT(1) FROM t4;

-- for t0
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..100 LOOP
INSERT INTO t0 VALUES(i,j,i+j);
END LOOP;
COMMIT;
END LOOP;
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'T0', cascade=>true, no_invalidate=>false);
END;
/
SELECT COUNT(1) FROM t0;

SCOTT@orclpdb1> @make_data4oracle

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

COUNT(1)
----------
10

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

COUNT(1)
----------
50

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

COUNT(1)
----------
100

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

COUNT(1)
----------
500

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

COUNT(1)
----------
1000

PostgreSQL 13.4向けデータ登録と統計取得からデータ件数確認までのスクリプトと実行例

perftestdb=> \i cre_advent_tabs.sql
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
perftestdb=>

PostgreSQL向けデータ登録と統計取得からデータ件数確認までのスクリプトと実行例

make_data4postgresql.sql

-- for t1
DO $$
BEGIN
FOR i IN 1..10 LOOP
INSERT INTO t1 VALUES(i,i);
END LOOP;
COMMIT;
END
$$
;

VACUUM VERBOSE ANALYZE t1;
SELECT COUNT(1) FROM t1;

-- for t2
DO $$
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..5 LOOP
INSERT INTO t2 VALUES(i,j,i+1);
END LOOP;
END LOOP;
COMMIT;
END
$$
;

VACUUM VERBOSE ANALYZE t2;
SELECT COUNT(1) FROM t2;

-- for t3
DO $$
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..10 LOOP
INSERT INTO t3 VALUES(i,j,i+1);
END LOOP;
END LOOP;
COMMIT;
END
$$
;

VACUUM VERBOSE ANALYZE t3;
SELECT COUNT(1) FROM t3;

-- for t4
DO $$
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..10 LOOP
FOR k IN 1..5 LOOP
INSERT INTO t4 VALUES(i,j,k,i+j);
END LOOP;
END LOOP;
COMMIT;
END LOOP;
END
$$
;

VACUUM VERBOSE ANALYZE t4;
SELECT COUNT(1) FROM t4;

-- for t0
DO $$
BEGIN
FOR i IN 1..10 LOOP
FOR j IN 1..100 LOOP
INSERT INTO t0 VALUES(i,j,i+j);
END LOOP;
COMMIT;
END LOOP;
END
$$
;

VACUUM VERBOSE ANALYZE t0;
SELECT COUNT(1) FROM t0;

perftestdb=> \i make_data4postgresql.sql 
DO
psql:make_data4postgresql.sql:12: INFO: vacuuming "public.t1"
psql:make_data4postgresql.sql:12: INFO: "t1": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 530026
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
psql:make_data4postgresql.sql:12: INFO: analyzing "public.t1"
psql:make_data4postgresql.sql:12: INFO: "t1": scanned 1 of 1 pages, containing 10 live rows and 0 dead rows; 10 rows in sample, 10 estimated total rows
VACUUM
count
-------
10
(1 行)

DO
psql:make_data4postgresql.sql:30: INFO: vacuuming "public.t2"
psql:make_data4postgresql.sql:30: INFO: "t2": found 0 removable, 50 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 530028
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
psql:make_data4postgresql.sql:30: INFO: analyzing "public.t2"
psql:make_data4postgresql.sql:30: INFO: "t2": scanned 1 of 1 pages, containing 50 live rows and 0 dead rows; 50 rows in sample, 50 estimated total rows
VACUUM
count
-------
50
(1 行)

DO
psql:make_data4postgresql.sql:48: INFO: vacuuming "public.t3"
psql:make_data4postgresql.sql:48: INFO: "t3": found 0 removable, 100 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 530030
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
psql:make_data4postgresql.sql:48: INFO: analyzing "public.t3"
psql:make_data4postgresql.sql:48: INFO: "t3": scanned 1 of 1 pages, containing 100 live rows and 0 dead rows; 100 rows in sample, 100 estimated total rows
VACUUM
count
-------
100
(1 行)

DO
psql:make_data4postgresql.sql:67: INFO: vacuuming "public.t4"
psql:make_data4postgresql.sql:67: INFO: "t4": found 0 removable, 500 nonremovable row versions in 3 out of 3 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 530041
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
psql:make_data4postgresql.sql:67: INFO: analyzing "public.t4"
psql:make_data4postgresql.sql:67: INFO: "t4": scanned 3 of 3 pages, containing 500 live rows and 0 dead rows; 500 rows in sample, 500 estimated total rows
VACUUM
count
-------
500
(1 行)

DO
psql:make_data4postgresql.sql:85: INFO: vacuuming "public.t0"
psql:make_data4postgresql.sql:85: INFO: "t0": found 0 removable, 1000 nonremovable row versions in 6 out of 6 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 530052
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
psql:make_data4postgresql.sql:85: INFO: analyzing "public.t0"
psql:make_data4postgresql.sql:85: INFO: "t0": scanned 6 of 6 pages, containing 1000 live rows and 0 dead rows; 1000 rows in sample, 1000 estimated total rows
VACUUM
count
-------
1000
(1 行)

MySQL 8.0.32向けデータ登録と統計取得からデータ件数確認までのスクリプトと実行例

mysql> \. cre_advent_tabs.sql
Query OK, 0 rows affected (0.06 sec)

Query OK, 0 rows affected (0.05 sec)

Query OK, 0 rows affected (0.05 sec)

Query OK, 0 rows affected (0.04 sec)

Query OK, 0 rows affected (0.07 sec)

mysql>

MySQL向けデータ登録と統計取得からデータ件数確認までのスクリプトと実行例

make_data4mysql.sql

DELIMITER //
CREATE PROCEDURE make_data4mysql()
BEGIN
DECLARE i, j, k INT;
-- for t1
SET i = 1;
WHILE i <= 10 DO
INSERT INTO t1 VALUES(i,i);
SET i = i + 1;
END WHILE;
COMMIT;

-- for t2
SET i = 1;
WHILE i <= 10 DO
SET j = 1;
WHILE j <= 5 DO
INSERT INTO t2 VALUES(i,j,i+1);
SET j = j + 1;
END WHILE;
SET i = i + 1;
END WHILE;
COMMIT;

-- for t3
SET i = 1;
WHILE i <= 10 DO
SET j = 1;
WHILE j <= 10 DO
INSERT INTO t3 VALUES(i,j,i+1);
SET j = j + 1;
END WHILE;
SET i = i + 1;
END WHILE;
COMMIT;

-- for t4
SET i = 1;
WHILE i <= 10 DO
SET j = 1;
WHILE j <= 10 DO
SET k = 1;
WHILE k <= 5 DO
INSERT INTO t4 VALUES(i,j,k,i+j);
SET k = k + 1;
END WHILE;
SET j = j + 1;
END WHILE;
SET i = i + 1;
COMMIT;
END WHILE;

-- for t0
SET i = 1;
WHILE i <= 10 DO
SET j = 1;
WHILE j <= 100 DO
INSERT INTO t0 VALUES(i,j,i+j);
SET j = j + 1;
END WHILE;
SET i = i + 1;
COMMIT;
END WHILE;
END
//

DELIMITER ;

CALL make_data4mysql();

ANALYZE TABLE t1, t2, t3, t4, t0;

SELECT COUNT(1) FROM t1;
SELECT COUNT(1) FROM t2;
SELECT COUNT(1) FROM t3;
SELECT COUNT(1) FROM t4;
SELECT COUNT(1) FROM t0;

DROP PROCEDURE make_data4mysql;

mysql> \. make_data4mysql.sql
Query OK, 0 rows affected (0.03 sec)

Query OK, 0 rows affected (11.20 sec)

+---------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------------+---------+----------+----------+
| perftestdb.t1 | analyze | status | OK |
| perftestdb.t2 | analyze | status | OK |
| perftestdb.t3 | analyze | status | OK |
| perftestdb.t4 | analyze | status | OK |
| perftestdb.t0 | analyze | status | OK |
+---------------+---------+----------+----------+
5 rows in set (0.07 sec)

+----------+
| COUNT(1) |
+----------+
| 10 |
+----------+
1 row in set (0.01 sec)

+----------+
| COUNT(1) |
+----------+
| 50 |
+----------+
1 row in set (0.00 sec)

+----------+
| COUNT(1) |
+----------+
| 100 |
+----------+
1 row in set (0.02 sec)

+----------+
| COUNT(1) |v+----------+
| 500 |
+----------+
1 row in set (0.01 sec)

+----------+
| COUNT(1) |
+----------+
| 1000 |
+----------+
1 row in set (0.02 sec)

Query OK, 0 rows affected (0.01 sec)

mysql>
準備できた!




実行計画を理解するための参考
(Oracleのマニュアルで解説されているBushy Join Treeは、Zigzag Join Treeと言われている結合ツリーに見えますよね。Oracleは、Bushy/Zigzagは区別してなさそう)

参考)
Hash JoinのBuild/Probeって何? という方は、以下も読んでおくと良いです

Oracle Database Release 23 / SQL Tuning Guide - 9.2.2.2.2 Hash Join: Basic Steps

https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/joins.html#GUID-9EE5CD4C-B90C-4E61-83DC-BD585D79635C


結合ツリーついて

Oracle Database Release 23 / SQL Tuning Guide - 9.1.1 Join Trees

https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/joins.html#GUID-31B0F249-A5AA-41E9-AE98-A484FC5C487C


20231123-203825




では最初に、Oracle Database 21cから。
それっぽいの形にしたHash Joinの実行計画をお見せします。
これは、Oracleのマニュアルで言うと、Bushy Join Tree です。

SCOTT@orclpdb1> @ora_sql_hj.sql
1 SELECT
2 /*+
3 MONITOR
4 USE_HASH(t1 t2 t3 t4)
5 */
6 t1.id
7 , t1.t1_c1
8 , t2.s_id
9 , t2.t2_c1
10 , t3.b_id
11 , t3.t3_c1
12 , t4.a_id
13 , t4.c_id
14 , t4.t4_c1
15 FROM
16 t1
17 INNER JOIN t2
18 ON
19 t1.id = t2.id
20 INNER JOIN t3
21 ON
22 t1.id = t3.id
23 INNER JOIN t4
24 ON
25* t1.id = t4.id

...略...

SQL Plan Monitoring Details (Plan Hash Value=122725940)
===================================================================================================================================================
| 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 | | | | 1 | +0 | 1 | 25000 | | | . | | |
| 1 | HASH JOIN | | 25000 | 12 | 1 | +0 | 1 | 25000 | | | 1MB | | |
| 2 | TABLE ACCESS FULL | T4 | 500 | 3 | 1 | +0 | 1 | 500 | 2 | 49152 | . | | |
| 3 | HASH JOIN | | 500 | 9 | 1 | +0 | 1 | 500 | | | 1MB | | |
| 4 | HASH JOIN | | 50 | 6 | 1 | +0 | 1 | 50 | | | 1MB | | |
| 5 | TABLE ACCESS FULL | T1 | 10 | 3 | 1 | +0 | 1 | 10 | 2 | 49152 | . | | |
| 6 | TABLE ACCESS FULL | T2 | 50 | 3 | 1 | +0 | 1 | 50 | 2 | 49152 | . | | |
| 7 | TABLE ACCESS FULL | T3 | 100 | 3 | 1 | +0 | 1 | 100 | 2 | 49152 | . | | |
===================================================================================================================================================

上記の解説

Id=5 T1をTable Full Scan (Build表)
Id=6 T2をTable Full Scan (Probe表).
Id=4 T1とT2をHash Join (on memoryでHash Join完了)

Id=4 Id=5,6のHash Joinの結果(Build表)
Id=7 T3をTable Full Scan (Probe表)
Id=3 Id=4の結合結果とT3をHash Join (on memoryでHash Join完了)

Id=2 T4をTable Full Scan (Build表)
Id=3 Id=4の結合結果とT3の結合結果 (Probe表)
Id=2 T4とId=3の結合結果をHash Join (On memoryでHash Join完了)

という順序なので、
引用したOracleのマニュアルで言う、Bushy Join Tree になっています。
Oracleのマニュアルでは、Bushy Join Treeとなっていますが、他では、Zigzag Join Treeとして分類されていたりするツリーです。。LeftとRight Deep Join TreeがMixされた形ですよね、これ。

これを結合ツリーで書くと、以下のようになります。

20231208-195525


次は、PostgreSQL 13.4 with pg_hint_plan 1.3.9で同じくHash Joinになるようにヒントで強制した実行計画です。

この結合ツリーは、Bushy Join Treeですね。

perftestdb=> \! cat pg_sql_hj.sql
explain (analyze)
SELECT
/*+
HashJoin(t1 t2 t3 t4)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;
perftestdb=> \i pg_sql_hj.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Id=1 Hash Join (cost=17.48..309.84 rows=25000 width=36) (actual time=3.405..10.466 rows=25000 loops=1)
Hash Cond: (t1.id = t2.id)
Id=2 -> Hash Join (cost=1.23..11.09 rows=500 width=24) (actual time=1.696..1.989 rows=500 loops=1)
Hash Cond: (t4.id = t1.id)
Id=3 -> Seq Scan on t4 (cost=0.00..8.00 rows=500 width=16) (actual time=0.907..0.993 rows=500 loops=1)
Id=4 -> Hash (cost=1.10..1.10 rows=10 width=8) (actual time=0.721..0.722 rows=10 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Id=5 -> Seq Scan on t1 (cost=0.00..1.10 rows=10 width=8) (actual time=0.705..0.707 rows=10 loops=1)
Id=6 -> Hash (cost=10.00..10.00 rows=500 width=24) (actual time=1.687..1.687 rows=500 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 36kB
Id=7 -> Hash Join (cost=2.12..10.00 rows=500 width=24) (actual time=1.400..1.554 rows=500 loops=1)
Hash Cond: (t3.id = t2.id)
Id=8 -> Seq Scan on t3 (cost=0.00..2.00 rows=100 width=12) (actual time=0.701..0.712 rows=100 loops=1)
Id=9 -> Hash (cost=1.50..1.50 rows=50 width=12) (actual time=0.679..0.680 rows=50 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
Id=10 -> Seq Scan on t2 (cost=0.00..1.50 rows=50 width=12) (actual time=0.648..0.656 rows=50 loops=1)

解説しやすいように実行計画のノードにIdを振っています:)

Oracleの実行計画と大きく違うのは、Build表とProbe表の順序です。Oracleの場合、同一階層の上位が、Build表で、その下にProbe表がリストされますが、
PostgreSQL(MySQLも同様)では、同一階層上で、Probe表が上位に、その下に Hash(ハッシュ表を作成している操作。Oracleでは実行計画上この操作は現れません)、さらにその下位階層にBuild表のTable Full scanという形で表現されています。
階層の一番深い部分から実行されるので、上記例だと、Id=16のt2(Build表)のSeq Scan (Oracleで言うTable Full Scanが最初に実行され、次に、Id=15のHashが行われていることがわかります。


Id=10 t2 Seq Scan (Oracleで言うTable Full Scan)
Id=9 Id=10の結果を元にHash表作成 (つまり、t2がBuild)
Id=8 t3 Seq Scan (Probe)
Id=7 t2, t3をHash Join

Id=6 Id=7(t2, t3のHash Join)の結果を元にHash表作成(Build)

Id=5 t1 Seq Scan
Id=4 Id=5の結果を元にHash表作成(Build)
Id=3 t4 Seq Scan (Probe)
Id=2 t1, t4をHash Join

Id=1 Id=6の結果(Build), Id=2の結果(Probe)としてHash Join

こうやって1Step毎に追っていくと、OracleとPostgreSQLの実行計画のTree listで、Hash JoinのBuildとProbeの位置が異なっていることに気づきやすいですよね。(ちなみに、MySQLのTree formatも同様です)

これを結合ツリーで書くと、以下のようになります。

20231208-195537


最後に、MySQL 8.0.32です。
Hash Joinを強制する直接的なヒントも、Budling/Probe表を制御するヒントも方法存在しませんが、Hash Joinになるよう結合順を指定した上で、Nested Loop Joinになってしまうのを抑止するために、Nested Loop Joindで利用する主キー索引の利用を抑止しました。
(MySQLの主キーはクラスター索引なので、主キーがNested Loop Joinに利用される場合、索引の利用を抑止するには、NO_JOIN_INDEXヒントで primary を指定する必要があります)

なお、JOIN_ORDERヒントで結合順を指定した理由は、結合順を指定することでNested Loop Joinに利用される主キー(primary)を、NO_JOIN_INDEXヒントで確実抑止したかったためです。

ちなみに、この結合ツリーは、Left Deep Join Treeですね。

mysql> \! cat my_sql_leftdj.sql
explain analyze
SELECT
/*+
JOIN_ORDER(t1,t2,t3,t4)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
*/
t1.id
, t1.t1_c1
, t2.s_id
, t2.t2_c1
, t3.b_id
, t3.t3_c1
, t4.a_id
, t4.c_id
, t4.t4_c1
FROM
t1
INNER JOIN t2
ON
t1.id = t2.id
INNER JOIN t3
ON
t1.id = t3.id
INNER JOIN t4
ON
t1.id = t4.id
;

mysql> \. my_sql_leftdj.sql
+-----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------------------+
Id=1 | -> Inner hash join (t4.id = t1.id) (cost=25552.55 rows=25000) (actual time=0.643..5.045 rows=25000 loops=1)
Id=2 -> Table scan on t4 (cost=0.01 rows=500) (actual time=0.052..0.346 rows=500 loops=1)
Id=3 -> Hash
Id=4 -> Inner hash join (t3.id = t1.id) (cost=551.75 rows=500) (actual time=0.216..0.380 rows=500 loops=1)
Id=5 -> Table scan on t3 (cost=0.03 rows=100) (actual time=0.041..0.069 rows=100 loops=1)
Id=6 -> Hash
Id=7 -> Inner hash join (t2.id = t1.id) (cost=51.50 rows=50) (actual time=0.100..0.141 rows=50 loops=1)
Id=8 -> Table scan on t2 (cost=0.08 rows=50) (actual time=0.026..0.041 rows=50 loops=1)
Id=9 -> Hash
Id=10 -> Table scan on t1 (cost=1.25 rows=10) (actual time=0.039..0.047 rows=10 loops=1)
|
+-----------------------------------------------------------------------------------------------------------------------+

既にお分かりだと思いますが、答え合わせです。:)

OracleのRight Deep Joinに見慣れている皆さん(私も含むw)が、Right Deep Joinと勘違いしちゃうケースはありますが、Build表とProbe表の現れる位置が、Oracleの実行計画と逆なんですよね。
(何度見ても、OracleでRight Deep Join Treeにした実行計画の形とダブってしまって、慣れないw)
このような形になっているHash Joinの実行計画って、PostgreSQLも含め、Left Deep Join Treeなんですよね。


参考)MySQLが、Left Deep Join Treeにしかならない理由はこれ。
Chapter 4. Query Performance Optimization The execution plan
https://www.oreilly.com/library/view/high-performance-mysql/9780596101718/ch04.html#how_mysql_joins_multiple_table
"MySQL always begins with one table and finds matching rows in the next table. Thus, MySQL’s query execution plans always take the form of a left-deep tree"とあります。

Nested Loop Joinって、Left Deep Join Treeになるから、MySQLだとHash Joinでも、 Left Deep Join Treeなのかなぁ。と。ただ、一時期、クラウド方面にあるMySQL互換で、HASH_JOIN_BUILDINGとかいうヒントが加えられていたこともありました。NewSQL系でも拡張されているケースもあるようです。

前述のMySQLの実行計画を追っかけてみると、、、


Id=10 t1 Table Scan
Id=11 Id=10で読み込んだ結果からHash表を作成(つまり、t1がBuild)
Id=7,8 T2 Table Scan (Probe)してId=11とHash Join
Id=6 Id=7のHash Joinの結果からHash表作成 (Build)
Id=4,5 T3 Table Scan (Probe)してId=6とHash Join
Id=3 Id=4のHash Joinの結果からHash表作成(Build)
Id=1,2 T4 Table Scan (Probe)しId=3とHash Join


これを結合ツリーで書くと、以下のように Left Deep Treeになりまよね。
(表示されているExlplainの実行計画を見ていると、OracleのHash JoinでRight Deep Join Treeに錯覚してしまうのなんとかならんかねぇ。と思いつつ、慣れるしかないな。と言う答えしか出てこなかった。PostgreSQLも同じ表示方法だし。慣れろ→自分w)

20231208-195545




ということで、
MySQLのHash Joinの実行計画ツリーを見て、何か気づきませんか? 
みなさん! これ試験に出ますよw(嘘

の答えは、

MySQLは、Left Deep Join Tree しか、しない。でした!



ざっくりとしたまとめ的なやつ
Oracle/PostgreSQL/MySQLそれぞれ、Hash Joinだけを強制して結合順はオプティマイザー任せにした場合。

Oracle/PostgreSQLは、Bushy Join Tree
データ次第では、Left/Right Deep Join Treeの可能性もあるでしょうけども。今回のケースでは、Bushy Join Tree, 細かめに分類すると、Oracleの結合ツリーは、Zigzagですね。ちなみに、Oracleには、BUSHY_JOINというヒントがあったりします(使ったことないけどw)。

MySQLは、Left Deep Join Tree にしかならんよ。と。

以下の方針が変更されない限り、変わることはなさそう。

再掲

https://www.oreilly.com/library/view/high-performance-mysql/9780596101718/ch04.html#how_mysql_joins_multiple_table
"MySQL always begins with one table and finds matching rows in the next table. Thus, MySQL’s query execution plans always take the form of a left-deep tree"


予習、その3へ続く。

では、また。:)







関連エントリー

標準はあるにはあるが癖の多い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 でも癖が多い


| | | コメント (0)

2023年11月22日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い

癖の多いSQLシリーズネタは沢山あるわけですが、今回は、Oracle/PostgreSQL/MySQLで利用可能なオプティマイザヒントのざっくりしたまとめ。

というか、Advent Calendar向けの準備運動的ななエントリーその1. 意味の同じオプティマイザーヒントを使った何かをネタにしようとしてまーすw(その2は、どうするか検討中w)

各DBの Optimizer Hint (PostgreSQLは、pg_hint_plan拡張機能を利用)の数(特定の最適化の有効化、無効化ヒントはそれぞれ1ヒントとしてカウント。e.g. INDEX, NO_INDEXで2つとカウントする)

 

Oracle Database 21c : 388

(圧倒的に多いヒント。まあ、Oracle Database 8.1ぐらいからありますからねぇw 様々な最適化を制御できるようになっているということでもあるわけですけども。それにしても多いですね)

 

PostgreSQL with pg_hint_plan : 26

(pg_hint_planのドキュメントから拾って数得ました。これぐらいなんですね。)

 

MySQL 8.0.x : 37

(MySQL 8.0の Optimizer Hint には、HASH_JOIN/NO_HASH_JOINのように特定のリリースでしか使えないものなども含まれる)

 

MySQL系のAurora MySQL compatible, TiDBなどは、独自のヒントをいくつか追加しているものもある。e.g. HASH JOINのBUILD/PROBE表を制御するものなど。。ただし、Aurora MySQLの場合は、Vanila MySQLでHASH_JOINヒントが廃止された影響で、HASH_JOIN_BUILDINGなどの関連ヒントが廃止されるなど、各DB毎に多少温度差のある対応があり、Vanila以外のMySQLを利用する場合には利用可能な Optimizer Hint は個別に調査動作確認しておくことをお勧めする。

 

参考 Aurora MySQL hints(独自拡張あり)

https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Reference.Hints.html

 

TiDB(独自拡張あり)

https://docs.pingcap.com/ja/tidb/stable/optimizer-hints

 

YagabyteDB (pg_hint_planを使うようなので、Vanila PostgreSQLと同じ!)

https://docs.yugabyte.com/preview/explore/query-1-performance/pg-hint-plan/

 

 

Oracleのマニュアルでは、全量が解説されているわけではないので、v$sql_hintビューを問い合わせた結果も載せておきます。(ドキュメントのリンク貼っても、OracleさんのドキュメントURLって簡単にリンク切れになるのですが、取り敢えず貼っときますw)
YOracle Database 21c SQL Language Reference / Table 2-23 Hints by Functional Category

https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E__BABEAFGF

中には、黒魔術すぎた影響でw 指定しても無視(ヒントレポートでは、常に Error 扱い)されるヒントもあります( 参考: https://blogs.oracle.com/otnjp/post/tsushima-hakushi-11 )


(10gぐらいまでのSQLチューニング経験者なら一度ぐらいは聞いたことがあるかもしれねい、BYPASS_UJVC ヒントが有名ですね. ちなみに、このヒント単体で使うと単純に無視されヒントレーポートにも現れません。なお、v$sql_hintには残されていますw)


DISCUS@ORCLCDB> select * from v$sql_hint order by name;

NAME SQL_FEATURE CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OU
---------------------------------------- ------------------------------ ------------------------------ ------------------------------ ------------ ---------- ---------- ----------
ADAPTIVE_PLAN QKSFM_ADAPTIVE_PLAN ADAPTIVE_PLAN NO_ADAPTIVE_PLAN 1 16 12.1.0.2 12.1.0.2
ALL_ROWS QKSFM_ALL_ROWS MODE 1 16 8.1.0 10.2.0.1
ANALYTIC_VIEW_SQL QKSFM_COMPILATION ANALYTIC_VIEW_SQL 2 0 20.1.0
AND_EQUAL QKSFM_AND_EQUAL ACCESS 4 304 8.1.0 8.1.7
ANSI_REARCH QKSFM_ANSI_REARCH ANSI_REARCH NO_ANSI_REARCH 2 16 12.1.0.2 12.1.0.2
ANSWER_QUERY_USING_STATS QKSFM_ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS NO_ANSWER_QUERY_USING_STATS 2 16 18.1.0 18.1.0
ANTIJOIN QKSFM_TRANSFORMATION ANTIJOIN 2 16 9.0.0
APPEND QKSFM_CBO APPEND NOAPPEND 1 0 8.1.0
APPEND_VALUES QKSFM_CBO APPEND_VALUES NOAPPEND 1 0 11.2.0.1
AUTO_REOPTIMIZE QKSFM_AUTO_REOPT AUTO_REOPTIMIZE NO_AUTO_REOPTIMIZE 1 0 12.1.0.1
AV_CACHE QKSFM_EXECUTION AV_CACHE 2 0 18.1.0
BATCH_TABLE_ACCESS_BY_ROWID QKSFM_EXECUTION BATCH_TABLE_ACCESS_BY_ROWID NO_BATCH_TABLE_ACCESS_BY_ROWID 4 272 12.1.0.1 12.1.0.1
BIND_AWARE QKSFM_CURSOR_SHARING BIND_AWARE NO_BIND_AWARE 1 0 11.1.0.7
BITMAP QKSFM_CBO BITMAP 2 256 8.1.0 8.1.5
BITMAP_AND QKSFM_BITMAP_TREE BITMAP_AND 4 48 12.1.0.1 12.1.0.1
BITMAP_TREE QKSFM_BITMAP_TREE ACCESS 4 304 10.2.0.1 10.2.0.1
BUFFER QKSFM_CBO BUFFER NO_BUFFER 2 0 8.1.5
BUSHY_JOIN QKSFM_BUSHY_JOIN BUSHY_JOIN NO_BUSHY_JOIN 2 16 12.2.0.1 12.2.0.1
BYPASS_RECURSIVE_CHECK QKSFM_ALL BYPASS_RECURSIVE_CHECK 2 0 9.0.0
BYPASS_UJVC QKSFM_CBO BYPASS_UJVC 2 0 8.1.5
CACHE QKSFM_EXECUTION CACHE NOCACHE 4 256 8.1.0
CACHE_CB QKSFM_CBO CACHE_CB NOCACHE 4 256 8.1.5
CARDINALITY QKSFM_STATS CARDINALITY 14 272 9.0.0
CHANGE_DUPKEY_ERROR_INDEX QKSFM_DML CHANGE_DUPKEY_ERROR_INDEX 4 288 11.1.0.7
CHECK_ACL_REWRITE QKSFM_CHECK_ACL_REWRITE CHECK_ACL_REWRITE NO_CHECK_ACL_REWRITE 1 0 11.1.0.6
CHOOSE QKSFM_CHOOSE MODE 1 16 8.1.0
CLUSTER QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7
CLUSTERING QKSFM_CLUSTERING CLUSTERING NO_CLUSTERING 1 0 12.1.0.1 12.1.0.1
CLUSTER_BY_ROWID QKSFM_CBO CLUSTER_BY_ROWID NO_CLUSTER_BY_ROWID 4 272 12.1.0.1 12.1.0.1
COALESCE_SQ QKSFM_COALESCE_SQ COALESCE_SQ NO_COALESCE_SQ 2 16 11.2.0.1 11.2.0.1
COLUMN_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
CONNECT_BY_CB_WHR_ONLY QKSFM_TRANSFORMATION CONNECT_BY_CB_WHR_ONLY NO_CONNECT_BY_CB_WHR_ONLY 2 16 10.2.0.5 10.2.0.5
CONNECT_BY_COMBINE_SW QKSFM_ALL CONNECT_BY_COMBINE_SW NO_CONNECT_BY_COMBINE_SW 2 16 10.2.0.4 10.2.0.4
CONNECT_BY_COST_BASED QKSFM_TRANSFORMATION CONNECT_BY_COST_BASED NO_CONNECT_BY_COST_BASED 2 16 10.2.0.2 10.2.0.2
CONNECT_BY_ELIM_DUPS QKSFM_ALL CONNECT_BY_ELIM_DUPS NO_CONNECT_BY_ELIM_DUPS 2 16 11.2.0.1 11.2.0.1
CONNECT_BY_FILTERING QKSFM_ALL CONNECT_BY_FILTERING NO_CONNECT_BY_FILTERING 2 16 10.2.0.2 10.2.0.2
CONTAINERS QKSFM_ALL CONTAINERS 1 0 12.2.0.1 12.2.0.1
COST_XML_QUERY_REWRITE QKSFM_COST_XML_QUERY_REWRITE COST_XML_QUERY_REWRITE NO_COST_XML_QUERY_REWRITE 1 0 11.1.0.6 11.1.0.6
CPU_COSTING QKSFM_CPU_COSTING CPU_COSTING NO_CPU_COSTING 2 16 9.0.0
CUBE_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 12.1.0.1 12.1.0.1
CUBE_GB QKSFM_CBO CUBE_GB 2 0 8.1.5
CUBE_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 12.1.0.1 12.1.0.1
CURRENT_INSTANCE QKSFM_ALL CURRENT_INSTANCE 1 0 18.1.0
CURSOR_SHARING_EXACT QKSFM_CBO CURSOR_SHARING_EXACT 2 0 9.0.0
DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS NO_DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
DATA_SECURITY_REWRITE_LIMIT QKSFM_DATA_SECURITY_REWRITE DATA_SECURITY_REWRITE_LIMIT NO_DATA_SECURITY_REWRITE 1 0 12.1.0.1 12.1.0.1
DATA_VALIDATE QKSFM_EXECUTION DATA_VALIDATE 1 0 12.2.0.1
DBMS_STATS QKSFM_DBMS_STATS DBMS_STATS 1 0 10.2.0.1
DB_VERSION QKSFM_ALL DB_VERSION 1 272 11.1.0.6 11.1.0.6
DECORRELATE QKSFM_DECORRELATE DECORRELATE NO_DECORRELATE 2 16 12.1.0.1 12.1.0.1
DENORM_AV QKSFM_COMPILATION DENORM_AV 2 0 20.1.0
DEREF_NO_REWRITE QKSFM_ALL DEREF_NO_REWRITE 1 0 8.1.0
DISABLE_PARALLEL_DML QKSFM_DML ENABLE_PARALLEL_DML ENABLE_PARALLEL_DML 1 0 11.2.0.4
DIST_AGG_PROLLUP_PUSHDOWN QKSFM_PQ DIST_AGG_PROLLUP_PUSHDOWN NO_DIST_AGG_PROLLUP_PUSHDOWN 2 16 12.2.0.1 12.2.0.1
DML_UPDATE QKSFM_CBO DML_UPDATE 1 0 9.0.0
DOMAIN_INDEX_FILTER QKSFM_CBO DOMAIN_INDEX_FILTER NO_DOMAIN_INDEX_FILTER 4 304 11.1.0.6 11.1.0.6
DOMAIN_INDEX_NO_SORT QKSFM_CBO DOMAIN_INDEX_SORT DOMAIN_INDEX_SORT 2 0 8.1.5 10.2.0.1
DOMAIN_INDEX_SORT QKSFM_CBO DOMAIN_INDEX_SORT DOMAIN_INDEX_NO_SORT 2 0 8.1.5 10.2.0.1
DRIVING_SITE QKSFM_ALL DRIVING_SITE 4 256 8.1.0 8.1.7
DST_UPGRADE_INSERT_CONV QKSFM_ALL DST_UPGRADE_INSERT_CONV NO_DST_UPGRADE_INSERT_CONV 1 0 11.2.0.1
DYNAMIC_SAMPLING QKSFM_DYNAMIC_SAMPLING DYNAMIC_SAMPLING 6 272 9.2.0
DYNAMIC_SAMPLING_EST_CDN QKSFM_DYNAMIC_SAMPLING_EST_CDN DYNAMIC_SAMPLING_EST_CDN 4 272 9.2.0
ELIMINATE_JOIN QKSFM_TABLE_ELIM ELIMINATE_JOIN NO_ELIMINATE_JOIN 4 16 10.2.0.1 10.2.0.1
ELIMINATE_OBY QKSFM_OBYE ELIMINATE_OBY NO_ELIMINATE_OBY 2 16 10.2.0.1 10.2.0.1
ELIMINATE_SQ QKSFM_ELIMINATE_SQ ELIMINATE_SQ NO_ELIMINATE_SQ 2 16 12.2.0.1 12.2.0.1
ELIM_GROUPBY QKSFM_TRANSFORMATION ELIM_GROUPBY NO_ELIM_GROUPBY 2 16 12.1.0.2 12.1.0.2
ENABLE_PARALLEL_DML QKSFM_DML ENABLE_PARALLEL_DML DISABLE_PARALLEL_DML 1 0 11.2.0.4
EXPAND_GSET_TO_UNION QKSFM_TRANSFORMATION EXPAND_GSET_TO_UNION NO_EXPAND_GSET_TO_UNION 2 0 9.2.0 10.1.0
EXPAND_TABLE QKSFM_TABLE_EXPANSION EXPAND_TABLE NO_EXPAND_TABLE 4 16 11.2.0.1 11.2.0.1
EXPR_CORR_CHECK QKSFM_CBO EXPR_CORR_CHECK 1 0 8.0.0
FACT QKSFM_STAR_TRANS FACT NO_FACT 4 272 8.1.0 8.1.7
FACTORIZE_JOIN QKSFM_JOINFAC FACTORIZE_JOIN NO_FACTORIZE_JOIN 2 16 11.2.0.1 11.2.0.1
FBTSCAN QKSFM_CBO FBTSCAN 1 0 10.1.0.3
FIRST_ROWS QKSFM_FIRST_ROWS MODE 1 16 8.1.0 10.2.0.1
FORCE_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM NO_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
FORCE_XML_QUERY_REWRITE QKSFM_XML_REWRITE FORCE_XML_QUERY_REWRITE NO_XML_QUERY_REWRITE 1 0 9.2.0 11.1.0.6
FRESH_MV QKSFM_MVIEWS FRESH_MV 1 0 12.2.0.1
FULL QKSFM_FULL ACCESS 4 272 8.1.0 8.1.7
FULL_OUTER_JOIN_TO_OUTER QKSFM_CBO FULL_OUTER_JOIN_TO_OUTER NO_FULL_OUTER_JOIN_TO_OUTER 4 272 11.2.0.3 11.2.0.3
GATHER_OPTIMIZER_STATISTICS QKSFM_DBMS_STATS GATHER_OPTIMIZER_STATISTICS NO_GATHER_OPTIMIZER_STATISTICS 1 0 12.1.0.1
GATHER_PLAN_STATISTICS QKSFM_GATHER_PLAN_STATISTICS GATHER_PLAN_STATISTICS 1 0 10.1.0.3
GBY_CONC_ROLLUP QKSFM_TRANSFORMATION GBY_CONC_ROLLUP 2 0 9.0.0
GBY_PUSHDOWN QKSFM_ALL GBY_PUSHDOWN NO_GBY_PUSHDOWN 2 16 10.2.0.5 10.2.0.5
HASH QKSFM_ALL ACCESS 4 272 8.1.0 8.1.7
HASHSET_BUILD QKSFM_EXECUTION HASHSET_BUILD 2 16 21.1.0 21.1.0
HASH_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.1.0 8.1.7
HASH_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.1.0 8.1.7
HWM_BROKERED QKSFM_CBO HWM_BROKERED 2 0 9.0.0
IGNORE_OPTIM_EMBEDDED_HINTS QKSFM_ALL IGNORE_OPTIM_EMBEDDED_HINTS 1 0 10.1.0.3 10.2.0.1
IGNORE_ROW_ON_DUPKEY_INDEX QKSFM_DML IGNORE_ROW_ON_DUPKEY_INDEX 4 288 11.1.0.7
IGNORE_WHERE_CLAUSE QKSFM_ALL IGNORE_WHERE_CLAUSE 1 0 9.2.0
INCLUDE_VERSION QKSFM_ALL INCLUDE_VERSION 1 0 10.1.0.3
INDEX QKSFM_INDEX ACCESS NO_INDEX 4 304 8.0.0 8.1.7
INDEX_ASC QKSFM_INDEX_ASC ACCESS NO_INDEX 4 304 8.1.0
INDEX_COMBINE QKSFM_INDEX_COMBINE ACCESS 4 432 8.1.0 8.1.7
INDEX_DESC QKSFM_INDEX_DESC ACCESS NO_INDEX 4 304 8.1.0 8.1.7
INDEX_FFS QKSFM_INDEX_FFS ACCESS 4 304 8.1.0 8.1.7
INDEX_JOIN QKSFM_INDEX_JOIN ACCESS 4 304 8.1.5 10.1.0.3
INDEX_RRS QKSFM_CBO ACCESS 4 304 9.0.0
INDEX_RS_ASC QKSFM_INDEX_RS_ASC ACCESS 4 304 11.1.0.6 11.1.0.6
INDEX_RS_DESC QKSFM_INDEX_RS_DESC ACCESS 4 304 11.1.0.6 11.1.0.6
INDEX_SS QKSFM_INDEX_SS ACCESS NO_INDEX_SS 4 304 9.0.0 10.2.0.1
INDEX_SS_ASC QKSFM_INDEX_SS_ASC ACCESS NO_INDEX_SS 4 304 9.0.0
INDEX_SS_DESC QKSFM_INDEX_SS_DESC ACCESS NO_INDEX_SS 4 304 9.0.0 10.2.0.1
INDEX_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
INLINE QKSFM_TRANSFORMATION INLINE MATERIALIZE 2 16 9.0.0 18.1.0
INLINE_XMLTYPE_NT QKSFM_ALL INLINE_XMLTYPE_NT 1 0 10.2.0.1
INMEMORY QKSFM_EXECUTION INMEMORY NO_INMEMORY 6 64 12.1.0.2 12.1.0.2
INMEMORY_PRUNING QKSFM_EXECUTION INMEMORY_PRUNING NO_INMEMORY_PRUNING 6 64 12.1.0.2 12.1.0.2
JSON_LENGTH QKSFM_EXECUTION JSON_LENGTH 1 0 19.1.0
LEADING QKSFM_JOIN_ORDER LEADING 8 272 8.1.6 10.1.0.3
LOCAL_INDEXES QKSFM_CBO LOCAL_INDEXES 2 0 9.0.0
MATERIALIZE QKSFM_TRANSFORMATION INLINE INLINE 2 16 9.0.0 18.1.0
MEMOPTIMIZE_WRITE QKSFM_EXECUTION MEMOPTIMIZE_WRITE 1 0 18.1.0
MERGE QKSFM_CVM MERGE NO_MERGE 6 16 8.1.0 10.1.0
MERGE_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.1.0 8.1.7
MERGE_CONST_ON QKSFM_CBO MERGE_CONST_ON 1 0 8.0.0
MERGE_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.1.0 8.1.7
MODEL_COMPILE_SUBQUERY QKSFM_TRANSFORMATION MODEL_COMPILE_SUBQUERY 2 0 10.2.0.1
MODEL_DONTVERIFY_UNIQUENESS QKSFM_TRANSFORMATION MODEL_DONTVERIFY_UNIQUENESS 2 0 10.1.0.3
MODEL_DYNAMIC_SUBQUERY QKSFM_TRANSFORMATION MODEL_DYNAMIC_SUBQUERY 2 0 10.2.0.1
MODEL_MIN_ANALYSIS QKSFM_TRANSFORMATION MODEL_MIN_ANALYSIS 2 0 10.1.0.3
MODEL_NO_ANALYSIS QKSFM_ALL MODEL_MIN_ANALYSIS 2 0 10.1.0.3
MODEL_PUSH_REF QKSFM_TRANSFORMATION MODEL_PUSH_REF NO_MODEL_PUSH_REF 2 0 10.1.0.3
MONITOR QKSFM_ALL MONITOR NO_MONITOR 1 0 11.1.0.6
MV_MERGE QKSFM_TRANSFORMATION MV_MERGE 2 0 9.0.0
NATIVE_FULL_OUTER_JOIN QKSFM_ALL NATIVE_FULL_OUTER_JOIN NO_NATIVE_FULL_OUTER_JOIN 2 16 10.2.0.3 10.2.0.3
NESTED_TABLE_FAST_INSERT QKSFM_ALL NESTED_TABLE_FAST_INSERT 1 0 10.1.0.3
NESTED_TABLE_GET_REFS QKSFM_ALL NESTED_TABLE_GET_REFS 1 0 8.1.0
NESTED_TABLE_SET_SETID QKSFM_ALL NESTED_TABLE_SET_SETID 1 0 8.1.5
NLJ_BATCHING QKSFM_EXECUTION ACCESS NO_NLJ_BATCHING 4 272 11.1.0.6 11.1.0.6
NLJ_PREFETCH QKSFM_EXECUTION NLJ_PREFETCH NO_NLJ_PREFETCH 4 272 11.1.0.6 11.1.0.6
NL_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.0.0
NL_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.0.0
NOAPPEND QKSFM_CBO APPEND APPEND 1 0 8.1.0
NOCACHE QKSFM_EXECUTION CACHE CACHE 4 256 8.1.0
NOPARALLEL QKSFM_PARALLEL SHARED SHARED 5 256 8.1.0
NO_ACCESS QKSFM_ALL NO_ACCESS 4 256 8.1.5 8.1.7
NO_ADAPTIVE_PLAN QKSFM_ADAPTIVE_PLAN ADAPTIVE_PLAN ADAPTIVE_PLAN 1 16 12.1.0.2 12.1.0.2
NO_ANSI_REARCH QKSFM_ANSI_REARCH ANSI_REARCH ANSI_REARCH 2 16 12.1.0.2 12.1.0.2
NO_ANSWER_QUERY_USING_STATS QKSFM_ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS 2 16 18.1.0 18.1.0
NO_AUTO_REOPTIMIZE QKSFM_AUTO_REOPT AUTO_REOPTIMIZE AUTO_REOPTIMIZE 1 0 12.1.0.1
NO_BASETABLE_MULTIMV_REWRITE QKSFM_ALL REWRITE REWRITE 2 16 10.1.0.3 10.1.0.3
NO_BATCH_TABLE_ACCESS_BY_ROWID QKSFM_EXECUTION BATCH_TABLE_ACCESS_BY_ROWID BATCH_TABLE_ACCESS_BY_ROWID 4 272 12.1.0.1 12.1.0.1
NO_BIND_AWARE QKSFM_CURSOR_SHARING BIND_AWARE BIND_AWARE 1 0 11.1.0.7
NO_BUFFER QKSFM_CBO BUFFER BUFFER 2 0 8.1.5
NO_BUSHY_JOIN QKSFM_BUSHY_JOIN BUSHY_JOIN BUSHY_JOIN 2 16 12.2.0.1 12.2.0.1
NO_CARTESIAN QKSFM_ALL NO_CARTESIAN 4 336 10.2.0.1
NO_CHECK_ACL_REWRITE QKSFM_CHECK_ACL_REWRITE NO_CHECK_ACL_REWRITE CHECK_ACL_REWRITE 1 0 11.1.0.6
NO_CLUSTERING QKSFM_CLUSTERING CLUSTERING CLUSTERING 1 0 12.1.0.1 12.1.0.1
NO_CLUSTER_BY_ROWID QKSFM_CBO CLUSTER_BY_ROWID CLUSTER_BY_ROWID 4 272 12.1.0.1 12.1.0.1
NO_COALESCE_SQ QKSFM_COALESCE_SQ COALESCE_SQ COALESCE_SQ 2 16 11.2.0.1 11.2.0.1
NO_CONNECT_BY_CB_WHR_ONLY QKSFM_TRANSFORMATION CONNECT_BY_CB_WHR_ONLY CONNECT_BY_CB_WHR_ONLY 2 16 10.2.0.5 10.2.0.5
NO_CONNECT_BY_COMBINE_SW QKSFM_ALL CONNECT_BY_COMBINE_SW CONNECT_BY_COMBINE_SW 2 16 10.2.0.4 10.2.0.4
NO_CONNECT_BY_COST_BASED QKSFM_TRANSFORMATION CONNECT_BY_COST_BASED CONNECT_BY_COST_BASED 2 16 10.2.0.2 10.2.0.2
NO_CONNECT_BY_ELIM_DUPS QKSFM_ALL CONNECT_BY_ELIM_DUPS CONNECT_BY_ELIM_DUPS 2 16 11.2.0.1 11.2.0.1
NO_CONNECT_BY_FILTERING QKSFM_ALL CONNECT_BY_FILTERING CONNECT_BY_FILTERING 2 16 10.2.0.2 10.2.0.2
NO_COST_XML_QUERY_REWRITE QKSFM_COST_XML_QUERY_REWRITE NO_COST_XML_QUERY_REWRITE COST_XML_QUERY_REWRITE 1 0 11.1.0.6 11.1.0.6
NO_CPU_COSTING QKSFM_CPU_COSTING CPU_COSTING CPU_COSTING 2 16 9.0.0
NO_DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
NO_DATA_SECURITY_REWRITE QKSFM_DATA_SECURITY_REWRITE DATA_SECURITY_REWRITE_LIMIT DATA_SECURITY_REWRITE_LIMIT 1 0 12.1.0.1 12.1.0.1
NO_DECORRELATE QKSFM_DECORRELATE DECORRELATE DECORRELATE 2 16 12.1.0.1 12.1.0.1
NO_DIST_AGG_PROLLUP_PUSHDOWN QKSFM_PQ DIST_AGG_PROLLUP_PUSHDOWN DIST_AGG_PROLLUP_PUSHDOWN 2 16 12.2.0.1 12.2.0.1
NO_DOMAIN_INDEX_FILTER QKSFM_CBO NO_DOMAIN_INDEX_FILTER DOMAIN_INDEX_FILTER 4 304 11.1.0.6 11.1.0.6
NO_DST_UPGRADE_INSERT_CONV QKSFM_ALL DST_UPGRADE_INSERT_CONV DST_UPGRADE_INSERT_CONV 1 0 11.2.0.1 21.1.0.1
NO_ELIMINATE_JOIN QKSFM_TABLE_ELIM ELIMINATE_JOIN ELIMINATE_JOIN 4 16 10.2.0.1 10.2.0.1
NO_ELIMINATE_OBY QKSFM_OBYE ELIMINATE_OBY ELIMINATE_OBY 2 16 10.2.0.1 10.2.0.1
NO_ELIMINATE_SQ QKSFM_ELIMINATE_SQ ELIMINATE_SQ ELIMINATE_SQ 2 16 12.2.0.1 12.2.0.1
NO_ELIM_GROUPBY QKSFM_TRANSFORMATION ELIM_GROUPBY ELIM_GROUPBY 2 16 12.1.0.2 12.1.0.2
NO_EXPAND QKSFM_USE_CONCAT OR_EXPAND USE_CONCAT 2 16 8.1.0 8.1.7
NO_EXPAND_GSET_TO_UNION QKSFM_TRANSFORMATION EXPAND_GSET_TO_UNION EXPAND_GSET_TO_UNION 2 0 9.2.0 10.1.0
NO_EXPAND_TABLE QKSFM_TABLE_EXPANSION EXPAND_TABLE EXPAND_TABLE 4 16 11.2.0.1 11.2.0.1
NO_FACT QKSFM_STAR_TRANS FACT FACT 4 272 8.1.0 8.1.7
NO_FACTORIZE_JOIN QKSFM_JOINFAC FACTORIZE_JOIN FACTORIZE_JOIN 2 16 11.2.0.1 11.2.0.1
NO_FULL_OUTER_JOIN_TO_OUTER QKSFM_CBO FULL_OUTER_JOIN_TO_OUTER FULL_OUTER_JOIN_TO_OUTER 4 272 11.2.0.3 11.2.0.3
NO_GATHER_OPTIMIZER_STATISTICS QKSFM_DBMS_STATS GATHER_OPTIMIZER_STATISTICS GATHER_OPTIMIZER_STATISTICS 1 0 12.1.0.1
NO_GBY_PUSHDOWN QKSFM_ALL GBY_PUSHDOWN GBY_PUSHDOWN 2 16 10.2.0.5 10.2.0.5
NO_INDEX QKSFM_INDEX NO_INDEX INDEX 4 304 8.1.5 8.1.7
NO_INDEX_FFS QKSFM_INDEX_FFS NO_INDEX_FFS INDEX_FFS 4 304 10.1.0.3 10.1.0.3
NO_INDEX_SS QKSFM_INDEX_SS NO_INDEX_SS INDEX_SS 4 304 10.1.0.3 10.1.0.3
NO_INMEMORY QKSFM_EXECUTION INMEMORY INMEMORY 6 64 12.1.0.2 12.1.0.2
NO_INMEMORY_PRUNING QKSFM_EXECUTION INMEMORY_PRUNING INMEMORY_PRUNING 6 64 12.1.0.2 12.1.0.2
NO_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM FORCE_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
NO_LOAD QKSFM_EXECUTION NO_LOAD 1 0 11.1.0.6
NO_MERGE QKSFM_CVM MERGE MERGE 6 16 8.0.0 10.1.0
NO_MODEL_PUSH_REF QKSFM_ALL MODEL_PUSH_REF MODEL_PUSH_REF 2 0 10.1.0.3
NO_MONITOR QKSFM_ALL MONITOR MONITOR 1 0 11.1.0.6
NO_MONITORING QKSFM_ALL NO_MONITORING 1 0 8.0.0
NO_MULTIMV_REWRITE QKSFM_ALL REWRITE REWRITE 2 16 10.1.0.3 10.1.0.3
NO_NATIVE_FULL_OUTER_JOIN QKSFM_ALL NATIVE_FULL_OUTER_JOIN NATIVE_FULL_OUTER_JOIN 2 16 10.2.0.3 10.2.0.3
NO_NLJ_BATCHING QKSFM_EXECUTION ACCESS NLJ_BATCHING 4 272 11.1.0.6 11.1.0.6
NO_NLJ_PREFETCH QKSFM_EXECUTION NLJ_PREFETCH NLJ_PREFETCH 4 272 11.1.0.6 11.1.0.6
NO_OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
NO_ORDER_ROLLUPS QKSFM_TRANSFORMATION NO_ORDER_ROLLUPS 2 0 8.0.0
NO_OR_EXPAND QKSFM_CBQT_OR_EXPANSION OR_EXPAND OR_EXPAND 2 16 12.2.0.1 12.2.0.1
NO_OUTER_JOIN_TO_ANTI QKSFM_CBO OUTER_JOIN_TO_ANTI OUTER_JOIN_TO_ANTI 4 272 11.2.0.3 11.2.0.3
NO_OUTER_JOIN_TO_INNER QKSFM_OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER 6 16 11.1.0.6 11.1.0.6
NO_PARALLEL QKSFM_CBO SHARED SHARED 5 256 10.1.0.3
NO_PARALLEL_INDEX QKSFM_PQ PARALLEL_INDEX PARALLEL_INDEX 4 288 8.1.0
NO_PARTIAL_COMMIT QKSFM_CBO NO_PARTIAL_COMMIT 1 0 10.1.0.3
NO_PARTIAL_JOIN QKSFM_PARTIAL_JOIN PARTIAL_JOIN PARTIAL_JOIN 4 272 12.1.0.1 12.1.0.1
NO_PARTIAL_ROLLUP_PUSHDOWN QKSFM_PQ PARTIAL_ROLLUP_PUSHDOWN PARTIAL_ROLLUP_PUSHDOWN 2 16 12.1.0.1 12.1.0.1
NO_PLACE_DISTINCT QKSFM_DIST_PLCMT PLACE_DISTINCT PLACE_DISTINCT 2 16 11.2.0.1 11.2.0.1
NO_PLACE_GROUP_BY QKSFM_PLACE_GROUP_BY PLACE_GROUP_BY PLACE_GROUP_BY 2 16 11.1.0.6 11.1.0.6
NO_PQ_CONCURRENT_UNION QKSFM_PQ PQ_CONCURRENT_UNION PQ_CONCURRENT_UNION 3 0 12.1.0.1 12.1.0.1
NO_PQ_EXPAND_TABLE QKSFM_TABLE_EXPANSION PQ_EXPAND_TABLE PQ_EXPAND_TABLE 4 16 19.1.0 19.1.0
NO_PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0
NO_PQ_REPLICATE QKSFM_PQ_REPLICATE PQ_REPLICATE PQ_REPLICATE 4 272 12.1.0.1 12.1.0.1
NO_PQ_SKEW QKSFM_PQ PQ_SKEW PQ_SKEW 4 272 12.1.0.1 12.1.0.1
NO_PRUNE_GSETS QKSFM_TRANSFORMATION NO_PRUNE_GSETS 2 0 9.0.0
NO_PULL_PRED QKSFM_PULL_PRED PULL_PRED PULL_PRED 4 16 10.2.0.1 10.2.0.1
NO_PUSH_HAVING_TO_GBY QKSFM_EXECUTION PUSH_HAVING_TO_GBY PUSH_HAVING_TO_GBY 2 0 18.1.0 18.1.0
NO_PUSH_PRED QKSFM_FILTER_PUSH_PRED PUSH_PRED PUSH_PRED 6 16 8.1.0 8.1.5
NO_PUSH_SUBQ QKSFM_TRANSFORMATION PUSH_SUBQ PUSH_SUBQ 2 16 9.2.0 10.2.0.5
NO_PX_FAULT_TOLERANCE QKSFM_PQ PX_FAULT_TOLERANCE PX_FAULT_TOLERANCE 1 0 12.1.0.1 12.1.0.1
NO_PX_JOIN_FILTER QKSFM_PX_JOIN_FILTER PX_JOIN_FILTER PX_JOIN_FILTER 4 336 10.2.0.1 11.1.0.6
NO_QKN_BUFF QKSFM_CBO NO_QKN_BUFF 2 0 9.2.0
NO_QUERY_TRANSFORMATION QKSFM_TRANSFORMATION NO_QUERY_TRANSFORMATION 1 16 10.1.0.3
NO_REF_CASCADE QKSFM_CBO REF_CASCADE_CURSOR REF_CASCADE_CURSOR 1 0 9.2.0
NO_REORDER_WIF QKSFM_PARTITION REORDER_WIF REORDER_WIF 2 0 18.1.0 18.1.0
NO_RESULT_CACHE QKSFM_EXECUTION RESULT_CACHE RESULT_CACHE 2 0 11.1.0.6
NO_REWRITE QKSFM_TRANSFORMATION REWRITE REWRITE 2 16 8.1.5 8.1.7
NO_SEMIJOIN QKSFM_TRANSFORMATION SEMIJOIN SEMIJOIN 2 16 9.0.0
NO_SEMI_TO_INNER QKSFM_CBO NO_SEMI_TO_INNER SEMI_TO_INNER 4 272 11.2.0.3 11.2.0.3
NO_SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
NO_SET_TO_JOIN QKSFM_SET_TO_JOIN SET_TO_JOIN SET_TO_JOIN 2 16 10.1.0.3 10.1.0.3
NO_SQL_TUNE QKSFM_ALL NO_SQL_TUNE 1 0 10.2.0.1
NO_STAR_TRANSFORMATION QKSFM_STAR_TRANS STAR_TRANSFORMATION STAR_TRANSFORMATION 6 16 10.1.0.3 10.1.0.3
NO_STATEMENT_QUEUING QKSFM_PARALLEL STATEMENT_QUEUING STATEMENT_QUEUING 1 0 11.2.0.1
NO_STATS_GSETS QKSFM_ALL NO_STATS_GSETS 2 0 8.0.0
NO_SUBQUERY_PRUNING QKSFM_CBO SUBQUERY_PRUNING SUBQUERY_PRUNING 4 272 11.1.0.6 11.1.0.6
NO_SUBSTRB_PAD QKSFM_EXECUTION NO_SUBSTRB_PAD 1 0 11.2.0.1
NO_SWAP_JOIN_INPUTS QKSFM_CBO SWAP_JOIN_INPUTS SWAP_JOIN_INPUTS 4 272 10.1.0.3 10.1.0.3
NO_TABLE_LOOKUP_BY_NL QKSFM_TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL 4 16 11.2.0.2 11.2.0.2
NO_TRANSFORM_DISTINCT_AGG QKSFM_TRANSFORMATION TRANSFORM_DISTINCT_AGG TRANSFORM_DISTINCT_AGG 2 0 11.2.0.1 11.2.0.1
NO_UNNEST QKSFM_UNNEST UNNEST UNNEST 2 16 8.1.6 10.1.0
NO_USE_CUBE QKSFM_USE_CUBE JOIN USE_CUBE 4 336 12.1.0.1 12.1.0.1
NO_USE_DAGG_UNION_ALL_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS USE_DAGG_UNION_ALL_GSETS 2 0 12.2.0.1 12.2.0.1
NO_USE_HASH QKSFM_USE_HASH NO_USE_HASH USE_HASH 4 336 10.1.0.3 10.1.0.3
NO_USE_HASH_AGGREGATION QKSFM_ALL USE_HASH_AGGREGATION USE_HASH_AGGREGATION 2 0 10.2.0.1 10.2.0.5
NO_USE_HASH_GBY_FOR_DAGGPSHD QKSFM_ALL USE_HASH_GBY_FOR_DAGGPSHD USE_HASH_GBY_FOR_DAGGPSHD 2 0 12.2.0.1 12.2.0.1
NO_USE_HASH_GBY_FOR_PUSHDOWN QKSFM_ALL USE_HASH_GBY_FOR_PUSHDOWN USE_HASH_GBY_FOR_PUSHDOWN 2 0 11.2.0.2 11.2.0.2
NO_USE_INVISIBLE_INDEXES QKSFM_INDEX USE_INVISIBLE_INDEXES USE_INVISIBLE_INDEXES 1 0 11.1.0.6 11.1.0.6
NO_USE_MERGE QKSFM_USE_MERGE NO_USE_MERGE USE_MERGE 4 336 10.1.0.3 10.1.0.3
NO_USE_NL QKSFM_USE_NL NO_USE_NL USE_NL 4 336 10.1.0.3 10.1.0.3
NO_USE_PARTITION_WISE_DISTINCT QKSFM_PARTITION USE_PARTITION_WISE_DISTINCT USE_PARTITION_WISE_DISTINCT 2 0 12.2.0.1 12.2.0.1
NO_USE_PARTITION_WISE_GBY QKSFM_PARTITION USE_PARTITION_WISE_GBY USE_PARTITION_WISE_GBY 2 0 12.2.0.1 12.2.0.1
NO_USE_PARTITION_WISE_WIF QKSFM_PARTITION USE_PARTITION_WISE_WIF USE_PARTITION_WISE_WIF 2 0 18.1.0 18.1.0
NO_USE_SCALABLE_GBY_INVDIST QKSFM_PQ USE_SCALABLE_GBY_INVDIST USE_SCALABLE_GBY_INVDIST 2 0 19.1.0 19.1.0
NO_USE_VECTOR_AGGREGATION QKSFM_VECTOR_AGG USE_VECTOR_AGGREGATION USE_VECTOR_AGGREGATION 2 16 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM QKSFM_VECTOR_AGG VECTOR_TRANSFORM VECTOR_TRANSFORM 2 16 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM_DIMS QKSFM_VECTOR_AGG VECTOR_TRANSFORM_DIMS VECTOR_TRANSFORM_DIMS 4 80 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM_FACT QKSFM_VECTOR_AGG VECTOR_TRANSFORM_FACT VECTOR_TRANSFORM_FACT 4 80 12.1.0.2 12.1.0.2
NO_XDB_FASTPATH_INSERT QKSFM_ALL XDB_FASTPATH_INSERT XDB_FASTPATH_INSERT 1 0 11.2.0.2
NO_XMLINDEX_REWRITE QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE XMLINDEX_REWRITE 1 0 11.1.0.6 11.1.0.6
NO_XMLINDEX_REWRITE_IN_SELECT QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE XMLINDEX_REWRITE_IN_SELECT 1 0 11.1.0.6 11.1.0.6
NO_XML_DML_REWRITE QKSFM_XML_REWRITE NO_XML_DML_REWRITE 1 0 10.2.0.1 11.1.0.6
NO_XML_QUERY_REWRITE QKSFM_XML_REWRITE FORCE_XML_QUERY_REWRITE FORCE_XML_QUERY_REWRITE 1 0 9.2.0 11.1.0.6
NO_ZONEMAP QKSFM_ZONEMAP ZONEMAP ZONEMAP 4 256 12.1.0.1 12.1.0.1
NUM_INDEX_KEYS QKSFM_CBO ACCESS 4 304 10.2.0.3 10.2.0.3
OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE NO_OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
OLD_PUSH_PRED QKSFM_OLD_PUSH_PRED OLD_PUSH_PRED 6 16 10.2.0.1 10.2.0.1
OPAQUE_TRANSFORM QKSFM_TRANSFORMATION OPAQUE_TRANSFORM 1 0 10.1.0.3
OPAQUE_XCANONICAL QKSFM_TRANSFORMATION OPAQUE_XCANONICAL 1 0 10.1.0.3
OPTIMIZER_FEATURES_ENABLE QKSFM_ALL OPTIMIZER_FEATURES_ENABLE 1 272 10.1.0.3 10.2.0.1
OPT_ESTIMATE QKSFM_OPT_ESTIMATE OPT_ESTIMATE 14 272 10.1.0.3
OPT_PARAM QKSFM_ALL OPT_PARAM 1 272 10.2.0.1 10.2.0.1
ORDERED QKSFM_CBO ORDERED 2 16 8.1.0 8.1.7
ORDERED_PREDICATES QKSFM_CBO ORDERED_PREDICATES 2 16 8.0.0
ORDER_KEY_VECTOR_USE QKSFM_VECTOR_AGG ORDER_KEY_VECTOR_USE 2 272 21.1.0 21.1.0
ORDER_SUBQ QKSFM_TRANSFORMATION ORDER_SUBQ 2 16 12.2.0.1 12.2.0.1
OR_EXPAND QKSFM_CBQT_OR_EXPANSION OR_EXPAND NO_OR_EXPAND 2 16 12.2.0.1 12.2.0.1
OSON_GET_CONTENT QKSFM_JSON OSON_GET_CONTENT 1 0 21.1.0 21.1.0
OUTER_JOIN_TO_ANTI QKSFM_CBO OUTER_JOIN_TO_ANTI NO_OUTER_JOIN_TO_ANTI 4 272 11.2.0.3 11.2.0.3
OUTER_JOIN_TO_INNER QKSFM_OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER NO_OUTER_JOIN_TO_INNER 6 16 11.1.0.6 11.1.0.6
OUTLINE QKSFM_ALL OUTLINE 2 0 10.2.0.1 10.2.0.1
OUTLINE_LEAF QKSFM_ALL OUTLINE_LEAF 2 0 10.2.0.1 10.2.0.1
OVERFLOW_NOMOVE QKSFM_CBO OVERFLOW_NOMOVE 2 0 9.0.0
PARALLEL_INDEX QKSFM_PQ PARALLEL_INDEX NO_PARALLEL_INDEX 4 288 8.1.0
PARTIAL_JOIN QKSFM_PARTIAL_JOIN PARTIAL_JOIN NO_PARTIAL_JOIN 4 272 12.1.0.1 12.1.0.1
PARTIAL_ROLLUP_PUSHDOWN QKSFM_PQ PARTIAL_ROLLUP_PUSHDOWN NO_PARTIAL_ROLLUP_PUSHDOWN 2 16 12.1.0.1 12.1.0.1
PDB_LOCAL_ONLY QKSFM_DML PDB_LOCAL_ONLY 1 0 18.1.0
PIV_GB QKSFM_ALL PIV_GB 2 0 8.1.0
PIV_SSF QKSFM_ALL PIV_SSF 2 0 8.1.0
PLACE_DISTINCT QKSFM_DIST_PLCMT PLACE_DISTINCT NO_PLACE_DISTINCT 2 16 11.2.0.1 11.2.0.1
PLACE_GROUP_BY QKSFM_PLACE_GROUP_BY PLACE_GROUP_BY NO_PLACE_GROUP_BY 2 16 11.1.0.6 11.1.0.6
PQ_CONCURRENT_UNION QKSFM_PQ PQ_CONCURRENT_UNION NO_PQ_CONCURRENT_UNION 3 0 12.1.0.1 12.1.0.1
PQ_DISTRIBUTE QKSFM_PQ_DISTRIBUTE PQ_DISTRIBUTE 4 272 8.1.5 8.1.7
PQ_DISTRIBUTE_WINDOW QKSFM_PQ PQ_DISTRIBUTE_WINDOW 2 16 12.1.0.1 12.1.0.1
PQ_EXPAND_TABLE QKSFM_TABLE_EXPANSION PQ_EXPAND_TABLE NO_PQ_EXPAND_TABLE 4 16 19.1.0 19.1.0
PQ_FILTER QKSFM_PQ PQ_FILTER 2 0 12.1.0.1 12.1.0.1
PQ_MAP QKSFM_PQ_MAP PQ_MAP PQ_NOMAP 4 272 9.0.0 10.2.0.1
PQ_NOMAP QKSFM_PQ_MAP PQ_MAP PQ_MAP 4 272 9.0.0 10.2.0.1
PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW NO_PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0
PQ_REPLICATE QKSFM_PQ_REPLICATE PQ_REPLICATE NO_PQ_REPLICATE 4 272 12.1.0.1 12.1.0.1
PQ_SKEW QKSFM_PQ PQ_SKEW NO_PQ_SKEW 4 272 12.1.0.1 12.1.0.1
PRECOMPUTE_SUBQUERY QKSFM_TRANSFORMATION PRECOMPUTE_SUBQUERY 2 0 10.2.0.1
PRESERVE_OID QKSFM_ALL PRESERVE_OID 1 0 10.2.0.1
PULL_PRED QKSFM_PULL_PRED PULL_PRED NO_PULL_PRED 4 16 10.2.0.1 10.2.0.1
PUSH_HAVING_TO_GBY QKSFM_EXECUTION PUSH_HAVING_TO_GBY NO_PUSH_HAVING_TO_GBY 2 0 18.1.0 18.1.0
PUSH_PRED QKSFM_FILTER_PUSH_PRED PUSH_PRED NO_PUSH_PRED 6 16 8.1.0 8.1.5
PUSH_SUBQ QKSFM_TRANSFORMATION PUSH_SUBQ NO_PUSH_SUBQ 2 16 8.1.0 10.2.0.5
PX_FAULT_TOLERANCE QKSFM_PQ PX_FAULT_TOLERANCE NO_PX_FAULT_TOLERANCE 1 0 12.1.0.1 12.1.0.1
PX_JOIN_FILTER QKSFM_PX_JOIN_FILTER PX_JOIN_FILTER NO_PX_JOIN_FILTER 4 336 10.2.0.1 11.1.0.6
QB_NAME QKSFM_ALL QB_NAME 2 256 10.1.0.3
QUARANTINE QKSFM_EXECUTION QUARANTINE 1 0 19.1.0
QUEUE_CURR QKSFM_CBO ACCESS 4 256 8.0.0
QUEUE_ROWP QKSFM_CBO ACCESS 4 256 8.0.0
RBO_OUTLINE QKSFM_RBO RBO_OUTLINE 1 0 10.2.0.1 10.2.0.1
REF_CASCADE_CURSOR QKSFM_CBO REF_CASCADE_CURSOR NO_REF_CASCADE 1 0 9.2.0
REMOTE_MAPPED QKSFM_ALL REMOTE_MAPPED 2 272 8.1.0
REORDER_WIF QKSFM_PARTITION REORDER_WIF NO_REORDER_WIF 2 0 18.1.0 18.1.0
RESERVOIR_SAMPLING QKSFM_EXECUTION RESERVOIR_SAMPLING 1 0 12.1.0.2
RESTORE_AS_INTERVALS QKSFM_CBO RESTORE_AS_INTERVALS 2 0 8.1.5
RESTRICT_ALL_REF_CONS QKSFM_ALL RESTRICT_ALL_REF_CONS 1 0 10.1.0.3
RESULT_CACHE QKSFM_EXECUTION RESULT_CACHE NO_RESULT_CACHE 2 0 11.1.0.6
RETRY_ON_ROW_CHANGE QKSFM_DML RETRY_ON_ROW_CHANGE 1 0 11.1.0.7
REWRITE QKSFM_TRANSFORMATION REWRITE NO_REWRITE 2 16 8.1.5 8.1.7
REWRITE_OR_ERROR QKSFM_TRANSFORMATION REWRITE 2 0 10.1.0.3
ROWID QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7
RULE QKSFM_RBO MODE 1 16 8.1.0 8.1.5
SAVE_AS_INTERVALS QKSFM_CBO SAVE_AS_INTERVALS 2 0 8.1.5
SCN_ASCENDING QKSFM_ALL SCN_ASCENDING 1 0 8.1.5
SEMIJOIN QKSFM_TRANSFORMATION SEMIJOIN NO_SEMIJOIN 2 16 9.0.0
SEMIJOIN_DRIVER QKSFM_CBO SEMIJOIN_DRIVER 2 16 8.1.0 8.1.7
SEMI_TO_INNER QKSFM_CBO SEMI_TO_INNER NO_SEMI_TO_INNER 4 272 11.2.0.3 11.2.0.3
SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN NO_SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
SET_TO_JOIN QKSFM_SET_TO_JOIN SET_TO_JOIN NO_SET_TO_JOIN 2 16 10.1.0.3 10.1.0.3
SHARED QKSFM_PARALLEL SHARED NO_PARALLEL 5 256 8.1.0
SKIP_EXT_OPTIMIZER QKSFM_CBO SKIP_EXT_OPTIMIZER 2 16 9.0.0
SKIP_PROXY QKSFM_ALL SKIP_PROXY 1 0 18.1.0
SKIP_UNQ_UNUSABLE_IDX QKSFM_CBO SKIP_UNQ_UNUSABLE_IDX 1 0 10.1.0.3
SQLLDR QKSFM_CBO SQLLDR 1 0 9.0.0
SQL_SCOPE QKSFM_COMPILATION SQL_SCOPE 1 0 12.2.0.1
STAR QKSFM_STAR_TRANS STAR 2 16 8.1.0
STAR_TRANSFORMATION QKSFM_STAR_TRANS STAR_TRANSFORMATION NO_STAR_TRANSFORMATION 6 16 8.1.0 8.1.7
STATEMENT_QUEUING QKSFM_PARALLEL STATEMENT_QUEUING NO_STATEMENT_QUEUING 1 0 11.2.0.1
STREAMS QKSFM_CBO STREAMS 1 0 10.1.0.3
SUBQUERY_PRUNING QKSFM_CBO SUBQUERY_PRUNING NO_SUBQUERY_PRUNING 4 272 11.1.0.6 11.1.0.6
SUPPRESS_LOAD QKSFM_DDL SUPPRESS_LOAD 1 0 18.1.0
SWAP_JOIN_INPUTS QKSFM_CBO SWAP_JOIN_INPUTS NO_SWAP_JOIN_INPUTS 4 272 8.1.0 8.1.7
SYSTEM_STATS QKSFM_ALL SYSTEM_STATS 1 272 18.1.0
SYS_DL_CURSOR QKSFM_CBO SYS_DL_CURSOR 1 0 9.2.0
SYS_PARALLEL_TXN QKSFM_CBO SYS_PARALLEL_TXN 2 0 8.1.6
SYS_RID_ORDER QKSFM_ALL SYS_RID_ORDER 2 0 9.2.0
TABLE_LOOKUP_BY_NL QKSFM_TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL NO_TABLE_LOOKUP_BY_NL 4 16 11.2.0.2 11.2.0.2
TABLE_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
TIV_GB QKSFM_ALL PIV_GB 2 0 8.1.0
TIV_SSF QKSFM_ALL PIV_SSF 2 0 8.1.0
TRACING QKSFM_EXECUTION TRACING 1 0 10.1.0.3
TRANSFORM_DISTINCT_AGG QKSFM_TRANSFORMATION TRANSFORM_DISTINCT_AGG NO_TRANSFORM_DISTINCT_AGG 2 0 11.2.0.1 11.2.0.1
UNNEST QKSFM_UNNEST UNNEST NO_UNNEST 2 16 8.1.6 10.1.0
USE_ANTI QKSFM_CBO USE_ANTI 4 272 8.1.0
USE_CONCAT QKSFM_USE_CONCAT OR_EXPAND NO_EXPAND 2 16 8.1.0 8.1.7
USE_CUBE QKSFM_USE_CUBE JOIN NO_USE_CUBE 4 336 12.1.0.1 12.1.0.1
USE_DAGG_UNION_ALL_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS NO_USE_DAGG_UNION_ALL_GSETS 2 0 12.2.0.1 12.2.0.1
USE_HASH QKSFM_USE_HASH JOIN NO_USE_HASH 4 464 8.1.0 8.1.7
USE_HASH_AGGREGATION QKSFM_ALL USE_HASH_AGGREGATION NO_USE_HASH_AGGREGATION 2 0 10.2.0.1 10.2.0.5
USE_HASH_GBY_FOR_DAGGPSHD QKSFM_ALL USE_HASH_GBY_FOR_DAGGPSHD NO_USE_HASH_GBY_FOR_DAGGPSHD 2 0 12.2.0.1 12.2.0.1
USE_HASH_GBY_FOR_PUSHDOWN QKSFM_ALL USE_HASH_GBY_FOR_PUSHDOWN NO_USE_HASH_GBY_FOR_PUSHDOWN 2 0 11.2.0.2 11.2.0.2
USE_HIDDEN_PARTITIONS QKSFM_PARTITION USE_HIDDEN_PARTITIONS 2 0 12.1.0.1
USE_INVISIBLE_INDEXES QKSFM_INDEX USE_INVISIBLE_INDEXES NO_USE_INVISIBLE_INDEXES 1 0 11.1.0.6 11.1.0.6
USE_MERGE QKSFM_USE_MERGE JOIN NO_USE_MERGE 4 336 8.1.0 8.1.7
USE_MERGE_CARTESIAN QKSFM_USE_MERGE_CARTESIAN JOIN 4 336 11.1.0.6 11.1.0.6
USE_NL QKSFM_USE_NL JOIN NO_USE_NL 4 336 8.1.0 8.1.7
USE_NL_WITH_INDEX QKSFM_USE_NL_WITH_INDEX USE_NL_WITH_INDEX NO_USE_NL 4 304 10.1.0.3
USE_PARTITION_WISE_DISTINCT QKSFM_PARTITION USE_PARTITION_WISE_DISTINCT NO_USE_PARTITION_WISE_DISTINCT 2 0 12.2.0.1 12.2.0.1
USE_PARTITION_WISE_GBY QKSFM_PARTITION USE_PARTITION_WISE_GBY NO_USE_PARTITION_WISE_GBY 2 0 12.2.0.1 12.2.0.1
USE_PARTITION_WISE_WIF QKSFM_PARTITION USE_PARTITION_WISE_WIF NO_USE_PARTITION_WISE_WIF 2 0 18.1.0 18.1.0
USE_SCALABLE_GBY_INVDIST QKSFM_PQ USE_SCALABLE_GBY_INVDIST NO_USE_SCALABLE_GBY_INVDIST 2 0 19.1.0 19.1.0
USE_SEMI QKSFM_CBO USE_SEMI 4 272 8.1.0
USE_TTT_FOR_GSETS QKSFM_TRANSFORMATION USE_TTT_FOR_GSETS 2 0 9.0.0
USE_VECTOR_AGGREGATION QKSFM_VECTOR_AGG USE_VECTOR_AGGREGATION NO_USE_VECTOR_AGGREGATION 2 16 12.1.0.2 12.1.0.2
USE_WEAK_NAME_RESL QKSFM_ALL USE_WEAK_NAME_RESL 1 0 10.1.0.3
VECTOR_READ QKSFM_CBO VECTOR_READ 1 0 10.1.0.3
VECTOR_READ_TRACE QKSFM_CBO VECTOR_READ_TRACE 1 0 10.1.0.3
VECTOR_TRANSFORM QKSFM_VECTOR_AGG VECTOR_TRANSFORM NO_VECTOR_TRANSFORM 2 16 12.1.0.2 12.1.0.2
VECTOR_TRANSFORM_DIMS QKSFM_VECTOR_AGG VECTOR_TRANSFORM_DIMS NO_VECTOR_TRANSFORM_DIMS 4 80 12.1.0.2 12.1.0.2
VECTOR_TRANSFORM_FACT QKSFM_VECTOR_AGG VECTOR_TRANSFORM_FACT NO_VECTOR_TRANSFORM_FACT 4 80 12.1.0.2 12.1.0.2
WITH_PLSQL QKSFM_ALL WITH_PLSQL 1 0 12.1.0.1
XDB_FASTPATH_INSERT QKSFM_ALL XDB_FASTPATH_INSERT NO_XDB_FASTPATH_INSERT 1 0 11.2.0.2
XMLINDEX_REWRITE QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE NO_XMLINDEX_REWRITE 1 0 11.1.0.6 11.1.0.6
XMLINDEX_REWRITE_IN_SELECT QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE NO_XMLINDEX_REWRITE_IN_SELECT 1 0 11.1.0.6 11.1.0.6
XMLINDEX_SEL_IDX_TBL QKSFM_ALL XMLINDEX_SEL_IDX_TBL 1 0 11.2.0.1
XMLTSET_DML_ENABLE QKSFM_ALL XMLTSET_DML_ENABLE 1 0 12.2.0.1
XML_DML_RWT_STMT QKSFM_XML_REWRITE XML_DML_RWT_STMT 1 0 11.1.0.6 11.1.0.6
X_DYN_PRUNE QKSFM_CBO X_DYN_PRUNE 2 0 10.1.0.3
ZONEMAP QKSFM_ZONEMAP ZONEMAP NO_ZONEMAP 4 256 12.1.0.1 12.1.0.1

388行が選択されました。

 

 

PostgreSQL / pg_hint_plan (22-Nov-2023時点)

pg_hint_planのヒントもOracleの影響を受けているところもあるけども、PostgreSQL独特の言い回しを使う傾向はありますよね。面白いのは、LEADINGヒントで(a b)みたいなペアで優先度を記述する部分。これかなり慣れが必要な気がします。

https://github.com/ossc-db/pg_hint_plan

https://github.com/ossc-db/pg_hint_plan/blob/master/docs/hint_list.md

Hint list

  Format Description
Scan method SeqScan(table) Forces sequential scan on the table.
  TidScan(table) Forces TID scan on the table.
  IndexScan(table[ index...]) Forces index scan on the table. Restricts to specified indexes if any.
  IndexOnlyScan(table[ index...]) Forces index-only scan on the table. Restricts to specified indexes if any. Index scan may be used if index-only scan is not available.
  BitmapScan(table[ index...]) Forces bitmap scan on the table. Restricts to specified indexes if any.
  IndexScanRegexp(table[ POSIX Regexp...])
IndexOnlyScanRegexp(table[ POSIX Regexp...])
BitmapScanRegexp(table[ POSIX Regexp...])
Forces index scan, index-only scan (For PostgreSQL 9.2 and later) or bitmap scan on the table. Restricts to indexes that matches the specified POSIX regular expression pattern.
  NoSeqScan(table) Forces to not do sequential scan on the table.
  NoTidScan(table) Forces to not do TID scan on the table.
  NoIndexScan(table) Forces to not do index scan and index-only scan on the table.
  NoIndexOnlyScan(table) Forces to not do index only scan on the table.
  NoBitmapScan(table) Forces to not do bitmap scan on the table.
Join method NestLoop(table table[ table...]) Forces nested loop for the joins on the tables specified.
  HashJoin(table table[ table...]) Forces hash join for the joins on the tables specified.
  MergeJoin(table table[ table...]) Forces merge join for the joins on the tables specified.
  NoNestLoop(table table[ table...]) Forces to not do nested loop for the joins on the tables specified.
  NoHashJoin(table table[ table...]) Forces to not do hash join for the joins on the tables specified.
  NoMergeJoin(table table[ table...]) Forces to not do merge join for the joins on the tables specified.
Join order Leading(table table[ table...]) Forces join order as specified.
  Leading(<join pair>) Forces join order and directions as specified. A join pair is a pair of tables and/or other join pairs enclosed by parentheses, which can make a nested structure.
Behavior control on Join Memoize(table table[ table...]) Allows the topmost join of a join among the specified tables to Memoize the inner result. Not enforced.
  NoMemoize(table table[ table...]) Inhibits the topmost join of a join among the specified tables from Memoizing the inner result.
Row number correction Rows(table table[ table...] correction) Corrects row number of a result of the joins on the tables specified. The available correction methods are absolute (#), addition (+), subtract (-) and multiplication (*). should be a string that strtod() can understand.
Parallel query configuration Parallel(table <# of workers> [soft|hard]) Enforces or inhibits parallel execution of the specified table. <# of workers> is the desired number of parallel workers, where zero means inhibiting parallel execution. If the third parameter is soft (default), it just changes max_parallel_workers_per_gather and leaves everything else to the planner. Hard enforces the specified number of workers.
GUC Set(GUC-param value) Sets GUC parameter to the value defined while planner is running.

 

MySQL 8.0

https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html

Table 8.2 Optimizer Hints Available

MySQLのOptimizer HintsはOracleにもある同一用途のヒントもちらほら。ヒント名も同一で:)
Hint Name Description Applicable Scopes
BKA, NO_BKA Affects Batched Key Access join processing Query block, table
BNL, NO_BNL Prior to MySQL 8.0.20: affects Block Nested-Loop join processing; MySQL 8.0.18 and later: also affects hash join optimization; MySQL 8.0.20 and later: affects hash join optimization only Query block, table
DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN Use or ignore the derived condition pushdown optimization for materialized derived tables (Added in MySQL 8.0.22) Query block, table
GROUP_INDEX, NO_GROUP_INDEX Use or ignore the specified index or indexes for index scans in GROUP BY operations (Added in MySQL 8.0.20) Index
HASH_JOIN, NO_HASH_JOIN Affects Hash Join optimization (MySQL 8.0.18 only Query block, table
INDEX, NO_INDEX Acts as the combination of JOIN_INDEX, GROUP_INDEX, and ORDER_INDEX, or as the combination of NO_JOIN_INDEX, NO_GROUP_INDEX, and NO_ORDER_INDEX (Added in MySQL 8.0.20) Index
INDEX_MERGE, NO_INDEX_MERGE Affects Index Merge optimization Table, index
JOIN_FIXED_ORDER Use table order specified in FROM clause for join order Query block
JOIN_INDEX, NO_JOIN_INDEX Use or ignore the specified index or indexes for any access method (Added in MySQL 8.0.20) Index
JOIN_ORDER Use table order specified in hint for join order Query block
JOIN_PREFIX Use table order specified in hint for first tables of join order Query block
JOIN_SUFFIX Use table order specified in hint for last tables of join order Query block
MAX_EXECUTION_TIME Limits statement execution time Global
MERGE, NO_MERGE Affects derived table/view merging into outer query block Table
MRR, NO_MRR Affects Multi-Range Read optimization Table, index
NO_ICP Affects Index Condition Pushdown optimization Table, index
NO_RANGE_OPTIMIZATION Affects range optimization Table, index
ORDER_INDEX, NO_ORDER_INDEX Use or ignore the specified index or indexes for sorting rows (Added in MySQL 8.0.20) Index
QB_NAME Assigns name to query block Query block
RESOURCE_GROUP Set resource group during statement execution Global
SEMIJOIN, NO_SEMIJOIN Affects semijoin strategies; beginning with MySQL 8.0.17, this also applies to antijoins Query block
SKIP_SCAN, NO_SKIP_SCAN Affects Skip Scan optimization Table, index
SET_VAR Set variable during statement execution Global
SUBQUERY Affects materialization, IN-to-EXISTS subquery strategies Query block

 

いよいよ、Advent Calendar 2023の季節が近くなってきました。今年は、全部俺シリーズは、やらずに、Oracle/PostgreSQL/MySQLのカレンダーへクロスポストすることだけは決めてます〜 :)
全部俺では無いけど、いくつかエントリーは書く予定ではいます。このエントリーもAdvent Calendarネタへ繋がるネタなのですけどね。w

では、また。



関連エントリー


Oracle Database 20c 20.1.0以降〜21c 21.1.0で v$sql_hintに追加されたヒント/ FAQ

標準はあるにはあるが癖の多い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の取得でも癖がでる

 

 

 

| | | コメント (0)

2023年11月17日 (金)

Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160167

ということで、ちょっと一休み的なネタです。

Apple Silicon対応はほぼ諦めていたVirtualBoxが 7.0でBETA版をリリースして大喜びしてたわけですが、
7.0.12でも正常に起動してくれないですよね。まだ。
ただ、表には出てこないですが、Test Buildでは結構進んでいるようで、嬉しいお知らせです。:)

まだ、Oracle Databaseとかは起動しないようですが、最新のTest Buildをダメ元で試してみたら、Oracle Linux 8とMySQL 8.0.32の組み合わせならDBのお遊びに使えそうな状況(次のBuild ではどうなるかわかりませんが)であることに気づきました。確実に進捗してそうで一安心。

新規VM作成ではなく、以下の手順で簡易に確認してみました。

macOS/Intel MBAのVirtualBoxにて、以下、2つのovaを作成し、M2 MBAのTest Build最新版のVirtualBoxへインポートして動作するかどうかを確認

1) Oracle Linux 8 に、PostgreSQL 13.4 と MySQL 8.0.32 を構成したVMをエクスポートして ova 作成
2) Oracle Linux 8 に、Oracle 23c Free Developer Editionを構成していたVMをエクスポートして ova 作成

結果は、
1)のMySQLは起動成功
1)のMySQLは起動成功
PostgreSQLは起動できず。
2)はOSは起動したがOracle起動中にエラーでVM停止

という状況。

でも、MySQL 8.0.32が、M2 MBAのVirtualBoxで使えるようになっているのはかなりの進捗なのではないかと思います。そもそもApple SiliconでVirtualBoxインストールできなくなったりしていた悲しい時期から比べれば。
いずれ、Oracleが起動するようになってくれることを期待しつつ。。Advent Calendarの記事を考えよう :)

 

以下、軽めの確認ログとスクリーンショットなど。

VirtualBox Test Build 7.0.97 BETA5
20231117-30344

OVAインポート成功後、OSは正常に起動したものの、PostgreSQL 13.4 は起動失敗。 MySQL 8.0.32は起動成功!。
Pg_ng_mysql_ok

Oracle Database 23c Free Developer Editionを構成したovaインポート後、Database起動中にfatal errorでVM停止。惜しいね。

 20231117-122036

MySQL 8.0.32 on Oracle Linux 8.5 on VirtualBox Test Build 7.0.97 BETA5 for macOS/M1/M2でMySQLへ接続!
Mysql-8032

 



 

VirtualBoxホストの情報 M2 MBAです。

leaffish ~ % /usr/sbin/system_profiler SPHardwareDataType | grep -E '(Processor|Cores|Memory|Chip|Model Name)'
Model Name: MacBook Air
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB


VirtualBoxは、Text Build最新(17-Nov-2023)

VirtualBox test builds
https://www.virtualbox.org/wiki/Testbuilds より、macOS/ARM64 BETAとExtension Packを利用

leaffish ~ % VBoxManage -v
7.0.97_BETA5r160167

 

M2 MBAのTerminalからsshでローカルのVMへ。 M2 MBAのVirtualBox Guest OSは、Oracle Linux 8.5 です。起動してますね!

leaffish ~ % ssh master@192.168.1.117
master@192.168.1.117's password:
Activate the web console with: systemctl enable --now cockpit.socket

[master@localhost ~]$
[master@localhost ~]$ cat /etc/oracle-release
Oracle Linux Server release 8.5

[master@localhost ~]$ cat /etc/redhat-release
Red Hat Enterprise Linux release 8.5 (Ootpa)
[master@localhost ~]$

 

起動しているのは確認できましたが、MySQLへ接続してSQLを実行するまでが確認!
おおおおおおおおーーーーーーっ!。

MySQL 8.0.32 on Oracle Linux 8.5 on M2 MacBook Air VirtualBox 。Oracle Databaseではないけど、うれしくて涙が。。。出ますね。これ。

[master@localhost ~]$ mysql -u scott -D perftestdb -p 
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 Source distribution

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| perftestdb |
+--------------------+
3 rows in set (0.02 sec)

mysql> show tables;
+----------------------+
| Tables_in_perftestdb |
+----------------------+
| detail |
| hoge |
| master |
| t0 |
| t1 |
| t1017 |
| t1017_2 |
| t2 |
| t3 |
| t4 |
+----------------------+
10 rows in set (0.07 sec)

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)

mysql> select * from t1;
+----+-------+
| id | t1_c1 |
+----+-------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 10 |
+----+-------+
10 rows in set (0.04 sec)

mysql> exit
Bye

 

ついでなので、停止も試しておきましょう:)

[master@localhost ~]$ sudo service mysqld stop
[[sudo] master のパスワード:
Redirecting to /bin/systemctl stop mysqld.service
[master@localhost ~]$ sudo shutdown -h now
Connection to 192.168.1.117 closed by remote host.
Connection to 192.168.1.117 closed.
leaffish ~ %

 

週末天気悪そうで、Walkingできないかもしれない。。..

 

では、また。

 

 

 

| | | コメント (0)

2023年10月11日 (水)

Oracle SQL Hinting Tips / #JoelKallmanDay


Joel Kallman Day 2023 : Announcement
の通り、本ポストは、Joel Kallman Day 2023 向けのブログポストでもあります。詳細はTimeのブログ参照のこと。


Previously on Mac De Oracle
前回は、rebuild index後のindexサイズって結局、create indexのexplainで見積もれるんだよね、というお話でした。

今回から数回、OracleのSQL ヒントの使い方テクニックでも紹介して行こうかなと思っています。
まず、最近のヒント周りの劇的な進化というか便利になったこと、最近といっても、数ヶ月とかいう単位ではないのですけどもw

Oracle 19c から hint usage report が表示されるようになりました。

19.3.3.4 ヒント使用状況レポート: 例
https://docs.oracle.com/cd/F19136_01/tgsql/influencing-the-optimizer.html#GUID-1697E7CA-9DD0-4C0D-9BC9-E4E17334C0AA


これ、今までなんでなかったの? という感じはあります。(全くなかったわけではないのですが、よほどのことがなけれそこまでトレースしなかったというのが一番の理由でしょうね。取得および、確認方法が面倒だったのでw)

最近は、ヒントの種類も、使用頻度も多くなってきたことが影響しているのでしょうか。。。ヒント使用状況レポートがほしいという要望が多くなったのでしょうかね? 

ただ、hint usage report で簡単に確認できるようになったのは良いことだと思うのですが、その副作用というような現象に気づくことも、それなりに多くなってきました。。。

その副作用とは、ヒントが、Unusedとなるような記述は避けるべき! のような話です
(風の便り程度で実際そうなっている状況に遭遇はしていないのですが)

それ以来、
ヒントの書き方の標準を決める際に、過度に、strictになって、逆にヒントを使いこなせない状況になったりしていないだろうか?
もしくは、その兆候があるのではないかと心配になることがあります。
昔から使われてきた柔軟なヒント記述方法が、変に制限されたりすることのないようにしてほしいものだとは思います。
(そう意味も込めて、マニュアルにも書いてるからね、この方法というのも点も強調しつつ、簡単な検証方法も合わせて紹介しています)

USE_HASHヒント
https://docs.oracle.com/cd/F19136_01/sqlrf/Comments.html#GUID-FA1147B3-BCAA-41F9-B6A2-8DEDABF1C021

USE_NLヒント
https://docs.oracle.com/cd/F19136_01/sqlrf/Comments.html#GUID-56DAA0EC-54BB-4E9D-9049-BCEA934F7A89


マニュアルにも例が記載されていますが、USE_NLやUSE_HASHヒントには、内部表(HASH結合の場合は、プローブ表)を指定します、単一表指定、複数表指定どちらも可能です。

例えば、A表とB表を2表を結合する例で言えば、
Nested Loop Join(NLJ)の駆動表、Hash Join(HJ)のビルド表が、A表だとして、

/*+ LEADING(A) USE_NL(B) */
/*+ LEADING(A) USE_HASH(B) */

のようにヒントを書くことができます。LEADINGヒントで駆動表(NLJの場合)または、ビルド表(HJの場合)を指定し、内部表または、ビルド表をUSE_NL(NLJの場合)または、USE_HASH(HJの場合)で指定する。

では、マニュアルにも記載されている以下のような記述は、どのような意味なのか、みなさん、お分かりでしょうか?

/*+ USE_NL(A B) */ 同じ意味ですが、 /*+ USE_NL(A) USE_NL(B) */
/*+ USE_HASH(A B) */ 同じ意味ですが、 /*+ USE_HASH(A) USE_HASH(B) */

A表とB表の2表ですから、どちらかが、駆動表(NLJの場合)、または、ビルド表(HJの場合)で、どちらかが、内部表(NLJの場合)、または、プローブ表(HJの場合)となります。
これらのヒントに指定した表は、指定しても無視される表(駆動表または、ビルド表)、つまり、Unused となる表も含めて記載しています。(エラーではないく、使われないだけだからですが)

あえて、そうする意味はなんでしょう。みなさんお分かりでしょうか?

答えは、結合順序はオプティマイザの判断に任せ、結合方法だけを、NLJ や HJ にしたい!!!!!

ということです。


意外にこのような状況は多くあります。
要するに、駆動表や、ビルド表は状況に応じて柔軟に変えてもらって良いが、結合方法だけは、絶対、NLJにしたい。もしくは、HJにしたいというケースですね!
(チューニングで呼ばれて行った先で、業務観点からどの表が駆動表や、ビルド表ですか? と聞いて、即答してくれないと、おじさん困ってしまうんですねw わからんと言われてたら、リスク覚悟で現状のデータ量から決めるか、このように決めないで、オプティマイザに任せる。みなさんもオプティマイザを信じてください! と、言ったもののw、統計情報取得止められてたりすると、まあ、信じられないみたいな状況もあるわけです。はい。wwwww)


他のTech Tipsとして、結合順は状況に応じて人の手で書き換えること(前述したように担当者も結合順を把握していないとか、一旦、固定したけども、やはり間違ってたケースや、経年で傾向が変わったので変更したい etc.)を想定して、ヒント修正によるミスのリスクを最小化するため。(修正範囲をLEADINGヒントだけにして、変更箇所を最小にしたい場合です

駆動表または、ビルド表の変更前
/*+ LEADING(A B) USE_NL(A B) */
/*+ LEADING(A B) USE_HASH(A B) */
/*+ LEADING(A) USE_NL(A B) */
/*+ LEADING(A) USE_HASH(A B) */

駆動表または、ビルド表の変更後
/*+ LEADING(B A) USE_NL(A B) */
/*+ LEADING(B A) USE_HASH(A B) */
/*+ LEADING(B) USE_NL(A B) */
/*+ LEADING(B) USE_HASH(A B) */

これらのヒントは、LEADINGヒントの結合順を変更するだけで、駆動表やビルド表を切り替えることができます。USE_NL、USE_HASHヒントは変更する必要がありません。(ヒント指定時のミスの発生箇所を最小化できるわけです=変更箇所を少なくした)

しかし、以下のように記述していた場合はどうでしょう?
駆動表や、ビルド表を変更する場合、LEADING/USE_NL/USE_HASH全てのヒントを適切に修正する必要があります。

変更前
/*+ LEADING(A) USE_NL(B) */
/*+ LEADING(A) USE_HASH(B) */

変更後
/*+ LEADING(B) USE_NL(A) */
/*+ LEADING(B) USE_HASH(A) */

変更し忘れたことによりヒントが無効となり、遅延に繋がってしまったということは意外に多いです。修正しているのは、人ですからね。間違いはあります。修正する箇所と量を最小化すれば、ミスの発生リスクは減らせます。
もう一つの効果として、可読性が向上する(個人的な感覚かもしれませんが)のではないかと考えています。


ということで長い前置きはこれぐらいにして挙動を確認してみましょう。


結合順はオプティマイザ任せにして固定せず、結合方法だけをHJにした場合、Hint Usage Reportにはどうのようにレポートされるのか、また実行計画は想定通りなのか等を確認してみましょう。


Oracle Database 21cを利用して検証します。ちなみに、この方法は、Oracle 11gの頃から実戦で利用されている方法です。(マニュアルにも例が記載されているので、将来的にも挙動が変わることはないでしょう。影響でかいですからね。挙動が変わるとw)
なお、隠しパラメータのカスタマイズはせず、インストール時のままです。

SCOTT@orclpdb1> select banner from v$version;

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


検証用の表とデータ登録、統計情報取得、データ件数確認
(なお、本検証で利用したスクリプトはこのブログの後半に記載してあります)

SCOTT@orclpdb1> @hinting_tech1

表が削除されました。

経過: 00:00:00.84

表が作成されました。

経過: 00:00:00.19

表が削除されました。

経過: 00:00:00.51

表が作成されました。

経過: 00:00:00.07

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

経過: 00:00:06.41

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

経過: 00:00:13.93
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
3* END;

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

経過: 00:00:01.71
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
3* END;

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

経過: 00:00:01.16
1* SELECT COUNT(1) FROM table_a

COUNT(1)
----------
50000

経過: 00:00:00.01
1* SELECT COUNT(1) FROM table_b

COUNT(1)
----------
100000

経過: 00:00:00.01

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

経過: 00:00:00.03


何もチューニングしていないオリジナルのSQLと実行計画を確認しておきます。少量のデータに絞って、結合(索引あり)しています。
駆動表がTABLE_Aで、NLJしていることが確認できます。

  1  SELECT
2 *
3 FROM
4 table_a a
5 INNER JOIN table_b b
6 ON
7 a.id = b.id
8* AND a.id BETWEEN :s AND :e

経過: 00:00:00.04

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

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 203 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_A | 125 | 244K| 78 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | SYS_C009317 | 225 | | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | SYS_C009318 | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TABLE_B | 1 | 2006 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
5 - access("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
6 - access("A"."ID"="B"."ID")
filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

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


統計
----------------------------------------------------------
73 recursive calls
21 db block gets
143 consistent gets
8 physical reads
4108 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
11 sorts (memory)
0 sorts (disk)
5 rows processed


NLJをヒント使って、HJに強制します。索引アクセスを行わないようにFULLヒントも併用していますが、LEADINGヒントは利用せず、ビルド表の決定はオプティマイザに任せています。
実行結果とヒント使用状況レポートを確認すると、オプティマイザの判断により、TABLE_Aがビルド表となっため、USE_HASH(A)がUnusedとしてレポートされてますが、残るヒントは利用され、NLJからHJへ変更が行われています。
Unusedは最終的に利用されなかったという意味で、ヒント構文場問題があるわけではなくヒントとしては正しいが、最終的に利用されなかった。このケースではビルド表となった表TABLE_Aを指定していた、USE_HASH(A)が利用されなかったということですね。
理由は、USE_HASH/USE_NLに指定する表は、プローブ表または、内部表となっているためで、このケースではオプティマイザが最終的にTABLE_Aをビルド表としたためUSE_HASH(A)が利用されなかったということを意味しています。

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a)
6 USE_HASH(b)
7 */
8 *
9 FROM
10 table_a a
11 INNER JOIN table_b b
12 ON
13 a.id = b.id
14* AND a.id BETWEEN :s AND :e

経過: 00:00:00.24

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

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_A | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_B | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
4 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "A"@"SEL$1"
U - USE_HASH(a)


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


USE_HASH(a b)という記載ですが、前述のヒントと同じ意味です。この例のような指定方法だと、ヒント使用状況レポートの読み方の理解が重要になります。
U - USE_HASH(a b) というレポートがありますが、これだけだと、ヒント全体が利用されなかったのか? と思われるかもしれませんが、
もう一つ上の、3 - SEL$58A6D7F6 / "A"@"SEL$1"に着目する必要があります。 該当ヒントの A が利用されなかったということを意味しています。

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a b)
6 */
7 *
8 FROM
9 table_a a
10 INNER JOIN table_b b
11 ON
12 a.id = b.id
13* AND a.id BETWEEN :s AND :e

経過: 00:00:00.23

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

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_A | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_B | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
4 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "A"@"SEL$1"
U - USE_HASH(a b)


統計
----------------------------------------------------------
24 recursive calls
0 db block gets
50298 consistent gets
50282 physical reads
0 redo size
8944 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


この検証では、LEADINGヒントを利用せず、オプティマイザ任せにしてあります。もう一つの検証として、オプティマイザが駆動表というかビルド表にする表を変えたケースも確認しておきたいですよね! ヒント指定の思惑通りの挙動になるか。。。確認は必要ですよからねw (実案件ではここまで確認はしませんけどもw 本記事の目的がその確認ですのでw)

データをからにして、行数を逆にして、駆動表というかビルド表が変わるように仕掛けておきます。

  1* truncate table table_a

表が切り捨てられました。

経過: 00:00:00.44
1* truncate table table_b

表が切り捨てられました。

経過: 00:00:00.13

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

経過: 00:00:07.02

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

経過: 00:00:13.49
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
3* END;

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

経過: 00:00:03.19
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
3* END;

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

経過: 00:00:01.54
1* SELECT COUNT(1) FROM table_a

COUNT(1)
----------
100000

経過: 00:00:00.02
1* SELECT COUNT(1) FROM table_b

COUNT(1)
----------
50000

経過: 00:00:00.01


何もしていないオプティマイザ任せの実行計画は、NLJで、狙い通りにTABLE_Bが駆動表に切り替わっています。(うまく行ってよかったw)

  1  SELECT
2 *
3 FROM
4 table_a a
5 INNER JOIN table_b b
6 ON
7 a.id = b.id
8* AND b.id BETWEEN :s AND :e

経過: 00:00:00.05

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

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 203 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_B | 125 | 244K| 78 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | SYS_C009318 | 225 | | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | SYS_C009317 | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TABLE_A | 1 | 2006 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
5 - access("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
6 - access("A"."ID"="B"."ID")
filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

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


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


先ほどと同じヒントを指定しました。LEADINGヒントはないので、オプティマイザは、TABLE_Bをビルド表として選択しました。狙い通りですね。
ということは、今度は、USE_HASH(B)がUnusedになるはずです。ビルド表になりましたからね。先ほどは、プローブ表だったわけですが。

ヒント使用状況レポートの詳細にはいかのようにレポートされました。USE_HASH(A B)という表よりは直感的に理解しやすいと思いますが、B Unusedですよということですね。
3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(b)

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a)
6 USE_HASH(b)
7 */
8 *
9 FROM
10 table_a a
11 INNER JOIN table_b b
12 ON
13 a.id = b.id
14* AND b.id BETWEEN :s AND :e

経過: 00:00:00.22

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

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_B | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_A | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
4 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(b)


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


USE_HASH(a b)という表記にした例です意味は同じですが、USE_HASH(a b)全体がUnusedなのかと勘違いしそうですよね。よーく見てみましょう。SEL$58A6D7F6 / "B"@"SEL$1" とあり、 USE_HASH(a b)のうち、B が Unusedであることが確認できます。
3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(a b)

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a b)
6 */
7 *
8 FROM
9 table_a a
10 INNER JOIN table_b b
11 ON
12 a.id = b.id
13* AND b.id BETWEEN :s AND :e

経過: 00:00:00.23

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

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_B | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_A | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

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

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
4 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(a b)


統計
----------------------------------------------------------
5 recursive calls
0 db block gets
50541 consistent gets
505