2026年2月 5日 (木)

Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #最終回 - ぐるぐるしちゃう影響

Previously on Mac De Oracle
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #4 - The SQL was transformed!

前回は、Multi row Insertをリモート表へインサートするとSQL transformの影響で、
DUAL表アクセスがオーバーヘッドとなり Multi row Insertのメリットが削がれてしまう(現時点の仕様では)ということを確認しました!
偶々リモート表に実行したから気づけたわけですがw。あの仕様に気付けたのはラッキーというべきかw


ということで、脇道にそれまくったこのシリーズも、やっと最終回です!


リモート表を使ってぐるぐるしてネットワークラウンドトリップを乗せる必要はなくて、
それが自然に乗るAPサーバーとDBサーバー間の状況を作ればよいだけなので、
最終回は素直にw
JavaからOracle Databaseへアクセスしローカル表に対してぐるぐるしちゃいながら、
Single row insert を繰り返すぐるぐる系と、
Multi row insert を利用して、ゆるやかに、ぐーるぐーるするタイプで 100,000 行を登録してみようと思いますw

N+1問題の類とネットワークラウンドトリップとネットワークレイテンシーと、コミット間隔などパラメータは多いですが、だいたい 100 - 1000 行程度付近前後にリーズナブルなポイントが現れていますよね。。。
(ちなにみSQL*Netのパラメータ等はデフォのままです。また、リモート表ではないので、OPEN_CURSORSもデフォルトのままの 300 で問題ありません。参考まで) 

バインド変数利用と、どの程度の単位でまとめてインサートするか、コミットの間隔など沢山のパラメータがあるので、そららの様子をみながら表を見てもらうと面白いと思います。
なお、いつものように後半にログと利用したコードなどをまとめて載せています。
(今回は、生成AIのGeminiくんにサクッと書いてもらいましたw)

Multi Row Insertで、100 - 1000行程度まとめるとメモリにもCPUにも優しくなりますね。単純に、1行毎ぐるぐるすると無駄が多くなるのは一目瞭然だと思います。

Oracle-database-multi-row-insert-5-1

一応、ログは以下のような感じ。
Client -> Datatabase - Single row Insert / commit間隔の調整
いわゆる、普通のぐるぐる系ですw

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 1; ./post_process.sh

...略...

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
に接続されました。

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
212 CPU used by this session 4
212 CPU used when call started 1
212 SQL*Net roundtrips to/from client 2
...略...
212 execute count 621
...略...
212 parse count (hard) 100
212 parse count (total) 144
212 parse time cpu 2
212 parse time elapsed 2
...略...
212 session pga memory 3337208
212 session pga memory max 5189280
212 session uga memory 1904696
212 session uga memory max 3119464
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 1)
完了! 総時間: 43.42 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
212 CPU used by this session 694
212 CPU used when call started 694
...略...
212 SQL*Net roundtrips to/from client 200002
...略...
212 execute count 100669
...略...
212 parse count (hard) 106
212 parse count (total) 100149
212 parse time cpu 4
212 parse time elapsed 23
...略...
212 session pga memory 3402744
212 session pga memory max 5189280
212 session uga memory 1904696
212 session uga memory max 3119464
...略...
212 user commits 100000

...略...

COUNT(1)
----------
100000

SEGMENT_NAME MB
------------------------------ ----------
MROWS_INS_TAB 45

表が切り捨てられました。

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 10; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 4
176 CPU used when call started 1
176 SQL*Net roundtrips to/from client 2
...略...
176 execute count 417
...略...
176 parse count (hard) 78
176 parse count (total) 126
176 parse time cpu 5
176 parse time elapsed 5
...略...
176 session pga memory 3026592
176 session pga memory max 5123744
176 session uga memory 1773632
176 session uga memory max 3053928
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 10)
完了! 総時間: 43.25 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 705
176 CPU used when call started 705
...略...
176 SQL*Net roundtrips to/from client 200002
...略...
176 execute count 100441
...略...
176 parse count (hard) 81
176 parse count (total) 100129
176 parse time cpu 9
176 parse time elapsed 21
...略...
176 session pga memory 3026592
176 session pga memory max 5123744
176 session uga memory 1773632
176 session uga memory max 3053928
...略...
176 user commits 100000
...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 100; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
44 CPU used by this session 5
44 CPU used when call started 1
44 SQL*Net roundtrips to/from client 2
...略...
44 execute count 621
...略...
44 parse count (hard) 100
44 parse count (total) 144
44 parse time cpu 4
44 parse time elapsed 6
...略...
44 session logical reads 2518
44 session pga memory 3206136
44 session pga memory max 5123744
44 session uga memory 1904800
44 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 100)
完了! 総時間: 43.68 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
44 CPU used by this session 723
44 CPU used when call started 723
...略...
44 SQL*Net roundtrips to/from client 200002
...略...
44 execute count 100669
...略...
44 parse count (hard) 106
44 parse count (total) 100149
44 parse time cpu 8
44 parse time elapsed 28
...略...
44 session pga memory 3271672
44 session pga memory max 5123744
44 session uga memory 1904800
44 session uga memory max 3054064
...略...
44 user commits 100000
...略...

COUNT(1)
----------
100000
...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 1000; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 4
39 CPU used when call started 1
39 SQL*Net roundtrips to/from client 2
...略...
39 execute count 621
...略...
39 parse count (hard) 100
39 parse count (total) 144
39 parse time cpu 3
39 parse time elapsed 1
...略...
39 session pga memory 3206136
39 session pga memory max 5123744
39 session uga memory 1904800
39 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 1000)
完了! 総時間: 42.41 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 698
39 CPU used when call started 698
...略...
39 SQL*Net roundtrips to/from client 200002
...略...
39 execute count 100669
...略...
39 parse count (hard) 106
39 parse count (total) 100149
39 parse time cpu 10
39 parse time elapsed 23
...略...
39 session pga memory 3271672
39 session pga memory max 5123744
39 session uga memory 1904800
39 session uga memory max 3054064
...略...
39 user commits 100000

...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 10000; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 3
176 CPU used when call started 1
176 SQL*Net roundtrips to/from client 2
...略...
176 execute count 621
...略...
176 parse count (hard) 100
176 parse count (total) 144
176 parse time cpu 1
176 parse time elapsed 3
...略...
176 session pga memory 3206136
176 session pga memory max 5123744
176 session uga memory 1904800
176 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 10000)
完了! 総時間: 41.98 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 673
176 CPU used when call started 673
...略...
176 SQL*Net roundtrips to/from client 200002
...略...
176 execute count 100669
...略...
176 parse count (hard) 106
176 parse count (total) 100149
176 parse time cpu 6
176 parse time elapsed 22
...略...
176 session pga memory 3271672
176 session pga memory max 5123744
176 session uga memory 1904800
176 session uga memory max 3054064
...略...
176 user commits 100000

...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1 100000; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 5
176 CPU used when call started 1
176 SQL*Net roundtrips to/from client 2
...略...
176 execute count 621
...略...
176 parse count (hard) 100
176 parse count (total) 144
176 parse time cpu 1
176 parse time elapsed 3
...略...
176 session pga memory 3206136
176 session pga memory max 5123744
176 session uga memory 1904800
176 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1 コミット間隔: 100000)
完了! 総時間: 41.62 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 698
176 CPU used when call started 698
...略...
176 SQL*Net roundtrips to/from client 200002
...略...
176 execute count 100669
...略...
176 parse count (hard) 106
176 parse count (total) 100149
176 parse time cpu 4
176 parse time elapsed 26
...略...
176 session pga memory 3271672
176 session pga memory max 5123744
176 session uga memory 1904800
176 session uga memory max 3054064
...略...
176 user commits 100000
...略...

COUNT(1)
----------
100000

...略...


Client -> Datatabase - Multi row Insert / バルクロード行数調整
Multi row Insertなので繰り返し実行ではありますが、ぐるぐる というより、ぐーーーーる、ぐーーーーる系な感じw です。( N+1だと ぐるぐる、ぐーーーる、ぐーーるの違いを表現できなーーーいw )

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 10 1; ./post_process.sh
...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 4
38 CPU used when call started 1
38 SQL*Net roundtrips to/from client 2
...略...
38 execute count 621
...略...
38 parse count (hard) 100
38 parse count (total) 144
38 parse time cpu 2
38 parse time elapsed 5
...略...
38 session pga memory 3206136
38 session pga memory max 5123744
38 session uga memory 1904800
38 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 10 コミット間隔: 1)
完了! 総時間: 5.44 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 98
38 CPU used when call started 98
38 SQL*Net roundtrips to/from client 20002
...略...
38 execute count 10719
...略...
38 parse count (hard) 107
38 parse count (total) 10154
38 parse time cpu 5
38 parse time elapsed 10
...略...
38 session pga memory 3337208
38 session pga memory max 5123744
38 session uga memory 1970280
38 session uga memory max 3054064
...略...
38 user commits 10000

...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 100 1; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 4
38 CPU used when call started 1
38 SQL*Net roundtrips to/from client 2
...略...
38 execute count 621
...略...
38 parse count (hard) 100
38 parse count (total) 144
38 parse time cpu 2
38 parse time elapsed 2
...略...
38 session pga memory 3206136
38 session pga memory max 5123744
38 session uga memory 1904800
38 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 100 コミット間隔: 1)
完了! 総時間: 1.34 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 35
38 CPU used when call started 35
38 SQL*Net roundtrips to/from client 2002
...略...
38 execute count 1719
...略...
38 parse count (hard) 107
38 parse count (total) 1154
38 parse time cpu 2
38 parse time elapsed 2
...略...
38 session pga memory 3795960
38 session pga memory max 7400440
38 session uga memory 2101240
38 session uga memory max 3054064
...略...
38 user commits 1000
...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 1000 1; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 5
38 CPU used when call started 1
38 SQL*Net roundtrips to/from client 2
...略...
38 execute count 621
...略...
38 parse count (hard) 100
38 parse count (total) 144
38 parse time cpu 3
38 parse time elapsed 3
...略...
38 session pga memory 3206136
38 session pga memory max 5123744
38 session uga memory 1904800
38 session uga memory max 3054064
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 1000 コミット間隔: 1)
完了! 総時間: 1.21 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 63
38 CPU used when call started 63
38 SQL*Net roundtrips to/from client 202
...略...
38 execute count 819
...略...
38 parse count (hard) 107
38 parse count (total) 254
38 parse time cpu 9
38 parse time elapsed 11
...略...
38 session pga memory 3533816
38 session pga memory max 34925560
38 session uga memory 2232200
38 session uga memory max 5702640
...略...
38 user commits 100
...略...

COUNT(1)
----------
100000

...略...

[oracle@arm64-oraclelinux8u10 ~]$ java -classpath ./:$CLASSPATH Oracle23aiDynamicBulkLoad 100000 10000 1; ./post_process.sh

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
19 CPU used by this session 5
19 CPU used when call started 2
19 SQL*Net roundtrips to/from client 2
...略...
19 execute count 559
...略...
19 parse count (hard) 100
19 parse count (total) 142
19 parse time cpu 5
19 parse time elapsed 3
...略...
19 session pga memory 3271672
19 session pga memory max 5123744
19 session uga memory 1899488
19 session uga memory max 3053984
...略...

ロード開始: 総計 100000 行 (チャンクサイズ: 10000 コミット間隔: 1)
完了! 総時間: 33.74 秒

...略...

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
19 CPU used by this session 3334
19 CPU used when call started 3334
19 SQL*Net roundtrips to/from client 22
...略...
19 execute count 667
...略...
19 parse count (hard) 107
19 parse count (total) 162
19 parse time cpu 728
19 parse time elapsed 730
...略...
19 session pga memory 6286328
19 session pga memory max 286518264
19 session uga memory 5043712
19 session uga memory max 31643696
...略...
19 user commits 10

...略...

COUNT(1)
----------
100000

...略...


ふーーーっ。

完!


では、また、別のネタでお会いしましょう :)





テスト環境の情報
macOS Apple SiliconのVirtualBox
oracle@Mac ~ % ./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: 26.2
BuildVersion: 25C56

*** VirtualBox ver. ***
7.2.4r170995

VMのOS、および、Java

[oracle@arm64-oraclelinux8u10 ~]$ java -version
openjdk version "11.0.25" 2024-10-15 LTS
OpenJDK Runtime Environment (Red_Hat-11.0.25.0.9-1.0.1) (build 11.0.25+9-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-11.0.25.0.9-1.0.1) (build 11.0.25+9-LTS, mixed mode, sharing)


[oracle@arm64-oraclelinux8u10 ~]$ uname -rpo
5.15.0-313.189.5.3.el8uek.aarch64 aarch64 GNU/Linux
[oracle@arm64-oraclelinux8u10 ~]$ cat /etc/os-release
NAME="Oracle Linux Server"
VERSION="8.10"

...略...


ソース

[oracle@arm64-oraclelinux8u10 ~]$ cat show_mrows_ins_tab_size.sql
select segment_name,bytes/1024/1024 as "MB" from dba_segments where owner='SCOTT' and segment_name = upper('mrows_ins_tab')
/

[oracle@arm64-oraclelinux8u10 ~]$ cat post_process.sh
sqlplus system/hogehoge@localhost:1521/freepdb1 @post_process
[oracle@arm64-oraclelinux8u10 ~]$ cat post_process.sql
SELECT COUNT(1) FROM scott.mrows_ins_tab
/

@show_mrows_ins_tab_size

truncate table scott.mrows_ins_tab
/
exit


[oracle@arm64-oraclelinux8u10 ~]$ cat show_mystats.sh
sqlplus system/hogehoge@localhost:1521/freepdb1 @show_mystats2 scott

[oracle@arm64-oraclelinux8u10 ~]$ cat show_mystats2.sql
set veri off
SELECT
s.sid,
n.name,
s.value
FROM
v$sesstat s
INNER JOIN v$statname n
ON
s.statistic# = n.statistic#
AND s.sid = (SELECT sid FROM v$session WHERE username = UPPER('&1'))
WHERE
s.value > 0
AND (
n.name LIKE '%memory%'
OR n.name LIKE '%CPU%'
OR n.name LIKE '%I/O%'
OR n.name LIKE '%write%'
OR n.name LIKE '%read%'
OR n.name LIKE 'redo%'
OR n.name LIKE 'SQL*Net%'
OR n.name LIKE '%commit%'
OR n.name LIKE 'execute count'
OR n.name LIKE 'parse%'
)
ORDER BY
n.name;

UNDEFINE 1
set veri on
exit


Geminiくんに書いてもらったJavaのコードw

[oracle@arm64-oraclelinux8u10 ~]$ cat Oracle23aiDynamicBulkLoad.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Collections;

public class Oracle23aiDynamicBulkLoad {

private static final String URL = "jdbc:oracle:thin:@localhost:1521/freepdb1";
private static final String USER = "scott";
private static final String PASSWORD = "hogehoge";

public static void main(String[] args) {
// インサートする行数、デフォルト値(10万行)
int totalRows = (args.length > 0) ? Integer.parseInt(args[0]) : 100000;
// 1回あたりの同時インサート行数、デフォルト100行
int chunkSize = (args.length > 1) ? Integer.parseInt(args[1]) : 100;
// commit interval, デフォルト1行
int commitInterval = (args.length > 2) ? Integer.parseInt(args[2]) : 1;


try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
conn.setAutoCommit(false);
showSessionStats();

System.out.println("ロード開始: 総計 " + totalRows + " 行 (チャンクサイズ: " + chunkSize + " コミット間隔: " + commitInterval + ")");
long startTime = System.currentTimeMillis();

for (int i = 0; i < totalRows; i += chunkSize) {
int currentBatchSize = Math.min(chunkSize, totalRows - i);
executeMultiRowInsert(conn, i, currentBatchSize);

if (chunkSize == 1 && (i % commitInterval) == 0) {
conn.commit();
} else {
conn.commit();
}
}

long endTime = System.currentTimeMillis();
System.out.printf("完了! 総時間: %.2f 秒%n", (endTime - startTime) / 1000.0);

showSessionStats();
conn.disconnect();

} catch (SQLException e) {
e.printStackTrace();
}
}

public static void showSessionStats() {
try {
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "/home/oracle/show_mystats.sh");
Process process = pb.start();

// 結果の取得
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

// 終了コードを取得
int exitCode = process.waitFor();
System.out.println("Exited with code: " + exitCode);

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

private static String lpad(String original, int length, String padChar) {
if (original.length() >= length) return original;
return padChar.repeat(length - original.length()) + original;
}

private static void executeMultiRowInsert(Connection conn, int offset, int rowCount) throws SQLException {
String rowPlaceholder = "(?, ?)";
String allPlaceholders = String.join(", ", Collections.nCopies(rowCount, rowPlaceholder));

String sql = "INSERT /* MONITOR */ INTO mrows_ins_tab (id, col8) VALUES " + allPlaceholders;

try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
for (int i = 0; i < rowCount; i++) {
int id = offset + i + 1;
int baseIdx = i * 2;
String col8 = lpad(String.valueOf(id), 373, "x");
pstmt.setInt(baseIdx + 1, id);
pstmt.setString(baseIdx + 2, col8);
}
pstmt.executeUpdate();
}
}
}






関連エントリ
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT
Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #2
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #3 - ローカル表とリモート表での挙動の差異?!
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #4 - The SQL was transformed!

| | | コメント (0)

2026年2月 4日 (水)

Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #4 - The SQL was transformed!

Previously on Mac De Oracle
前回は、Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #3 - ローカル表とリモート表での挙動の差異?! でした。

復習を兼ねて、前回の表(再掲)をみつつ。23aiでサポートされたMulti row Insert文をローカル表とリモート表(via DB Link)へ実行してみると。。なんと。想定外の結果に。。。

Oracle-database-multi-row-inser_20260204210701

v$mystatから得られた統計値をみると、execution countやparse count (total) - parse count (hard) それに伴うパースタイムなどなど、一体何が起きてるの。。。。。(ニヤニヤ
という感じでした。

さらに、リモート表に対して、1,000 rows / INSERT で Multi row Insert すると、OPEN_CURSORS = 300(default)では足らず、 1,300まで増やすと不足しないという、状況。

なにか引っかかりますよね。単純にSQLをまるっとリモートDB (インスタンスは同じだけど、DB Linkでパススルーして投げているだけでは??。。。と思っていたが) へ投げているだけではなさそうな様子。

ということで、その謎を追い Oracle Database の奥へ進んでいきましょう ;)

まずは、10046トレースでローカル表とリモート表への実行でどういう差があるのかを見ておく。

バインド変数を利用し、1000 rows / INSERTをローカル表へ実行した場合の10046トレース

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
に接続されました。
SCOTT@localhost:1521/freepdb1> alter session set tracefile_identifier='10046_mrows_local';

セッションが変更されました。
SCOTT@localhost:1521/freepdb1> alter session set statistics_level=all;

セッションが変更されました。
SCOTT@localhost:1521/freepdb1> alter session set max_dump_file_size = unlimited;

セッションが変更されました。
SCOTT@localhost:1521/freepdb1> alter system flush shared_pool;

システムが変更されました。
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context forever,level 12';

セッションが変更されました。
SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 38
201 CPU used when call started 36
201 SQL*Net roundtrips to/from client 11
...略...
201 execute count 2329
...略...
201 session pga memory 6696608
201 session pga memory max 9055904
201 session uga memory 3178192
201 session uga memory max 4776536
...略...

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

経過: 00:00:02.79

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 313
201 CPU used when call started 313
201 SQL*Net roundtrips to/from client 16
...略...
201 execute count 2724
...略...
201 session pga memory 8055800
201 session pga memory max 52702880
201 session uga memory 4658456
201 session uga memory max 4776536
...略...
201 user commits 100
...略...

COUNT(1)
----------
100000

...略...
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context off';
...略...

 

 

バインド変数を利用し、1000 rows / INSERTをリモート表(via DB Link)へ実行した場合の10046トレース

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
に接続されました。
SCOTT2@localhost:1521/freepdb1> alter session set tracefile_identifier='10046_mrows_remote';

セッションが変更されました。
SCOTT2@localhost:1521/freepdb1> alter session set statistics_level=all;

セッションが変更されました。
SCOTT2@localhost:1521/freepdb1> alter session set max_dump_file_size = unlimited;

セッションが変更されました。
SCOTT2@localhost:1521/freepdb1> alter system flush shared_pool;

システムが変更されました。
SCOTT2@localhost:1521/freepdb1> alter session set events '10046 trace name context forever,level 12';

セッションが変更されました。
SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 20
201 CPU used when call started 18
201 SQL*Net roundtrips to/from client 11
...略...
201 execute count 2661
...略...
201 session pga memory 7024288
201 session pga memory max 9121440
201 session uga memory 3769944
201 session uga memory max 5045168
...略...

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

経過: 00:00:28.67

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 1653
201 CPU used when call started 1653
201 SQL*Net roundtrips to/from client 16
201 SQL*Net roundtrips to/from dblink 202411
...略...
201 execute count 103112
...略...
201 session pga memory 14167712
201 session pga memory max 27799200
201 session uga memory 11361872
201 session uga memory max 12865392
...略...
201 user commits 100
...略...

COUNT(1)
----------
100000
...略...

SCOTT2@localhost:1521/freepdb1> alter session set events '10046 trace name context off';
...略...

 

出力された10046トレースファイルのサイズ。リモートのほうが圧倒的に大きいですね。

[oracle@arm64-oraclelinux8u10 trace]$ ll FREE_ora_*_10046_mrows_*.trc
-rw-r-----. 1 oracle oinstall 62680689 Feb 3 22:16 FREE_ora_4704_10046_mrows_local.trc
-rw-r-----. 1 oracle oinstall 136508011 Feb 3 22:19 FREE_ora_4724_10046_mrows_remote.trc

 

比較しつつ覗いてみると。
ローカル表への Multi Row Insert では、投げたままのSQL文がみつかります。これは想定通りですよね。

...略...

SQL ID: 7vkx7q1gbfwr3 Plan Hash: 1

INSERT INTO mrows_ins_tab
VALUES
(:c11, null, null, null, null, null, null, null, :c81), (:c12, null, null,
null, null, null, null, null, :c82), (:c13, null, null, null, null, null,
null, null, :c83), (:c14, null, null, null, null, null, null, null, :c84),
(:c15, null, null, null, null, null, null, null, :c85), (:c16, null, null,
null, null, null, null, null, :c86), (:c17, null, null, null, null, null,
null, null, :c87), (:c18, null, null, null, null, null, null, null, :c88),
(:c19, null, null, null, null, null, null, null, :c89), (:c110, null, null,
null, null, null, null, null, :c810), (:c111, null, null, null, null, null,

...略...

null, null, null, null, :c8994), (:c1995, null, null, null, null, null,
null, null, :c8995), (:c1996, null, null, null, null, null, null, null,
:c8996), (:c1997, null, null, null, null, null, null, null, :c8997),
(:c1998, null, null, null, null, null, null, null, :c8998), (:c1999, null,
null, null, null, null, null, null, :c8999), (:c11000, null, null, null,
null, null, null, null, :c81000);

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.13 0.13 0 0 0 0
Execute 100 1.81 1.84 7 7031 61178 100000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 1.95 1.98 7 7031 61178 100000

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 134 (recursive depth: 1)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
0 0 0 LOAD TABLE CONVENTIONAL MROWS_INS_TAB (cr=519 pr=8 pw=0 time=7342 us starts=1 direct read=0 direct write=0)
1000 1000 1000 VALUES SCAN (cr=0 pr=0 pw=0 time=633 us starts=1 direct read=0 direct write=0 cost=2000 size=0 card=1000)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Allocate PGA memory from OS 2 0.00 0.00
Allocate CGA memory from OS 62 0.00 0.00
Disk file operations I/O 2 0.00 0.00
db file sequential read 7 0.00 0.00
log file switch (private strand flush incomplete)
2 0.00 0.01

 

一方、リモート表に対して同一Multi Row Insert文を実行した場合の10046トレースでは。。。
やたらと、SYS.DUALへのSELECT文が出現しており、それにより10046トレースファイルサイズが大きくなっていました。
見えて来ましたよね。なんとなく。。。。

SQL ID: 7vkx7q1gbfwr3 Plan Hash: 0

INSERT INTO mrows_ins_tab
VALUES
(:c11, null, null, null, null, null, null, null, :c81), (:c12, null, null,
null, null, null, null, null, :c82), (:c13, null, null, null, null, null,
null, null, :c83), (:c14, null, null, null, null, null, null, null, :c84),
(:c15, null, null, null, null, null, null, null, :c85), (:c16, null, null,
null, null, null, null, null, :c86), (:c17, null, null, null, null, null,
null, null, :c87), (:c18, null, null, null, null, null, null, null, :c88),
(:c19, null, null, null, null, null, null, null, :c89), (:c110, null, null,

...略...

null, null, null, null, :c8994), (:c1995, null, null, null, null, null,
null, null, :c8995), (:c1996, null, null, null, null, null, null, null,
:c8996), (:c1997, null, null, null, null, null, null, null, :c8997),
(:c1998, null, null, null, null, null, null, null, :c8998), (:c1999, null,
null, null, null, null, null, null, :c8999), (:c11000, null, null, null,
null, null, null, null, :c81000);


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.13 0.14 0 0 0 0
Execute 100 14.90 26.22 0 18 100 100000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 15.04 26.36 0 18 100 100000

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 136 (recursive depth: 1)

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Allocate PGA memory from OS 2 0.00 0.00
single-task message 1 0.36 0.36
SQL*Net message from dblink 202312 0.17 18.65
SQL*Net message to dblink 202311 0.00 0.12
SQL*Net more data to dblink 5800 0.00 0.02
********************************************************************************

...略...

SELECT /*+ FULL(P) +*/ *
FROM
"SYS"."DUAL" P


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1002 0.00 0.00 0 0 0 0
Execute 0 0.00 0.00 0 0 0 0
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1002 0.00 0.00 0 0 0 0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 136 (recursive depth: 2)
********************************************************************************

...略...

SQL ID: 40x1xzzgzd101 Plan Hash: 1388734953

SELECT 0
FROM
"SYS"."DUAL" "A1002"


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 100 0.00 0.00 0 0 0 0
Execute 100 0.00 0.00 0 0 0 0
Fetch 200 0.00 0.00 0 0 0 100
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 400 0.00 0.00 0 0 0 100

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 136 (recursive depth: 2)
Number of plan statistics captured: 100

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 FAST DUAL (cr=0 pr=0 pw=0 time=0 us starts=1 direct read=0 direct write=0 cost=2 size=0 card=1)
...略...

 

SYS.DUALへのアクセスが目立つ、かつ、SYS.DUALへの表エイリアスが皆異なるようなので、SYS.DUALだけで何行あるかカウントしてみました。
おおおおおおおおおおーーーー!!!!!! これって!!

リモート表へMulti Row Insertを実行した場合のSYS.DUALへのアクセスを含む単純なSELECT文の数!! は、 1,002件!!!!

[oracle@arm64-oraclelinux8u10 trace]$ cat FREE_ora_4724_10046_mrows_remote_aggregate.txt | grep "SELECT 0" | wc -l
1002

[oracle@arm64-oraclelinux8u10 trace]$ cat FREE_ora_4704_10046_mrows_local_aggregate.txt | grep "SELECT 0" | wc -l
0

 

23aiからサポートされた、Multi Row Insert 構文って、内部で TRANSFORM されちゃってないですかね???

10053トレースを取得すれば確実に見えるはず。。。。。。
10046トレースと同様の方法で。。

ローカル表で。

SCOTT2@localhost:1521/freepdb1> alter session set tracefile_identifier='10053_mrows_local';

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

...略...

SCOTT@localhost:1521/freepdb1> alter session set events '10053 trace name context forever, level 1';

セッションが変更されました。
SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

...略...

 

リモート表で。

SCOTT2@localhost:1521/freepdb1> alter session set tracefile_identifier='10053_mrows_remote';

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

...略...

SCOTT2@localhost:1521/freepdb1> alter session set events '10053 trace name context forever, level 1';

セッションが変更されました。
SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

...略...

 

10053トレースログを見てみると。。。
はっけーーーーーーーーーーーーーーーーーん!!

23ai以降でサポートされたMulti Row Insert文は、ローカル表でもリモート表でも同様にTRANSFORMされていました!!!!!

INSERT INTO 表[(列...)] VALUES 
(列...)
, (列...)
, ....;

 

は、内部的に以下のように、行毎に SYS.DUALをアクセスするSELECT文に分解された後、UNION ALL で縦方向に結合されたインラインビューに!!

INSERT INTO 表[(列...)] 
SELECT 列... FROM
(
SELECT 列... FROM SYS.DUAL
UNION ALL
SELECT 列... FROM SYS.DUAL
UNION ALL
....
)

 

のように書き換えられて実行される。

これはリモート表でもローカル表でも書き換えられています。

では、なにが大きな負荷なっているのか。。
それは、23ai以降、記述が必須ではなくなった、そう、DUAL 表!!!!!

以下のように書き換えられた、Multi row insert 文において、mrows_ins_tab がローカル表である場合は、同一インスタンスのオブジェクトだけを参照するので問題はありません。
高速です!(この形に書き換えられてたのは今知ったのですがw)

INSERT INTO mrows_ins_tab
SELECT 列... FROM
(
SELECT 列... FROM SYS.DUAL
UNION ALL
SELECT 列... FROM SYS.DUAL
UNION ALL
....
)

 

ところが、mrows_ins_tab が DB Link 経由のリモート表である場合、ローカル表扱いの SYS.DUAL が重荷になってきます!!!!
俺書いてないけど! DUAL。

想定では、execution countは、Multi Row Insert文が実行された回数を想定していましたが、Insert対象の表がリモート表である場合は、SYS.DUALはローカル扱いのままなので、
リモートDML(すべてのオブジェクトがDBリンクの先のDBとして扱われる)ではなく、分散DMLとなり、ローカルのSYS.DUALが、合計で 100,000回アクセスされ、それに加えて、リモート表へのINSERT文が、Multi Row Insert文の実行回数分加わる!!

今一度、冒頭の表(前回のエントリの表再掲)を見つつ確認してみましょう!!!

リモート表へのexecute count謎の増加理由
1)10 rows / INSERT  10 rows / INSERT毎に10,000回実行して 100,000行インサートする場合、リモート表だと、
 10 rows * 10,000回 + 10,000回 = 110,000回 + recursive call分なのどオーバーヘッド
 (想定していた、execute countは、 10,000 + recursive call overhead程度)

2) 100 rows / INSERT  100 rows / INSERT毎に1,000回実行して 100,000行インサートする場合、リモート表だと、
 100 rows * 1,000回 + 1,000回 = 101,000回 + recursive call分なのどオーバーヘッド
 (想定していた、execute countは、 1,000 + recursive call overhead程度)

3) 1,000 rows / INSERT  1,000 rows / INSERT毎に100回実行して 100,000行インサートする場合、リモート表だと、
 1,000 rows * 100回 + 100回 = 100,100回 + recursive call分なのどオーバーヘッド
 (想定していた、execute countは、 100 + recursive call overhead程度)

リモート表へのMulti Row Insertのexecute countの実測値と合っていますよね!! その結果、ソフトパースも同様に上昇、パース時間も増加、おそらく、SQL*Net roundtrips to/from dblinkもその影響で増加しているはずです。

Oracle-database-multi-row-inser_20260204210701

 

やっと、謎が解けた。。。。。

ということで、PL/SQLで、Bulk Loadするなら、FORALL構文なのですが、FORALL構文はそもそもリモート表には行えません!
Multi Row Insert が使えるかなーと思いましたが、PL/SQLではローカル表としてアクセスする場合以外は、避けた方がよいですね。いまのところ。

どうしてもPL/SQLでリモート表へバルクロードしなければいけないという辛い状況では、ぐるぐる系でSingle Row Insertしつつ、Commitの間隔を100, 1000などのように調整する泥臭い方式しかないのではないでしょうか。。
なお、リモートへパススルーできないかなーと、ヒントを試しましたが効果はありませんでした。残念

 

ということで、PL/SQLで無理やり、ネットワークラウンドトリップの影響を含めてみるのは諦めましたw
より一般的に、APサーバーとDBサーバー間のネットワークラウンドドリップの影響も含むMulti row Insert vs Single row insert + commit interval方式で、
ぐーるぐーる vs ぐるぐる で比較しつつ、このシリーズをまとめることにしましょう!
なお、Javaのコード書くのめんどくさくなったので、Geminiくんに書いてもらって多少追加して。。。本題へ戻る。。。

 

To be Continued....


おまけの10053トレース抜粋 ローカル表でもリモート表でも以下のようにトランスフォームされる。1,000 rows / INSERTの場合。

[oracle@arm64-oraclelinux8u10 trace]$ view FREE_ora_5503_10053_mrows_remote.txt

Query after VW_MRG2:
qb SEL$1 (#0):******* UNPARSED QUERY IS *******
SELECT :B1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B2 FROM "SYS"."DUAL" "DUAL"

...略...

Query after VW_MRG2:
qb SEL$1000 (#0):******* UNPARSED QUERY IS *******
SELECT :B1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B2 FROM "SYS"."DUAL" "DUAL"
Query after VW_MRG2:
qb SET$1 (#0):******* UNPARSED QUERY IS *******
(SELECT :B1 ":1",NULL "NULL",NULL "NULL",NULL "NULL",NULL "NULL",NULL "NULL",NULL "NULL",NULL "NULL",:B2 ":1" FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B3,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B4 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B5,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B6 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B7,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B8 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B9,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B10 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B11,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B12 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B13,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B14 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B15,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B16 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B17,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B18 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B19,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B20 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B21,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B22 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B23,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B24 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B25,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B26 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B27,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B28 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B29,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B30 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B31,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B32 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B33,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B34 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B35,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B36 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B37,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B38 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B39,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B40 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B41,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B42 FROM "SYS"."DUAL" "DUAL") UNION ALL

...略...

(SELECT :B1991,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B1992 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B1993,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B1994 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B1995,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B1996 FROM "SYS"."DUAL" "DUAL") UNION ALL (SELECT :B1997,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B1998 FROM "SYS"."DUAL" "DUAL") UNION ALL
(SELECT :B1999,NULL,NULL,NULL,NULL,NULL,NULL,NULL,:B2000 FROM "SYS"."DUAL" "DUAL")

 

 


関連エントリ
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT
Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #2
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #3 - ローカル表とリモート表での挙動の差異?!

 

 

 

| | | コメント (0)

2026年2月 3日 (火)

Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #3 - ローカル表とリモート表での挙動の差異?!

Previously on Mac De Oracle
前回は、
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #2
このシリーズものの本題でした。(それ書くまでの寄り道が長かったわけですがw)

ということで今日は、その続編!!

前回で完結じゃないの?!

はいw 、というか再び、脱線していきます! www

前回使った無名PL/SQLブロックのスクリプト(バインド変数を使っている方だけですが)を使って、ローカル表とリモート表(via Database Link)へMulti row Insertするとどうなるのか?
覚えていますか? 前々回、いろいろなバグやら未実装やらのエラーにハマりまくり、なんとかリモート表へMulti row Insert文を投げることに成功した話を。。。。

ローカル表とリモート表だとどのような景色の違いがあるか、絶対、Network Round Trips(dblinkの)が増加するよね!!! 

だとすると、差分(処理時間など含め)の多くは、そのDatabase Linkを介して発生するNetwork Round Trips部分だけのはず。。。ネットワークレイテンシーの影響が見えやすくなる? だろう。。。。。か。
(PL/SQLだからリモート表にするしかなかったのですが、本来なら、JavaやらPythonやらアプリケーションから実行するだけでその部分は見えるわけですけどもね。一応、PL/SQLでやってた流れで、やってみようかなと。。。。w 数々のバグやら仕様やらにハマりましたが。。。w)

ログが長いので、まとめから!w
ポイントになりそうなところだけv$mystatからまとめた表ですが、一目瞭然で、妙な箇所があります。
私が、事前に想定していたのは、execute countはローカル表と同じ値ですし、当然ですが、parse count (total) - parse count (hard) の数もローカル表と同じ想定でした。100rows付近がもっとも結果が良いのはどちらでも同じではあるのですが。。
また、それらに加えて、1,000rows/INSERTにしたケースでは、リモート表へのINSERTで、OPEN_CURSORS(デフォルト 300)が枯渇し、+1,000の 1,300に増加すると枯渇しかなった点です。1,000rowsの時に+1,000したOPEN_CURSORSで枯渇回避になるというのも、気になりますよね。。。。。

SQL*Net roundtrips to/from dblinkが乗ってくるのは、想定通りですが、なんとなく数も多めですしね。。。なんだろうこの違和感w。。。。
想定していた挙動と随分違いそう。。。。DB Linkをつかっちゃったからからもしれないですけども。。。。。。。。

Oracle-database-multi-row-insert-3-1



以下、ローカル表とリモート表でバインド変数を利用したMulti row Insertを10行、100行、1,000行ごとで実行し、合計で 100,000行登録したログです。
なお、今回利用したスクリプトは前回のエントリの後半に載せたものと同じです。
また、DB Linkでリモート表としてアクセスできるようにした内容は前々回のエントリーを参照ください。

ローカル表で、Multi table Insert を 10行、100行、1000行単位で、100,000行登録(バイント変数利用)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_10 100000 10

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
46 CPU used by this session 9
46 CPU used when call started 8
46 SQL*Net roundtrips to/from client 9
...略...
46 execute count 1326
...略...
46 parse count (hard) 140
46 parse count (total) 319
46 parse time cpu 8
46 parse time elapsed 11
...略...
46 session pga memory 4385784
46 session pga memory max 6762144
46 session uga memory 2035760
46 session uga memory max 4893336
...略...

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

経過: 00:00:00.88

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
46 CPU used by this session 95
46 CPU used when call started 95
46 SQL*Net roundtrips to/from client 16
...略...
46 execute count 11479
...略...
46 parse count (hard) 149
46 parse count (total) 353
46 parse time cpu 8
46 parse time elapsed 12
...略...
46 session pga memory 3533816
46 session pga memory max 6762144
46 session uga memory 2101240
46 session uga memory max 4893336
...略...
46 user commits 10000
...略...

COUNT(1)
----------
100000

SEGMENT_NAME MB
------------------------------ ----------
MROWS_INS_TAB 45

表が切り捨てられました。

SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_100 100000 100

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
46 CPU used by this session 4
46 CPU used when call started 3
46 SQL*Net roundtrips to/from client 6
...略...
46 execute count 815
...略...
46 parse count (hard) 100
46 parse count (total) 226
46 parse time cpu 3
46 parse time elapsed 4
...略...
46 session pga memory 4123640
46 session pga memory max 6762144
46 session uga memory 1904800
46 session uga memory max 4893304
...略...

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

経過: 00:00:00.66

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
46 CPU used by this session 70
46 CPU used when call started 70
46 SQL*Net roundtrips to/from client 10
...略...
46 execute count 2119
...略...
46 parse count (hard) 123
46 parse count (total) 286
46 parse time cpu 6
46 parse time elapsed 7
...略...
46 session pga memory 3402744
46 session pga memory max 12119032
46 session uga memory 2101240
46 session uga memory max 4893304
...略...
46 user commits 1000
...略...

COUNT(1)
----------
100000

...略...
SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
46 CPU used by this session 3
46 CPU used when call started 2
46 SQL*Net roundtrips to/from client 6
...略...
46 execute count 464
...略...
46 parse count (hard) 46
46 parse count (total) 193
46 parse time cpu 3
46 parse time elapsed 1
...略...
46 session pga memory 3878560
46 session pga memory max 3878560
46 session uga memory 1527152
46 session uga memory max 2279696
...略...

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

経過: 00:00:01.28

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
46 CPU used by this session 129
46 CPU used when call started 129
46 SQL*Net roundtrips to/from client 10
...略...
46 execute count 872
...略...
46 parse count (hard) 69
46 parse count (total) 254
46 parse time cpu 46
46 parse time elapsed 43
...略...
46 session pga memory 4337312
46 session pga memory max 49229472
46 session uga memory 2997304
46 session uga memory max 3259328
...略...
46 user commits 100

COUNT(1)
----------
100000

...略...


リモート表(via DB Link)で、Multi table Insert を 10行、100行、1000行単位で、100,000行登録(バイント変数利用)
リモート表は、Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?で作成した環境をそのまま利用しています。

SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_10 100000 10

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
179 CPU used by this session 4
179 CPU used when call started 3
179 SQL*Net roundtrips to/from client 9
...略...
179 execute count 603
...略...
179 parse count (hard) 55
179 parse count (total) 221
179 parse time cpu 2
179 parse time elapsed 4
...略...
179 session pga memory 3927032
179 session pga memory max 3927032
179 session uga memory 1637320
179 session uga memory max 2345032
...略...

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

経過: 00:00:22.88

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
179 CPU used by this session 896
179 CPU used when call started 896
179 SQL*Net roundtrips to/from client 16
179 SQL*Net roundtrips to/from dblink 240031
...略...
179 execute count 110719
...略...
179 parse count (hard) 91
179 parse count (total) 100268
179 parse time cpu 7
179 parse time elapsed 21
...略...
179 session pga memory 4320248
179 session pga memory max 4582392
179 session uga memory 2277856
179 session uga memory max 2858632
...略...
179 user commits 10000
...略...

COUNT(1)
----------
100000

...略...

SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_100 100000 100

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
179 CPU used by this session 5
179 CPU used when call started 5
179 SQL*Net roundtrips to/from client 6
...略...
179 execute count 831
...略...
179 parse count (hard) 107
179 parse count (total) 229
179 parse time cpu 4
179 parse time elapsed 4
...略...
179 session pga memory 4123640
179 session pga memory max 5648032
179 session uga memory 1904800
179 session uga memory max 3779144
...略...

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

経過: 00:00:18.01

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
179 CPU used by this session 817
179 CPU used when call started 817
179 SQL*Net roundtrips to/from client 10
179 SQL*Net roundtrips to/from dblink 204211
...略...
179 execute count 102095
...略...
179 parse count (hard) 234
179 parse count (total) 101389
179 parse time cpu 26
179 parse time elapsed 47
...略...
179 session pga memory 4582392
179 session pga memory max 6548472
179 session uga memory 3142472
179 session uga memory max 3779144
...略...
179 user commits 1000
...略...

COUNT(1)
----------
100000

...略...


リモート表へ、1000rows/INSERTを実行したら。。。。

SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
...略...
179 CPU used by this session 5
179 CPU used when call started 4
179 SQL*Net roundtrips to/from client 6
...略...
179 execute count 558
...略...
179 parse count (hard) 73
179 parse count (total) 212
179 parse time cpu 5
179 parse time elapsed 5
...略...


DECLARE
*
行1でエラーが発生しました。:
ORA-01000: セッションの最大オープン・カーソル数がPotential Leaked SQL_ID: を超えました ORA-02063:
先行のエラー・メッセージを参照してくださいline(FREEPDB1)。 ORA-02063:
先行のエラー・メッセージを参照してください2 lines(LINK2SCOTT)。 ORA-06512: 行59
ヘルプ: https://docs.oracle.com/error-help/db/ora-01000/

*
行1でエラーが発生しました。:
RA-01000: セッションの最大オープン・カーソル数がを超えました ヘルプ:
https://docs.oracle.com/error-help/db/ora-01000/


ん? 妙ですねぇー。OPEN_CURSORS=300(デフォルト)を超えちゃったようです。。。仕方ないので、一時的に大きめに。
なんかきになるなー。ローカル表だとそんなこと起きないのに。。。ちょうど+1,000したら回避できたというのも、なんとなく気になる値ではあるし。。。。

SYSTEM@localhost:1521/freepdb1> alter system set open_cursors = 1300 scope=memory;

システムが変更されました。

SCOTT2@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
16 CPU used by this session 5
16 CPU used when call started 5
16 SQL*Net roundtrips to/from client 6
...略...
16 execute count 805
...略...
16 parse count (hard) 107
16 parse count (total) 229
16 parse time cpu 4
16 parse time elapsed 4
...略...
16 session pga memory 4058104
16 session pga memory max 5123744
16 session uga memory 1899488
16 session uga memory max 3053984
...略...

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

経過: 00:00:24.14

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
16 CPU used by this session 1213
16 CPU used when call started 1213
16 SQL*Net roundtrips to/from client 10
16 SQL*Net roundtrips to/from dblink 202411
...略...
16 execute count 100996
...略...
16 parse count (hard) 1121
16 parse count (total) 101346
16 parse time cpu 85
16 parse time elapsed 135
...略...
16 session pga memory 12119032
16 session pga memory max 25619448
16 session uga memory 10382272
16 session uga memory max 12082240
...略...
16 user commits 100
...略...

COUNT(1)
----------
100000

...略...


ということで、新たな謎を追って、Matrix...いや、Oracleの奥地へ....wwww

To be continued....



関連エントリ
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT
Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?
Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #2

| | | コメント (0)

2026年2月 2日 (月)

Oracle Database - Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね #2

Previously on Mac De Oracle
前回は、
Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?
でした。本題からちょっと脱線気味でしたがw、今日は、Multi Row INSERT、バインド変数を使うとリテラル値を使う場合では見える景色が変わるんだよね。の本題。(やっとw)


前々回、帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERTのようにリテラル値のままでMulti row Insertしちゃうと言う無茶なことやっていると、いくらメモリ(メモリだけでもないけども)があったも足らなくなるよなーーー。というのはイメージできたと思うので、今日はバインド変数化すると景色がどう変わるか(良い方に)見てみましょう。


Use The Bind Variable, Luke!

メモリ消費も抑えられますし。ハードパースコストも下げられますし。:)
特にOracle Databaseのようにハードパースなど比較的重量級かつカーソルシェアリングなど含むキャッシュ機能満載のRDBMSでは絶大な効果を発揮します。はい。

リテラル値のままのmulti row insertとバインド変数化した場合のPGA周り含めた景色、どうかわるでしょうねぇ。。。。(そんなことわかっとる! と言う方は見なくて良いです ;)


前回色々ハマりながら作ってたw 無名PL/SQLブロックを含むスクリプトを利用(Geminiくんにもお手伝いしてもらいw)して、ローカル表(リモート表の話は別ネタにてw)へアクセスして、バインド変数有無でそのような景色の違いがでるかを見てみましょう。

23aiでサポートされたMulti row Insert構文で、10行、100行、1000行まとめてインサートする文のPGA/UGAの最大サイズを比較。


バインド変数を利用せず、1000行まとめたMulti row Insertでは、SQL文をCLOBで保持した影響で、PGAから溢れて、一時LOBを利用した影響で、log write/readが発生。PGA/UGAは多少減り、変わり一時表領域へのIO増加ということからパース時間が随分伸びたことがわかります!
バインド変数を利用した場合とのPGA/UGA消費サイズ、および、パースタイムは大幅に軽減されていて、各種リソースにも優しくなっているのが見えますよね!(ハードパース回数でも一目瞭然)
あと、傾向として、100行毎ぐらいにまとめるのが全体的にリーズナブルな傾向はありますよね。(この前後がリーズナブルって肌感覚とも一致してるんですよね。何行ぐらいにしようかなー。と探る時もだいだいこれぐらいの前後一桁ずらしてってことは多いかな)

パース時間とPGA/UGAの最大サイズ比較
Oracle-database-multi-row-insert-2-pga1


以下、ログの一部を載せておきます。
Multi row Insertでバインド変数を利用しない(リテラル値) - 10行ごと、100,000行登録(10 row Insert * 10,000回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_nobind 100000 10

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 8
201 CPU used when call started 7
201 SQL*Net roundtrips to/from client 9
...略...
201 execute count 1383
...略...
201 parse count (hard) 144
201 parse count (total) 322
201 parse time cpu 7
201 parse time elapsed 8
...略...
201 session pga memory 4385784
201 session pga memory max 5189280
201 session uga memory 2035656
201 session uga memory max 3119464

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

経過: 00:00:09.98

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 1003
201 CPU used when call started 1003
201 SQL*Net roundtrips to/from client 16
...略...
201 execute count 21481
...略...
201 parse count (hard) 10149
201 parse count (total) 20348
201 parse time cpu 854
201 parse time elapsed 883
...略...
201 session pga memory 4647928
201 session pga memory max 5189280
201 session uga memory 2232024
201 session uga memory max 3119464
...略...
201 user commits 10000

COUNT(1)
----------
100000

SEGMENT_NAME MB
------------------------------ ----------
MROWS_INS_TAB 45

表が切り捨てられました。


Multi row Insertでバインド変数を利用しない(リテラル値) - 100行ごと、100,000行登録(100 row Insert * 1,000回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_nobind 100000 100

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 3
48 CPU used when call started 2
48 SQL*Net roundtrips to/from client 9
...略...
48 execute count 410
...略...
48 parse count (hard) 52
48 parse count (total) 204
48 parse time cpu 3
48 parse time elapsed 3
...略...
48 session pga memory 3681952
48 session pga memory max 3681952
48 session uga memory 1577072
48 session uga memory max 1642592

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

経過: 00:00:08.34

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 827
48 CPU used when call started 827
48 SQL*Net roundtrips to/from client 16
...略...
48 execute count 2465
...略...
48 parse count (hard) 1060
48 parse count (total) 2227
48 parse time cpu 695
48 parse time elapsed 733
...略...
48 session pga memory 207547384
48 session pga memory max 207547384
48 session uga memory 201509624
48 session uga memory max 201509624
...略...
48 user commits 1000

COUNT(1)
----------
100000

...略...


Multi row Insertでバインド変数を利用しない(リテラル値) - 1,000行ごと、100,000行登録(1000 row Insert * 100回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_nobind 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 6
48 CPU used when call started 6
48 SQL*Net roundtrips to/from client 9
...略...
48 execute count 1357
...略...
48 parse count (hard) 144
48 parse count (total) 329
48 parse time cpu 5
48 parse time elapsed 8
...略...
48 session pga memory 4254712
48 session pga memory max 5123744
48 session uga memory 1970280
48 session uga memory max 3054144

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

経過: 00:00:34.79

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 3481
48 CPU used when call started 3481
48 SQL*Net roundtrips to/from client 16
...略...
48 execute count 1677
48 lob reads 831888
48 lob writes 415994
48 lob writes unaligned 415994
...略...
48 parse count (hard) 252
48 parse count (total) 557
48 parse time cpu 3015
48 parse time elapsed 3019
...略...
48 session pga memory 66841592
48 session pga memory max 109177848
48 session uga memory 64872848
48 session uga memory max 69655680
...略...
48 user commits 100

COUNT(1)
----------
100000
...略...


Multi row Insertでバインド変数を利用 - 10行ごと、100,000行登録(10 row Insert * 10,000回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_10 100000 10

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 6
48 CPU used when call started 6
48 SQL*Net roundtrips to/from client 9
...略...
48 execute count 1383
...略...
48 parse count (hard) 144
48 parse count (total) 322
48 parse time cpu 5
48 parse time elapsed 9
...略...
48 session pga memory 4385784
48 session pga memory max 5189280
48 session uga memory 2035760
48 session uga memory max 3119544

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

経過: 00:00:00.97

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
48 CPU used by this session 99
48 CPU used when call started 99
48 SQL*Net roundtrips to/from client 16
...略...
48 execute count 11535
...略...
48 parse count (hard) 153
48 parse count (total) 356
48 parse time cpu 6
48 parse time elapsed 9
...略...
48 session pga memory 3533816
48 session pga memory max 5189280
48 session uga memory 2101240
48 session uga memory max 3119544
...略...
48 user commits 10000

COUNT(1)
----------
100000
...略...


Multi row Insertでバインド変数を利用 - 100行ごと、100,000行登録(100 row Insert * 1,000回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_100 100000 100

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 5
176 CPU used when call started 5
176 SQL*Net roundtrips to/from client 6
...略...
176 execute count 831
...略...
176 parse count (hard) 107
176 parse count (total) 229
176 parse time cpu 6
176 parse time elapsed 3
...略...
176 session pga memory 4123640
176 session pga memory max 5123744
176 session uga memory 1904800
176 session uga memory max 3119472

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

経過: 00:00:00.77

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
176 CPU used by this session 82
176 CPU used when call started 82
176 SQL*Net roundtrips to/from client 10
...略...
176 execute count 2109
...略...
176 parse count (hard) 130
176 parse count (total) 289
176 parse time cpu 9
176 parse time elapsed 5
...略...
176 session pga memory 3468280
176 session pga memory max 12119032
176 session uga memory 2101240
176 session uga memory max 3119472
...略...
176 user commits 1000

COUNT(1)
----------
100000
...略...


Multi row Insertでバインド変数を利用 - 1000行ごと、100,000行登録(1000 row Insert * 100回)

SCOTT@localhost:1521/freepdb1> @multi_row_insert_bind_1000 100000 1000

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 1
201 CPU used when call started 1
201 SQL*Net roundtrips to/from client 6
...略...
201 execute count 10
...略...
201 parse count (total) 11
...略...
201 session pga memory 1715872
201 session pga memory max 1715872
201 session uga memory 529464
201 session uga memory max 594992

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

経過: 00:00:01.30

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
201 CPU used by this session 130
201 CPU used when call started 130
201 SQL*Net roundtrips to/from client 10
...略...
201 execute count 115
...略...
201 parse count (hard) 2
201 parse count (total) 17
201 parse time cpu 42
201 parse time elapsed 42
...略...
201 session pga memory 3730424
201 session pga memory max 48049824
201 session uga memory 2232032
201 session uga memory max 2297536
...略...
201 user commits 100

COUNT(1)
----------
100000
...略...

Use The Bind Variable, Luke!

サーバーの電力消費節約にもなるかな。。

では、また。



いつものように、今回使ったスクリプトとかコードとかは以下。(めんどくさくなって、Geminiくんにお願いしたりしてますがww)
show_mystats.sql
SELECT
s.sid,
n.name,
s.value
FROM
v$mystat s
INNER JOIN v$statname n
ON
s.statistic# = n.statistic#
WHERE
s.value > 0
AND (
n.name LIKE '%memory%'
OR n.name LIKE '%CPU%'
OR n.name LIKE '%I/O%'
OR n.name LIKE '%write%'
OR n.name LIKE '%read%'
OR n.name LIKE 'redo%'
OR n.name LIKE 'SQL*Net%'
OR n.name LIKE '%commit%'
OR n.name LIKE 'execute count'
OR n.name LIKE 'parse%'
)
ORDER BY
n.name;


show_mrows_ins_tab_size.sql

select 
segment_name
, bytes/1024/1024 as "MB"
from
dba_segments
where
owner='SCOTT'
and segment_name = upper('mrows_ins_tab')
/


multi_row_insert_nobind.sql

set veri off
@show_mystats

DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rows_per_stmt CONSTANT NUMBER := &2;
--
sql_stmt CLOB := EMPTY_CLOB();
start_row NUMBER := 1;
end_row NUMBER := c_rows_per_stmt;
--
BEGIN
FOR k IN 1..c_max_rows / c_rows_per_stmt LOOP
sql_stmt := 'INSERT INTO mrows_ins_tab VALUES';
--
FOR i IN start_row..end_row LOOP
sql_stmt :=
sql_stmt
|| CASE WHEN i - (c_rows_per_stmt * (k - 1)) > 1 THEN ', ' END
|| '('
|| TO_CHAR(i)
|| ', null, null, null, null, null, null, null, ''' || LPAD(TO_CHAR(i),373,'x') || ''')';
END LOOP;
--
start_row := start_row + c_rows_per_stmt;
end_row := end_row + c_rows_per_stmt;
--
EXECUTE IMMEDIATE sql_stmt;
COMMIT;
sql_stmt := EMPTY_CLOB();
END LOOP;
END;
/

set veri on
UNDEFINE 1
UNDEFINE 2

@show_mystats

SELECT COUNT(1) FROM scott.mrows_ins_tab
/
@show_mrows_ins_tab_size
truncate table scott.mrows_ins_tab
/


multi_row_insert_bind_10.sql

set veri off
SET SERVEROUTPUT ON
@show_mystats

DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rows_per_stmt CONSTANT NUMBER := &2;
--
sql_stmt CLOB := EMPTY_CLOB();
start_row NUMBER := 1;
end_row NUMBER := c_rows_per_stmt;
n NUMBER;
o NUMBER;
TYPE c1_type IS VARRAY(c_rows_per_stmt) OF NUMBER;
TYPE c8_type IS VARRAY(c_rows_per_stmt) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
m NUMBER;
--
BEGIN
--
c1.extend(c_rows_per_stmt);
c8.extend(c_rows_per_stmt);
--
sql_stmt := 'INSERT INTO mrows_ins_tab VALUES';
FOR i IN 1..c_rows_per_stmt LOOP
--
sql_stmt :=
sql_stmt
|| CASE WHEN i > 1 THEN ', ' END
|| '('
|| ':c1'||TO_CHAR(i)||', null, null, null, null, null, null, null, :c8'||TO_CHAR(i)|| ')';
END LOOP;
sql_stmt := sql_stmt || ';';
--
FOR l IN 1..c_max_rows / c_rows_per_stmt LOOP
FOR j IN start_row..end_row LOOP
o := l * c_rows_per_stmt;
n := MOD(j, c_rows_per_stmt);
m := CASE WHEN MOD(l, c_rows_per_stmt) > c_rows_per_stmt
THEN j - o
ELSE CASE WHEN n = 0
THEN c_rows_per_stmt
ELSE n
END
END;
c1(m) := j;
c8(m) := LPAD(TO_CHAR(j),373,'x');
END LOOP;
start_row := start_row + c_rows_per_stmt;
end_row := end_row + c_rows_per_stmt;
--
EXECUTE IMMEDIATE sql_stmt
USING
c1(1), c8(1), c1(2), c8(2), c1(3), c8(3), c1(4), c8(4), c1(5), c8(5), c1(6), c8(6), c1(7), c8(7), c1(8), c8(8), c1(9), c8(9), c1(10), c8(10)
;
COMMIT;
END LOOP;
END;
/

SET SERVEROUTPUT OFF
set veri on
UNDEFINE 1
UNDEFINE 2

@show_mystats
SELECT COUNT(1) FROM scott.mrows_ins_tab
/
@show_mrows_ins_tab_size
truncate table scott.mrows_ins_tab
/


multi_row_insert_bind_100.sql

set veri off
@show_mystats

DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rows_per_stmt CONSTANT NUMBER := &2;
--
sql_stmt CLOB := EMPTY_CLOB();
start_row NUMBER := 1;
end_row NUMBER := c_rows_per_stmt;
n NUMBER;
o NUMBER;
TYPE c1_type IS VARRAY(c_rows_per_stmt) OF NUMBER;
TYPE c8_type IS VARRAY(c_rows_per_stmt) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
m NUMBER;
--
BEGIN
--
c1.extend(c_rows_per_stmt);
c8.extend(c_rows_per_stmt);
--
sql_stmt := 'INSERT INTO mrows_ins_tab VALUES';
FOR i IN 1..c_rows_per_stmt LOOP
--
sql_stmt :=
sql_stmt
|| CASE WHEN i > 1 THEN ', ' END
|| '('
|| ':c1'||TO_CHAR(i)||', null, null, null, null, null, null, null, :c8'||TO_CHAR(i)|| ')';
END LOOP;
sql_stmt := sql_stmt || ';';
--
FOR l IN 1..c_max_rows / c_rows_per_stmt LOOP
FOR j IN start_row..end_row LOOP
o := l * c_rows_per_stmt;
n := MOD(j, c_rows_per_stmt);
m := CASE WHEN MOD(l, c_rows_per_stmt) > c_rows_per_stmt
THEN j - o
ELSE CASE WHEN n = 0
THEN c_rows_per_stmt
ELSE n
END
END;
c1(m) := j;
c8(m) := LPAD(TO_CHAR(j),373,'x');
END LOOP;
start_row := start_row + c_rows_per_stmt;
end_row := end_row + c_rows_per_stmt;
--
EXECUTE IMMEDIATE sql_stmt
USING
c1(1) ,c8(1) ,c1(2) ,c8(2) ,c1(3) ,c8(3) ,c1(4) ,c8(4) ,c1(5) ,c8(5) ,c1(6) ,c8(6) ,c1(7) ,c8(7) ,c1(8) ,c8(8) ,c1(9) ,c8(9) ,c1(10) ,c8(10)
, c1(11) ,c8(11) ,c1(12) ,c8(12) ,c1(13) ,c8(13) ,c1(14) ,c8(14) ,c1(15) ,c8(15) ,c1(16) ,c8(16) ,c1(17) ,c8(17) ,c1(18) ,c8(18) ,c1(19) ,c8(19) ,c1(20) ,c8(20)
, c1(21) ,c8(21) ,c1(22) ,c8(22) ,c1(23) ,c8(23) ,c1(24) ,c8(24) ,c1(25) ,c8(25) ,c1(26) ,c8(26) ,c1(27) ,c8(27) ,c1(28) ,c8(28) ,c1(29) ,c8(29) ,c1(30) ,c8(30)
, c1(31) ,c8(31) ,c1(32) ,c8(32) ,c1(33) ,c8(33) ,c1(34) ,c8(34) ,c1(35) ,c8(35) ,c1(36) ,c8(36) ,c1(37) ,c8(37) ,c1(38) ,c8(38) ,c1(39) ,c8(39) ,c1(40) ,c8(40)
, c1(41) ,c8(41) ,c1(42) ,c8(42) ,c1(43) ,c8(43) ,c1(44) ,c8(44) ,c1(45) ,c8(45) ,c1(46) ,c8(46) ,c1(47) ,c8(47) ,c1(48) ,c8(48) ,c1(49) ,c8(49) ,c1(50) ,c8(50)
, c1(51) ,c8(51) ,c1(52) ,c8(52) ,c1(53) ,c8(53) ,c1(54) ,c8(54) ,c1(55) ,c8(55) ,c1(56) ,c8(56) ,c1(57) ,c8(57) ,c1(58) ,c8(58) ,c1(59) ,c8(59) ,c1(60) ,c8(60)
, c1(61) ,c8(61) ,c1(62) ,c8(62) ,c1(63) ,c8(63) ,c1(64) ,c8(64) ,c1(65) ,c8(65) ,c1(66) ,c8(66) ,c1(67) ,c8(67) ,c1(68) ,c8(68) ,c1(69) ,c8(69) ,c1(70) ,c8(70)
, c1(71) ,c8(71) ,c1(72) ,c8(72) ,c1(73) ,c8(73) ,c1(74) ,c8(74) ,c1(75) ,c8(75) ,c1(76) ,c8(76) ,c1(77) ,c8(77) ,c1(78) ,c8(78) ,c1(79) ,c8(79) ,c1(80) ,c8(80)
, c1(81) ,c8(81) ,c1(82) ,c8(82) ,c1(83) ,c8(83) ,c1(84) ,c8(84) ,c1(85) ,c8(85) ,c1(86) ,c8(86) ,c1(87) ,c8(87) ,c1(88) ,c8(88) ,c1(89) ,c8(89) ,c1(90) ,c8(90)
, c1(91) ,c8(91) ,c1(92) ,c8(92) ,c1(93) ,c8(93) ,c1(94) ,c8(94) ,c1(95) ,c8(95) ,c1(96) ,c8(96) ,c1(97) ,c8(97) ,c1(98) ,c8(98) ,c1(99) ,c8(99) ,c1(100) ,c8(100)
;
COMMIT;
END LOOP;
END;
/
set veri on
UNDEFINE 1
UNDEFINE 2

@show_mystats

SELECT COUNT(1) FROM mrows_ins_tab
/
@show_mrows_ins_tab_size
truncate table scott.mrows_ins_tab
/


multi_row_insert_bind_1000.sql

set veri off
@show_mystats

DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rows_per_stmt CONSTANT NUMBER := &2
; --
sql_stmt CLOB := EMPTY_CLOB();
start_row NUMBER := 1;
end_row NUMBER := c_rows_per_stmt;
n NUMBER;
o NUMBER;
TYPE c1_type IS VARRAY(c_rows_per_stmt) OF NUMBER;
TYPE c8_type IS VARRAY(c_rows_per_stmt) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
m NUMBER;
--
BEGIN
--
c1.extend(c_rows_per_stmt);
c8.extend(c_rows_per_stmt);
--
sql_stmt := 'INSERT /*+ REMOTE_MAPPED(link2scott) */ INTO mrows_ins_tab VALUES';
FOR i IN 1..c_rows_per_stmt LOOP
--
sql_stmt :=
sql_stmt
|| CASE WHEN i > 1 THEN ', ' END
|| '('
|| ':c1'||TO_CHAR(i)||', null, null, null, null, null, null, null, :c8'||TO_CHAR(i)|| ')';
END LOOP;
sql_stmt := sql_stmt || ';';
--
FOR l IN 1..c_max_rows / c_rows_per_stmt LOOP
FOR j IN start_row..end_row LOOP
o := l * c_rows_per_stmt;
n := MOD(j, c_rows_per_stmt);
m := CASE WHEN MOD(l, c_rows_per_stmt) > c_rows_per_stmt
THEN j - o
ELSE CASE WHEN n = 0
THEN c_rows_per_stmt
ELSE n
END
END;
c1(m) := j;
c8(m) := LPAD(TO_CHAR(j),373,'x');
END LOOP;
start_row := start_row + c_rows_per_stmt;
end_row := end_row + c_rows_per_stmt;
--
EXECUTE IMMEDIATE sql_stmt
USING
c1(1) ,c8(1) ,c1(2) ,c8(2) ,c1(3) ,c8(3) ,c1(4) ,c8(4) ,c1(5) ,c8(5) ,c1(6) ,c8(6) ,c1(7) ,c8(7) ,c1(8) ,c8(8) ,c1(9) ,c8(9) ,c1(10) ,c8(10)
, c1(11) ,c8(11) ,c1(12) ,c8(12) ,c1(13) ,c8(13) ,c1(14) ,c8(14) ,c1(15) ,c8(15) ,c1(16) ,c8(16) ,c1(17) ,c8(17) ,c1(18) ,c8(18) ,c1(19) ,c8(19) ,c1(20) ,c8(20)
, c1(21) ,c8(21) ,c1(22) ,c8(22) ,c1(23) ,c8(23) ,c1(24) ,c8(24) ,c1(25) ,c8(25) ,c1(26) ,c8(26) ,c1(27) ,c8(27) ,c1(28) ,c8(28) ,c1(29) ,c8(29) ,c1(30) ,c8(30)
, c1(31) ,c8(31) ,c1(32) ,c8(32) ,c1(33) ,c8(33) ,c1(34) ,c8(34) ,c1(35) ,c8(35) ,c1(36) ,c8(36) ,c1(37) ,c8(37) ,c1(38) ,c8(38) ,c1(39) ,c8(39) ,c1(40) ,c8(40)
, c1(41) ,c8(41) ,c1(42) ,c8(42) ,c1(43) ,c8(43) ,c1(44) ,c8(44) ,c1(45) ,c8(45) ,c1(46) ,c8(46) ,c1(47) ,c8(47) ,c1(48) ,c8(48) ,c1(49) ,c8(49) ,c1(50) ,c8(50)
, c1(51) ,c8(51) ,c1(52) ,c8(52) ,c1(53) ,c8(53) ,c1(54) ,c8(54) ,c1(55) ,c8(55) ,c1(56) ,c8(56) ,c1(57) ,c8(57) ,c1(58) ,c8(58) ,c1(59) ,c8(59) ,c1(60) ,c8(60)
, c1(61) ,c8(61) ,c1(62) ,c8(62) ,c1(63) ,c8(63) ,c1(64) ,c8(64) ,c1(65) ,c8(65) ,c1(66) ,c8(66) ,c1(67) ,c8(67) ,c1(68) ,c8(68) ,c1(69) ,c8(69) ,c1(70) ,c8(70)
, c1(71) ,c8(71) ,c1(72) ,c8(72) ,c1(73) ,c8(73) ,c1(74) ,c8(74) ,c1(75) ,c8(75) ,c1(76) ,c8(76) ,c1(77) ,c8(77) ,c1(78) ,c8(78) ,c1(79) ,c8(79) ,c1(80) ,c8(80)
, c1(81) ,c8(81) ,c1(82) ,c8(82) ,c1(83) ,c8(83) ,c1(84) ,c8(84) ,c1(85) ,c8(85) ,c1(86) ,c8(86) ,c1(87) ,c8(87) ,c1(88) ,c8(88) ,c1(89) ,c8(89) ,c1(90) ,c8(90)
, c1(91) ,c8(91) ,c1(92) ,c8(92) ,c1(93) ,c8(93) ,c1(94) ,c8(94) ,c1(95) ,c8(95) ,c1(96) ,c8(96) ,c1(97) ,c8(97) ,c1(98) ,c8(98) ,c1(99) ,c8(99),c1(100),c8(100)
, c1(101),c8(101),c1(102),c8(102),c1(103),c8(103),c1(104),c8(104),c1(105),c8(105),c1(106),c8(106),c1(107),c8(107),c1(108),c8(108),c1(109),c8(109),c1(110),c8(110)
, c1(111),c8(111),c1(112),c8(112),c1(113),c8(113),c1(114),c8(114),c1(115),c8(115),c1(116),c8(116),c1(117),c8(117),c1(118),c8(118),c1(119),c8(119),c1(120),c8(120)
, c1(121),c8(121),c1(122),c8(122),c1(123),c8(123),c1(124),c8(124),c1(125),c8(125),c1(126),c8(126),c1(127),c8(127),c1(128),c8(128),c1(129),c8(129),c1(130),c8(130)
, c1(131),c8(131),c1(132),c8(132),c1(133),c8(133),c1(134),c8(134),c1(135),c8(135),c1(136),c8(136),c1(137),c8(137),c1(138),c8(138),c1(139),c8(139),c1(140),c8(140)
, c1(141),c8(141),c1(142),c8(142),c1(143),c8(143),c1(144),c8(144),c1(145),c8(145),c1(146),c8(146),c1(147),c8(147),c1(148),c8(148),c1(149),c8(149),c1(150),c8(150)
, c1(151),c8(151),c1(152),c8(152),c1(153),c8(153),c1(154),c8(154),c1(155),c8(155),c1(156),c8(156),c1(157),c8(157),c1(158),c8(158),c1(159),c8(159),c1(160),c8(160)
, c1(161),c8(161),c1(162),c8(162),c1(163),c8(163),c1(164),c8(164),c1(165),c8(165),c1(166),c8(166),c1(167),c8(167),c1(168),c8(168),c1(169),c8(169),c1(170),c8(170)
, c1(171),c8(171),c1(172),c8(172),c1(173),c8(173),c1(174),c8(174),c1(175),c8(175),c1(176),c8(176),c1(177),c8(177),c1(178),c8(178),c1(179),c8(179),c1(180),c8(180)
, c1(181),c8(181),c1(182),c8(182),c1(183),c8(183),c1(184),c8(184),c1(185),c8(185),c1(186),c8(186),c1(187),c8(187),c1(188),c8(188),c1(189),c8(189),c1(190),c8(190)
, c1(191),c8(191),c1(192),c8(192),c1(193),c8(193),c1(194),c8(194),c1(195),c8(195),c1(196),c8(196),c1(197),c8(197),c1(198),c8(198),c1(199),c8(199),c1(200),c8(200)
, c1(201),c8(201),c1(202),c8(202),c1(203),c8(203),c1(204),c8(204),c1(205),c8(205),c1(206),c8(206),c1(207),c8(207),c1(208),c8(208),c1(209),c8(209),c1(210),c8(210)
, c1(211),c8(211),c1(212),c8(212),c1(213),c8(213),c1(214),c8(214),c1(215),c8(215),c1(216),c8(216),c1(217),c8(217),c1(218),c8(218),c1(219),c8(219),c1(220),c8(220)
, c1(221),c8(221),c1(222),c8(222),c1(223),c8(223),c1(224),c8(224),c1(225),c8(225),c1(226),c8(226),c1(227),c8(227),c1(228),c8(228),c1(229),c8(229),c1(230),c8(230)
, c1(231),c8(231),c1(232),c8(232),c1(233),c8(233),c1(234),c8(234),c1(235),c8(235),c1(236),c8(236),c1(237),c8(237),c1(238),c8(238),c1(239),c8(239),c1(240),c8(240)
, c1(241),c8(241),c1(242),c8(242),c1(243),c8(243),c1(244),c8(244),c1(245),c8(245),c1(246),c8(246),c1(247),c8(247),c1(248),c8(248),c1(249),c8(249),c1(250),c8(250)
, c1(251),c8(251),c1(252),c8(252),c1(253),c8(253),c1(254),c8(254),c1(255),c8(255),c1(256),c8(256),c1(257),c8(257),c1(258),c8(258),c1(259),c8(259),c1(260),c8(260)
, c1(261),c8(261),c1(262),c8(262),c1(263),c8(263),c1(264),c8(264),c1(265),c8(265),c1(266),c8(266),c1(267),c8(267),c1(268),c8(268),c1(269),c8(269),c1(270),c8(270)
, c1(271),c8(271),c1(272),c8(272),c1(273),c8(273),c1(274),c8(274),c1(275),c8(275),c1(276),c8(276),c1(277),c8(277),c1(278),c8(278),c1(279),c8(279),c1(280),c8(280)
, c1(281),c8(281),c1(282),c8(282),c1(283),c8(283),c1(284),c8(284),c1(285),c8(285),c1(286),c8(286),c1(287),c8(287),c1(288),c8(288),c1(289),c8(289),c1(290),c8(290)
, c1(291),c8(291),c1(292),c8(292),c1(293),c8(293),c1(294),c8(294),c1(295),c8(295),c1(296),c8(296),c1(297),c8(297),c1(298),c8(298),c1(299),c8(299),c1(300),c8(300)
, c1(301),c8(301),c1(302),c8(302),c1(303),c8(303),c1(304),c8(304),c1(305),c8(305),c1(306),c8(306),c1(307),c8(307),c1(308),c8(308),c1(309),c8(309),c1(310),c8(310)
, c1(311),c8(311),c1(312),c8(312),c1(313),c8(313),c1(314),c8(314),c1(315),c8(315),c1(316),c8(316),c1(317),c8(317),c1(318),c8(318),c1(319),c8(319),c1(320),c8(320)
, c1(321),c8(321),c1(322),c8(322),c1(323),c8(323),c1(324),c8(324),c1(325),c8(325),c1(326),c8(326),c1(327),c8(327),c1(328),c8(328),c1(329),c8(329),c1(330),c8(330)
, c1(331),c8(331),c1(332),c8(332),c1(333),c8(333),c1(334),c8(334),c1(335),c8(335),c1(336),c8(336),c1(337),c8(337),c1(338),c8(338),c1(339),c8(339),c1(340),c8(340)
, c1(341),c8(341),c1(342),c8(342),c1(343),c8(343),c1(344),c8(344),c1(345),c8(345),c1(346),c8(346),c1(347),c8(347),c1(348),c8(348),c1(349),c8(349),c1(350),c8(350)
, c1(351),c8(351),c1(352),c8(352),c1(353),c8(353),c1(354),c8(354),c1(355),c8(355),c1(356),c8(356),c1(357),c8(357),c1(358),c8(358),c1(359),c8(359),c1(360),c8(360)
, c1(361),c8(361),c1(362),c8(362),c1(363),c8(363),c1(364),c8(364),c1(365),c8(365),c1(366),c8(366),c1(367),c8(367),c1(368),c8(368),c1(369),c8(369),c1(370),c8(370)
, c1(371),c8(371),c1(372),c8(372),c1(373),c8(373),c1(374),c8(374),c1(375),c8(375),c1(376),c8(376),c1(377),c8(377),c1(378),c8(378),c1(379),c8(379),c1(380),c8(380)
, c1(381),c8(381),c1(382),c8(382),c1(383),c8(383),c1(384),c8(384),c1(385),c8(385),c1(386),c8(386),c1(387),c8(387),c1(388),c8(388),c1(389),c8(389),c1(390),c8(390)
, c1(391),c8(391),c1(392),c8(392),c1(393),c8(393),c1(394),c8(394),c1(395),c8(395),c1(396),c8(396),c1(397),c8(397),c1(398),c8(398),c1(399),c8(399),c1(400),c8(400)
, c1(401),c8(401),c1(402),c8(402),c1(403),c8(403),c1(404),c8(404),c1(405),c8(405),c1(406),c8(406),c1(407),c8(407),c1(408),c8(408),c1(409),c8(409),c1(410),c8(410)
, c1(411),c8(411),c1(412),c8(412),c1(413),c8(413),c1(414),c8(414),c1(415),c8(415),c1(416),c8(416),c1(417),c8(417),c1(418),c8(418),c1(419),c8(419),c1(420),c8(420)
, c1(421),c8(421),c1(422),c8(422),c1(423),c8(423),c1(424),c8(424),c1(425),c8(425),c1(426),c8(426),c1(427),c8(427),c1(428),c8(428),c1(429),c8(429),c1(430),c8(430)
, c1(431),c8(431),c1(432),c8(432),c1(433),c8(433),c1(434),c8(434),c1(435),c8(435),c1(436),c8(436),c1(437),c8(437),c1(438),c8(438),c1(439),c8(439),c1(440),c8(440)
, c1(441),c8(441),c1(442),c8(442),c1(443),c8(443),c1(444),c8(444),c1(445),c8(445),c1(446),c8(446),c1(447),c8(447),c1(448),c8(448),c1(449),c8(449),c1(450),c8(450)
, c1(451),c8(451),c1(452),c8(452),c1(453),c8(453),c1(454),c8(454),c1(455),c8(455),c1(456),c8(456),c1(457),c8(457),c1(458),c8(458),c1(459),c8(459),c1(460),c8(460)
, c1(461),c8(461),c1(462),c8(462),c1(463),c8(463),c1(464),c8(464),c1(465),c8(465),c1(466),c8(466),c1(467),c8(467),c1(468),c8(468),c1(469),c8(469),c1(470),c8(470)
, c1(471),c8(471),c1(472),c8(472),c1(473),c8(473),c1(474),c8(474),c1(475),c8(475),c1(476),c8(476),c1(477),c8(477),c1(478),c8(478),c1(479),c8(479),c1(480),c8(480)
, c1(481),c8(481),c1(482),c8(482),c1(483),c8(483),c1(484),c8(484),c1(485),c8(485),c1(486),c8(486),c1(487),c8(487),c1(488),c8(488),c1(489),c8(489),c1(490),c8(490)
, c1(491),c8(491),c1(492),c8(492),c1(493),c8(493),c1(494),c8(494),c1(495),c8(495),c1(496),c8(496),c1(497),c8(497),c1(498),c8(498),c1(499),c8(499),c1(500),c8(500)
, c1(501),c8(501),c1(502),c8(502),c1(503),c8(503),c1(504),c8(504),c1(505),c8(505),c1(506),c8(506),c1(507),c8(507),c1(508),c8(508),c1(509),c8(509),c1(510),c8(510)
, c1(511),c8(511),c1(512),c8(512),c1(513),c8(513),c1(514),c8(514),c1(515),c8(515),c1(516),c8(516),c1(517),c8(517),c1(518),c8(518),c1(519),c8(519),c1(520),c8(520)
, c1(521),c8(521),c1(522),c8(522),c1(523),c8(523),c1(524),c8(524),c1(525),c8(525),c1(526),c8(526),c1(527),c8(527),c1(528),c8(528),c1(529),c8(529),c1(530),c8(530)
, c1(531),c8(531),c1(532),c8(532),c1(533),c8(533),c1(534),c8(534),c1(535),c8(535),c1(536),c8(536),c1(537),c8(537),c1(538),c8(538),c1(539),c8(539),c1(540),c8(540)
, c1(541),c8(541),c1(542),c8(542),c1(543),c8(543),c1(544),c8(544),c1(545),c8(545),c1(546),c8(546),c1(547),c8(547),c1(548),c8(548),c1(549),c8(549),c1(550),c8(550)
, c1(551),c8(551),c1(552),c8(552),c1(553),c8(553),c1(554),c8(554),c1(555),c8(555),c1(556),c8(556),c1(557),c8(557),c1(558),c8(558),c1(559),c8(559),c1(560),c8(560)
, c1(561),c8(561),c1(562),c8(562),c1(563),c8(563),c1(564),c8(564),c1(565),c8(565),c1(566),c8(566),c1(567),c8(567),c1(568),c8(568),c1(569),c8(569),c1(570),c8(570)
, c1(571),c8(571),c1(572),c8(572),c1(573),c8(573),c1(574),c8(574),c1(575),c8(575),c1(576),c8(576),c1(577),c8(577),c1(578),c8(578),c1(579),c8(579),c1(580),c8(580)
, c1(581),c8(581),c1(582),c8(582),c1(583),c8(583),c1(584),c8(584),c1(585),c8(585),c1(586),c8(586),c1(587),c8(587),c1(588),c8(588),c1(589),c8(589),c1(590),c8(590)
, c1(591),c8(591),c1(592),c8(592),c1(593),c8(593),c1(594),c8(594),c1(595),c8(595),c1(596),c8(596),c1(597),c8(597),c1(598),c8(598),c1(599),c8(599),c1(600),c8(600)
, c1(601),c8(601),c1(602),c8(602),c1(603),c8(603),c1(604),c8(604),c1(605),c8(605),c1(606),c8(606),c1(607),c8(607),c1(608),c8(608),c1(609),c8(609),c1(610),c8(610)
, c1(611),c8(611),c1(612),c8(612),c1(613),c8(613),c1(614),c8(614),c1(615),c8(615),c1(616),c8(616),c1(617),c8(617),c1(618),c8(618),c1(619),c8(619),c1(620),c8(620)
, c1(621),c8(621),c1(622),c8(622),c1(623),c8(623),c1(624),c8(624),c1(625),c8(625),c1(626),c8(626),c1(627),c8(627),c1(628),c8(628),c1(629),c8(629),c1(630),c8(630)
, c1(631),c8(631),c1(632),c8(632),c1(633),c8(633),c1(634),c8(634),c1(635),c8(635),c1(636),c8(636),c1(637),c8(637),c1(638),c8(638),c1(639),c8(639),c1(640),c8(640)
, c1(641),c8(641),c1(642),c8(642),c1(643),c8(643),c1(644),c8(644),c1(645),c8(645),c1(646),c8(646),c1(647),c8(647),c1(648),c8(648),c1(649),c8(649),c1(650),c8(650)
, c1(651),c8(651),c1(652),c8(652),c1(653),c8(653),c1(654),c8(654),c1(655),c8(655),c1(656),c8(656),c1(657),c8(657),c1(658),c8(658),c1(659),c8(659),c1(660),c8(660)
, c1(661),c8(661),c1(662),c8(662),c1(663),c8(663),c1(664),c8(664),c1(665),c8(665),c1(666),c8(666),c1(667),c8(667),c1(668),c8(668),c1(669),c8(669),c1(670),c8(670)
, c1(671),c8(671),c1(672),c8(672),c1(673),c8(673),c1(674),c8(674),c1(675),c8(675),c1(676),c8(676),c1(677),c8(677),c1(678),c8(678),c1(679),c8(679),c1(680),c8(680)
, c1(681),c8(681),c1(682),c8(682),c1(683),c8(683),c1(684),c8(684),c1(685),c8(685),c1(686),c8(686),c1(687),c8(687),c1(688),c8(688),c1(689),c8(689),c1(690),c8(690)
, c1(691),c8(691),c1(692),c8(692),c1(693),c8(693),c1(694),c8(694),c1(695),c8(695),c1(696),c8(696),c1(697),c8(697),c1(698),c8(698),c1(699),c8(699),c1(700),c8(700)
, c1(701),c8(701),c1(702),c8(702),c1(703),c8(703),c1(704),c8(704),c1(705),c8(705),c1(706),c8(706),c1(707),c8(707),c1(708),c8(708),c1(709),c8(709),c1(710),c8(710)
, c1(711),c8(711),c1(712),c8(712),c1(713),c8(713),c1(714),c8(714),c1(715),c8(715),c1(716),c8(716),c1(717),c8(717),c1(718),c8(718),c1(719),c8(719),c1(720),c8(720)
, c1(721),c8(721),c1(722),c8(722),c1(723),c8(723),c1(724),c8(724),c1(725),c8(725),c1(726),c8(726),c1(727),c8(727),c1(728),c8(728),c1(729),c8(729),c1(730),c8(730)
, c1(731),c8(731),c1(732),c8(732),c1(733),c8(733),c1(734),c8(734),c1(735),c8(735),c1(736),c8(736),c1(737),c8(737),c1(738),c8(738),c1(739),c8(739),c1(740),c8(740)
, c1(741),c8(741),c1(742),c8(742),c1(743),c8(743),c1(744),c8(744),c1(745),c8(745),c1(746),c8(746),c1(747),c8(747),c1(748),c8(748),c1(749),c8(749),c1(750),c8(750)
, c1(751),c8(751),c1(752),c8(752),c1(753),c8(753),c1(754),c8(754),c1(755),c8(755),c1(756),c8(756),c1(757),c8(757),c1(758),c8(758),c1(759),c8(759),c1(760),c8(760)
, c1(761),c8(761),c1(762),c8(762),c1(763),c8(763),c1(764),c8(764),c1(765),c8(765),c1(766),c8(766),c1(767),c8(767),c1(768),c8(768),c1(769),c8(769),c1(770),c8(770)
, c1(771),c8(771),c1(772),c8(772),c1(773),c8(773),c1(774),c8(774),c1(775),c8(775),c1(776),c8(776),c1(777),c8(777),c1(778),c8(778),c1(779),c8(779),c1(780),c8(780)
, c1(781),c8(781),c1(782),c8(782),c1(783),c8(783),c1(784),c8(784),c1(785),c8(785),c1(786),c8(786),c1(787),c8(787),c1(788),c8(788),c1(789),c8(789),c1(790),c8(790)
, c1(791),c8(791),c1(792),c8(792),c1(793),c8(793),c1(794),c8(794),c1(795),c8(795),c1(796),c8(796),c1(797),c8(797),c1(798),c8(798),c1(799),c8(799),c1(800),c8(800)
, c1(801),c8(801),c1(802),c8(802),c1(803),c8(803),c1(804),c8(804),c1(805),c8(805),c1(806),c8(806),c1(807),c8(807),c1(808),c8(808),c1(809),c8(809),c1(810),c8(810)
, c1(811),c8(811),c1(812),c8(812),c1(813),c8(813),c1(814),c8(814),c1(815),c8(815),c1(816),c8(816),c1(817),c8(817),c1(818),c8(818),c1(819),c8(819),c1(820),c8(820)
, c1(821),c8(821),c1(822),c8(822),c1(823),c8(823),c1(824),c8(824),c1(825),c8(825),c1(826),c8(826),c1(827),c8(827),c1(828),c8(828),c1(829),c8(829),c1(830),c8(830)
, c1(831),c8(831),c1(832),c8(832),c1(833),c8(833),c1(834),c8(834),c1(835),c8(835),c1(836),c8(836),c1(837),c8(837),c1(838),c8(838),c1(839),c8(839),c1(840),c8(840)
, c1(841),c8(841),c1(842),c8(842),c1(843),c8(843),c1(844),c8(844),c1(845),c8(845),c1(846),c8(846),c1(847),c8(847),c1(848),c8(848),c1(849),c8(849),c1(850),c8(850)
, c1(851),c8(851),c1(852),c8(852),c1(853),c8(853),c1(854),c8(854),c1(855),c8(855),c1(856),c8(856),c1(857),c8(857),c1(858),c8(858),c1(859),c8(859),c1(860),c8(860)
, c1(861),c8(861),c1(862),c8(862),c1(863),c8(863),c1(864),c8(864),c1(865),c8(865),c1(866),c8(866),c1(867),c8(867),c1(868),c8(868),c1(869),c8(869),c1(870),c8(870)
, c1(871),c8(871),c1(872),c8(872),c1(873),c8(873),c1(874),c8(874),c1(875),c8(875),c1(876),c8(876),c1(877),c8(877),c1(878),c8(878),c1(879),c8(879),c1(880),c8(880)
, c1(881),c8(881),c1(882),c8(882),c1(883),c8(883),c1(884),c8(884),c1(885),c8(885),c1(886),c8(886),c1(887),c8(887),c1(888),c8(888),c1(889),c8(889),c1(890),c8(890)
, c1(891),c8(891),c1(892),c8(892),c1(893),c8(893),c1(894),c8(894),c1(895),c8(895),c1(896),c8(896),c1(897),c8(897),c1(898),c8(898),c1(899),c8(899),c1(900),c8(900)
, c1(901),c8(901),c1(902),c8(902),c1(903),c8(903),c1(904),c8(904),c1(905),c8(905),c1(906),c8(906),c1(907),c8(907),c1(908),c8(908),c1(909),c8(909),c1(910),c8(910)
, c1(911),c8(911),c1(912),c8(912),c1(913),c8(913),c1(914),c8(914),c1(915),c8(915),c1(916),c8(916),c1(917),c8(917),c1(918),c8(918),c1(919),c8(919),c1(920),c8(920)
, c1(921),c8(921),c1(922),c8(922),c1(923),c8(923),c1(924),c8(924),c1(925),c8(925),c1(926),c8(926),c1(927),c8(927),c1(928),c8(928),c1(929),c8(929),c1(930),c8(930)
, c1(931),c8(931),c1(932),c8(932),c1(933),c8(933),c1(934),c8(934),c1(935),c8(935),c1(936),c8(936),c1(937),c8(937),c1(938),c8(938),c1(939),c8(939),c1(940),c8(940)
, c1(941),c8(941),c1(942),c8(942),c1(943),c8(943),c1(944),c8(944),c1(945),c8(945),c1(946),c8(946),c1(947),c8(947),c1(948),c8(948),c1(949),c8(949),c1(950),c8(950)
, c1(951),c8(951),c1(952),c8(952),c1(953),c8(953),c1(954),c8(954),c1(955),c8(955),c1(956),c8(956),c1(957),c8(957),c1(958),c8(958),c1(959),c8(959),c1(960),c8(960)
, c1(961),c8(961),c1(962),c8(962),c1(963),c8(963),c1(964),c8(964),c1(965),c8(965),c1(966),c8(966),c1(967),c8(967),c1(968),c8(968),c1(969),c8(969),c1(970),c8(970)
, c1(971),c8(971),c1(972),c8(972),c1(973),c8(973),c1(974),c8(974),c1(975),c8(975),c1(976),c8(976),c1(977),c8(977),c1(978),c8(978),c1(979),c8(979),c1(980),c8(980)
, c1(981),c8(981),c1(982),c8(982),c1(983),c8(983),c1(984),c8(984),c1(985),c8(985),c1(986),c8(986),c1(987),c8(987),c1(988),c8(988),c1(989),c8(989),c1(990),c8(990)
, c1(991),c8(991),c1(992),c8(992),c1(993),c8(993),c1(994),c8(994),c1(995),c8(995),c1(996),c8(996),c1(997),c8(997),c1(998),c8(998),c1(999),c8(999),c1(1000),c8(1000)
;
COMMIT;
END LOOP;
END;
/

set veri on
UNDEFINE 1
UNDEFINE 2

@show_mystats
SELECT COUNT(1) FROM mrows_ins_tab
/
@show_mrows_ins_tab_size
truncate table scott.mrows_ins_tab
/



関連エントリ
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT
Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?

| | | コメント (0)

2026年1月27日 (火)

Oracle Database - Multi Row INSERT、バインド変数を使うと、リテラル値を使う場合では見える景色が変わるんだよね #1 - バグなのか現時点の仕様なのか?

Previously on Mac De Oracle
前回は、
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT でした。
TVC(Table Value Constractor)の流れではあったのですが、通常やるとは思えないリテラルを使ったMulti row insertで大量の行を詰め込んでみました!
TVCはともかく、Multi row insertをリテラル値で、かつ、大量の行を詰め込むのはリーズナブルな方法ではないですよね。特にOracle Databaseでは。というあたりに気づければそれで良いと思います:)

PostgreSQL/MySQLは幾分マイルドなw 結果でしたが、Oracle Databaseは、らしい特徴がでていましたよね。
ハードパースコストがめちゃめちゃ高くて。 

 

そういえば、
以前、
パースが重いぞーというネタをやってたことありましたが、取り上げたハードパースがキツくなるネタは、Oracle Databaseで索引のある列に対してIN句を使い大量の値をセットするネタでした。(これ意外と見かけます)
それ以外のケースだと、MySQLで結合する表が多くて考え過ぎてるケース。
どちらも実行計画を立てるためにオプティマイザが考え過ぎてしまう(得られる実行計画は良いのですが)ことでパース時間が異常に長くなる症状でしたよね!
悩ませ過ぎは及ばざるがごとし #3
悩ませ過ぎは及ばざるがごとし #4
悩ませ過ぎは及ばざるがごとし #7 - おまけ
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)

ただ、今回取り上げたOracle DatabaseのINSERT(Multi row insert)は、どちらかというとメモリリソース確保の影響が色濃い状況でした。(パース時の帯域イベントもほぼそれだけ)
リテラル値を使い、大量の行を詰め込んだ巨大なmulti row insert文を実行するのは考えものですよね。まじでw


前置きはそれぐらいにして、
前回、そんなことやる人いないでしょーっ、というノリで、リテラル値を使い巨大なMulti row insertを行いましたが、
通常なら、バインド変数使いますよね! 絶対!w

ということで、バインド変数を利用したMulti row insertの準備をしていた際に気づいたバグというか現時点の仕様というかなんというかw にハマった記録です! が今回のネタ。
(Oracle Database 23ai FREEのPL/SQLからの実行だと、現時点では、EXECUTE IMMEDIATEを利用してバインド変数化することぐらいしかできなそう!)
まじで?! という状況なので、細かい確認は次のリリース以降で再度試してみようかなと。

 

 

不具合?なのか現時点の仕様なのか、制限っぽいエラーについてのログは以下。
運良く見つけた、現状、唯一の逃げ道も最後に記載しています。
(PL/SQLだと以前から他の方法(Bulk Insert)もあるので困ることはないとは思いますが。。。)

前回作成した表を利用します。(SCOTTスキーマに作成)

create table meows_ins_tab
(
id integer not null primary key,
col1 varchar2(1000),
col2 varchar2(1000),
col3 varchar2(1000),
col4 varchar2(1000),
col5 varchar2(1000),
col6 varchar2(1000),
col7 varchar2(1000),
col8 varchar2(500)
);

 

前提 PL/SQLを利用しリテラル値のままのMulti row insert、バインド変数を利用したMulti row insertが対象です。(PL/SQLで利用する場合だけで発生していると思われるエラー。それ以外の言語ではどうかは未確認)

以下のような構文で、バインド変数を使ういたかったわけです。PL/SQLなので直接!

INSERT INTO mrows_ins_tab(id, col8) VALUES (...), (...) ...;

 

と書きたいところでしたが、
これが。。。。いろいろと引き当ててしまう原因になるとは。。。知る由もなかった。。。。
いろいろあります。。。よ! (^^ ;;;;;

 

バグ? or 現時点の仕様、または制限? その1

後で気づいたのですが、このエラーには2つの要素が絡んでいるようでした。
ひとつは、INSERTする表の列指定。もう一つは、バインド変数として利用しているVARRAY (可変サイズの配列)の二つ。(詳細は後続のエラーにて)
SQL文が無効ですというエラーに惑わされますが、エラー自体はORA-00600です。

SCOTT@localhost:1521/freepdb1> !cat bug1.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
TYPE c1_type IS VARRAY(2) OF NUMBER;
TYPE c8_type IS VARRAY(2) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
BEGIN
c1.extend(2);
c8.extend(2);
c1(1) := 1;
c1(2) := 2;
c8(1) := LPAD(TO_CHAR(c1(1)),373,'x');
c8(2) := LPAD(TO_CHAR(c1(2)),373,'x');
INSERT INTO mrows_ins_tab(id,col8) VALUES
(c1(1), c8(1))
, (c1(2), c8(2))
;
END;
/

SCOTT@localhost:1521/freepdb1> @bug1
c82 VARCHAR2(500);
*
行5でエラーが発生しました。:
ORA-00900: SQL文が無効です。
ORA-00600: 内部エラー・コード, 引数: [qcsTVCApplyqbc:nameres], [], [], [], [], [], [], [], [], [], [], [] ヘルプ:
https://docs.oracle.com/error-help/db/ora-00900/

 

.trcファイルにも同様のログあり

Incident 222887 created, dump file: /opt/oracle/diag/rdbms/free/FREE/incident/incdir_222887/FREE_ora_4633_i222887.trc
ORA-00600: 内部エラー・コード, 引数: [qcsTVCApplyqbc:nameres], [], [], [], [], [], [], [], [], [], [], []

 

上記無名PL/SQLブロックスクリプトを以下のように書き換えてみます。 違いはVARRAY配列を利用していないという1点です。
VARRAY配列の要素をmulti row insert文で直接使うことはできない(現状)ことがわかります!(まじかー!)
なお、このケースではINSERT文で表の列を指定しても指定しなくても影響はありません。(他のケースではこの表の列指定が影響する問題がありました。後述)

SCOTT@localhost:1521/freepdb1> !cat bug1_1.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab(id,col8) VALUES
(c11, c81)
, (c12, c82)
;
END;
/

SCOTT@localhost:1521/freepdb1> @bug1_1

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

 

さらに書き換えてみます。このテストケースでは、VARRAY配列の要素を使うことが原因でエラーになっているようです。

SCOTT@localhost:1521/freepdb1> !cat bug1_2.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab(id, col1, col2, col3, col4, col5, col6, col7, col8) VALUES
(c11, null, null, null, null, null, null, null, c81)
, (c12, null, null, null, null, null, null, null, c82)
;
END;
/

SCOTT@localhost:1521/freepdb1> @bug1_2

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

 

さらに書き換えて、表の列指定を行わない構文(このような構文はほとんどの現場では使えないと思いますが、。。。)でも正常に実行できています。ポイントは、VARRAY配列の要素を直接渡せない(現状)って部分だけです(ここまでのテストケースでは)。
なお、ここまでは表の所有者であるSCOTTユーザで接続して検証を行いました。
次のテストケースでは、SCOTT2ユーザで接続し、DB Link経由でSCOTTの表へMulti row insertするテストを行ってみます。。。そこでも色々おきます!

SCOTT@localhost:1521/freepdb1> !cat bug1_3.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab VALUES
(c11, null, null, null, null, null, null, null, c81)
, (c12, null, null, null, null, null, null, null, c82)
;
END;
/

SCOTT@localhost:1521/freepdb1> @bug1_3

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

 

バグ? or 現時点の仕様、または制限? その2 PL/SQLではローカルのデータベースへアクセスことになるため、通常はSQL*Netを経由するNetwork Round Tripは発生しません。
ただ、一般的なアプリケーションではアプリケーションサーバーとデータベースサーバー間では、SQL*Net経由でNetwork Round Tripが発生するほうが普通です。
そのような状況を想定してDB Linkを経由させることで、意図的にSQL*Net経由でNetwork Round Tripを発生させるテストケースも試したいなーと、準備していた時に気づいた、とうかハマったバグ?、または現時点の仕様、制限です。

まず、scott.emp表を単一インスタンではありますが、無理やりscott2ユーザーからdb link経由でリモート表としてMulti row insertする準備から。

権限は雑に許可しちゃってます(特にシステム権限あたり)
準備

SYSTEM@localhost:1521/freepdb1> create user scott2 identified by [password] default tablespace users temporary tablespace temp quota unlimited on users;

ユーザーが作成されました。
SYSTEM@localhost:1521/freepdb1> grant select any dictionary to scott2;

権限付与が成功しました。
SYSTEM@localhost:1521/freepdb1> grant drop any table to scott2;

権限付与が成功しました。
SYSTEM@localhost:1521/freepdb1> grant connect , resource , create database link to scott2;

権限付与が成功しました。
SCOTT@localhost:1521/freepdb1> grant select on scott.mrows_ins_tab to scott2;

権限付与が成功しました。
SCOTT@localhost:1521/freepdb1> grant insert on mrows_ins_tab to scott2;

権限付与が成功しました。
SCOTT@localhost:1521/freepdb1> grant delete on mrows_ins_tab to scott2;

権限付与が成功しました。
SCOTT@localhost:1521/freepdb1> grant update on mrows_ins_tab to scott2;

権限付与が成功しました。
[oracle@arm64-oraclelinux8u10 ~]$ sqlplus scott2@localhost:1521/freepdb1

SCOTT2@localhost:1521/freepdb1> create database link link2scott connect to scott identified by [password] using 'localhost:1521/freepdb1';

データベース・リンクが作成されました。
COTT2@localhost:1521/freepdb1> select * from mrows_ins_tab@link2scott;

レコードが選択されませんでした。
SCOTT2@localhost:1521/freepdb1> create synonym mrows_ins_tab for mrows_ins_tab@link2scott;

シノニムが作成されました。
SCOTT2@localhost:1521/freepdb1> select * from mrows_ins_tab;

レコードが選択されませんでした。
SCOTT2@localhost:1521/freepdb1> select table_name from user_tables;

レコードが選択されませんでした。
SCOTT2@localhost:1521/freepdb1> truncate table scott.mrows_ins_tab;

表が切り捨てられました。

 

scott2に接続して、その1と同じことを試してみます。(scott.mrows_ins_tabをDB Link経由でアクセスしている点が異なります)
以下は、ローカル表アクセスと同じ結果なので想定通りの結果です。

SCOTT2@localhost:1521/freepdb1> !cat bug1.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
cTYPE c1_type IS VARRAY(2) OF NUMBER;
cTYPE c8_type IS VARRAY(2) OF VARCHAR2(500);
cc1 c1_type := c1_type();
cc8 c8_type := c8_type();
BEGIN
c1.extend(2);
c8.extend(2);
c1(1) := 1;
c1(2) := 2;
c8(1) := LPAD(TO_CHAR(c1(1)),373,'x');
c8(2) := LPAD(TO_CHAR(c1(2)),373,'x');
INSERT INTO mrows_ins_tab(id,col8) VALUES
(c1(1), c8(1))
, (c1(2), c8(2))
;
END;
/


SCOTT2@localhost:1521/freepdb1> @bug1
c82 VARCHAR2(500);
*
行5でエラーが発生しました。:
ORA-00900: SQL文が無効です。
ORA-00600: 内部エラー・コード, 引数: [qcsTVCApplyqbc:nameres], [], [], [], [], [], [], [], [], [], [], [] ヘルプ:
https://docs.oracle.com/error-help/db/ora-00900/

 

問題はローカル表へのMulti row insertでは正常に実行できた残りの3ケース。 以下のケース。ローカル表では正常に実行されましたが、VARRAY配列を利用した場合と異なる点は、ORA-07445 になっているということです。SIGSEGVなので、これはバグの類です。

SCOTT2@localhost:1521/freepdb1> !cat bug1_1.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab(id,col8) VALUES
(c11, c81)
, (c12, c82)
;
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_1
c82 VARCHAR2(500);
*
行5でエラーが発生しました。:
OORA-03113: 通信チャネルでend-of-fileが検出されました プロセスID:
5150
セッションID: 17、シリアル番号: 17264 ヘルプ:
https://docs.oracle.com/error-help/db/ora-03113/

 

.trcファイルから本当の原因を確認してみると。。。。 ORA-07445!

Incident 222926 created, dump file: /opt/oracle/diag/rdbms/free/FREE/incident/incdir_222926/FREE_ora_5225_i222926.trc
OORA-07445: 例外が検出されました: コア・ダンプ [qcsorcqb()+784] [SIGSEGV] [ADDR:0x0] [PC:0xF0F3084] [Address not mapped to object] []

 

次のケースでは、また別の原因でエラーに? この文もローカル表では正常に実行できていましたよね?(前述)

SCOTT2@localhost:1521/freepdb1> !cat bug1_2.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab(id, col1, col2, col3, col4, col5, col6, col7, col8) VALUES
(c11, null, null, null, null, null, null, null, c81)
, (c12, null, null, null, null, null, null, null, c82)
;
END;
/


SCOTT2@localhost:1521/freepdb1> @bug1_2
INSERT INTO mrows_ins_tab(id, col1, col2, col3, col4, col5, col6, col7, col8) VALUES
*
行11でエラーが発生しました。:
ORA-06550: 行11、列77:
PL/SQL: ORA-00904: "COL8": 無効な識別子です。

ORA-06550: 行11、列5:
PL/SQL: SQL Statement ignored
ヘルプ: https://docs.oracle.com/error-help/db/ora-06550/

 

.trcファイルから本当の原因を探ると。。でてました。また別のログが!!!! 
これって、現時点では実装されてない? ってこと? だとすると現時点の仕様という制限だろうか。。

 fsd_notify_cb: FsDirect not implemented

 


さて、最後に、留め! (違 以下のケースもローカル表へ実行した場合は正常に実行できていました(前述)
これ、おそらく、実装されてない感じですかね。
PL/SQLのエンジンがOracle Databaseのカーネルに組み込まれていなかったOracle 7の頃とか、この類の新規構文の実装時期がズレるって珍しくなかったのですが、久々にこのような状況を見てノスタルジーを感じる、セピア色www

 

これどうやって回避できんのかなぁ!?

SCOTT2@localhost:1521/freepdb1> !cat bug1_3.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
INSERT INTO mrows_ins_tab VALUES
(c11, null, null, null, null, null, null, null, c81)
, (c12, null, null, null, null, null, null, null, c82)
;
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_3
DECLARE
*
行1でエラーが発生しました。:
ORA-00900: SQL文が無効です。
ORA-00600: 内部エラー・コード, 引数: [qctcopn_internal: null_colkcc], [0], [0], [0], [0], [0], [1], [0], [], [], [], [] ヘルプ:
https://docs.oracle.com/error-help/db/ora-00900/

 

.trcファイルでも同じ

Incident 222671 created, dump file: /opt/oracle/diag/rdbms/free/FREE/incident/incdir_222671/FREE_ora_5572_i222671.trc
ORA-00600: 内部エラー・コード, 引数: [qctcopn_internal: null_colkcc], [0], [0], [0], [0], [0], [1], [0], [], [], [], []

 

 


さて、ほんとうに試した事とから随分と遠くにきてしまった感じがしますが、、逃げ道を見つけましたw

 

対策をしばし考え中。。。。現状回避できる方法はあるのでは?。。。。わずかな期待。。。

PL/SQLには、他の方法として、 EXECUTE IMMEDIATEを使う方法はあるか。。。その分のオーバーヘッドは乗ってしまうわけだが。。。。

試してみる! (全テストケース NGとなったDB Link経由のケースで確認する。このケースで実行できればローカルでも動作するはずだ。。。。と)

 

EXECUTE IMMEDIATEでバイント変数を利用するためにUSING句を使ってみました。ためにし、VARRAY配列を使っています。また、表も列指定しています。ここでローカル表でのテストでも発生した
fsd_notify_cb: FsDirect not implementedがトレースファイルに記録されていました。!!! VARRAYはEXECUTE IMMEDIATEでMulti row insertする場合でもダメなのか??。

SCOTT2@localhost:1521/freepdb1> !cat bug1_4.sql
DECLARE
sql_text CLOB := EMPTY_CLOB();
TYPE c1_type IS VARRAY(2) OF NUMBER;
TYPE c8_type IS VARRAY(2) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
BEGIN
c1.extend(2);
c8.extend(2);
c1(1) := 1;
c1(2) := 2;
c8(1) := LPAD(TO_CHAR(c1(1)),373,'x');
c8(2) := LPAD(TO_CHAR(c1(2)),373,'x');
sql_text := 'INSERT INTO mrows_ins_tab(id, col8) VALUES'
|| '(:c11, :c81)'
|| ', (:c12, :c82);';
EXECUTE IMMEDIATE sql_text
USING c1(1), c8(1), c1(2), c8(2);
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_4
DECLARE
*
行1でエラーが発生しました。:
ORA-00904: "COL8": 無効な識別子です。
ORA-06512: 行17
ヘルプ: https://docs.oracle.com/error-help/db/ora-00904/

 

.trcファイル

fsd_notify_cb: FsDirect not implemented

 

まずは、切り分けのために、VARRAY配列の使用をやめてみます。表の列指定だけは残してありますが、これでも同じエラーが発生します!!

SCOTT2@localhost:1521/freepdb1> !cat bug1_5.sql
DECLARE
sql_text CLOB := EMPTY_CLOB();
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
sql_text := 'INSERT INTO mrows_ins_tab(id, col8) VALUES'
|| '(:c11, :c81)'
|| ', (:c12, :c82);';
EXECUTE IMMEDIATE sql_text
USING c11, c81, c12, c82;
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_5
DECLARE
*
行1でエラーが発生しました。:
ORA-00904: "COL8": 無効な識別子です。
ORA-06512: 行15
ヘルプ: https://docs.oracle.com/error-help/db/ora-00904/

 

.trcファイル

fsd_notify_cb: FsDirect not implemented

 

気を取り直してw
USING句ではVARRAY配列を利用し、表の列指定だけを行わないようにしてみます。

 

おおおおおおお、やっと逃げ道発見! w 変数は配列でもOKなので、取り合えず、行数増やしてもプログラミングで捌ける:)

SCOTT2@localhost:1521/freepdb1> !cat bug1_6.sql
DECLARE
sql_text CLOB := EMPTY_CLOB();
TYPE c1_type IS VARRAY(2) OF NUMBER;
TYPE c8_type IS VARRAY(2) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
BEGIN
c1.extend(2);
c8.extend(2);
c1(1) := 1;
c1(2) := 2;
c8(1) := LPAD(TO_CHAR(c1(1)),373,'x');
c8(2) := LPAD(TO_CHAR(c1(2)),373,'x');
sql_text := 'INSERT INTO mrows_ins_tab VALUES'
|| '(:c11, null, null, null, null, null, null, null, :c81)'
|| ', (:c12, null, null, null, null, null, null, null, :c82);';
EXECUTE IMMEDIATE sql_text
USING c1(1), c8(1), c1(2), c8(2);
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_6

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

 

 

この方法でもいける! 
ただ、配列使えるなら配列のほうが便利なので、一つ前の方式に決定 EXECUTE IMMEDIATEだと全部リテラルというケースも試せるからEXECUTE IMMEDIATEでのるある程度のオーバーヘッドは見えて良いのかも。
ただ、EXECUTE IMMEDIATEを利用しない場合のポテンシャルは確認できないのが、ちょっと残念。

SCOTT2@localhost:1521/freepdb1> !cat bug1_7.sql
DECLARE
c11 NUMBER;
c12 NUMBER;
c81 VARCHAR2(500);
c82 VARCHAR2(500);
sql_text CLOB := EMPTY_CLOB();
BEGIN
c11 := 1;
c12 := 2;
c81 := LPAD(TO_CHAR(c11),373,'x');
c82 := LPAD(TO_CHAR(c12),373,'x');
sql_text := 'INSERT INTO mrows_ins_tab VALUES'
|| '(:c11, null, null, null, null, null, null, null, :c81)'
|| ', (:c12, null, null, null, null, null, null, null, :c82);';
EXECUTE IMMEDIATE sql_text
USING c11, c81, c12, c82;
END;
/

SCOTT2@localhost:1521/freepdb1> @bug1_7

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

 

いや、まじで、Multi row insert構文をPL/SQLで使おうとすると結構な壁というか問題残ってそうですね。
とはいえ、PL/SQLでやるなら FORALLのバルクインサートもあるわけで、困ることはないだろうなとも思われ(現状)
とはいえ、PL/SQLでやるならローカル表の場合なら、FORALLでバルクインサートでもよいのだろうけども、
リモート表だとFORALLは使えないのでまあ、DB Link使ってる環境では注意した方が良いよね。ローカルからにする方法にするとか。

そもそもエラーで挙動周りの差異を見切れてないのは心残りではあるけども、次回りリースのai新版以降のお楽しみということで.... :)

ほんとうに試したかったのとはちょっとちがうが、Multi row insert文、リテラル値じゃなくてバインド変数を使えば、メモリ消費は抑えられるよっていう、ネタ含めた次の話題は書けそうな気がしてきた。

以下の方式(EXECUTE IMMEDIATEを利用する方法を代替策にして)でバインド変数化とリテラル値を利用したMulti row insertのメモリ消費を見るネタへ続く。(予定w)

DECLARE
sql_text CLOB := EMPTY_CLOB();
TYPE c1_type IS VARRAY(2) OF NUMBER;
TYPE c8_type IS VARRAY(2) OF VARCHAR2(500);
c1 c1_type := c1_type();
c8 c8_type := c8_type();
BEGIN
c1.extend(2);
c8.extend(2);
c1(1) := 1;
c1(2) := 2;
c8(1) := LPAD(TO_CHAR(c1(1)),373,'x');
c8(2) := LPAD(TO_CHAR(c1(2)),373,'x');
sql_text := 'INSERT INTO mrows_ins_tab VALUES'
|| '(:c11, null, null, null, null, null, null, null, :c81)'
|| ', (:c12, null, null, null, null, null, null, null, :c82);';
EXECUTE IMMEDIATE sql_text
USING c1(1), c8(1), c1(2), c8(2);
END;
/

 

いや、まじでいろいろハマり過ぎだろ、ってぐらいハマったw

 

ではまた。

 


関連エントリ
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK
帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT

| | | コメント (0)

2026年1月 8日 (木)

帰ってきた! 標準はあるにはあるが癖の多いSQL #22 - Multi Row INSERT

Previously on Mac De Oracle
と言いたいところですが、前回は、読了ネタだったのでw 前々回の話に関連して。。。

前々回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACKでした。
おまけのおまけ的な内容で前々回のネタと関連はあるのですが、TVCではなく、Multi Row INSERTの癖の確認というか、メモリ消費量などの傾向を、肌感覚で覚えておきましょうね。 というネタです。

Oracle Databaseで以下のような表を作成、PostgreSQL/MySQLでも同様に作成しておきます。

create table mrows_ins_tab
(
id integer not null primary key,
col1 varchar2(1000),
col2 varchar2(1000),
col3 varchar2(1000),
col4 varchar2(1000),
col5 varchar2(1000),
col6 varchar2(1000),
col7 varchar2(1000),
col8 varchar2(500)
);


Oracle Databaseの方言、multi table insertの対象を単一表にしてmulti row insertのPGA消費傾向を見る。
行数を変えつつ以下のようなINSERT ALL文にて検証。

SCOTT@localhost:1521/freepdb1> !cat sql_mrows_ins_5000.sql
INSERT ALL
INTO mrows_ins_tab(id, col8) VALUES(1,
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')

...略...

INTO mrows_ins_tab(id, col8) VALUES(5000,
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5000')
SELECT * FROM dual;

手始めに5000行をガツンとINSERT ALLにて。この程度の行サイズと行数でも300MB超えのPGA消費ですね。

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
に接続されました。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 4
39 CPU used when call started 3

...略...

39 session pga memory 4254712
39 session pga memory max 5123744
39 session uga memory 1844408
39 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_5000

5000行が作成されました。

経過: 00:00:05.95
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 550
39 CPU used when call started 550

...略...

39 session logical reads 26402
39 session pga memory 88534008
39 session pga memory max 339274744
39 session uga memory 84055376
39 session uga memory max 85103896

...略...


行数を3倍の15,000行に。こんなことする方はいないと思いますけども。。。予想以上にPGAを消費しますね。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
205 CPU used by this session 5
205 CPU used when call started 4

...略...

205 session pga memory 4123640
205 session pga memory max 5123744
205 session uga memory 1839072
205 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_15000

15000行が作成されました。

経過: 00:01:45.96
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
205 CPU used by this session 9762
205 CPU used when call started 9762

...略...

205 session pga memory 257551352
205 session pga memory max 1402072056
205 session uga memory 248118456
205 session uga memory max 252312704

...略...


前述のINSERT ALLで 5,000行、15,000行の一括インサートでのPGA/UGAの最大サイズは以下の通りでした。
session pga memory max

  • 318.7 MB / 5,000rows
  • 1,332.23MB / 15,000rows

session uga memory max

  • 78.2 MB / 5,000rows
  • 237.71MB / 15,000rows


今回の環境とINSERT ALLの行数、行サイズでは 20,000行で、PGA_AGGREGATE_LIMITを超える結果となりました。
23ai FREEなので使えるメモリサイズが元々少ないので比較的簡単に制限を超過しちゃいますね

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
203 CPU used by this session 5
203 CPU used when call started 4

...略...

203 session pga memory 4254712
203 session pga memory max 5123744
203 session uga memory 1844408
203 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_20000
INTO mrows_ins_tab(id, col8) VALUES(1,
*
行2でエラーが発生しました。:
ORA-04036: インスタンスまたはPDBにより使用されるPGAメモリーがPGA_AGGREGATE_LIMITを超えています。 ヘルプ:
https://docs.oracle.com/error-help/db/ora-04036/

上記で利用したINSERT文を生成したスクリプトです。参考まで。

set feed off
set timi off
set head off
set termout off
set veri off
set trimspool on

col col1 for a20
col col2 for a20
col col3 for a20
col col4 for a20
col col5 for a20
col col6 for a20
col col7 for a20
col col8 for a20
set linesize 400
set pagesize 1000
SET SERVEROUTPUT ON
spool sql_mrows_ins_&1..sql
DECLARE
c_max_rows CONSTANT NUMBER := &1;
BEGIN
DBMS_OUTPUT.PUT_LINE('INSERT ALL ');
FOR i IN 1..c_max_rows LOOP
DBMS_OUTPUT.PUT_LINE(
'INTO mrows_ins_tab(id, col8) VALUES('
|| TO_CHAR(i)
|| ', ''' || LPAD(TO_CHAR(i),373,'x') || ''')'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE('SELECT * FROM dual;');
END;
/
spool off
SET SERVEROUTPUT OFF
UNDEFINE 1

set head on
set termout on
set feed on
set veri on
set timi on
set trimspool off


ここまでは、Oracle Databaseのmulti table insert(Oracle Databaseの方言)をつかった multi row insertでしたが、
次はPostgreSQLでも実行できるmulti row insert構文で同様の検証を行ってみます!


multi row insertを利用した場合のPGA消費量確認

見ての通り、Multi table insert文より、PGAの消費は緩やかではあるものの、それなりの消費量ですね。

これらの結果から、適当な行数(行サイズにもよるが)、10行〜多くても1000行程度ぐらいの範囲で一括INSERTするのが
性能とメモリ消費ではバランスが良さそうな感じに思えますね。
物理メモリが大量にあってPGAにそれなりに割り振れるとしても、ハードパース時間はかなり伸びですしそれを回避するのは難しいので。

こんな感じのmulti row insert文を生成(PostgreSQLも同じ文を利用できます)。なお、行数は適宜調整。

[oracle@arm64-oraclelinux8u10 ~]$ cat sql_mrows_ins_65535.sql
INSERT INTO mrows_ins_tab(id, col8) VALUES
(1, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')

...略...

, (65535, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx65535')
;


まずは、5,000行から。
147MB程度のPGA消費なので、INSERT ALLにくらべると半分ぐらいの消費になってますね。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
203 CPU used by this session 2
203 CPU used when call started 1

...略...

203 session pga memory 3419808
203 session pga memory max 3747488
203 session uga memory 1456416
203 session uga memory max 1769824

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_5000

5000行が作成されました。

経過: 00:00:04.91
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
203 CPU used by this session 491
203 CPU used when call started 491

...略...

203 session pga memory 3730424
203 session pga memory max 147647480
203 session uga memory 2297424
203 session uga memory max 4523744

...略...


同様に、3倍の15,000行。
INSERT ALL文と比べPGAの消費量は少なく、行数の割に緩やかに増加しているようですね。興味深い。
410MB程度のPGA消費。INSERT ALLで同様の行数を扱った検証では、1.3GB程度だったので50%以下に抑えられています。

23ai以降で、Multi row insertを行うなら、INSERT ALLは避けるべきですね。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 5
39 CPU used when call started 3

...略...

39 session pga memory 4254712
39 session pga memory max 5123744
39 session uga memory 1844408
39 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_15000

15000行が作成されました。

経過: 00:01:03.97
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
39 CPU used by this session 6381
39 CPU used when call started 6381

...略...

39 session pga memory 11660280
39 session pga memory max 412019704
39 session uga memory 4589264
39 session uga memory max 11204640

...略...


次のケース。
INSERT ALLでは、PGA_AGGREGATE_LIMITの制限サイズを超過してしまいましたが、PGA消費はおさえられ、本環境でも正常に処理されますね。ほう!
まだ、行けますね。
とはいえ、処理時間も随分長くなってます。これTVC同様ハードパース時間の影響が大きそうですよね。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 5
38 CPU used when call started 3

...略...

38 session pga memory 4254712
38 session pga memory max 5123744
38 session uga memory 1844408
38 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_20000

20000行が作成されました。

経過: 00:02:18.09
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 13538
38 CPU used when call started 13538

...略...

38 session pga memory 7007224
38 session pga memory max 550956024
38 session uga memory 5571464
38 session uga memory max 14349248

...略...


では、思い切って、50,000行にしてみましょう! なんとなくギリギリな感じですが、正常終了!
1,359MBほど使っちゃってますね。
この傾向、Table Value Constructorの傾向に近いですよね。はやり。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
40 CPU used by this session 5
40 CPU used when call started 3

...略...

40 session pga memory 4254712
40 session pga memory max 5123744
40 session uga memory 1844408
40 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_50000

50000行が作成されました。

経過: 00:29:08.74
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
40 CPU used by this session 173596
40 CPU used when call started 173596

...略...

40 session pga memory 31714296
40 session pga memory max 1359735800
40 session uga memory 11073624
40 session uga memory max 33220312

...略...


Table Value Constractorの傾向に類似しているので、行数上限がありそうだと思い。試してみた!
想像通り!!!!! エラーメッセージも同じです!!

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
38 CPU used by this session 5
38 CPU used when call started 4

...略...

38 session pga memory 4254712
38 session pga memory max 5123744
38 session uga memory 1844408
38 session uga memory max 3053904

...略...

SCOTT@localhost:1521/freepdb1> @sql_mrows_ins_65535
INSERT INTO mrows_ins_tab(id, col8) VALUES
*
行1でエラーが発生しました。:
ORA-63805: 表値コンストラクタのタプルの最大数を超えました ヘルプ:
https://docs.oracle.com/error-help/db/ora-63805/


経過: 00:00:01.07


上記で利用したINSERT文を生成したスクリプトです。参考まで。
Oracle Database 23ai以降で利用できるMulti row insert文を生成するスクリプトは以下のとおり。

set feed off
set timi off
set head off
set termout off
set veri off
set trimspool on

col col1 for a20
col col2 for a20
col col3 for a20
col col4 for a20
col col5 for a20
col col6 for a20
col col7 for a20
col col8 for a20
set linesize 400
set pagesize 1000
SET SERVEROUTPUT ON
spool sql_mrows_ins_&1..sql
DECLARE
c_max_rows CONSTANT NUMBER := &1;
BEGIN
DBMS_OUTPUT.PUT_LINE('INSERT INTO mrows_ins_tab(id, col8) VALUES');
FOR i IN 1..c_max_rows LOOP
DBMS_OUTPUT.PUT_LINE(
CASE WHEN i > 1
THEN ', '
END
|| '('
|| TO_CHAR(i)
|| ', ''' || LPAD(TO_CHAR(i),373,'x') || ''')'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE(';');
END;
/
spool off
SET SERVEROUTPUT OFF
UNDEFINE 1

set head on
set termout on
set feed on
set veri on
set timi on
set trimspool off


では、同様の方法で、PostgreSQL/MySQLのざっくりとしたメモリ消費などを確認しておきましょう。

次は、PostgreSQL

PostgreSQL、メモリ構造がOracle Databaseとは違うといっても、65535行登録で、302MB程度。随分違う。
とはいっても行数が多くなれば消費量は増えると思われ、Oracle Database同様、詰めすぎず、性能とメモリ消費のバランスが取れる単位にまとめるってのは、同じだろうと思います。

Password for user scott: 
psql (17.7)
Type "help" for help.

...略...

perftestdb=> \d+ mrows_ins_tab
Table "scott.mrows_ins_tab"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | not null | | plain | | |
col1 | character varying(1000) | | | | extended | | |
col2 | character varying(1000) | | | | extended | | |
col3 | character varying(1000) | | | | extended | | |
col4 | character varying(1000) | | | | extended | | |
col5 | character varying(1000) | | | | extended | | |
col6 | character varying(1000) | | | | extended | | |
col7 | character varying(1000) | | | | extended | | |
col8 | character varying(500) | | | | extended | | |
Indexes:
"mrows_ins_tab_pkey" PRIMARY KEY, btree (id)
Access method: heap



2026-01-07 20:45:03.765 JST [14222] LOG: PARSER STATISTICS
2026-01-07 20:45:03.765 JST [14222] DETAIL: ! system usage stats:
! 0.063931 s user, 0.000000 s system, 0.069344 s elapsed
! [0.063931 s user, 0.008076 s system total]
! 114684 kB max resident size
! 0/0 [0/0] filesystem blocks in/out
! 0/4816 [12/5839] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [6/0] voluntary/involuntary context switches
2026-01-07 20:45:03.765 JST [14222] STATEMENT: INSERT INTO mrows_ins_tab(id, col8) VALUES
(1, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')
, (2,

...略...

, (65535, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx65535')
;
2026-01-07 20:45:03.853 JST [14222] LOG: PARSE ANALYSIS STATISTICS
2026-01-07 20:45:03.853 JST [14222] DETAIL: ! system usage stats:
! 0.017787 s user, 0.000000 s system, 0.019316 s elapsed
! [0.144499 s user, 0.008076 s system total]
! 166148 kB max resident size
! 0/0 [0/0] filesystem blocks in/out
! 44/3185 [56/11332] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [120/0] voluntary/involuntary context switches
2026-01-07 20:45:03.853 JST [14222] STATEMENT: INSERT INTO mrows_ins_tab(id, col8) VALUES

...略...

2026-01-07 20:45:03.922 JST [14222] LOG: REWRITER STATISTICS
2026-01-07 20:45:03.922 JST [14222] DETAIL: ! system usage stats:
! 0.001438 s user, 0.000081 s system, 0.001645 s elapsed
! [0.203669 s user, 0.011451 s system total]
! 209480 kB max resident size
! 0/0 [0/0] filesystem blocks in/out
! 0/1 [56/13938] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [234/1] voluntary/involuntary context switches
2026-01-07 20:45:03.922 JST [14222] STATEMENT: INSERT INTO mrows_ins_tab(id, col8) VALUES

...略...

2026-01-07 20:45:04.183 JST [14222] LOG: PLANNER STATISTICS
2026-01-07 20:45:04.183 JST [14222] DETAIL: ! system usage stats:
! 0.176192 s user, 0.003606 s system, 0.195045 s elapsed
! [0.436538 s user, 0.018692 s system total]
! 235792 kB max resident size
! 0/0 [0/0] filesystem blocks in/out
! 2/3555 [58/17991] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/1 [338/2] voluntary/involuntary context switches
2026-01-07 20:45:04.183 JST [14222] STATEMENT: INSERT INTO mrows_ins_tab(id, col8) VALUES

...略...

2026-01-07 20:45:04.346 JST [14222] LOG: XECUTOR STATISTICS
2026-01-07 20:45:04.346 JST [14222] DETAIL: ! system usage stats:
! 0.050664 s user, 0.022169 s system, 0.093750 s elapsed
! [0.542249 s user, 0.048167 s system total]
! 302548 kB max resident size
! 0/91376 [0/91376] filesystem blocks in/out
! 7272/5528 [7330/25997] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 17/2 [474/4] voluntary/involuntary context switches
2026-01-07 20:45:04.346 JST [14222] STATEMENT: INSERT INTO mrows_ins_tab(id, col8) VALUES

...略...


最後に、MySQL

MySQLでは列値コンストラクタが必要なこと以外、Oracle Database/PostgreSQLと同じです。

[oracle@arm64-oraclelinux8u10 ~]$ cat sql_mrows_ins_65535_mysql.sql
INSERT INTO mrows_ins_tab(id, col8) VALUES
ROW(1, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')

...略...

, ROW(65535, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx65535')
;

MySQLもPostgreSQL同様、Oracle Databaseのような行数上限は無いですが、メモリ消費はそれなりにありますね。
やはり注意しておきたい部分ではありますね。無邪気に詰め込みすぎないのがリーズナブルだと思います。
65535行で、263MBぐらい。

[master@Oracle-Linux-8u10-arm64-2 ~]$ mysql -u scott -D perftestdb -p -h localhost 
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.4.7 MySQL Community Server - GPL

...略...

mysql> desc mrows_ins_tab;
+-------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| col1 | varchar(1000) | YES | | NULL | |
| col2 | varchar(1000) | YES | | NULL | |
| col3 | varchar(1000) | YES | | NULL | |
| col4 | varchar(1000) | YES | | NULL | |
| col5 | varchar(1000) | YES | | NULL | |
| col6 | varchar(1000) | YES | | NULL | |
| col7 | varchar(1000) | YES | | NULL | |
| col8 | varchar(500) | YES | | NULL | |
+-------+---------------+------+-----+---------+-------+
9 rows in set (0.00 sec)

mysql> SELECT * from performance_schema.users WHERE USER='scott';
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| USER | CURRENT_CONNECTIONS | TOTAL_CONNECTIONS | MAX_SESSION_CONTROLLED_MEMORY | MAX_SESSION_TOTAL_MEMORY |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| scott | 1 | 1 | 1465424 | 2914540 |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
1 row in set (0.00 sec)

mysql> SELECT MAX_TOTAL_MEMORY from performance_schema.events_statements_history WHERE SQL_TEXT LIKE 'INSERT INTO mrows_ins_tab(id, col8) VALUES%';
Empty set (0.00 sec)

mysql> \. sql_mrows_ins_65535_mysql.sql
Query OK, 65535 rows affected (0.59 sec)
Records: 65535 Duplicates: 0 Warnings: 0

mysql> SELECT MAX_TOTAL_MEMORY from performance_schema.events_statements_history WHERE SQL_TEXT LIKE 'INSERT INTO mrows_ins_tab(id, col8) VALUES%';
+------------------+
| MAX_TOTAL_MEMORY |
+------------------+
| 263061721 |
+------------------+
1 row in set (0.00 sec)

mysql> SELECT * from performance_schema.users WHERE USER='scott';
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| USER | CURRENT_CONNECTIONS | TOTAL_CONNECTIONS | MAX_SESSION_CONTROLLED_MEMORY | MAX_SESSION_TOTAL_MEMORY |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| scott | 1 | 1 | 235981664 | 263061721 |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
1 row in set (0.00 sec)


では、また。


デスクワークだと日中動かないので、手の部分がやたら冷える。。。
軽くBeat Saberで体動かすと、一時的には温まるのだけども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のソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!
帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある
帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)
帰ってきた! 標準はあるにはあるが癖の多いSQL #15 - 実行計画でスカラー副問合せの見せ方にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #16 - FROM句のインラインビューのエイリアスにもクセがある(必須だったり、任意だったり)
帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - ANY_VALUE() ってなかなかいいじゃん、癖無さそう!
帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #19 - c_alias の癖(おまけ)
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)
帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK

| | | コメント (0)

2025年12月26日 (金)

帰ってきた! 標準はあるにはあるが癖の多いSQL #21 - Table Value Constructer(TVC)- ハードパース時間とメモリ消費量 / BONUS TRACK

https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2025/12/post-d04e78.htmlのおまけです!

前回は、マニュアルでは言及されたりしていますが、TVCで多くの行を生成するのは複数の問題を引き起こしそう。。。
Oracle Databaseでは生成行数上限があるのですが、なにか異様に時間がかかってました。どのあたりだろう?。とか
MySQL/PostgreSQLは行数こそ制限されてないようですが、メモリ消費には影響しそうだよなぁ。
というあたり気になりますよね。今日はその辺りをざっくりと確認しておこうという。


マニュアルでも言及されてるし、TVCで大量行生成しねーーーだろーーーっ。
とも思うわけですが、世の中広いので、油断禁物ww
それやっちゃうと、実際にどうなりそうかって、肌感覚で知ってたほうが良いだろうという意図もあり。

まずは、
前回利用したSQLをCOUNT(1)に書き換えたものを利用します

Oracle Databaseの例(MySQLではRVCを利用する点以外違いはありません)
e.g.
sql_oracle_65534.sql

SELECT COUNT(1) FROM ( VALUES
(1)
,(2)
,(3)
,(4)

...略...

,(65530)
,(65531)
,(65532)
,(65533)
,(65534)
) t1 ( id )
/


環境はいつものとおり、arm64向け Oracle Database/MySQL/PostgreSQL環境をVirtualBox for macOS / Apple Siliconにて

oracle@Mac ~ % ./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: 26.2
BuildVersion: 25C56

*** VirtualBox ver. ***
7.2.4r170995

[master@arm64-oraclelinux8u10 ~]$ cat /etc/oracle-release
Oracle Linux Server release 8.10
[master@arm64-oraclelinux8u10 ~]$ uname -r
5.15.0-313.189.5.3.el8uek.aarch64

以降, 変化確認のために実行時間も記録しておきます
PostgreSQL 17.6だと、29ms程度。

[postgres@Oracle-Linux-8u10-arm64-2 ~]$ psql -U scott -d perftestdb -h localhost
Password for user scott:
psql (17.6)
Type "help" for help.

perftestdb=> \timing
Timing is on.
perftestdb=> select version();
version
---------------------------------------------------------------------------------------------------------------
PostgreSQL 17.6 on aarch64-unknown-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-26), 64-bit
(1 row)

Time: 1.848 ms
perftestdb=>
perftestdb=> \i sql_postgresql_65534.sql
count
-------
65534
(1 row)

Time: 28.617 ms


MySQL 8.4.7 だと 60ms程度のようですね。

[master@Oracle-Linux-8u10-arm64-2 ~]$ mysql -u root -D perftestdb -p -h localhost 
Enter password:

...略...

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

mysql>
mysql> \. sql_mysql_65534.sql
+----------+
| COUNT(1) |
+----------+
| 65534 |
+----------+
1 row in set (0.06 sec)


さて、今日の真打w Oracle Database、
前回のエントリで気づいたかもしれませんが、Oracle DatabaseのTVCどうやらハードパースにものすごく時間がかかっている雰囲気。
かといってソフトパースでも17秒ぐらいなので決して速くはないのですが、ハードパース時間がすごいですね。

[oracle@arm64-oraclelinux8u10 ~]$ sqlplus scott@localhost:1521/freepdb1 

...略...

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.8.0.25.04


-- ハードパースだと..
SCOTT@localhost:1521/freepdb1> @sql_oracle_65534.sql

COUNT(1)
----------
65534

経過: 00:50:43.88

-- ソフトパースだと...
SCOTT@localhost:1521/freepdb1> @sql_oracle_65534.sql

COUNT(1)
----------
65534

経過: 00:00:17.99


ということで、Oracle Database。ハードハース時間が長いのですが、もう少し掘り下げて覗いてみようと思います。
10046トレース(久々w)でログをとって追ってみます。

SCOTT@localhost:1521/freepdb1> alter session set tracefile_identifier='10046_tvc';
セッションが変更されました。

SCOTT@localhost:1521/freepdb1> alter session set statistics_level=all;
セッションが変更されました。

SCOTT@localhost:1521/freepdb1> alter session set max_dump_file_size = unlimited;
セッションが変更されました。

SCOTT@localhost:1521/freepdb1> alter system flush shared_pool;
システムが変更されました。

SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context forever,level 12';
セッションが変更されました。

SCOTT@localhost:1521/freepdb1> @sql_oracle_65534

COUNT(1)
----------
65534

経過: 00:43:01.31
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context off';
セッションが変更されました。

[oracle@arm64-oraclelinux8u10 trace]$ ls -l *10046_tvc*
-rw-r-----. 1 oracle oinstall 1289063 Dec 25 20:36 FREE_ora_5169_10046_tvc.trc
-rw-r-----. 1 oracle oinstall 18615 Dec 25 20:36 FREE_ora_5169_10046_tvc.trm

[oracle@arm64-oraclelinux8u10 trace]$ tkprof FREE_ora_5169_10046_tvc.trc FREE_ora_5169_10046_tvc.trc.txt explain=scott/tiger@localhost:1521/freepdb1 sys=yes waits=yes aggregate=no

...略...

該当箇所を見ると、やはり!
ハードパース時間がほとんどですね。 時間の単位は秒なので、43分ほどであることと、ほぼCPU時間に等しいことも見えますね。

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 2524.81 2551.05 0 60 0 0
Execute 1 0.01 0.01 0 0 0 0
Fetch 2 0.03 0.03 0 0 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 2524.86 2551.10 0 60 0 1


実行計画は以前みたものと同じで、VALUES SCANになっています。

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 134 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 SORT AGGREGATE (cr=0 pr=0 pw=0 time=36096 us starts=1 direct read=0 direct write=0)
65534 65534 65534 VIEW (cr=0 pr=0 pw=0 time=34577 us starts=1 direct read=0 direct write=0 cost=131076 size=0 card=65534)
65534 65534 65534 VALUES SCAN (cr=0 pr=0 pw=0 time=30701 us starts=1 direct read=0 direct write=0 cost=131076 size=0 card=65534)
待機イベントをみると、前回topコマンドで気になっていたメモリー関連の待機イベントでの待機回数が非常に多くなっています。しかも、PGA内のCGA/UGA使っているようにみえますね。CGAなんて久々に見ました。(2007年のネタを思い出しますw - Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #3)TVCで行を生成すると、PGA、CGAが拡大しその影響でUGAも増加、専用サーバーなのでその流れでPGAも拡大している様子が想像できますよね。23ai FREEだとメモリ制限もきついので、もう少しメモリを消費させれば、PGAのLIMITや23ai FREEのメモリ制限などに抵触する可能性はありますよね。。。それは後ほど試します!
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Allocate CGA memory from OS 488 0.00 0.00
Allocate PGA memory from OS 6 0.00 0.00
Free private memory to OS 22 0.02 0.03
Allocate UGA memory from OS 216 0.00 0.00
SQL*Net message to client 2 0.00 0.00
SQL*Net message from client 2 9.11 9.11
********************************************************************************


比較のためにソフトパースの場合は以下のとおり。

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.03 0.03 0 0 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.03 0.03 0 0 0 1

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 134 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 SORT AGGREGATE (cr=0 pr=0 pw=0 time=36156 us starts=1 direct read=0 direct write=0)
65534 65534 65534 VIEW (cr=0 pr=0 pw=0 time=34656 us starts=1 direct read=0 direct write=0 cost=131076 size=0 card=65534)
65534 65534 65534 VALUES SCAN (cr=0 pr=0 pw=0 time=30708 us starts=1 direct read=0 direct write=0 cost=131076 size=0 card=65534)

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Allocate UGA memory from OS 38 0.00 0.00
SQL*Net message to client 2 0.00 0.00
SQL*Net message from client 2 26.66 26.66
********************************************************************************

おまけ。PGA/UGA/CGAに関する懐かしいエントリ。(知ってる方いるかなw)
ソートに関する検証 その2 / InsightTechnology 旧ブログ

PGA/UGAサイズの変化 / 23ai FREEのメモリー制限は2GBである前提は頭の片隅に置いておく必要はあるが、
いまのところ制限内に収まっているようなのでこのながれのまま、サイズの変化を見ておきましょう。

ハードパースさせつつ試しています。

SCOTT@localhost:1521/freepdb1> alter system flush shared_pool;
システムが変更されました。

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

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
40 CPU used by this session 1
40 logical read bytes from cache 4243456
40 no work - consistent read gets 281
40 physical read IO requests 9
40 physical read bytes 106496
40 physical read total IO requests 9
40 physical read total bytes 106496
40 physical reads 13
40 physical reads cache 13
40 redo synch writes 1
40 redo write info find 1
40 session logical reads 518
40 session pga memory 2764448
40 session pga memory max 2829984
40 session uga memory 791336
40 session uga memory max 791384
40 sorts (memory) 41

17行が選択されました。

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> @sql_oracle_65534

COUNT(1)
----------
65534

経過: 00:49:53.83
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
40 CPU used by this session 273602
40 CPU used when call started 273602
40 logical read bytes from cache 4243456
40 no work - consistent read gets 281
40 physical read IO requests 9
40 physical read bytes 106496
40 physical read total IO requests 9
40 physical read total bytes 106496
40 physical reads 13
40 physical reads cache 13
40 redo synch writes 1
40 redo write info find 1
40 session logical reads 518
40 session pga memory 12053496
40 session pga memory max 1600383992
40 session uga memory 10680464
40 session uga memory max 40035216
40 sorts (memory) 42

18行が選択されました。


統計値差を確認!

PGAが1.5Gほど!!!!!!!に拡張!この辺りは 10046とレースの待機イベントにも現れていたCGAが占めていそうですね。UGAよりも。。。
(統計情報の詳細は、Database Reference E.2 Statistics Descriptions参照のこと)

単純な数値型1列で、65534行をTVCで生成しましたが、こんなにPGAを消費しちゃうんんですね。驚き!
PGAも無制限に利用できるわけではないので、TVCで大量に行データを生成するとPGAの制限にあたってエラーになるだろうなぁ。というのは容易に想像できます。

SCOTT@localhost:1521/freepdb1> @list_diff 2

STAT_NAME STAT_VALUE UNIT
---------------------------------------- ---------- -----
CPU used by this session 2736.01 sec
CPU used when call started 2736.02 sec
physical read total IO requests 9 times
session pga memory 8.86 MB
session pga memory max 1523.55 MB
session uga memory 9.43 MB
session uga memory max 37.43 MB
sorts (memory) 1 times


ついでなので、
数値型1行で65534行から半減させつつ16行まで、どの程度のPGAが消費されるか計測してグラフにしてみました。
TVCによる大量の行生成はやめた方が良いですよね。まじで。
PGAサイズの増加に合わせハードパース時間もとんでもないことになりますし。。。ご利用は計画的に! という感じです。
Tvc_pga_size

1列の行数だけ仕様上限だと発生まだ余裕は多少あるので、列数を多くし、1行で1ブロック(8K)程度になるようなイメージで作ってみました。PGA_AGGREGATE_LIMITでエラーになるか、
もしくは、23ai FREEのメモリ制限に先に当たってエラーになるか。。。どちらかでエラーになるはず!!!

ということで、8列かつ1行/ブロックになるような行サイズで、65534行をTVCで生成してCOUNT()するSQLを生成。(@make_tvc_sql.sql は後半に載せてあります)

SCOTT@localhost:1521/freepdb1> @make_tvc_sql.sql 65534 oracle

SELECT count(1) FROM ( VALUES
(1,
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1',

...略...

'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );


では実行!!!、コケると思いますよw 絶対!!!!!

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
に接続されました。
SCOTT@localhost:1521/freepdb1> @show_mystats.sql

SID NAME VALUE
---------- ---------------------------------------------------------------- ----------
203 CPU used by this session 1
203 CPU used when call started 1
203 logical read bytes from cache 3334144
203 no work - consistent read gets 215
203 physical read IO requests 16
203 physical read bytes 131072
203 physical read total IO requests 16
203 physical read total bytes 131072
203 physical reads 16
203 physical reads cache 16
203 redo synch writes 1
203 redo write info find 1
203 session logical reads 407
203 session pga memory 2698912
203 session pga memory max 2698912
203 session uga memory 791312
203 session uga memory max 791312
203 sorts (memory) 37

18行が選択されました。

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> @sql_oracle_65534
SELECT count(1) FROM ( VALUES
*
行1でエラーが発生しました。:
ORA-00028: セッションは終了しました ヘルプ:
https://docs.oracle.com/error-help/db/ora-00028/


経過: 00:05:51.08


ね! 狙い通りにエラー発生!!!!!w
ORA-00028エラーは、副産物なので根本原因をログから確認してみましょう!

以下トレースファイルより。

$ORACLE_BASE/diag/rdbms/free/FREE/incident/incdir_134953/FREE_ora_4958_i134953.trc

...略...

23ai FREEなのでそもそも利用可能なメモリサイズ上限はあるのですが、この例では、PGA_AGGREGATE_LIMITに抵触してエラーとなったようですね。まあ、想像通りの結果なのですがw
...略...

=======================================
PRIVATE MEMORY SUMMARY FOR THIS PROCESS
---------------------------------------

******************************************************
PRIVATE HEAP SUMMARY DUMP
1842 MB total:
1795 MB commented, 523 KB permanent
47 MB free (0 KB in empty extents),
877 MB, 2 heaps: "callheap " 14 MB free held
480 MB, 1 heap: "Alloc environm " 16 MB free held
480 MB, 2 chunks: "kgh stack " 16 MB free held

...略...

Summary of subheaps at depth 2
1326 MB total:
40 MB commented, 1286 MB permanent
408 KB free (0 KB in empty extents),
signalling ORA-4036 interrupt

...略...

Incident 134953 created, dump file: /opt/oracle/diag/rdbms/free/FREE/incident/incdir_134953/FREE_ora_4958_i134953.trc
ORA-04036: インスタンスまたはPDBにより使用されるPGAメモリーがPGA_AGGREGATE_LIMITを超えています。

TVCで生成した行数のみだけではなく、列数や列サイズもPGA消費に影響するすることを意味しています!!!
とにかく、マニュアルに記載されているように、大量の行を生成するのは避けるのが吉という癖の強い機能なので、ご利用は計画的にw

ついでなので、行数上限の制約は無いPostgreSQLとMySQLのメモリ消費量をざっくりみておきました。
これらもメモリ消費は大きくなるので、TVCによる大量の行生成はさけたほうがよいでしょうね。(ほかの方法はあるわけですし)

PostgreSQLでOracle Databaseで実行したSQLと同じ文を実行してみると。。。

[postgres@Oracle-Linux-8u10-arm64-2 ~]$ psql -U scott -d perftestdb -h localhost
Password for user scott:
psql (17.6)
Type "help" for help.

perftestdb=> \i sql_postgresql_65534.sql
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=983.01..983.02 rows=1 width=8) (actual time=34.131..34.132 rows=1 loops=1)
Output: count(1)
-> Values Scan on "*VALUES*" (cost=0.00..819.18 rows=65534 width=0) (actual time=0.003..31.264 rows=65534 loops=1)
Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3, "*VALUES*".column4, "*VALUES*".column5,
"*VALUES*".column6, "*VALUES*".column7, "*VALUES*".column8, "*VALUES*".column9
Planning:
Buffers: shared hit=3
Memory: used=602113kB allocated=607745kB
Planning Time: 2697.696 ms
Execution Time: 34.330 ms
(9 rows)


Tvc_65534rows_postgresql

以下のパラメータを設定、再起動してログ出力にてざっくりとと、max resident sizeを見てみた。
EXECUTOR STATISTICSは、2.9GBぐらいまで増加してますね。
log_statement_stats = off
log_parser_stats = on
log_planner_stats = on
log_executor_stats = on

にして再起動!

2025-12-25 20:10:25.291 JST [8356] LOG:  PARSER STATISTICS
2025-12-25 20:10:25.291 JST [8356] DETAIL: ! system usage stats:
! 1.042480 s user, 0.067788 s system, 1.112459 s elapsed
! [1.105611 s user, 0.125358 s system total]
! 1505652 kB max resident size
! 2416/0 [2416/376] filesystem blocks in/out
! 2/35500 [176/36935] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 1/1 [10/1] voluntary/involuntary context switches
2025-12-25 20:10:25.291 JST [8356] STATEMENT: SELECT count(1) FROM ( VALUES

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );
2025-12-25 20:10:27.072 JST [8356] LOG: PARSE ANALYSIS STATISTICS
2025-12-25 20:10:27.072 JST [8356] DETAIL: ! system usage stats:
! 0.271937 s user, 0.031995 s system, 0.309900 s elapsed
! [2.689273 s user, 0.260475 s system total]
! 1621520 kB max resident size
! 608/0 [3024/376] filesystem blocks in/out
! 44/38352 [220/79769] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 19/0 [936/3] voluntary/involuntary context switches
2025-12-25 20:10:27.072 JST [8356] STATEMENT: SELECT count(1) FROM ( VALUES

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );
2025-12-25 20:10:28.571 JST [8356] LOG: REWRITER STATISTICS
2025-12-25 20:10:28.571 JST [8356] DETAIL: ! system usage stats:
! 0.000005 s user, 0.000001 s system, 0.000003 s elapsed
! [4.003534 s user, 0.396938 s system total]
! 2098660 kB max resident size
! 0/0 [3024/376] filesystem blocks in/out
! 0/0 [220/84364] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [1781/6] voluntary/involuntary context switches
2025-12-25 20:10:28.571 JST [8356] STATEMENT: SELECT count(1) FROM ( VALUES

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );
2025-12-25 20:10:32.469 JST [8356] LOG: PLANNER STATISTICS
2025-12-25 20:10:32.469 JST [8356] DETAIL: ! system usage stats:
! 2.298387 s user, 0.046537 s system, 2.349685 s elapsed
! [7.677502 s user, 0.547417 s system total]
! 2220484 kB max resident size
! 80/0 [3104/376] filesystem blocks in/out
! 4/38971 [224/127833] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 2/4 [3274/11] voluntary/involuntary context switches
2025-12-25 20:10:32.469 JST [8356] STATEMENT: SELECT count(1) FROM ( VALUES

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );
2025-12-25 20:10:34.034 JST [8356] LOG: EXECUTOR STATISTICS
2025-12-25 20:10:34.034 JST [8356] DETAIL: ! system usage stats:
! 0.040565 s user, 0.000000 s system, 0.040742 s elapsed
! [9.047784 s user, 0.654338 s system total]
! 2699340 kB max resident size
! 0/0 [3136/376] filesystem blocks in/out
! 0/9 [228/132686] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [5130/13] voluntary/involuntary context switches
2025-12-25 20:10:34.034 JST [8356] STATEMENT: SELECT count(1) FROM ( VALUES

...略...

) t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );

最後は、MySQL
ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytesってエラーになったので
max_allowed_packetパラメータを64MBから768MBへ大きく設定しなおして無理やりエラーを回避して確認!

MySQLでもかなりメモリ消費しちゃってますね。。

[master@Oracle-Linux-8u10-arm64-2 ~]$ mysql -u root -D perftestdb -p -h localhost 
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.4.7 MySQL Community Server - GPL

Copyright (c) 2000, 2025, 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> \. sql_mysql_65534.sql
ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes
No connection. Trying to reconnect...
Connection id: 9
Current database: perftestdb

...略...

Current database: perftestdb

+--------------------+----------+
| Variable_name | Value |
+--------------------+----------+
| max_allowed_packet | 67108864 |
+--------------------+----------+
1 row in set (0.01 sec)

...略...

[master@Oracle-Linux-8u10-arm64-2 ~]$ sudo vi /etc/my.cnf
[master@Oracle-Linux-8u10-arm64-2 ~]$ sudo service mysqld restart
Redirecting to /bin/systemctl restart mysqld.service
[master@Oracle-Linux-8u10-arm64-2 ~]$ mysql -u scott -D perftestdb -p -h localhost
Enter password:

...略...

-- 768MBに
mysql> show variables like 'max_allowed_packet';
+--------------------+-----------+
| Variable_name | Value |
+--------------------+-----------+
| max_allowed_packet | 805306368 |
+--------------------+-----------+
1 row in set (0.00 sec)

mysql>


Tvc_65534rows_mysql

3GBぐらいまで消費しちゃってますね。

mysql> SELECT * from performance_schema.users WHERE USER='scott';
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| USER | CURRENT_CONNECTIONS | TOTAL_CONNECTIONS | MAX_SESSION_CONTROLLED_MEMORY | MAX_SESSION_TOTAL_MEMORY |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| scott | 1 | 1 | 647288 | 1398335 |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
1 row in set (0.00 sec)

mysql> \. sql_mysql_65534.sql
+----------+
| COUNT(*) |
+----------+
| 65534 |
+----------+
1 row in set (3.68 sec)

mysql> SELECT * from performance_schema.users WHERE USER='scott';
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| USER | CURRENT_CONNECTIONS | TOTAL_CONNECTIONS | MAX_SESSION_CONTROLLED_MEMORY | MAX_SESSION_TOTAL_MEMORY |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
| scott | 1 | 1 | 2404782200 | 2893645114 |
+-------+---------------------+-------------------+-------------------------------+--------------------------+
1 row in set (0.00 sec)

mysql> SELECT MAX_TOTAL_MEMORY from performance_schema.events_statements_history WHERE SQL_TEXT LIKE 'SELECT COUNT(*)%';
Empty set (0.00 sec)

mysql> \. sql_mysql_65534.sql
+----------+
| COUNT(*) |
+----------+
| 65534 |
+----------+
1 row in set (3.93 sec)

mysql> SELECT MAX_TOTAL_MEMORY from performance_schema.events_statements_history WHERE SQL_TEXT LIKE 'SELECT COUNT(*)%';
+------------------+
| MAX_TOTAL_MEMORY |
+------------------+
| 2893680900 |
+------------------+
1 row in set (0.00 sec)


では、Advent Calendarも終わり、今年の残すところあとわずか。

みなさま、よいお年をお迎えください。






make_tvc_sql.sql
1行に複数列を持たせかつ、1行1ブロック程度になるような行サイズとなるTVCクエリを生成するスクリプト
set feed off
set timi off
set head off
set termout off
set veri off
set trimspool on

col col1 for a20
col col2 for a20
col col3 for a20
col col4 for a20
col col5 for a20
col col6 for a20
col col7 for a20
col col8 for a20
set linesize 400
set pagesize 1000
SET SERVEROUTPUT ON
spool sql_&2._&1..sql
DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rvc_text_mysql CONSTANT CHAR(3) := 'ROW';
c_type_mysql CONSTANT CHAR(5) := 'MYSQL';
c_type CONSTANT VARCHAR2(10) := UPPER('&2');
BEGIN
DBMS_OUTPUT.PUT_LINE('SELECT COUNT(*) FROM ( VALUES');
FOR i IN 1..c_max_rows LOOP
DBMS_OUTPUT.PUT_LINE(
CASE WHEN i > 1 THEN ',' END
|| CASE WHEN c_type = c_type_mysql THEN c_rvc_text_mysql END
|| '(' || TO_CHAR(i)
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),1000,'x') || ''''
|| ', ''' || LPAD(TO_CHAR(i),373,'x') || ''')'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE(') t1 ( id, col1, col2, col3, col4, col5, col6, col7, col8 );');
END;
/
spool off
SET SERVEROUTPUT OFF
UNDEFINE 1
UNDEFINE 2


set head on
set termout on
set feed on
set veri on
set timi on
set trimspool off






関連エントリー
標準はあるにはあるが癖の多い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 - 実行計画でスカラー副問合せの見せ方にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #16 - FROM句のインラインビューのエイリアスにもクセがある(必須だったり、任意だったり)
帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - ANY_VALUE() ってなかなかいいじゃん、癖無さそう!
帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #19 - c_alias の癖(おまけ)
帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)


| | | コメント (0)

2025年12月12日 (金)

帰ってきた! 標準はあるにはあるが癖の多いSQL #20 - Table Value Constructer (TVC)


やってまいりました。年末恒例のアドベントカレンダー。

本エントリーは、以下アドベントカレンダーの12日目のクロスポストとなっています。
JPOUG Advent Calendar 2025 - Oracle Database
PostgreSQL Advent Calendar 2025
MySQL Advent Calendar 2025


11日目の窓は、それぞれ、
Oracle Databaseでマルチレイアウトのテーブルを作る方法その1 - HiroyukiNakaie さん / JPOUG Advent Calendar 2025 - Oracle Database
セキュリティ対策としての PostgreSQL マイナーバージョンアップ (PGCON2025 発表資料) - jri_narita さん / PostgreSQL Advent Calendar 2025
今年勉強会などで MySQL / HeatWave に関して話したことの振り返り+α - hmatsu47 さん / MySQL Advent Calendar 2025
でした。


今回のお題は、
帰ってきた! 標準はあるにはあるが癖の多いSQL #19 - c_alias の癖(おまけ)
ネタフリしていた、TVC です。と言っても、この曲じゃありません!!!!(この曲、知ってる人どれぐらいいるだろうw)
TVC15 / David Bowie


TVC = Table Value Constructor / 表値コンストラクタには、どのような癖があるのか、否か、、、確認しておきたいと思います。




ということで本題。

PostgreSQLではかなり前から実装されていた表値コンストラクタですが、MySQLではMySQL 8.0.19以降、Oracle Databaseでも、その流れで?!(どういう流れだw)、サポートされた感じがしますw(個人の感想です)

この表値コンストラクタ、注意点としては複数のマニュアルに記載されているので気づきやすいと思いますが、大量の行を生成することを意図したものではないという点のようですね。
メモリ消費量や最適化によっては、内部的に一時表などが利用されそうなの記述もありますね。
ということで、表値コンストラクタの癖探しの旅へw


まず、Oracle Database/MySQL/PostgreSQL、それぞれのマニュアルに目を通しておきましょう。
Oracle Database / Release 23 / values_clause::=
https://docs.oracle.com/cd/G11854_01/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__GUID-27159C8E-617B-4ECE-AA4C-1800287F0C9D

Oracle Database / Release 23 / values_clause
https://docs.oracle.com/cd/G11854_01/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__SECTION_UMB_QGC_FWB

values_clause::= 、および、expression_list::=
Oracle Databaseの場合、シンタックスを見る限り、value_clauseに含めることができる Expression_listの制限が、TVCで指定できる最大行数になりそうですよね。わかりにくいですが。。この点は今回確認しておきましょう。
https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/IN-Condition.html#SQLRF-GUID-C7961CB3-8F60-47E0-96EB-BDCF5DB1317C


MySQL 8.0 リファレンスマニュアル / SQL ステートメント / データ操作ステートメント / VALUES ステートメント
https://dev.mysql.com/doc/refman/8.0/ja/values.html


PostgreSQL 17.5文書 / SQLコマンド / VALUES
https://www.postgresql.jp/document/17/html/sql-values.html

いきなり癖、発見!www 癖多そう!!!!
これらのマニュアルを斜め読みしただけも癖のあることに気づきます。
Oracle Databaseは、VALUES句のみサポートされています、MySQL/PostgreSQLはVALUESコマンドとしても使える!。それ使う場面あるのか?! と思ったり。MySQLでは、ROW() 行値コンストラクタが必要であることなどがあります。(MySQLのINSERT文では行値コンストラクタは必須ではなさそうなので、SELECT文でも同様に扱って欲しいきがします

その他、TVCは少量のデータを想定していると記載されているものの、最大行数制限となりそうな記述は、Oracle Databaseぐらいですし、メモリ消費もそれなりに高めなので、やりたい放題って状況は避けるべきでしょうね。
なお、今回準備した環境制限ですが、Oracle Database 23ai FREE on VirtualBOXはメモリサイズが2GBに制限されているため、メモリサイズ(PGA含め)に依存しそうな上限確認のテストでは少々厳しめでした。



ログが多めなので、最初にTVCの癖の数々をサマっておきます!

  • TVCで生成できる行数の上限
    • Oracle Database : 65534行。マニュアル上は、65535行に読めるのだが、ここまで使うこともないはずw
    • MySQL/PostgreSQL : 明示的な制限なし

    • なお、少量データを想定した機能と記載されているので、大量のデータを生成するのは避けた方が無難。他の方法があるので。

  • TVCの表エイリアス記述
    • Oracle Database : 必須
    • MySQL : 必須
    • PostgreSQL : 任意

  • TVCの列エイリアス記述
    • Oracle Database : 必須。ただし、列値の個数と列エイリアスの個数は同一であること。
    • MySQL : 任意。ただし、列エイリアスを記述する場合は、列値の個数と列エイリアスの個数は同一であること。
      • e.g. SELECT * FROM (VALUES ROW(1,2)) t01; の場合、column_0 , column_1 という列エイリアスが付与される

    • PostgreSQL : 任意。列値の個数と列エイリアスの個数は一致する必要はない。列エイリアスのない列値には、デフォルトの列エイリアスが付与される。
      • e.g. SELECT * FROM (VALUES ROW(1,2)) t01; の場合、column1 , column2 という列エイリアスが付与される
      • e.g. SELECT * FROM (VALUES ROW(1,2)) t01 (c1); の場合、c1 , column2 という列エイリアスが付与される

    • 通常はコーディング規約で縛って、表エイリアスと列エイリアスの記述を必須にることがほとんどだと思われる。PostgreSQLはかなり緩め。MySQLは少々トリッキー、書き漏らした場合、気づくのが遅れることが多そうなので要注意。

  • 行値コンストラクが必要
    • Oracle Database : 行値コンストラクタ不要
    • MySQL : 行値コンストラクタ ROW() 必須
    • PostgreSQL : 行値コンストラクタ不要

  • VALUESコマンドのサポート
    • Oracle Database : サポートしていない
    • MySQL : サポートしている
    • PostgreSQL : サポートしている

    • コマンドとし単体で使えるのって嬉しいのかよくわからないのだが、どうなんだろう。


  • 実行計画
    • Oracle Database : VALUES SCAN として現れる。
    • MySQL : TREE形式の実行計画を見る限り、TVCが利用されていることを識別することはできない(8.4.7より後ではどうなるか、わからないが。)
    • PostgreSQL : Values Scan on "*VALUES*" として現れる(Oracle Databaseが後発なので、PostgreSQLの表示に近い表現にしたのかもしれない)







では、いろいろ動かして前述した癖の挙動を見ていきましょう。

後半で、大量生成しないことが推奨されているTVCで大量の値を生成したらどうなっちゃうのか。。。というあたりまで見ておきますw
そういうことやっちゃう方々は出てくるかもなーと予想しつつw。。。

e.g. IN句に仕様の限界まで値を詰めて、さらに OR条件でさらに繰り返しちゃう。。。とか、稀によく見ますし。w 
   TVCも無邪気に大量の行を生成させちゃうと。。。いろいろ副作用が強そうな部分もありw(今回はそこまで試しませんが。。。)

PostgreSQL (17.6)
マニュアルのバージョンを遡るとサポートされ始めたのは3種の中では最も古く、PostgreSQL 8.2.6文書 VALUESにあるように Ver. 8.2(2006年リリース)のころにはあったようですね。
Mac De OracleでPostgreSQL/MySQLも含めたネタが2005年12月のMac De Oracle Heterogeneous! #1で、PostgreSQL7.4.9/MySQL4.1.13a/MySQL4.0.25なので、そんな前だったか〜と、遠い目をしているところw。。。。。。

                                                    version                    
-------------------------------------------------------------------------------
PostgreSQL 17.6 on aarch64-unknown-linux-gnu,
compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-26), 64-bit
perftestdb=> VALUES (1, 'one'), (2, 'two'), (3, 'three');
column1 | column2
---------+---------
1 | one
2 | two
3 | three

TVCの行数が増加するとExecution Timeもそうですが、Plannningで消費するメモリサイズが増加しそうなのでExplain時にmemoryオプションも付加しています。

perftestdb=> explain (memory, buffers, analyze, verbose) VALUES (1, 'one'), (2, 'two'), (3, 'three');
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..0.04 rows=3 width=36) (actual time=0.002..0.003 rows=3 loops=1)
Output: column1, column2
Planning:
Memory: used=7kB allocated=8kB
Planning Time: 0.023 ms
Execution Time: 0.008 ms

MySQL (8.4.7)
(PostgreSQL同様、ARM版です)

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

PostgreSQLに似ているようで似てない癖もあるようです。少々脱線してますが、INSERT文と組み合わせる場合は、全列で列値コンストラクタROW()を使うか、全く使わないかのどちらか、というトリッキーな仕様もあるようです。

mysql> VALUES (1, 'one'), (2, 'two'), (3, 'three');
ERROR 1064 (42000): You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near '(1, 'one'), (2, 'two'), (3, 'three')' at line 1
mysql>
mysql>
mysql> VALUES ROW(1, 'one'), ROW(2, 'two'), ROW(3, 'three');
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| 1 | one |
| 2 | two |
| 3 | three |
+----------+----------+
3 rows in set (0.00 sec)

mysql> create table hoge (id integer);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into hoge(id) values (1),(2),(3),(4);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0

mysql> insert into hoge(id) values ROW(5),ROW(6),ROW(7),ROW(8);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0

mysql> insert into hoge(id) values ROW(9),ROW(10),(11),(12);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(11),(12)' at line 1


PostgreSQLとは異なり、実行計画上(TREEフォーマット)、TVCを利用しているということは読み取れないですね。実行計画からTVCを利用していると読み取れると判別しやすくて良いのではないだろうか。。。どう思います?

mysql> explain analyze format=tree VALUES ROW(1, 'one'), ROW(2, 'two'), ROW(3, 'three');
+---------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------+
| -> Rows fetched before execution (cost=0..0 rows=3) (actual time=167e-6..209e-6 rows=3 loops=1)
|
+---------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Oracle Database (23.8)

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.8.0.25.04


試すまでもないわけですがw、Oracle Databaseでは、PostgreSQL/MySQLのVALUESステートメントはどちらもエラー。

SCOTT@localhost:1521/freepdb1> VALUES (1, 'one'), (2, 'two'), (3, 'three');
SP2-0734: "VALUES (1,..."で開始するコマンドが不明です - 残りの行は無視されました。
ヘルプ: https://docs.oracle.com/error-help/db/sp2-0734/

SCOTT@localhost:1521/freepdb1> VALUES ROW(1, 'one'), ROW(2, 'two'), ROW(3, 'three');
SP2-0734: "VALUES ROW..."で開始するコマンドが不明です - 残りの行は無視されました。
ヘルプ: https://docs.oracle.com/error-help/db/sp2-0734/


つづいて、Oracle DatabaseでもサポートされているTVCの癖。SELECT文やWITH句で利用するケースです。

インラインビューの形で書いて、表エイリアスと列エイリアスも合わせて記述しています。
表エイリアスと列エイリアスの指定が必須か否か、など癖が多い(後述)

PostgreSQL (17.6)

perftestdb=> SELECT * 
perftestdb-> FROM
perftestdb-> (
perftestdb(> VALUES
perftestdb(> (1, 'SCOTT')
perftestdb(> ,(2, 'SMITH')
perftestdb(> ,(3, 'JOHN' )
perftestdb(> ) t1 (
perftestdb(> employee_id
perftestdb(> , first_name
perftestdb(> );
employee_id | first_name
-------------+------------
1 | SCOTT
2 | SMITH
3 | JOHN
(3 rows)

perftestdb=> explain (memory, buffers, analyze, verbose)
perftestdb-> SELECT *
perftestdb-> FROM
perftestdb-> (
perftestdb(> VALUES
perftestdb(> (1, 'SCOTT')
perftestdb(> ,(2, 'SMITH')
perftestdb(> ,(3, 'JOHN' )
perftestdb(> ) t1 (
perftestdb(> employee_id
perftestdb(> , first_name
perftestdb(> );
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..0.04 rows=3 width=36) (actual time=0.010..0.013 rows=3 loops=1)
Output: "*VALUES*".column1, "*VALUES*".column2
Planning:
Memory: used=11kB allocated=16kB
Planning Time: 0.174 ms
Execution Time: 0.050 ms
(6 rows)

Oracle Database (23.8)
PostgreSQLと同一シンタックスでOKです。

SCOTT@localhost:1521/freepdb1> l
1 SELECT /*+ MONITOR */ *
2 FROM
3 (
4 VALUES
5 (1, 'SCOTT')
6 ,(2, 'SMITH')
7 ,(3, 'JOHN' )
8 ) t1 (
9 employee_id
10 , first_name
11* )
SCOTT@localhost:1521/freepdb1> /

EMPLOYEE_ID FIRST
----------- -----
1 SCOTT
2 SMITH
3 JOHN

経過: 00:00:00.00


実行計画もPostgreSQLのようにVALUES SCANとして現れます。VIEWとあるようにインラインビューとして認識されている点も読み取れますよね

SCOTT@localhost:1521/freepdb1> @show_sqlmonitor

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

SQL Text
------------------------------
SELECT /*+ MONITOR */ * FROM ( VALUES (1, 'SCOTT') ,(2, 'SMITH') ,(3, 'JOHN' ) ) t1 ( employee_id , first_name )

...略...

Global Stats
=============================
| Elapsed | Cpu | Fetch |
| Time(s) | Time(s) | Calls |
=============================
| 0.00 | 0.00 | 2 |
=============================

SQL Plan Monitoring Details (Plan Hash Value=1233125608)
======================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
======================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 3 | | |
| 1 | VIEW | | 3 | 6 | 1 | +0 | 1 | 3 | | |
| 2 | VALUES SCAN | | 3 | 6 | 1 | +0 | 1 | 3 | | |
======================================================================================================================

MySQL (8.4)
MySQLはすでに癖があることは解説済みですが、SELECT文で使う場合も行値コンストラクタが必要です。

mysql> SELECT  * 
-> FROM
-> (
-> VALUES
-> (1, 'SCOTT')
-> ,(2, 'SMITH')
-> ,(3, 'JOHN' )
-> ) t1 (
-> employee_id
-> , first_name
-> );
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds
to your MySQL server version for the right syntax to use near '(1, 'SCOTT')

,(2, 'SMITH')
,(3, 'JOHN' )
) t1 (
employee_id
, first' at line 5
mysql>
mysql> SELECT *
-> FROM
-> (
-> VALUES
-> ROW(1, 'SCOTT')
-> ,ROW(2, 'SMITH')
-> ,ROW(3, 'JOHN' )
-> ) t1 (
-> employee_id
-> , first_name
-> );
+-------------+------------+
| employee_id | first_name |
+-------------+------------+
| 1 | SCOTT |
| 2 | SMITH |
| 3 | JOHN |
+-------------+------------+
3 rows in set (0.00 sec)

MySQLの場合、実行計画だけでTVCが利用されているということは判断できないのはVALUESコマンドと同様。
(小さい癖ですが。SQL文を合わせて見るようにしないと見落としてしまう可能性はありますね。実行計画だけ見るってこと自体があまり無いとは思いますが、そういう方も中にはいるので。)

mysql> explain analyze format=tree
-> SELECT *
-> FROM
-> (
-> VALUES
-> ROW(1, 'SCOTT')
-> ,ROW(2, 'SMITH')
-> ,ROW(3, 'JOHN' )
-> ) t1 (
-> employee_id
-> , first_name
-> );
+---------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------+
| -> Table scan on t1 (cost=1.15..2.84 rows=3) (actual time=0.078..0.0795 rows=3 loops=1)
-> Materialize (cost=0.3..0.3 rows=3) (actual time=0.0713..0.0713 rows=3 loops=1)
-> Rows fetched before execution (cost=0..0 rows=3) (actual time=993e-6..0.00162 rows=3 loops=1)
|
+---------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


すでに特徴的な癖がいくつかありますが、次は、TVCに関わる表エイリアスと列エイリアスの指定に有無に関わる癖の違いの確認。
三者三様の癖があります。試験に出るので覚えておきましょう!(ないないw

1) TVCインラインビューで、表エイリアスと列エイリアスを記述しなかった場合
Oracle DatabaseとMySQLでは表エイリアスは必須なのでエラーなのですが、PostgreSQLは許容範囲広いっすね!

PostgreSQL (17.6)

perftestdb=> select * from (values (1),(2));
column1
---------
1
2
(2 rows)

MySQL (8.4.7)

mysql> select * from (values row(1),row(2));
ERROR 1248 (42000): Every derived table must have its own alias

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> select * from (values (1),(2));
select * from (values (1),(2))
*
行1でエラーが発生しました。:
ORA-00931: 識別子がありません。
ヘルプ:
https://docs.oracle.com/error-help/db/ora-00931/


2) TVCインラインビューで、表エイリアス記述した場合
1)でエラーとなったMySQLはシンタックスエラーなし=表エイリアスの記述は必須!。列エイリアスは任意?!っぽい。
しかし、Oracle Databaseはエラーのままです、列エイリアスも必要!!!!!。

PostgreSQL (17.6)

perftestdb=> select * from (values (1),(2)) t01;
column1
---------
1
2
(2 rows)

MySQL (8.4.7)

mysql> select * from (values row(1),row(2)) t01;
+----------+
| column_0 |
+----------+
| 1 |
| 2 |
+----------+
2 rows in set (0.01 sec)

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1>  select * from (values (1),(2)) t01;
select * from (values (1),(2)) t01
*
行1でエラーが発生しました。:
ORA-63814: 表値コンストラクタの別名に列名を指定する必要があります。
ヘルプ:
https://docs.oracle.com/error-help/db/ora-63814/


3) TVCインラインビューで、表エイリアスと列エイリアスを記述した場合
やっと、全て正常に実行された!!!

MySQL/PostgreSQLでは任意とは言え、実際に利用する場合にはSQLコーディングルールで縛るでしょうね。絶対。
そういう意味では、Oracle Databaseのように必須にしちゃったほうがSQL各側にとっては楽なのではないだろうか。ミスるとエラーにしてくれし。

PostgreSQL (17.6)

perftestdb=> select * from (values (1),(2)) t01(id);
id
----
1
2
(2 rows)

MySQL (8.4.7)

mysql> select * from (values row(1),row(2)) t01(id);
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> select * from (values (1),(2)) t01(id);

ID
----------
1
2

4) TVCインラインビューで、表エイリアスと列エイリアスを記述したが、記述した列エイリアス数と列数不一致の場合
PostgreSQLとOracle Databaseは予想通りの挙動でしたが、MySQLは想定の斜め上の挙動!

PostgreSQLは列エイリアスは任意だし、列値の個数と一致しなくても、Whatever!
Oracle Database、列エイリアスは必須だし、列値の個数と一致してないと、ダメ、絶対!
MySQL、列エイリアスは任意だけど、指定するなら列値の個数と一致してないと、ダメ!

個性派揃いですね!!!!w

ところで、PostgreSQL付与の列エイリアスって、列順なのね。2列目の列エイリアスを記述しないと、column2 が付与される。

PostgreSQL (17.6)

perftestdb=> select * from (values (1,1),(2,2)) t01(id);
id | column2
----+---------
1 | 1
2 | 2
(2 rows)

MySQL (8.4.7)

mysql> select * from (values row(1,1),row(2,2)) t01(id);
ERROR 1353 (HY000): In definition of view, derived table or common table expression,
SELECT list and column names list have different column counts

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> select * from (values (1,1),(2,2)) t01(id);
select * from (values (1,1),(2,2)) t01(id)
*
行1でエラーが発生しました。:
ORA-63815: 列名の数は、表値コンストラクタの値の数と一致する必要があります。
ヘルプ:
https://docs.oracle.com/error-help/db/ora-63815/

5) TVCインラインビューで、表エイリアスと列値の数と同数の列エイリアスを指定した場合
こう書けば何も問題ないよーっ。

PostgreSQL (17.6)

perftestdb=> select * from (values (1,1),(2,2)) t01(id,seq1);
id | seq1
----+------
1 | 1
2 | 2
(2 rows)

MySQL (8.4.7)

mysql> select * from (values row(1,1),row(2,2)) t01(id,seq1);
+----+------+
| id | seq1 |
+----+------+
| 1 | 1 |
| 2 | 2 |
+----+------+
2 rows in set (0.00 sec)

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> select * from (values (1,1),(2,2)) t01(id,seq1);

ID SEQ1
---------- ----------
1 1
2 2

WITH句で使うこともできます!(細かい挙動までは追わないが)

PostgreSQL (17.6)

perftestdb=> WITH 
perftestdb-> t01 AS
perftestdb-> (
perftestdb(> SELECT *
perftestdb(> FROM
perftestdb(> (
perftestdb(> VALUES
perftestdb(> (1, 'SCOTT')
perftestdb(> ,(2, 'SMITH')
perftestdb(> ,(3, 'JOHN' )
perftestdb(> ) x01 (
perftestdb(> employee_id
perftestdb(> , first_name
perftestdb(> )
perftestdb(> )
perftestdb-> SELECT * FROM t01;
employee_id | first_name
-------------+------------
1 | SCOTT
2 | SMITH
3 | JOHN
(3 rows)

perftestdb=> explain (memory, buffers, analyze, verbose)
perftestdb-> WITH
perftestdb-> t01 AS
perftestdb-> (
perftestdb(> SELECT *
perftestdb(> FROM
perftestdb(> (
perftestdb(> VALUES
perftestdb(> (1, 'SCOTT')
perftestdb(> ,(2, 'SMITH')
perftestdb(> ,(3, 'JOHN' )
perftestdb(> ) x01 (
perftestdb(> employee_id
perftestdb(> , first_name
perftestdb(> )
perftestdb(> )
perftestdb-> SELECT * FROM t01;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..0.04 rows=3 width=36) (actual time=0.002..0.003 rows=3 loops=1)
Output: "*VALUES*".column1, "*VALUES*".column2
Planning:
Memory: used=22kB allocated=32kB
Planning Time: 0.039 ms
Execution Time: 0.011 ms
(6 rows)

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> l
1 WITH
2 t01 AS
3 (
4 SELECT *
5 FROM
6 (
7 VALUES
8 (1, 'SCOTT')
9 ,(2, 'SMITH')
10 ,(3, 'JOHN' )
11 ) x01 (
12 employee_id
13 , first_name
14 )
15 )
16* SELECT /*+ MONITOR */ * FROM t01
SCOTT@localhost:1521/freepdb1> /

EMPLOYEE_ID FIRST
----------- -----
1 SCOTT
2 SMITH
3 JOHN

SCOTT@localhost:1521/freepdb1> @show_sqlmonitor

...略...

Global Stats
========================================
| Elapsed | Cpu | Other | Fetch |
| Time(s) | Time(s) | Waits(s) | Calls |
========================================
| 0.00 | 0.00 | 0.00 | 2 |
========================================

SQL Plan Monitoring Details (Plan Hash Value=1233125608)
======================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
======================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 3 | | |
| 1 | VIEW | | 3 | 6 | 1 | +0 | 1 | 3 | | |
| 2 | VALUES SCAN | | 3 | 6 | 1 | +0 | 1 | 3 | | |
======================================================================================================================


Oracle Databaseの場合、通常はインラインビューへリライトされるケースでも、マテリアライズすることができるので、実行計画がどう変化するかも見ておきましょう!
一時表としてマテリアライズされ、CURSOR DURATION MEMORYによりPGA上に一時的に保持されています。すべてPGAに乗る程度のサイズなら繰り返し参照されるケースでは有利なのは自明です。このケースでは無駄ですがw

SCOTT@localhost:1521/freepdb1> l
1 WITH
2 t01 AS
3 (
4 SELECT /*+ MATERIALIZE */ *
5 FROM
6 (
7 VALUES
8 (1, 'SCOTT')
9 ,(2, 'SMITH')
10 ,(3, 'JOHN' )
11 ) x01 (
12 employee_id
13 , first_name
14 )
15 )
16* SELECT /*+ MONITOR */ * FROM t01
SCOTT@localhost:1521/freepdb1> /

EMPLOYEE_ID FIRST
----------- -----
1 SCOTT
2 SMITH
3 JOHN

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> @show_sqlmonitor

...略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.00 | 0.00 | 0.00 | 2 | 2 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1856684117)
=============================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=============================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 3 | . | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | 1 | +0 | 1 | 3 | . | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY) | SYS_TEMP_0FD9D660A_6FD25E | | | 1 | +0 | 1 | 1 | 1024 | | |
| 3 | VIEW | | 3 | 6 | 1 | +0 | 1 | 3 | . | | |
| 4 | VALUES SCAN | | 3 | | 1 | +0 | 1 | 3 | . | | |
| 5 | VIEW | | 3 | 2 | 1 | +0 | 1 | 3 | . | | |
| 6 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_6FD25E | 3 | 2 | 1 | +0 | 1 | 3 | . | | |
=============================================================================================================================================================================

MySQL (8.4.7)

mysql> WITH 
-> t01 AS
-> (
-> SELECT *
-> FROM
-> (
-> VALUES
-> ROW(1, 'SCOTT')
-> ,ROW(2, 'SMITH')
-> ,ROW(3, 'JOHN' )
-> ) x01 (
-> employee_id
-> , first_name
-> )
-> )
-> SELECT * FROM t01;
+-------------+------------+
| employee_id | first_name |
+-------------+------------+
| 1 | SCOTT |
| 2 | SMITH |
| 3 | JOHN |
+-------------+------------+
3 rows in set, 0 warning (0.00 sec)

mysql> explain analyze format=tree
-> WITH
-> t01 AS
-> (
-> SELECT *
-> FROM
-> (
-> VALUES
-> ROW(1, 'SCOTT')
-> ,ROW(2, 'SMITH')
-> ,ROW(3, 'JOHN' )
-> ) x01 (
-> employee_id
-> , first_name
-> )
-> )
-> SELECT * FROM t01;
+-------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------+
| -> Table scan on x01 (cost=1.15..2.84 rows=3) (actual time=0.0102..0.0105 rows=3 loops=1)
-> Materialize (cost=0.3..0.3 rows=3) (actual time=0.00871..0.00871 rows=3 loops=1)
-> Rows fetched before execution (cost=0..0 rows=3) (actual time=208e-6..417e-6 rows=3 loops=1)
|
+-------------------------------------------------------------------------------------------------------+
1 row in set, 0 warning (0.00 sec)

では、最後に、もう一つだけ確認。

再掲
Oracle Database / Release 23 / values_clause
https://docs.oracle.com/cd/G11854_01/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__SECTION_UMB_QGC_FWB

values_clause::= 、および、expression_list::=
Oracle Databaseの場合、シンタックスを見る限り、value_clauseに含めることができる Expression_listの制限が、TVCで指定できる最大行数になりそうですよね。わかりにくいですが。。この点は今回確認しておきましょう。
https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/IN-Condition.html#SQLRF-GUID-C7961CB3-8F60-47E0-96EB-BDCF5DB1317C


Oracle Database (23.8)のTVCでは、マニュアルのシンタックス等から推測するに、生成できる行数に制限があるように読み取れるのだが、MySQL/PostgreSQLともにそれに類する記載は見つけられなかった。
ただ、いずれもメモリはそれなりに消費するようなので、メモリ消費量はそれなりの影響はありそうではある。。。
(明示されている箇所があればマニュアルのURLを教えていただけるとありがたい)


Oracle Database (23.8)
ということで、Oracle Database (23.8)の上限と思われる。 65535行前後程度までTVCで生成し挙動だけ(上限でエラーになるのか?)を確認しておく。
なお、Oracle Database 23ai FREEはインスタンスが利用できるメモリサイズ上限自体が2GBなので、検証する前にメモリ関連エラーになる可能性はある。。どうなりますか。。。

コード生成 Oracle 無名PL/SQL (Oracle DatabaseでMySQL向けSQLも生成しちゃいますw)。コードは後述。


マニュアルだと、65535行まではできそうだったが、65534行までがただしいようだ。いずれにしても実際に使うとなると1000行以下だとおもうけど。

65535行を生成するTVCはORA-63805: 表値コンストラクタのタプルの最大数を超えました となりました。あれ?

SCOTT@localhost:1521/freepdb1> @make_tvc_sql0.sql 65535 oracle
SCOTT@localhost:1521/freepdb1> set autot traceonly
SCOTT@localhost:1521/freepdb1> @sql_oracle_65535
SELECT * FROM ( VALUES
*
行1でエラーが発生しました。:
ORA-63805: 表値コンストラクタのタプルの最大数を超えました
ヘルプ:
https://docs.oracle.com/error-help/db/ora-63805/

経過: 00:00:00.05


ということで、65534行生成するTVCにすると実行できました。とは言ってもtopで眺めてみるとメモリ消費は激しいなという状況。。。その辺り別の機会に。

ちなみに、Oracle Database 23ai FREEって2GBっていうメモリの制限があったりするので、このケースだと explain plan for や autotrace expとかで実行計画も取得しようとするとメモリがらみのエラーが発生した(FREEのメモリサイズ制限2GBまでなので増やせない罠)ので、実行統計だけにしてあります。:)
この手の限界テストしようとするとFREEのメモリ制限ってキツいですよねw

SCOTT@localhost:1521/freepdb1> @make_tvc_sql0 65534 oracle
SCOTT@localhost:1521/freepdb1> set autot on stat
SCOTT@localhost:1521/freepdb1> @sql_oracle_65534

ID
----------
1
2
3

...略...

65532
65533
65534

65534行が選択されました。

経過: 00:53:31.31

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
1269854 bytes sent via SQL*Net to client
680591 bytes received via SQL*Net from client
4370 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
65534 rows processed

Tvc_for_adventcalendar


PostgreSQL (17.6)

TVCで生成する行数制限はなさそうですが、Planningのメモリサイズは1行生成の単純なものと比べるとかなり増えてますね。

perftestdb=> \i /var/lib/pgsql/sql_postgresql_65534.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..819.18 rows=65534 width=4) (actual time=0.009..5.500 rows=65534 loops=1)
Output: "*VALUES*".column1
Planning:
Buffers: shared hit=3
Memory: used=19977kB allocated=26113kB
Planning Time: 10.755 ms
Execution Time: 7.745 ms
(7 rows)

perftestdb=> \i /var/lib/pgsql/sql_postgresql_65535.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*" (cost=0.00..819.19 rows=65535 width=4) (actual time=0.008..5.561 rows=65535 loops=1)
Output: "*VALUES*".column1
Planning:
Buffers: shared hit=3
Memory: used=19977kB allocated=26113kB
Planning Time: 10.969 ms
Execution Time: 7.825 ms
(7 rows)

MySQL (8.4.7)
MySQLも何事もなく実行できちゃいますね。マニュアルにはTVCの行数制限はないですが、多分、でかくするとメモリ消費は激しくなるんだろなぁ。と、想像しています。PostgreSQLもPlannerのメモリ使用量がかなり大きくなっていたので。。

mysql> \. sql_mysql_65535.sql
+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on t1 (cost=6554..7375 rows=65535) (actual time=15.9..20.9 rows=65535 loops=1)
-> Materialize (cost=6554..6554 rows=65535) (actual time=15.9..15.9 rows=65535 loops=1)
-> Rows fetched before execution (cost=0..0 rows=65535) (actual time=360e-6..11.2 rows=65535 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (0.09 sec)

mysql> \. sql_mysql_65534.sql
+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on t1 (cost=6553..7375 rows=65534) (actual time=13.4..18.5 rows=65534 loops=1)
-> Materialize (cost=6553..6553 rows=65534) (actual time=13.4..13.4 rows=65534 loops=1)
-> Rows fetched before execution (cost=0..0 rows=65534) (actual time=452e-6..9.02 rows=65534 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (0.08 sec)


ということで、今年のアドベントカレンダーネタ、TVCの癖! はここまで。


あす、13番目の窓は、それぞれ。
Kenji Hirano さんのターン / JPOUG Advent Calendar 2025
yuya_yoshida_forcia さんのターン / PostgreSQL Advent Calendar 2025
mita2 さんのターン / MySQL Advent Calendar 2025
です。おたのしみに〜。


私のターンdone. では、また。

Enjoy SQLs and SQLの癖!





テスト用SQL生成スクリプト(Oracle Database 23ai)
このスクリプトでMySQL、PostgreSQL、Oracle Databaseそれぞれのテストスクリプトを出力する無名PL/SQLブロック

Oracle向けtvc確認SELECT文生成(65534行を生成する例)
e.g.

SQL> @make_tvc_sql0 65534 oracle
SQL> @sql_oracle_65534

make_tvc_sql0.sql

set feed off
set timi off
set head off
set termout off
set veri off
set trimspool on

set linesize 400
set pagesize 1000
SET SERVEROUTPUT ON
spool sql_&2._&1..sql
DECLARE
c_max_rows CONSTANT NUMBER := &1;
c_rvc_text_mysql CONSTANT CHAR(3) := 'ROW';
c_type_mysql CONSTANT CHAR(5) := 'MYSQL';
c_type CONSTANT VARCHAR2(10) := UPPER('&2');
BEGIN
DBMS_OUTPUT.PUT_LINE('SELECT * FROM ( VALUES');
FOR i IN 1..c_max_rows LOOP
DBMS_OUTPUT.PUT_LINE(
CASE WHEN i > 1 THEN ',' END
|| CASE WHEN c_type = c_type_mysql THEN c_rvc_text_mysql END
|| '(' || TO_CHAR(i)
|| ')'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE(') t1 ( id );');
END;
/
spool off
SET SERVEROUTPUT OFF
UNDEFINE 1
UNDEFINE 2


set head on
set termout on
set feed on
set veri on
set timi on
set trimspool off






関連エントリー
標準はあるにはあるが癖の多い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 - 実行計画でスカラー副問合せの見せ方にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #16 - FROM句のインラインビューのエイリアスにもクセがある(必須だったり、任意だったり)
帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - ANY_VALUE() ってなかなかいいじゃん、癖無さそう!
帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #19 - c_alias の癖(おまけ)

| | | コメント (0)

2025年8月22日 (金)

帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - ANY_VALUE() ってなかなかいいじゃん、癖無さそう!

さて、
今回は久々に、標準はあるにはあるが癖の多いSQLシリーズです! 

データが小さいとその価値はほぼわからないかもしれませんが、ひょんなところで出会ってしまった! と、いうような状況で役立つかもしれませんwwww

今日のお題は、ANY_VALUE()関数。 集約関数の仲間です:)

MySQLやOracle Databaseのマニュアルでは使い所を理解しやすい解説があります。おすすめです。
一方、PostgreSQLのマニュアルシンプルすぎる解説ゆえ、この関数はなに? なにが美味しいの? みたいな顔になってしまうかもしれません。がw、ググってみてください、いろいろ見つかります!

この関数に出会ってよかった! ということを思いながらw
以下の曲をBGMにして眺めてみてください:)

ラブ・ストーリーは突然に / 小田和正


少量のデータだとその良さに気づきにくいのですが、万が一の時は、ANY_VALUE()集約関数を思い出してみてください。
リソース消費は数が多くなるとボディーブローにはなるので、リソース使用量削減に重箱の隅をつつくようなことしないといけないとかw そんな時にも役立つかも。。。しれないです。


参考)

Oracle / ANY_VALUE() - 19cからサポートされました
https://docs.oracle.com/cd/F19136_01/sqlrf/any_value.html

MySQL / ANY_VALUE() - 5.7からサポートされました
https://dev.mysql.com/doc/refman/8.0/ja/miscellaneous-functions.html#function_any-value

PostgreSQL / ANY_VALUE() - 16からサポートされました
https://www.postgresql.jp/document/17/html/functions-aggregate.html


環境

HostOS : macOS Sonoma 14.7.7 (arm64)
VirtualBox 7.1 (arm64)

GuestOS : Oracle Linux 8u10 (arm64)
 Oracle Database 23ai 23.8 (arm64)
 PostgreSQL 17.5 (arm64)
 MySQL 8.4.6 (arm64)

テストケース

テストケース1)

集約する列データ長が長がーーーい

テストケース2)

集約する列データは短めでもデータ量が多いケース

の2つを用意しました。
データ量はどちらも多めにしました。理由は、集計関数やGROUP BYの性能差分はデータ量が少ないと差分が見にくいためです:)
これぐらいデータにして、やっと、ふむふむと頷ける差分が見えるのではないかと思います。

計測は3回実行しています(1回目には諸々ノイズが乗りやすいので参考程度にしています)

 

では、先に結果から。
全体的に ANY_VALUE()が軽めの傾向として出てきているのは間違いないと思います。あえてそういう目的で追加してきた関数ですし。MySQLやOracle DatabaseのマニュアルではANY_VALUE()集約関数についての解説もわかりやすいとおもいます。
ANY_VALUE()の用途が広く認知されれば、可読性向上という意味もきっちり出てきそうな気はします。(個人的にはw。今は微妙は感じを持っている方は多いと思いますが、非集約列をGROUP BY句に記述するのも、MIN/MAX集約関数を使うのも可読性という意味では微妙だと思っているので、そういう目的の関数の登場で方向は定まるのではないかと。。。。w)
PostgreSQLのマニュアルに目を向けると、他の関数の説明とのバランスもあると思うのでw、さらりと書かれていて、初めて見た方は、君は何? 
という感じになりそうではあるのですが、ググると結構情報も多くなってきたので何ものかを知るのに困ることもないと思います。

 

個別のまとめ

Oracle Database 23ai free

環境による差異は多少ありそうですが、GROUP BYで対処する場合とANY_VALUE()で対処する場合では、列サイズが長い場合にはANY_VALUE()の方が効果的に対処できそうですね。CPUに優しくなっています。
一方該当列の列サイズが比較的短い場合には、GROUP BY / ANY_VALUE()大きな差はでにくです。ANYU_VALUE()の認知度次第ですが、この手のハンドリングのための記述として認知度があがると、可読性としては向上しそうな気がします。

注)軽かった順に列挙してます。

テストケース1)

ANY_VALUE() -> GROUP BY句で対処 -> MAX()

テストケース2)

ANY_VALUE() -> GROUP BY句で対処 -> MAX()

 

PostgreSQL

PostgreSQLでは、やはり、ANY_VALUE()が早いですが、MAX()とかなり近い結果となり、GROUP BYが最も遅いという結果になりました。
面白い。

テストケース1)

ANY_VALUE() -> MAX() -> GROUP BY句で対処

テストケース2)

ANY_VALUE() -> MAX() -> GROUP BY句で対処

 

MySQL

MySQLではすべてが、Aggregate using temporary table となっていたので条件的には同じ状態で比較できた分わかりやすい結果になっていました。
MySQLでもこの手のケースでANY_VALUE()を利用しておいた方がお得でしょうね。

テストケース1)

ANY_VALUE() -> MAX() -> GROUP BY句で対処

テストケース2)

ANY_VALUE() -> MAX() -> GROUP BY句で対処

 


Oracle Databaseでの処理時間まとめ

SQLモニターを利用して取得.

列サイズ長め GROUP BY

Global Stats
==============================================================================
| Elapsed | Cpu | IO | Application | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
==============================================================================
| 0.67 | 0.56 | 0.11 | 0.00 | 2 | 250K | 1981 | 2GB |
| 0.62 | 0.53 | 0.09 | | 2 | 250K | 1981 | 2GB |
| 0.61 | 0.52 | 0.09 | | 2 | 250K | 1981 | 2GB |
==============================================================================

 

MAX

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 0.78 | 0.67 | 0.11 | 2 | 250K | 1981 | 2GB |
| 0.74 | 0.65 | 0.09 | 2 | 250K | 1981 | 2GB |
| 0.73 | 0.65 | 0.09 | 2 | 250K | 1981 | 2GB |
================================================================

 

ANY_VALUE

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 0.34 | 0.25 | 0.09 | 2 | 250K | 1981 | 2GB |
| 0.33 | 0.25 | 0.09 | 2 | 250K | 1981 | 2GB |
| 0.34 | 0.25 | 0.09 | 2 | 250K | 1981 | 2GB |
================================================================

 

列サイズ短めで件数が多い

GROUP BY

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.65 | 1.42 | 0.23 | 2 | 171K | 1363 | 1GB |
| 1.15 | 1.10 | 0.06 | 2 | 171K | 1363 | 1GB |
| 1.16 | 1.10 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

 

MAX

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.66 | 1.55 | 0.12 | 2 | 171K | 1363 | 1GB |
| 1.29 | 1.23 | 0.06 | 2 | 171K | 1363 | 1GB |
| 1.29 | 1.23 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

 

ANY_VALUE

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.47 | 1.38 | 0.10 | 2 | 171K | 1363 | 1GB |
| 1.15 | 1.09 | 0.06 | 2 | 171K | 1363 | 1GB |
| 1.15 | 1.09 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

 


PostgreSQL

PostgreSQLのwork_memが小さかった影響で、GROUP BYによる対処ではTemp落ちして一人負けしてました。設定チューニングしていたらMAX/ANY_VALUEに近い結果になっていたかもね。(Oracleみたいにデフォでいい感じってあれではなかった、しくじりw)
とはいえ、MAX()とANY_VALUE()の差があまりないのもPostgreSQLの特徴ですかね。

 

列サイズ長め

GROUP BY

 Execution Time: 3533.979 ms
Execution Time: 3570.032 ms
Execution Time: 3553.467 ms

 

MAX

 Execution Time: 439.917 ms
Execution Time: 434.463 ms
Execution Time: 434.348 ms

 

ANY_VALUE

 Execution Time: 461.668 ms
Execution Time: 444.132 ms
Execution Time: 431.056 ms

 

 

列サイズ短めで件数が多い

GROUP BY

 Execution Time: 4300.000 ms
Execution Time: 4635.630 ms
Execution Time: 4595.763 ms

 

MAX

 Execution Time: 4449.759 ms
Execution Time: 4449.591 ms
Execution Time: 4425.708 ms

 

ANY_VALUE

 Execution Time: 4240.994 ms
Execution Time: 4145.707 ms
Execution Time: 4018.328 ms

 


MySQL

列サイズ長め

GROUP BY

1 row in set (13.16 sec)
1 row in set (13.03 sec)
1 row in set (13.03 sec)

 

MAX

1 row in set (8.59 sec)
1 row in set (8.60 sec)
1 row in set (9.40 sec)

 

ANY_VALUE

1 row in set (0.49 sec)
1 row in set (0.43 sec)
1 row in set (0.49 sec)

 

列サイズ短めで件数が多い

GROUP BY

1 row in set (21.31 sec)
1 row in set (21.90 sec)
1 row in set (20.89 sec)

 

MAX

1 row in set (15.68 sec)
1 row in set (16.16 sec)
1 row in set (16.16 sec)

 

ANY_VALUE

1 row in set (8.35 sec)
1 row in set (8.54 sec)
1 row in set (8.20 sec)

 

ということで、

帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - ANY_VALUE() 、
癖があるとおもったのですが、癖はなかったですw

では、また。

 

朝晩の風が、あきっぽい、北のエリアより。。。夏祭りが終われば、あっというまに秋、、、になるはずw

Enjoy SQL! and RDBMS!

 



関連エントリー

標準はあるにはあるが癖の多い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 - 実行計画でスカラー副問合せの見せ方にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #16 - FROM句のインラインビューのエイリアスにもクセがある(必須だったり、任意だったり)

 



以下、興味のある方向けのログと今回適当に作ったデータ作成スクリプトなどを載せています。以降は長いので興味のない方は飛ばしてくださいww


ーーーーーーーログーーーーーーー

Oracle Database

-- 列サイズ長め(準備)
SCOTT@localhost:1521/freepdb1> @any_value.sql
1* DROP TABLE IF EXISTS any_value_table PURGE

表が削除されました。

経過: 00:00:00.20
1 CREATE TABLE any_value_table
2 (
3 ordered_date DATE NOT NULL
4 , order_id INTEGER NOT NULL
5 , product_id INTEGER NOT NULL
6 , product_name VARCHAR(2000) NOT NULL
7 , qty INTEGER NOT NULL
8 , CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
9* )

表が作成されました。

経過: 00:00:00.04
1 DECLARE
2 o_date DATE := SYSDATE;
3 BEGIN
4 FOR i IN 1..1000000 LOOP
5 INSERT INTO any_value_table
6 (ordered_date
7 , order_id
8 , product_id
9 , product_name
10 , qty
11 ) VALUES (o_date, i, 1, 'ITEM_1'||lpad('*',1600,'*'), 1);
12 IF mod(i,100) = 0 THEN commit; END IF;
13 END LOOP;
14* END;

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

経過: 00:02:58.25

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

経過: 00:00:42.26

SEGMENT_NAME GB
------------------------------ ----------
ANY_VALUE_TABLE 1.96582031

経過: 00:00:00.03

...中略...

-- 2回目
非集計列がGROUP BY句に定義もされず、集計関数も利用されていない場合は、エラーになることの確認!
SCOTT@localhost:1521/freepdb1> @any_value_test
1 SELECT
2 product_id
3 ,product_name
4 ,SUM(qty) AS total
5 FROM
6 any_value_table
7 GROUP BY
8* product_id
,product_name
*
行3でエラーが発生しました。:
ORA-00979: "PRODUCT_NAME": GROUP BY句に出現するか、集計関数で使用される必要があります
ヘルプ:
https://docs.oracle.com/error-help/db/ora-00979/

経過: 00:00:00.01
1 SELECT /*+ MONITOR */
2 product_id
3 ,product_name
4 ,SUM(qty) AS total
5 FROM
6 any_value_table
7 GROUP BY
8 product_id
9* , product_name

PRODUCT_ID PRODUCT_NAME                                                     TOTAL
---------- ----------------------------------------------------------------------------------------------------------- ----------
1 ITEM_1***************************************************************************************************** 1000000
***********************************************************************************************************

...中略...

***********************************************************************************************************
***********************************************************************************************************
*

経過: 00:00:00.58

...中略...

SQL Text
------------------------------
SELECT /*+ MONITOR */ product_id ,product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id , product_name

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 0.62 | 0.53 | 0.09 | 2 | 250K | 1981 | 2GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 69634 | 1 | +1 | 1 | 1 | | | 100.00 | Cpu (1) |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 1M | 69619 | 1 | +1 | 1 | 1M | 1981 | 2GB | | |
=====================================================================================================================================================

...中略...

SQL Text
------------------------------
SELECT /*+ MONITOR */ product_id ,MAX(product_name) AS product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 0.74 | 0.65 | 0.09 | 2 | 250K | 1981 | 2GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 69634 | 1 | +0 | 1 | 1 | | | | |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 1M | 69619 | 1 | +0 | 1 | 1M | 1981 | 2GB | | |
=====================================================================================================================================================

...中略...

SQL Text
------------------------------
SELECT /*+ MONITOR */ product_id ,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 0.33 | 0.25 | 0.09 | 2 | 250K | 1981 | 2GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 69634 | 1 | +1 | 1 | 1 | | | 100.00 | Cpu (1) |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 1M | 69619 | 1 | +1 | 1 | 1M | 1981 | 2GB | | |
=====================================================================================================================================================

...中略...

-- 列サイズ短めで件数が多い(準備)
SCOTT@localhost:1521/freepdb1> @any_value2.sql
1* DROP TABLE IF EXISTS any_value_table PURGE

表が削除されました。

経過: 00:00:00.13
1 CREATE TABLE any_value_table
2 (
3 ordered_date DATE NOT NULL
4 , order_id INTEGER NOT NULL
5 , product_id INTEGER NOT NULL
6 , product_name VARCHAR(2000) NOT NULL
7 , qty INTEGER NOT NULL
8 , CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
9* )

表が作成されました。

経過: 00:00:00.01
1 DECLARE
2 o_date DATE := SYSDATE;
3 BEGIN
4 FOR i IN 1..20000000 LOOP
5 INSERT INTO any_value_table
6 (ordered_date
7 , order_id
8 , product_id
9 , product_name
10 , qty
11 ) VALUES (o_date, i, 1, 'ITEM_1'||LPAD('*',30,'*'), 1);
12 IF mod(i,1000) = 0 THEN commit; END IF;
13 END LOOP;
14* END;

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

経過: 00:12:27.16

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

経過: 00:00:17.83

SEGMENT_NAME GB
------------------------------ ----------
ANY_VALUE_TABLE 1.3125

経過: 00:00:00.06

...中略...

-- 2回目
SCOTT@localhost:1521/freepdb1> @any_value_test2
1 SELECT /*+ monitor */
2 product_id
3 ,product_name
4 ,SUM(qty) AS total
5 FROM
6 any_value_table
7 GROUP BY
8 product_id
9* , product_name

PRODUCT_ID PRODUCT_NAME TOTAL
---------- ------------------------------------ ----------
1 ITEM_1****************************** 20000000

経過: 00:00:01.11

...中略...

SQL Text
------------------------------
SELECT /*+ monitor */ product_id ,product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id , product_name

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.15 | 1.10 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 46860 | 1 | +1 | 1 | 1 | | | | |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 20M | 46510 | 1 | +1 | 1 | 20M | 1363 | 1GB | 100.00 | Cpu (1) |
=====================================================================================================================================================

...中略...

SQL Text
------------------------------
SELECT /*+ monitor */ product_id ,MAX(product_name) AS product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.29 | 1.23 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +2 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 46860 | 1 | +2 | 1 | 1 | | | | |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 20M | 46510 | 2 | +1 | 1 | 20M | 1363 | 1GB | 100.00 | Cpu (1) |
=====================================================================================================================================================

...中略...

SQL Text
------------------------------
SELECT /*+ monitor */ product_id ,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total FROM any_value_table
GROUP BY product_id

...中略...

Global Stats
================================================================
| Elapsed | Cpu | IO | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
================================================================
| 1.15 | 1.09 | 0.06 | 2 | 171K | 1363 | 1GB |
================================================================

SQL Plan Monitoring Details (Plan Hash Value=3772843140)
==========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
==========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 1 | | | | |
| 1 | HASH GROUP BY | | 1 | 46860 | 1 | +1 | 1 | 1 | | | | |
| 2 | TABLE ACCESS FULL | ANY_VALUE_TABLE | 20M | 46510 | 2 | +0 | 1 | 20M | 1363 | 1GB | 100.00 | direct path read (1) |
==========================================================================================================================================================

 


PostgreSQL

-- 列サイズ長め(準備)
perftestdb=> \i ./any_value.sql
Timing is on.
DROP TABLE
Time: 7.248 ms
CREATE TABLE
Time: 3.827 ms
DO
Time: 24680.064 ms (00:24.680)
ANALYZE
Time: 104.675 ms
Timing is off.

...中略...

-- 2回目
PostgreSQLでも非集計列をGROUP BYに記述しないと、エラーになりますよね。
perftestdb=> \i ./any_value_test.sql
psql:any_value_test.sql:11: ERROR: column "any_value_table.product_name" must appear in the GROUP BY clause
or be used in an aggregate function
LINE 3: ,product_name
^


注)出力内容は見やすく加工しちゃってます
product_id | product_name | total
------------+-------------------------------------------------------------------------------------------------------------+--------
1 | ITEM_1***************************************************************************************************** | 1000000
***********************************************************************************************************
***********************************************************************************************************

...中略...

***********************************************************************************************************
***********************************************************************************************************
*
(1 row)


GROUP BYで対処したケースで、work_memセットし忘れてデフォのままだったので Temp落ちして一人負けしてました。すみません。
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=2471963.84..2491963.84 rows=1000000 width=1622) (actual time=3044.982..3044.983 rows=1 loops=1)
Output: product_id, product_name, sum(qty)
Group Key: any_value_table.product_id, any_value_table.product_name
Buffers: shared hit=250000, temp read=598111 written=598519
-> Sort (cost=2471963.84..2474463.84 rows=1000000 width=1618) (actual time=2479.915..2867.067 rows=1000000 loops=1)
Output: product_id, product_name, qty
Sort Key: any_value_table.product_id, any_value_table.product_name
Sort Method: external merge Disk: 1595032kB
Buffers: shared hit=250000, temp read=598111 written=598519
-> Seq Scan on scott.any_value_table (cost=0.00..260000.00 rows=1000000 width=1618) (actual time=0.012..216.822 rows=1000000 loops=1)
Output: product_id, product_name, qty
Buffers: shared hit=250000
Planning:
Buffers: shared hit=2
Memory: used=14kB allocated=16kB
Planning Time: 0.409 ms
Execution Time: 3570.032 ms
(17 rows)

...中略...

QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=267500.00..267500.01 rows=1 width=44) (actual time=434.445..434.445 rows=1 loops=1)
Output: product_id, max((product_name)::text), sum(qty)
Group Key: any_value_table.product_id
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=250000
-> Seq Scan on scott.any_value_table (cost=0.00..260000.00 rows=1000000 width=1618) (actual time=0.003..122.488 rows=1000000 loops=1)
Output: ordered_date, order_id, product_id, product_name, qty
Buffers: shared hit=250000
Planning:
Memory: used=14kB allocated=16kB
Planning Time: 0.324 ms
Execution Time: 434.463 ms
(12 rows)

...中略...

QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=267500.00..267500.01 rows=1 width=44) (actual time=444.114..444.114 rows=1 loops=1)
Output: product_id, any_value(product_name), sum(qty)
Group Key: any_value_table.product_id
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=250000
-> Seq Scan on scott.any_value_table (cost=0.00..260000.00 rows=1000000 width=1618) (actual time=0.003..138.239 rows=1000000 loops=1)
Output: ordered_date, order_id, product_id, product_name, qty
Buffers: shared hit=250000
Planning:
Memory: used=14kB allocated=16kB
Planning Time: 0.043 ms
Execution Time: 444.132 ms
(12 rows)

...中略...

-- 列サイズ短めで件数が多い(準備)
perftestdb=> \i ./any_value2.sql
Timing is on.
DROP TABLE
Time: 126.466 ms
CREATE TABLE
Time: 6.143 ms
DO
Time: 80158.605 ms (01:20.159)
ANALYZE
Time: 136.012 ms
Timing is off.
perftestdb=>

...中略...

-- 2回目
perftestdb=> \i ./any_value_test2.sql
SET
product_id | product_name | total
------------+--------------------------------------+----------
1 | ITEM_1****************************** | 20000000
(1 row)

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=556186.00..556186.01 rows=1 width=49) (actual time=4635.607..4635.608 rows=1 loops=1)
Output: product_id, product_name, sum(qty)
Group Key: any_value_table.product_id, any_value_table.product_name
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=206186
-> Seq Scan on scott.any_value_table (cost=0.00..406186.00 rows=20000000 width=45) (actual time=0.004..1041.109 rows=20000000 loops=1)
Output: ordered_date, order_id, product_id, product_name, qty
Buffers: shared hit=206186
Planning:
Buffers: shared hit=3
Memory: used=14kB allocated=16kB
Planning Time: 0.063 ms
Execution Time: 4635.630 ms
(13 rows)

...中略...

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=556186.00..556186.01 rows=1 width=44) (actual time=4449.572..4449.572 rows=1 loops=1)
Output: product_id, max((product_name)::text), sum(qty)
Group Key: any_value_table.product_id
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=206186
-> Seq Scan on scott.any_value_table (cost=0.00..406186.00 rows=20000000 width=45) (actual time=0.004..1007.065 rows=20000000 loops=1)
Output: ordered_date, order_id, product_id, product_name, qty
Buffers: shared hit=206186
Planning:
Memory: used=14kB allocated=16kB
Planning Time: 0.049 ms
Execution Time: 4449.591 ms
(12 rows)

...中略...

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=556186.00..556186.01 rows=1 width=44) (actual time=4145.688..4145.688 rows=1 loops=1)
Output: product_id, any_value(product_name), sum(qty)
Group Key: any_value_table.product_id
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=206186
-> Seq Scan on scott.any_value_table (cost=0.00..406186.00 rows=20000000 width=45) (actual time=0.005..1006.129 rows=20000000 loops=1)
Output: ordered_date, order_id, product_id, product_name, qty
Buffers: shared hit=206186
Planning:
Memory: used=14kB allocated=16kB
Planning Time: 0.050 ms
Execution Time: 4145.707 ms
(12 rows)

 

MySQL

-- 列サイズ長め(準備)
mysql> \. /home/master/any_value.sql
Query OK, 0 rows affected (0.03 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.05 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.05 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------+
| @@AUTOCOMMIT |
+--------------+
| 0 |
+--------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (2 min 38.38 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

+--------------+
| @@AUTOCOMMIT |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)

+----------------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------------------+---------+----------+----------+
| perftestdb.any_value_table | analyze | status | OK |
+----------------------------+---------+----------+----------+
1 row in set (0.01 sec)

...中略...

-- 2回目
MySQLでも今のリリースでは、非集計列をGROUP BY 句に記述しないとエラーになりますよね。
mysql> \. /home/master/any_value_test.sql
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column
'perftestdb.any_value_table.product_name' which is not functionally dependent on columns in GROUP BY clause;
this is incompatible with sql_mode=only_full_group_by


注)出力内容は見やすく加工しちゃってます
+------------+-------------------------------------------------------------------------------------------------------------+----------+
| product_id | product_name | total |
+------------+-------------------------------------------------------------------------------------------------------------+----------+
| 1 | ITEM_1***************************************************************************************************** | 20000000 |
| | *********************************************************************************************************** | |
| | *********************************************************************************************************** | |

...中略...

| | *********************************************************************************************************** | |
| | *********************************************************************************************************** | |
| | * | |
+------------+-------------------------------------------------------------------------------------------------------------+----------+
1 row in set (13.03 sec)

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=12979..12979 rows=1 loops=1)
-> Aggregate using temporary table (actual time=12979..12979 rows=1 loops=1)
-> Table scan on any_value_table (cost=116731 rows=888992) (actual time=0.0124..290 rows=1e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (12.98 sec)

...中略...

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=8620..8620 rows=1 loops=1)
-> Aggregate using temporary table (actual time=8620..8620 rows=1 loops=1)
-> Table scan on any_value_table (cost=116731 rows=888992) (actual time=0.0136..288 rows=1e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (8.62 sec)

...中略...

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=492..492 rows=1 loops=1)
-> Aggregate using temporary table (actual time=492..492 rows=1 loops=1)
-> Table scan on any_value_table (cost=116731 rows=888992) (actual time=0.0163..258 rows=1e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (0.49 sec)

...中略...


-- 列サイズ短めで件数が多い(準備)
mysql> \. /home/master/any_value2.sql
Query OK, 0 rows affected (0.03 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.04 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------+
| @@AUTOCOMMIT |
+--------------+
| 0 |
+--------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (9 min 11.20 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------+
| @@AUTOCOMMIT |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)

+----------------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------------------+---------+----------+----------+
| perftestdb.any_value_table | analyze | status | OK |
+----------------------------+---------+----------+----------+
1 row in set (0.03 sec)

...中略...

-- 2回目
mysql> \. /home/master/any_value_test2.sql
+------------+--------------------------------------+----------+
| product_id | product_name | total |
+------------+--------------------------------------+----------+
| 1 | ITEM_1****************************** | 20000000 |
+------------+--------------------------------------+----------+
1 row in set (21.90 sec)

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=22964..22964 rows=1 loops=1)
-> Aggregate using temporary table (actual time=22964..22964 rows=1 loops=1)
-> Table scan on any_value_table (cost=2.01e+6 rows=19.9e+6) (actual time=0.0134..4447 rows=20e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (22.97 sec)

...中略...

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=17478..17478 rows=1 loops=1)
-> Aggregate using temporary table (actual time=17478..17478 rows=1 loops=1)
-> Table scan on any_value_table (cost=2.01e+6 rows=19.9e+6) (actual time=0.0115..4399 rows=20e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (17.48 sec)

...中略...

+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (actual time=9273..9273 rows=1 loops=1)
-> Aggregate using temporary table (actual time=9273..9273 rows=1 loops=1)
-> Table scan on any_value_table (cost=2.01e+6 rows=19.9e+6) (actual time=0.0116..4208 rows=20e+6 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
1 row in set (9.28 sec)

 



-------------------- Scripts ----------------------

Oracle Database

列長の長いテストケース準備

any_value.sql

DROP TABLE IF EXISTS any_value_table PURGE
.
l
/

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
)
.
l
/

DECLARE
o_date DATE := SYSDATE;
BEGIN
FOR i IN 1..1000000 LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, 'ITEM_1'||lpad('*',1600,'*'), 1);
IF mod(i,100) = 0 THEN commit; END IF;
END LOOP;
END;
.
l
/

EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'any_value_table',cascade=>true,no_invalidate=>false);
select segment_name,bytes/1024/1024/1024 "GB" from user_segments where segment_name = upper('any_value_table');

 

列長の長いケースのテスト(エラーになるSQL含む)

any_value_test.sql

SET LINESIZE 300
SET PAGESIZE 1000
SET LONGCHUNK 1000
SET LONG 100000

-- error --
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
.
l
/

SELECT /*+ monitor */
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
.
l
/
select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');

SELECT /*+ monitor */
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
.
l
/
select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');


SELECT /*+ monitor */
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
.
l
/
select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');

 

 

列サイズ短めで件数が多いテストケースの準備

any_value2.sql


DROP TABLE IF EXISTS any_value_table PURGE
.
l
/

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
)
.
l
/


DECLARE
o_date DATE := SYSDATE;
BEGIN
FOR i IN 1..20000000 LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, 'ITEM_1'||LPAD('*',30,'*'), 1);
IF mod(i,1000) = 0 THEN commit; END IF;v END LOOP;
END;
.
l
/

col product_name for a30
EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'any_value_table',cascade=>true,no_invalidate=>false);
select segment_name,bytes/1024/1024/1024 "GB" from user_segments where segment_name = upper('any_value_table');

 

列サイズ短めで件数が多いテストケースの準備

any_value_test2.sql

SET LINESIZE 300
SET PAGESIZE 1000
SET LONGCHUNK 1000
SET LONG 100000


SELECT /*+ monitor */
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
.
l
/

select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');

SELECT /*+ monitor */
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
.
l
/

select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');

SELECT /*+ monitor */
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
.
l
/
select DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT');

 


PostgreSQL

 

列長の長いテストケース準備

any_value.sql

\timing
DROP TABLE IF EXISTS any_value_table;

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
);

DO $$
DECLARE
o_date DATE := CURRENT_DATE;
BEGIN
FOR i IN 1..1000000 LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, 'ITEM_1'||lpad('*',1600,'*'), 1);
IF mod(i,100) = 0 THEN
COMMIT;
END IF;
END LOOP;
END
$$;

analyze any_value_table;
\timing

 

列長の長いテストケース(エラーケース含む)
any_value_test.sql

-- error --
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

set max_parallel_workers_per_gather = 0;
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
;

explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
;

SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

 

列サイズ短めで件数が多いテストケースの準備

any_value2.sql

\timing
DROP TABLE IF EXISTS any_value_table;

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
);

DO $$
DECLARE
o_date DATE := CURRENT_DATE;
BEGIN
FOR i IN 1..20000000 LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, 'ITEM_1'||LPAD('*',30,'*'), 1);
IF mod(i,1000) = 0 THEN commit; END IF;
END LOOP;
END
$$
;

analyze any_value_table;
\timing

 

列サイズ短めで件数が多いテストケース

any_value_test2.sql

set max_parallel_workers_per_gather = 0;

SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name;

explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name;


SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;


explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;


SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;

explain (analyze,buffers,memory,summary,verbose)
SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;

 

 


MySQL

 

列長の長いテストケース準備

any_value.sql

DROP TABLE IF EXISTS any_value_table;
show warnings;

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
);
show warnings;

DROP PROCEDURE IF EXISTS make_any_table_data;
show warnings;

DELIMITER $$
CREATE PROCEDURE make_any_table_data()
BEGIN
DECLARE o_date DATE DEFAULT CURRENT_DATE;
DECLARE i INTEGER DEFAULT 1;
DECLARE r_count INTEGER DEFAULT 1000000;
loop1: LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, CONCAT('ITEM_1', lpad('*',1600,'*')), 1);
IF mod(i,100) = 0 THEN commit; END IF;
SET i = i + 1;
IF i > r_count THEN LEAVE loop1; END IF;
END LOOP loop1;
END
$$
DELIMITER ;

set AUTOCOMMIT=0;
select @@AUTOCOMMIT;

CALL make_any_table_data;
show warnings;

set AUTOCOMMIT=1;
select @@AUTOCOMMIT;

analyze table any_value_table;

 

列長の長いテストケース(エラーケース含む)

any_value_test.sql

-- error --
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;


SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
;

explain analyze format=tree
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name
;


SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

explain analyze format=tree
SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;


explain analyze format=tree
SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
;

 

列サイズ短めで件数が多いテストケース(準備)

any_value2.sql

DROP TABLE IF EXISTS any_value_table;
show warnings;

CREATE TABLE any_value_table
(
ordered_date DATE NOT NULL
, order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
, product_name VARCHAR(2000) NOT NULL
, qty INTEGER NOT NULL
, CONSTRAINT pk_any_value_table PRIMARY KEY (order_id, product_id, ordered_date)
);
show warnings;

DROP PROCEDURE IF EXISTS make_any_table_data;
show warnings;

DELIMITER $$
CREATE PROCEDURE make_any_table_data()
BEGIN
DECLARE o_date DATE DEFAULT CURRENT_DATE;
DECLARE i INTEGER DEFAULT 1;
DECLARE r_count INTEGER DEFAULT 20000000;
loop1: LOOP
INSERT INTO any_value_table
(ordered_date
, order_id
, product_id
, product_name
, qty
) VALUES (o_date, i, 1, CONCAT('ITEM_1', LPAD('*',30,'*')), 1);
IF mod(i,1000) = 0 THEN commit; END IF;
SET i = i + 1;
IF i > r_count THEN LEAVE loop1; END IF;
END LOOP loop1;
END
$$
DELIMITER ;

set AUTOCOMMIT=0;
select @@AUTOCOMMIT;

CALL make_any_table_data;
show warnings;

set AUTOCOMMIT=1;
select @@AUTOCOMMIT;

analyze table any_value_table;

 

列サイズ短めで件数が多いテストケース

any_value_test2.sql

SELECT  
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name;

explain analyze format=tree
SELECT
product_id
,product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id
, product_name;

SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;


explain analyze format=tree
SELECT
product_id
,MAX(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;


SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;

explain analyze format=tree
SELECT
product_id
,ANY_VALUE(product_name) AS product_name
,SUM(qty) AS total
FROM
any_value_table
GROUP BY
product_id;

 

| | | コメント (0)

2025年7月 4日 (金)

実行計画は, SQL文のレントゲン写真だ! No.67 / AI Vector Search - VECTOR INDEX HNSW SCAN のバリエーション

Previously on Mac De Oracle
前回は、VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(完結編)でした。
今日は、再びレントゲン写真に戻り、VECTOR INDEX HNSW SCAN のバリエーションをいくつか確認しておこうと思います。
(なかなか興味深いので、一度診ておけば、いざというときに慌てなくて済むと思います)

 

いきなりってのもあれなので、先に以下のマニュアルを一読しておくと良いと思います。マニュアルでもポイントが解説されているネタなので:) 解説しないとちょっと分かりずらい点が多いからだと思いますがw

Oracle Database 23ai / Oracle AI Vector Search ユーザーズ・ガイド / HNSW ベクトルインデックスのオプティマイザプランのバリエーションを解説している章があります。なかなか興味深い。
おそらく、表には積極的に登場してこない補助表が、突然実行計画に現れることへの戸惑いと実行計画の読み方にちょっとした癖がある点の緩和と実行計画のバリエーションごとのメリデメを理解してもらうためにも解説が必要だったのだろうなぁ。と想像。
Oracle Database / Release 23 / Oracle AI Vector Search User's Guide / Optimizer Plans for HNSW Vector Indexes

INMEMORYな索引なのに、OperationにINMEMORYというキーワードが無くて、おや? と違和感があったり、ちょっとめんどくさい癖があるなぁと。。。w

まずは、
vector_index_neighbor_graph_reloadパラメータはCDBレベルで restart に設定した ( Oracle Database 23ai 23.6以降はデフォルトが restart になっています ) ので再起動しても vector index (HNSW) はポピュレートされメモリー上に復活しているはず。。。という確認から。

[oracle@localhost ~]$ sudo service oracle stop
[sudo] oracle のパスワード:
Stopping oracle (via systemctl):
[ OK ]
[oracle@localhost ~]$
[oracle@localhost ~]$ sudo service oracle start
Starting oracle (via systemctl):
[ OK ]
[oracle@localhost ~]$
[oracle@localhost ~]$ sqlplus scott@localhost:1521/freepdb1

...略...

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05
に接続されました。
SCOTT@localhost:1521/freepdb1> @show_vector_mem_pool

POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS
-------------------------- ----------- ---------- --------------------------
1MB POOL 369098752 236978176 DONE
64KB POOL 150994944 2686976 DONE
IM POOL METADATA 16777216 16777216 DONE

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> @show_vector_segments

OBJ MEMBYTES
---------- ----------
0 131072
80126 239534080

経過: 00:00:00.02

 

それぞれの実行計画のpros/consはマニュアルで読んでもらうとして、
VECTOR INDEX HNSW SCAN PRE-FILTER WITH JOIN BACKから診てみましょう。

 

JOIN BACKするかしないかの違いは最後にもう一度ベース表をアクセスするかどうか。(次の実行計画で言うと、Id=5でSEARCH_DATAをtable access by index rowidでアクセスしている箇所がJOIN BACK)
これらのバリエーションはデータ量とフィルタリング量との兼ね合いになるのでベクトル索引で近傍検索の実行計画をヒントで固定化するのは比較的難易度が高そう(どちらの傾向に固定した方が良いかの判断は難しい)だろうな、と思っているところ。固定できるか、したほうが良いかの見極めというか、割り切りなのかもしれないが決め打ちするだけの情報は揃えた上で決める必要はりそう。最初は経過観察なのが良いだろうと思っているところだが。。。とはいえ、覚えていて損ないかなぁ。

また、これらのバリエーションでは、VECTOR INDEX (HNSW)以外に、補助表の主役であるMAP表が登場します(Id=9の部分)。
VECTOR INDEX (HNSW)本体だけでなく補助表の存在も把握しておくことが大切な理由はここにもあります。(ベース表に比べるとサイズは小さいわけですが)

後半で別途まとめますが、この実行では内部ビューが新たに作られています。
Id=7の VW_HPJ_91CF1FF7 がそれです。内部的に作成されるビューにはそれぞれのトランスフォームに関連する短縮名が付与されるのが、これまでのOracle Databaseのオプティマイザのお約束ですね。
VW_HPJ_、 Hnsw scan Pre-filter with Join back -> HPJ になりそうですよね。 VW_HPJ_という内部ビューをみたらVECTOR_INDEX_TRANSFORM VECTOR INDEX HNSW SCAN PRE_FILTER WITH JOIN BACKが行われていると考えてよいでしょうね。

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_with_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


Plan hash value: 3994424349

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | | 6649 (100)| | 20 |00:00:00.93 | 412 | 410 | | | |
| 1 | VIEW | | 1 | 1 | 157 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | | 20 |00:00:00.93 | 412 | 410 | | | |
| 3 | VIEW | | 1 | 1 | 157 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 1 | 1665 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | 4096 | 4096 | 4096 (0)|
|* 5 | TABLE ACCESS BY INDEX ROWID | SEARCH_DATA | 1 | 1 | 1665 | | 6648 (1)| 00:00:01 | 20 |00:00:00.94 | 412 | 410 | | | |
| 6 | VECTOR INDEX HNSW SCAN PRE-FILTER| SEARCH_DATA_HNSW_IX | 1 | 1 | 1665 | | 6648 (1)| 00:00:01 | 20 |00:00:00.93 | 392 | 390 | | | |
| 7 | VIEW | VW_HPJ_91CF1FF7 | 1 | 6235 | 152K| | 6647 (1)| 00:00:01 | 6235 |00:00:00.06 | 392 | 390 | | | |
|* 8 | HASH JOIN RIGHT OUTER | | 1 | 6235 | 9M| 3296K| 6647 (1)| 00:00:01 | 6235 |00:00:00.06 | 392 | 390 | 8506K| 2096K| 9004K (0)|
| 9 | TABLE ACCESS FULL | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 1 | 125K| 1831K| | 102 (0)| 00:00:01 | 125K|00:00:00.01 | 373 | 371 | | | |
|* 10 | INDEX RANGE SCAN | SEARCH_DATA_COMMNITY_IX | 1 | 6235 | | | 23 (0)| 00:00:01 | 6235 |00:00:00.01 | 19 | 19 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$7185E227 / "from$_subquery$_003"@"SEL$3"
4 - SEL$7185E227
5 - SEL$7185E227 / "SEARCH_DATA"@"SEL$2"
6 - SEL$7185E227 / "SEARCH_DATA"@"SEL$2"
7 - SEL$BF33016E / "VW_HPJ_91CF1FF7"@"SEL$2"
8 - SEL$BF33016E
9 - SEL$BF33016E / "VTIX_RIDVID"@"SEL$2"
10 - SEL$BF33016E / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
5 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')
8 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")
10 - access("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

10 - SEL$BF33016E / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_with_join_back)

 

次に、VECTOR INDEX HNSW SCAN PRE-FILTER WITHOUT JOIN BACKを診てみましょう。
違いは、JOIN BACKがないところですよね、ヒントの通りw。
実行計画のId=5でVECTOR INDEX HNSW SCAN PRE_FILTERが行われていますが、その後でベース表を再度アクセスすることはありません。
なお、今回の検索パターンだと join back したほうが多少軽めですよね。んーーー難しい。普段はオプティマイザにお任せのほうが良いかもなと思うわけです。よほどのことがない限り。はい。

ここでも、新たな内部ビュー、 VW_HPF_475999B9 が作成されています。 VW_HPF_、 Hnsw scan Pre-Filter without join back -> HPF でしょうか。ちょっとムズイw VW_HPF_内部ビューを見つけたら、VECTOR INDEX HNSW SCAN PRE-FILTER WITHOUT JOIN BACKが行われてると理解して良さそうです。

このケースでも補助表であるMAP表が登場します。なにこれ? 俺は作ってないぞ! と驚かないようにしてくださいねw。(当ブログを読んだ方は驚くことはないはずですがw)
また、マニュアルにも記載されていますが、Hash join だけでなく Nested Loops Joinになることもあります。データ量と索引有無次第ではありますが、覚えておくと良いでしょう。(この例では Apaptive Planが選択されているため、どちらの結合方式になるかは、Join Cardinarity次第です)

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_without_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 5998 (100)| | 20 |00:00:02.77 | 5929 | 5904 | | | |
| 1 | VIEW | | 1 | 20 | 3140 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | | | |
| * 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:02.77 | 5929 | 5904 | | | |
| 3 | VIEW | | 1 | 20 | 3140 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | | | |
| * 4 | SORT ORDER BY STOPKEY | | 1 | 20 | 35220 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | 4096 | 4096 | 4096 (0)|
| 5 | VECTOR INDEX HNSW SCAN PRE-FILTER | SEARCH_DATA_HNSW_IX | 1 | 20 | 35220 | 5997 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | 1278K| 1076K| 1175K (0)|
| 6 | VIEW | VW_HPF_475999B9 | 1 | 6235 | 1071K| 5996 (1)| 00:00:01 | 6235 |00:00:02.13 | 5914 | 5904 | | | |
| * 7 | HASH JOIN OUTER | | 1 | 6235 | 499K| 5996 (1)| 00:00:01 | 6235 |00:00:02.13 | 5914 | 5904 | 1448K| 1287K| 1856K (0)|
|- 8 | NESTED LOOPS OUTER | | 1 | 6235 | 499K| 5996 (1)| 00:00:01 | 6235 |00:00:10.37 | 5540 | 5533 | | | |
|- 9 | STATISTICS COLLECTOR | | 1 | | | | | 6235 |00:00:10.36 | 5540 | 5533 | | | |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED| SEARCH_DATA | 1 | 6235 | 407K| 5893 (1)| 00:00:01 | 6235 |00:00:10.36 | 5540 | 5533 | | | |
| * 11 | INDEX RANGE SCAN | SEARCH_DATA_COMMNITY_IX | 1 | 6235 | | 23 (0)| 00:00:01 | 6235 |00:00:00.01 | 19 | 19 | | | |
|- 12 | TABLE ACCESS BY INDEX ROWID | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 0 | 1 | 15 | 102 (0)| 00:00:01 | 0 |00:00:00.01 | 0 | 0 | | | |
|- * 13 | INDEX UNIQUE SCAN | SYS_C0013920 | 0 | | | | | 0 |00:00:00.01 | 0 | 0 | | | |
| 14 | TABLE ACCESS FULL | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 1 | 125K| 1831K| 102 (0)| 00:00:01 | 125K|00:00:00.03 | 374 | 371 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$2D1A9934 / "from$_subquery$_003"@"SEL$3"
4 - SEL$2D1A9934
5 - SEL$2D1A9934 / "SEARCH_DATA"@"SEL$2"
6 - SEL$6D23FDEA / "VW_HPF_475999B9"@"SEL$475999B9"
7 - SEL$6D23FDEA
8 - SEL$6D23FDEA
10 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
11 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
12 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"
13 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"
14 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
7 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")
11 - access("SEARCH_DATA"."COMMUNITY"='AUSTIN')
13 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

10 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_without_join_back)

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

 

次は、VECTOR INDEX TRANSFORM IN-FILTER WITH JOIN BACK これまでの PRE-FILTERではなく、IN-FILTER + JOIN BACKをヒントで強制しています

なお、マニュアルにも記載がありますが、In-filterの実行計画の読み方は少々癖があります!

Id=8の元表のアクセスを見てください!。いきなり、TABLE ACCESS BY USER ROWID で SEARCH_DATA 表をアクセスしています!!!
この実行計画の開始ポイントは、Id=8ではなく、Id=6の VECTOR INDEX HNSW SCAN IN-FILTER で、VECTOR INDEX (HNSW)である SEARCH_DATA_HNSW_IX 索引をトラバースしている部分です:)
Id=6で識別されたベクトルごとに、Id=8の元表に対応するrowidのフィルタが適用され、関連する列が抽出されます!!!!!!!

VECTOR INDEX TRANSFORMの時の実行計画の読み方は正しく覚えないと軽くハマりそうですねw 少々癖があるので覚えるしかないですよ!w(ここも試験にでるよ!!w しらんけど)

最後に、Id=5でJOIN BACKして、COUNT STOPKEYの操作へ遷移していきます!!!!

また、ここでも内部生成の新たなビューが登場しています。
VW_HIJ_ ですね。 HIJ -> Hnsw scan In-filter with Join back ということでしょうね。想像するに。 

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_with_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 3 (100)| | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 1 | VIEW | | 1 | 1 | 157 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 3 | VIEW | | 1 | 1 | 157 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 1 | 1652 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | 4096 | 4096 | 4096 (0)|
|* 5 | TABLE ACCESS BY INDEX ROWID | SEARCH_DATA | 1 | 1 | 1652 | 2 (0)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 6 | VECTOR INDEX HNSW SCAN IN-FILTER| SEARCH_DATA_HNSW_IX | 1 | 1 | 1652 | 2 (0)| 00:00:01 | 20 |00:00:02.28 | 4705 | 4423 | | | |
| 7 | VIEW | VW_HIJ_475999B9 | 4773 | 1 | | 1 (0)| 00:00:01 | 265 |00:00:01.59 | 4705 | 4423 | | | |
|* 8 | TABLE ACCESS BY USER ROWID | SEARCH_DATA | 4773 | 1 | 1652 | 1 (0)| 00:00:01 | 265 |00:00:01.59 | 4705 | 4423 | | | |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$79710E8E / "from$_subquery$_003"@"SEL$3"
4 - SEL$79710E8E
5 - SEL$79710E8E / "SEARCH_DATA"@"SEL$2"
6 - SEL$79710E8E / "SEARCH_DATA"@"SEL$2"
7 - SEL$860F096D / "VW_HIJ_475999B9"@"SEL$2"
8 - SEL$860F096D / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
5 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')
8 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

8 - SEL$860F096D / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_with_join_back)

 

最後に、VECTOR INDEX TRANSFORM IN-FILTER WITHOUT JOIN BACK In-filtewrでJoin Backなしというタイプです。

このタイプがもっとも無駄がなさそうですね。今回の例で使っている検索パターンだと。。。:)

これもマニュアルの記述されているとおり、実行計画の開始位置にクセがあります。
Id=5の VECTOR INDEX HNSW SCAN IN-FILTER で、VECTOR INDEX (HNSW) をトラバースするところがスタートです。
次に、Id=7のベース表をVECTOR INDEXから取得したrowidでアクセス。
その後、COUNT STOPKEYの操作へ入ります。

そして、ここでも新顔の内部ビュー、 VW_HIF_ -> Hnsw scan In-Filter with join back ということで、 HIFになっていると思われます:)

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_without_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 3 (100)| | 20 |00:00:00.52 | 4705 | 733 | | | |
| 1 | VIEW | | 1 | 20 | 3140 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:00.52 | 4705 | 733 | | | |
| 3 | VIEW | | 1 | 20 | 3140 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 20 | 34960 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | 4096 | 4096 | 4096 (0)|
| 5 | VECTOR INDEX HNSW SCAN IN-FILTER| SEARCH_DATA_HNSW_IX | 1 | 20 | 34960 | 2 (0)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | 835K| 835K| 543K (0)|
| 6 | VIEW | VW_HIF_475999B9 | 4773 | 1 | 151 | 1 (0)| 00:00:01 | 265 |00:00:00.02 | 4705 | 733 | | | |
|* 7 | TABLE ACCESS BY USER ROWID | SEARCH_DATA | 4773 | 1 | 67 | 1 (0)| 00:00:01 | 265 |00:00:00.02 | 4705 | 733 | | | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$81BAFB36 / "from$_subquery$_003"@"SEL$3"
4 - SEL$81BAFB36
5 - SEL$81BAFB36 / "SEARCH_DATA"@"SEL$2"
6 - SEL$066A4CD4 / "VW_HIF_475999B9"@"SEL$475999B9"
7 - SEL$066A4CD4 / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
7 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

7 - SEL$066A4CD4 / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_without_join_back)

 

まとめ VECTOR INDEX TRANSFORM の特徴

・VECTOR INDEX (HNSW)以外に、補助表であるMAP表が使われる(場合によってはその索引も)
・In-filter時の実行計画開始の開始ポイントに癖があるので要注意
・内部で生成されるビューがバリエーション分増加した

 

最後に、今回新たに登場した内部ビューと、これまでに把握されている内部ビューのまとめ。

VECTOR INDEX TRANSFORM

VW_HPJ_ / Hnsw scan Pre-filter with Join back
VW_HPF_ / Hnsw scan Pre-Filter with join back
VW_HIJ_ / Hnsw scan In-filter with Join back
VW_HIF_ / Hnsw scan In-Filter with join back

 

上記に加え以前からいくつかメジャーな内部生成ビューがまとめられています。覚えておくとなにが行われているか分かり易いと思いますよ。
Internal Views / Oracle Scratchpad / Jonathan Lewis

 

では、また!

 

Enjoin Execution Plans, SQLs, and AI Vector Search!

 

 


Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ!

実行計画は, 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
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)
実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

 

| | | コメント (0)

2025年6月18日 (水)

VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(後編)

Previously on Mac De Oracle
前回は、VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編) でした。
今日はその後編です。

10046トレースよりVECTOR INDEX(HNSW)作成では前回の通り、補助表3つ(うち2つは変更をトラックするためなので作成直後は空)、それらの表に加え、各1つの索引が作成されるということがハッキリしました。
ただ、VECTOR INDEXの補助表はvecsys.vector$indexという表にJSONとして保持されている点と、USER_INDEXESから確認できるVECTOR INDEX自体は実態を持たない、さらに、Oraclerにはお馴染みの*_INDEXESなどから単純に取得できないことも見えてきました(今後もっと便利になることを期待したいですね)

(2025/6/18追記)
これ、HNSWってインメモリーと言っても、元ネタは永続化されているわけで、それをメモリープールにポピュレートしてるって理解(間違ってないとは思うけど、違ってたらコメントもらえるとありがたいです)なので、後編ではそのあたりを見ておこうかと。。。
インメモリー近傍グラフ・ベクトル索引

(作成される補助表の名称にVECTOR INDEXのベース表の名称が入っているようなので中間一致で検索していますが、これだとノイズも多くなるので検索キツイですよね。索引名はシステム生成名称なのでベース表の名称で中間一致検索はできません!。このケースに限ればなんとか拾えてるけどという感じではありますね。。。ということで後半ではJSONから引っこ抜いてなんとかするパズルもwやってますw)

SCOTT@localhost:1521/freepdb1> select index_name , table_name, index_type from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME INDEX_TYPE
------------------------------ -------------------------------------------------------------------------------- ---------------------------
SYS_IL0000079525C00007$$ VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG LOB
SYS_IL0000078074C00009$$ SEARCH_DATA LOB
SEARCH_DATA_HNSW_IX SEARCH_DATA VECTOR
SYS_C0013800 VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP NORMAL
PK_XID_79519 VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS NORMAL

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 idx_name
3 ,(
4 SELECT
5 table_name
6 FROM
7 user_tables
8 WHERE
9 user_tables.table_name = (
10 SELECT
11 object_name
12 FROM
13 user_objects
14 WHERE
15 user_objects.object_id = vecsys.vector$index.idx_base_table_objn
16 )
17 ) AS tab_name
18 ,JSON_SERIALIZE(
19 idx_auxiliary_tables
20 RETURNING VARCHAR2 PRETTY
21 ) AS idx_auxiliary_tables
22 FROM
23 vecsys.vector$index
24 WHERE
25* idx_name = 'SEARCH_DATA_HNSW_IX'

IDX_NAME TAB_NAME IDX_AUXILIARY_TABLES
------------------------------ ------------------------------ ----------------------------------------------------------------------------------------------------------------------------------
SEARCH_DATA_HNSW_IX SEARCH_DATA {
"rowid_vid_map_objn" : 79520,
"shared_journal_transaction_commits_objn" : 79522,
"shared_journal_change_log_objn" : 79525,
"rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP",
"shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
}

 

これまでの調査の結果、VECTOR INEDEX (HNSW) であるSEARCH_DATA_HNSW_IXはオブジェクトでもなく、セグメントもない(当然、シノニムにもなれない。仮にも索引ですから)
VECTOR INDEX (HNSW)の索引セグメントのサイズは、作成直後のIDX_AUXILIARY_TABLESと、索引があればそれら索引のセグメントサイズの合計ということになりますよね!!!
(やっと見えてきたw、なんなんだこれ)

 

こんな感じでJSONからIDX_AUXILIARY_TABLESにある関連表のオブジェクトID、または、テーブル名を取り出しUSER_INDEXESから索引も合わせて取得したうえで、それぞれのセグメントサイズの合計を取得すれば物理的なサイズは見えますよね。。。。(めんどくさいw)

SCOTT@localhost:1521/freepdb1> l
1 WITH
2 vector_idx_auxiliary_tables
3 AS (
4 SELECT
5 idx_name AS vector_index_name
6 , REPLACE(aux_table_name,'"','') AS aux_table_name
7 FROM
8 (
9 SELECT
10 vvi.idx_name AS idx_name
11 ,vvi.idx_auxiliary_tables.rowid_vid_map_name
12 ,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
13 ,vvi.idx_auxiliary_tables.shared_journal_change_log_name
14 FROM
15 vecsys.vector$index vvi
16 ) objs
17 UNPIVOT (
18 aux_table_name FOR related_obj_name IN
19 (
20 rowid_vid_map_name
21 , shared_journal_transaction_commits_name
22 , shared_journal_change_log_name
23 )
24 )
25 )
26 SELECT
27 aux_table_name AS segment_name
28 FROM
29 vector_idx_auxiliary_tables
30 WHERE
31 vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
32 UNION ALL
33 SELECT
34 index_name AS segment_name
35 FROM
36 user_indexes
37 WHERE
38 EXISTS
39 (
40 SELECT
41 1
42 FROM
43 vector_idx_auxiliary_tables
44 WHERE
45 vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
46 AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
47 )
48*
SCOTT@localhost:1521/freepdb1> /

SEGMENT_NAME
---------------------------------------------------------------------------------------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG
SYS_C0013800
PK_XID_79519
SYS_IL0000079525C00007$$

6行が選択されました。

経過: 00:00:00.16

 

では、あらためて、EXPLAIN PLAN FOR CREATE VECTOR INDEX...の見積もりサイズと、実際のサイズを比較してみましょう!

SCOTT@localhost:1521/freepdb1> l
1 explain plan for
2 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
3 ORGANIZATION
4 INMEMORY NEIGHBOR GRAPH
5 DISTANCE COSINE
6* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

解析されました。

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Plan hash value: 2727344110

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | CREATE INDEX STATEMENT | | 125K| 188M| 14689 (1)| 00:00:01 |
| 1 | VECTOR INDEX BUILD | SEARCH_DATA_HNSW_IX | | | | |
----------------------------------------------------------------------------------------------

Note
-----
- estimated index size: 293M bytes



なんどかコメントしてますが、SEARCH_DATA_HNSW_IXという索引セグメントは無く!、複数の補助表と索引群から構成されている。それらの作成直後のサイズは....

SCOTT@localhost:1521/freepdb1> r
1 WITH
2 vector_idx_auxiliary_tables
3 AS (
4 SELECT
5 idx_name AS vector_index_name
6 , REPLACE(aux_table_name,'"','') AS aux_table_name
7 FROM
8 (
9 SELECT
10 vvi.idx_name AS idx_name
11 ,vvi.idx_auxiliary_tables.rowid_vid_map_name
12 ,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
13 ,vvi.idx_auxiliary_tables.shared_journal_change_log_name
14 FROM
15 vecsys.vector$index vvi
16 ) objs
17 UNPIVOT (
18 aux_table_name FOR related_obj_name IN
19 (
20 rowid_vid_map_name
21 , shared_journal_transaction_commits_name
22 , shared_journal_change_log_name
23 )
24 )
25 )
26 SELECT
27 user_segments.segment_name
28 , user_segments.segment_type
29 , user_segments.bytes / 1024 / 1024 AS "MB"
30 FROM
31 user_segments
32 INNER JOIN
33 (
34 SELECT
35 aux_table_name AS segment_name
36 FROM
37 vector_idx_auxiliary_tables
38 WHERE
39 vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
40 UNION ALL
41 SELECT
42 index_name AS segment_name
43 FROM
44 user_indexes
45 WHERE
46 EXISTS
47 (
48 SELECT
49 1
50 FROM
51 vector_idx_auxiliary_tables
52 WHERE
53 vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
54 AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
55 )
56 ) vector_idx_aux_objects
57 ON
58 vector_idx_aux_objects.segment_name = user_segments.segment_name
59*

SEGMENT_NAME SEGMENT_TYPE MB
-------------------------------------------------------------------------------- ------------------ ----------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP TABLE 3
SYS_C0013800 INDEX 6

 

誤差が大きいですよね。正しく見積もれてない可能性が高いように思いますが、少なく出るよりマシ程度かもしれません。今後のチューニングに期待。。。。。というところでしょうか。。。

SCOTT@localhost:1521/freepdb1> l
1 explain plan for
2 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
3 ORGANIZATION
4 INMEMORY NEIGHBOR GRAPH
5 DISTANCE COSINE
6* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

 

だと、

- estimated index size: 293M bytes
ですが、
実際には以下の通り、 9M Bytes程度でした。
(もしかして、VECTOR INDEX (HNSW)ってINMEMORYだからメモリーサイズ? んなことないか、いや、あったりしてw と思ったりし始めているw  23ai 23.6では、DBMS_VECTOR.INDEX_VECTOR_MEMORY_ADVISORプロシージャによりメモリーサイズを見積もれるようになっていたりする。。怪しい。2025/6/19追記)

SEGMENT_NAME                                                                     SEGMENT_TYPE               MB
-------------------------------------------------------------------------------- ------------------ ----------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP TABLE 3
SYS_C0013800 INDEX 6

 

Bツリー索引の見積り(19cで確かめたときは、これほど大きな差にはなっていませんでした)に比べると精度は低そうですよね。と言うより、VECTOR INDEXには対応できてないのかもしれませんね....

おまけで、
DBMS_SPACEパッケージの索引サイズ見積もりも試しておきましょう。。。。。やはり、explain plan for文同様にVECTOR INDEXのセグメントサイズ見積もりには対応してなさそう。(小さくでるわけではないのですがw)

SCOTT@localhost:1521/freepdb1> set serveroutput on
SCOTT@localhost:1521/freepdb1> l
1 DECLARE
2 used_bytes NUMBER;
3 segment_bytes NUMBER;
4 BEGIN
5 DBMS_SPACE.CREATE_INDEX_COST (
6 ddl=> 'CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )'
7 || ' ORGANIZATION'
8 || ' INMEMORY NEIGHBOR GRAPH'
9 || ' DISTANCE COSINE'
10 || ' WITH TARGET ACCURACY 90'
11 , used_bytes => used_bytes
12 , alloc_bytes => segment_bytes
13 );
14 DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
15 DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
16* END;
SCOTT@localhost:1521/freepdb1> /
Segment Size (MB) :280
Index data Size (MB) :188.94672393798828125

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

 

Enjoy SQL, PL/SQL and VECTOR SEARCH!

VECTOR INDEXの謎はもう少し探る必要があるようだ...w (後編のおまけにつづくw)

めちゃ快適な気温の場所なので、
東京は猛暑と聞いて恐怖している

では、また。

 


参考)
今回利用したコードの一部

WITH
vector_idx_auxiliary_tables
AS (
SELECT
idx_name AS vector_index_name
, REPLACE(aux_table_name,'"','') AS aux_table_name
FROM
(
SELECT
vvi.idx_name AS idx_name
,vvi.idx_auxiliary_tables.rowid_vid_map_name
,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
,vvi.idx_auxiliary_tables.shared_journal_change_log_name
FROM
vecsys.vector$index vvi
) objs
UNPIVOT (
aux_table_name FOR related_obj_name IN
(
rowid_vid_map_name
, shared_journal_transaction_commits_name
, shared_journal_change_log_name
)
)
)
SELECT
aux_table_name AS segment_name
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
UNION ALL
SELECT
index_name AS segment_name
FROM
user_indexes
WHERE
EXISTS
(
SELECT
1
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
)
;
WITH
vector_idx_auxiliary_tables
AS (
SELECT
idx_name AS vector_index_name
, REPLACE(aux_table_name,'"','') AS aux_table_name
FROM
(
SELECT
vvi.idx_name AS idx_name
,vvi.idx_auxiliary_tables.rowid_vid_map_name
,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
,vvi.idx_auxiliary_tables.shared_journal_change_log_name
FROM
vecsys.vector$index vvi
) objs
UNPIVOT (
aux_table_name FOR related_obj_name IN
(
rowid_vid_map_name
, shared_journal_transaction_commits_name
, shared_journal_change_log_name
)
)
)
SELECT
user_segments.segment_name
, user_segments.segment_type
, user_segments.bytes / 1024 / 1024 AS "MB"
FROM
user_segments
INNER JOIN
(
SELECT
aux_table_name AS segment_name
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
UNION ALL
SELECT
index_name AS segment_name
FROM
user_indexes
WHERE
EXISTS
(
SELECT
1
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
)
) vector_idx_aux_objects
ON
vector_idx_aux_objects.segment_name = user_segments.segment_name
;
set serveroutput on
DECLARE
used_bytes NUMBER;
segment_bytes NUMBER;
BEGIN
DBMS_SPACE.CREATE_INDEX_COST (
ddl=> 'CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )'
|| ' ORGANIZATION'
|| ' INMEMORY NEIGHBOR GRAPH'
|| ' DISTANCE COSINE'
|| ' WITH TARGET ACCURACY 90'
, used_bytes => used_bytes
, alloc_bytes => segment_bytes
);
DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
END;
/

 


Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN
VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編)

 

 

| | | コメント (0)

2025年6月17日 (火)

VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編)

Previously on Mac De Oracle
前回は、実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCANでした。
今日はそれに絡んで気づいたVECTOR INDEXの謎を追いかけてみることにします。

前回の最後の宿題というか謎の一つ目、覚えてますか?  そう、VECTOR INDEXを作成後、まあよくある、USER_INDEXESを検索して作成されたVECTOR INDEXの名称を確認して、EXPLAIN PLAN FORで見積もった索引サイズとどの程度乖離しているのだろうと、手癖でUSER_SEGMENTSをアクセスしてみると、なな、なーーーーーんと、ない、無い!、なーーーい!。 (INMEMORYとはいっても元ネタがあってINMEMORY化されるわけで仕組み的に理解しにくいのと、これまでのディクショナリービューの使い方と異なっていてものすごく追いにくい)
なんで? という謎。

SCOTT@localhost:1521/freepdb1> select index_name,index_type,table_name from user_indexes where table_name='SEARCH_DATA';

INDEX_NAME INDEX_TYPE TABLE_NAME
------------------------------ --------------------------- ------------------------------
SYS_IL0000078074C00009$$ LOB SEARCH_DATA
SEARCH_DATA_HNSW_IX VECTOR SEARCH_DATA

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> select segment_name from user_segments where segment_name = 'SEARCH_DATA_HNSW_IX';

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

 

その後、アッ! 
いつもと違うビューがあったことを思い出し、覗いてみると、
作成したVECTOR INDEX (HNSW)の名称とともに、IDX_AUXILIARY_TABLES列(JSONデータ型)に、なにやらそれらしい値があるじゃありませんか!!!
そーなんだーーーーっ。VECTOR INDEXの名称は、VECTOR INDEXの補助表の集まりをまとめる為だけの存在?!!!
というところまででした。

SCOTT@localhost:1521/freepdb1> r
1* SELECT * FROM vecsys.vector$index

IDX_OBJN IDX_OBJD IDX_OWNER# IDX_NAME IDX_BASE_TABLE_OBJN IDX_BASE_TABLE_OWNER# IDX_PARAMS IDX_AUXILIARY_TABLES
---------- ---------- ---------- ------------------------------ ------------------- --------------------- ------------------------------ ------------------------------
78797 153 SEARCH_DATA_HNSW_IX 78074 153 {"type":"HNSW","num_neighbors" {"rowid_vid_map_objn":78798,"s
:32,"efConstruction":200,"dist hared_journal_transaction_comm
ance":"COSINE","accuracy":90," its_objn":78800,"shared_journa
vector_type":"FLOAT32","vector l_change_log_objn":78803,"rowi
_dimension":384,"degree_of_par d_vid_map_name":"VECTOR$SEARCH
allelism":1,"pdb_id":3,"indexe _DATA_HNSW_IX$78074_78797_0$HN
d_col":"VECTOR_DESC"} SW_ROWID_VID_MAP","shared_jour
nal_transaction_commits_name":
"VECTOR$SEARCH_DATA_HNSW_IX$78
074_78797_0$HNSW_SHARED_JOURNA
L_TRANSACTION_COMMITS","shared
_journal_change_log_name":"VEC
TOR$SEARCH_DATA_HNSW_IX$78074_
78797_0$HNSW_SHARED_JOURNAL_CH
ANGE_LOG"}

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 JSON_SERIALIZE(idx_params RETURNING VARCHAR2 PRETTY) AS "INDEX PARAM"
3 ,JSON_SERIALIZE(idx_auxiliary_tables RETURNING VARCHAR2 PRETTY) AS "INDEX AUX"
4* FROM vecsys.vector$index

INDEX PARAM INDEX AUX
-------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------
{ {
"type" : "HNSW", rowid_vid_map_objn" : 78798,
"num_neighbors" : 32, "shared_journal_transaction_commits_objn" : 78800,
"efConstruction" : 200, "shared_journal_change_log_objn" : 78803,
"distance" : "COSINE", "rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_ROWID_VID_MAP",
"accuracy" : 90, "shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"vector_type" : "FLOAT32", "shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
"vector_dimension" : 384, }
"degree_of_parallelism" : 1,
"pdb_id" : 3,
"indexed_col" : "VECTOR_DESC"
}

 

一旦ここまでの情報を整理すると

VECTOR INDEX(この例ではHNSW)の実態は、複数の補助表から構成されており、それらをまとめているのが 作成時に指定したVECTOR INDEXの名称。
オブジェクトとしては存在しているが、それ自体は、セグメントを持たない!!!(IN MEMORY展開される索引だからとはいっても。。なにかしらあるのでは。。?)

現時点の23aiには、使い勝手の良いディクショナリービューは提供されていない。。。。まじか!

その代わりに、vecsys.vector$indexディクショナリー表(ビューではない)から詳細を追うことができる。(vecsysスキーマ)

さらに、補助表を見つけるためには、該当ディクショナリー表のIDX_AUXILIARY_TABLES列に格納されているJSONの*_objnや、補助表のオブジェクトID、*_nameから探れ!、と。
ようするに、このJSONに対象となるVECTOR INDEXとその実態の依存関係が入っている!!!!!
(ちなみに、表しかないが、おそらく、関連索引もありそうだ、というかあるだろうな。補助表の索引は補助表をキーにしてUSER_INDEXESから探すしかないだろうけども。)

ここまで見てきたところで、
めんどくせーーーーーーーーーーーーーっ! なぜ、依存関係をJSONに突っ込んだの?。。。。という顔をしているところwwwww

そしてもう一つの謎、EXPLAIN PLAN FOR CREATE VECTOR INDEX ...foo bar ..として見積もられたVECTOR INDEXの見積もりサイズって、こいつら補助表含めた合計値? 
なのか?  。。。だよなw 精度的に微妙な気がしなくも無い

全部かき集めて、合計して、見積もりサイズとの差分を見てみよう!
B*Treeの索引見積もりって、結構いい感じのサイズを弾き出してくれるわけだが。。。果たして。。。こいつは、どうなんだ? SEARCH_DATA_HNSW_IXって実態持ってないし。。。

20250613-142549

 

では、もっと探ってみなければw

 

 

そのまえに、CREATE VECTOR INDEXの謎を探るべくw
10046トレースで洞窟の奥に潜入することにします! ww (わからなくなったら、これしかない!)

SCOTT@localhost:1521/freepdb1> desc search_data
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NUMBER
PRIMARY_DESCRIPTION VARCHAR2(40)
DESCRIPTION VARCHAR2(100)
LOCATION_DESC VARCHAR2(100)
DISTRICT VARCHAR2(30)
WARD NUMBER
COMMUNITY VARCHAR2(30)
C_YEAR NUMBER
VECTOR_DESC VECTOR(*, *)

SCOTT@localhost:1521/freepdb1> select count(*) from search_data

COUNT(*)
----------
125000

 

作成済みVECTOR INDEX (HNSW)を削除しておきます。

SCOTT@localhost:1521/freepdb1> drop index search_data_hnsw_ix;

索引が削除されました。

経過: 00:00:01.77

SCOTT@localhost:1521/freepdb1> select index_name , table_name, index_type from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME INDEX_TYPE
------------------------------ ------------------------------ ---------------------------
SYS_IL0000078074C00009$$ SEARCH_DATA LOB

SCOTT@localhost:1521/freepdb1> select table_name,column_name,segment_name,index_name from user_lobs where table_name like '%SEARCH_DATA%';

TABLE_NAME COLUMN_NAME SEGMENT_NAME INDEX_NAME
------------------------------ ------------------------------ ------------------------------ ------------------------------
SEARCH_DATA VECTOR_DESC SYS_LOB0000078074C00009$$ SYS_IL0000078074C00009$$

経過: 00:00:00.07

 

10046トレースの準備ができたので、トレースを設定しCREATE VECTOR INDEX文を実行して洞窟の奥へw

SCOTT@localhost:1521/freepdb1> show parameter timed_statistics

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
timed_statistics boolean TRUE

SCOTT@localhost:1521/freepdb1> show parameter max_dump_file_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
max_dump_file_size string 32M
SCOTT@localhost:1521/freepdb1> alter session set max_dump_file_size = unlimited;

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

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> show parameter tracefile_identifier

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
tracefile_identifier string
SCOTT@localhost:1521/freepdb1> alter session set tracefile_identifier = 'create_vector_index';

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

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context forever, level 12';

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

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> l
1 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
2 ORGANIZATION
3 INMEMORY NEIGHBOR GRAPH
4 DISTANCE COSINE
5* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

索引が作成されました。

経過: 00:00:39.24
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context off';

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

経過: 00:00:00.01

...略...

[oracle@localhost ~]$ echo $ORACLE_BASE
/opt/oracle
[oracle@localhost ~]$ cd $ORACLE_BASE/diag/rdbms/free/FREE/trace
[oracle@localhost trace]$ pwd
/opt/oracle/diag/rdbms/free/FREE/trace
[oracle@localhost trace]$
[oracle@localhost trace]$ ll *create_vector_index*
-rw-r-----. 1 oracle oinstall 2836064 6月 12 00:24 FREE_ora_4133_create_vector_index.trc
-rw-r-----. 1 oracle oinstall 602896 6月 12 00:24 FREE_ora_4133_create_vector_index.trm
[oracle@localhost trace]$
[oracle@localhost trace]$ tkprof FREE_ora_4133_create_vector_index.trc FREE_ora_4133_create_vector_index.txt waits=yes explain=scott@localhost:1521/freepdb1 sys=yes

...略...

[oracle@localhost trace]$ ll *create_vector_index*
-rw-r-----. 1 oracle oinstall 2836064 6月 12 00:24 FREE_ora_4133_create_vector_index.trc
-rw-r-----. 1 oracle oinstall 602896 6月 12 00:24 FREE_ora_4133_create_vector_index.trm
-rw-rw-r--. 1 oracle oracle 500986 6月 12 00:30 FREE_ora_4133_create_vector_index.txt
[oracle@localhost trace]$
[oracle@localhost trace]$ view FREE_ora_4133_create_vector_index.txt

 

ということで、整形したトレースファイルからCREATE文を抜き出してみた。
これは私が実行したDDL文なので参考程度に...

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
ORGANIZATION
INMEMORY NEIGHBOR GRAPH
DISTANCE COSINE
WITH TARGET ACCURACY 90

 

以降、トレースから抜き出した補助表及関連する索引を作成するDDL
VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP表、および、主キー索引の作成(基本的にこの補助表が主役)

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP 
(base_table_rowid ROWID PRIMARY KEY, vertex_id NUMBER)

CREATE UNIQUE INDEX "SCOTT"."SYS_C0013760" on
"SCOTT"."VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP"("BASE_TABLE_ROWID")
NOPARALLEL



VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS表、および、主キー索引の作成

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS 
(usn NUMBER NOT NULL, slot NUMBER NOT NULL, seq
NUMBER NOT NULL, commit_scn NUMBER NOT NULL,
CONSTRAINT pk_xid_79461 PRIMARY KEY(usn, slot, seq))
PARTITION BY RANGE(commit_scn)
INTERVAL(100)
(PARTITION pdefault VALUES LESS THAN (0))

CREATE UNIQUE INDEX "SCOTT"."PK_XID_79461" on
"SCOTT"."VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS"("USN","SLOT","SEQ")
NOPARALLEL


VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG表の作成(HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS表の参照パーティションになっている)

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG 
(usn NUMBER NOT NULL, slot NUMBER NOT NULL, seq NUMBER NOT
NULL, xcn NUMBER, base_table_rowid ROWID, dml_op VARCHAR2(10),
data_vector vector(384, FLOAT32),
CONSTRAINT fk_xid_79461 FOREIGN KEY(usn, slot, seq)
REFERENCES
SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS(usn, slot, seq))
PARTITION BY REFERENCE(fk_xid_79461)

各表をdescribeしてみると...

VECTOR列を持つ表のROWIDとVERTEX_IDをマップしていますね、表を見てなんとなく想像できる列名で素敵w。RDBMSのモデリングで列からなにやってるか想像もできねー設計しているのを見たりしてると心が清くなった気がしますね。
これがVECTOR INDEXの主役といってもよいでしょうね。

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP
名前 NULL? 型
----------------------------------------- -------- ----------------------------
BASE_TABLE_ROWID NOT NULL ROWID
VERTEX_ID NUMBER

 

次の2つの表は、元の表のVECTORに影響のある更新がトラックされているみたい。詳しい資料読んではいないのですが、変更をトラックしていく表っぽうので、変更のない状況では空なんじゃないだろうか。。。あとで確認します。

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
名前 NULL? 型
----------------------------------------- -------- ----------------------------
USN NOT NULL NUMBER
SLOT NOT NULL NUMBER
SEQ NOT NULL NUMBER
COMMIT_SCN NOT NULL NUMBER

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG
名前 NULL? 型
----------------------------------------- -------- ----------------------------
USN NOT NULL NUMBER
SLOT NOT NULL NUMBER
SEQ NOT NULL NUMBER
XCN NUMBER
BASE_TABLE_ROWID ROWID
DML_OP VARCHAR2(10)
DATA_VECTOR VECTOR(384, FLOAT32)

 

一応user_indexesビューから全体を見てみます。USER_INDEXESビューを通して見ると新たに7つのオブジェクトが作成されたことがわかります!!!!(赤字部分)
vecsys.vector$index表からは、補助表の存在しか見えませんでしたが、索引も作成されてます。実際にはこの表と索引のセグメントサイズの合計が、VECTOR INDEX作成時のセグメントサイズってことですよね?!(INMEMORYだからメモリーサイズって可能性もなくはないけど、どっちかわからんw) とはいえ、CREATE VECTOR INDEX ほげほげ索引で、指定したほげほげ索引自体はセグメントは持たない!(INMEMORYテーブルとは異なる持ち方をしているだけなのかもしれないが、わかりにくいw)


SCOTT@localhost:1521/freepdb1> select index_name , table_name from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME
------------------------------ ----------------------------------------------------------------------------------------------------
SYS_IL0000078074C00009$$ SEARCH_DATA
SEARCH_DATA_HNSW_IX SEARCH_DATA
SYS_C0013720 VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_ROWID_VID_MAP
PK_XID_79421 VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
SYS_IL0000079427C00007$$ VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_SHARED_JOURNAL_CHANGE_LOG

経過: 00:00:00.29

表名を中間一致検索すると関連する索引と表をリストすることができますが、これだとなんかイケてない感じが強いのでvecsys.vector$index表より扱いやすいビューを提供してもらいたいですね! まじで (苦笑

長くなってしまったので、一旦、謎の1つをまとめておきたいと思います。
VECTOR INDEX (HNSW)のセグメントサイズとEXPLAIN PLAN FOR CREATE VECTOR INDEX...の見積もりサイズの差分確認は次回のおたのしみということで。

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
ORGANIZATION
INMEMORY NEIGHBOR GRAPH
DISTANCE COSINE
WITH TARGET ACCURACY 90

 

作成されるベクター索引(今回の例では、search_data_hnsw_ix)は *_INDEXESにはVECTOR索引としてリストされるが実体は持たない(INMEMORYとはいっても何がどうINMEMORY化されるかディクショナリーから追いやすかったテーブルのINMEMORY化とは傾向が違いそう)。
補助表とその索引を含めたオブジェクト群をアクセスするためのキーのような存在。
ベクター索引(今回のHNSW索引の例では、マップ表とベクターの更新をトラックするパーティション表と参照パーティション表からなる3表と付随する3つの索引から構成されている。 ベクター索引の主役は、ベクター列をもつベース表のROWIDをVERTEX_IDをマップするマップ表とその索引。変更がない状態であれば、おそらくこの2つのオブジェクトがセベクター索引のセグメントサイズの総量になりそう。

また、
これらオブジェクト(索引も含め)を簡単に一覧するビューは無く、中間一致検索で無理やり検索するか、vecsys.vector$index表のJSONデータを使って他のビューと結合して取得する必要がありそう(めんどくせぇw)

ということまでは見えた。(以下は、洞窟の奥へ潜入したときの手書きメモww)

もうひとつ、前述の通り、search_data_hnsw_ixというベクター索引の便宜上のオブジェクトは同一名のセグメントは持っていません。しかし、以下のようなSELECT文では、該当ベクター索引が利用されていることを示すオブジェクトとして現れてくるというのも特徴的ですね。
SQL文と実行計画によってはMAP表が現われてくることもあるようなので、その例についてもいずれ書こうと思っています:) (それはそれで謎いですよね。突然裏の主役が実行計画に顔を出してくるわけなのでw)

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )


10行が選択されました。

経過: 00:00:00.32

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

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | 2 (50)| 00:00:01 |
| 1 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY | | 10 | 16400 | 2 (50)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| SEARCH_DATA | 10 | 16400 | 1 (0)| 00:00:01 |
| 6 | VECTOR INDEX HNSW SCAN | SEARCH_DATA_HNSW_IX | 10 | 16400 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

 

なお、今回利用している Oracle Database 23.4は最近の更新ペースからするとすでに古いリリースになっていますw 
VECTOR関連追加など含め結構な差分がりそうですね。例えば、DBMS_VECTOR.INDEX_VECTOR_MEMORY_ADVISOR()プロシージャが無いなど。(マニュアルには記載されているんですが、リリースいくつから有効というような表記は見当たらず)
また、VirtualBox Applienceだと、現時点では、23.7 が最新みたいなので入れ替えないとな。。。

 

23って、機能的にいつ頃落ち着くんだろうなぁ〜っ。

次回へ、つづく!

Enjoy SQL! and JSON?!

 

 


Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

 

| | | コメント (0)

2025年6月 5日 (木)

実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

Previously on Mac De Oracle
前回は、Oracle Database 23ai freeで試すVector Search - データ準備編でした。
今回は、AI Vector SearchでVECTOR INDEX HNSW SCANとそれが利用できない場合の実行計画という名のSQL文のレントゲンを診ていきたいと思います。:)

その前に、VECTOR INDEX HNSW を利用する前に必要な準備がまだ残っていました。
vector_memory_sizeの設定です。デフォルトでは 0 になっています。

Oracle Dataabase 23ai / Oracle AI Vector Search User's Guide/ Oracle AI Vector Search Parameters

https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/oracle-ai-vector-search-parameters.html

SYS@FREE> show parameter memory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------

...略...

inmemory_xmem_size big integer 0
memory_max_size big integer 0
memory_max_target big integer 0
memory_size big integer 0
memory_target big integer 0
optimizer_inmemory_aware boolean TRUE
shard_apply_max_memory_size integer 0
shared_memory_address integer 0
vector_memory_size big integer 0

SYS@FREE> show parameter sga

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 1536M
sga_min_size big integer 0
sga_target big integer 1536M

 

Oracle Database 23ai / Oracle AI Vector Searchユーザーズ・ガイド / ベクトル・プールのサイズ設定

https://docs.oracle.com/cd/G11854_01/vecse/size-vector-pool.html

今回はギリギリですがw 512MBに設定して試します。

SYS@FREE> create pfile from spfile;

ファイルが作成されました。

経過: 00:00:00.01
SYS@FREE> ALTER SYSTEM SET vector_memory_size=512m SCOPE=spfile
SYS@FREE> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@FREE> startup
ORACLEインスタンスが起動しました。

Total System Global Area 1603726344 bytes
Fixed Size 5360648 bytes
Variable Size 654311424 bytes
Database Buffers 402653184 bytes
Redo Buffers 4530176 bytes
Vector Memory Area 536870912 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@FREE>
SYS@FREE> show parameter vector_memory_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
vector_memory_size big integer 512M

 

vector_memory_sizeサイズの準備ができたので、Vector Index (HNSW) を作成します。

まずは、HNSWタイプののVector Indexを作成します。

SCOTT@localhost:1521/freepdb1> l
1 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
2 ORGANIZATION
3 INMEMORY NEIGHBOR GRAPH
4 IDISTANCE COSINE
5* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

索引が作成されました。

経過: 00:00:36.13
SCOTT@localhost:1521/freepdb1> r
1 select index_name,table_name,index_type,index_subtype,uniqueness,ityp_name
2* from user_indexes where table_name = 'SEARCH_DATA'

INDEX_NAME TABLE_NAME INDEX_TYPE INDEX_SUBTYPE UNIQUENES ITYP_NAME
------------------------------ ------------------------------ --------------------------- ---------------------------- --------- --------------------
SYS_IL0000078074C00009$$ SEARCH_DATA LOB UNIQUE
SEARCH_DATA_HNSW_IX SEARCH_DATA VECTOR INMEMORY_NEIGHBOR_GRAPH_HNSW NONUNIQUE

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'SEARCH_DATA',no_invalidate=>false, cascade=>true);

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

 

ちなみに、vector_memory_sizeに指定したメモリサイズでは小さすぎる場合、以下のエラーが発生します。vector_memory_sizeを増加するか、データ量を削減するかどちらかなんですよね。Oracle Database 23ai Freeってメモリサイズの制限も厳しいので。

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
*
行1でエラーが発生しました。:
ORA-51961: ベクトル・メモリー領域が不足しています。 ヘルプ:
https://docs.oracle.com/error-help/db/ora-51961/

 

VECTOR INDEX HNSW SCAN + TABLE ACCESS BY INDEX ROWIDになるか確認してみましょう.

索引(Hierarchical Navigable Small World (HNSW)またはInverted File Flat (IVF)ベクトル索引)を利用させるためには、以下のガイドラインに従うことが重要なので一読しておくことをおすすめしておきます。(知らないと簡単にハマってしまうので)


Oracle Database 23ai / Oracle AI Vector Searchユーザーズ・ガイド / 索引使用のガイドライン

https://docs.oracle.com/cd/G11854_01/vecse/guidelines-using-vector-indexes.html

 

シカゴの犯罪データから、"Incident in which someone may have been murdered" という条件で殺人事件に絡んでそうな事件を10件検索してみます。APPROXというキーワードも重要なので忘れずに。

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH APPROX FIRST 10 ROWS ONLY
)
/

 

おおおおおお、索引使ってる:) ほっとした

ただ、これまでの INMEMORYとはことなり、INMEMORYにあるはずので索引アクセスのはずなのに、実行計画のOPERATIONには、INMEMORYというキーワードがないこと。ディクショナリービューの使い方もこれまでとは異なる傾向がり、なかなか分かりづらい部分も多い。今回気づいた違和感というか謎は要調査(2025/6/20追記)

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )
SCOTT@localhost:1521/freepdb1> /

ID DESCRIPTION COMMUNITY LOCATION_DESC DISTRICT V_DISTANCE
---------- ---------------------------------------- -------------------- ---------------------------------------- ---------- ----------
13405240 AGGRAVATED - OTHER DANGEROUS WEAPON AUSTIN APARTMENT 15TH .631286621
13405155 AGGRAVATED - OTHER DANGEROUS WEAPON AUSTIN HOSPITAL BUILDING / GROUNDS 15TH .631286621
13395521 AGGRAVATED - OTHER DANGEROUS WEAPON MORGAN PARK RESIDENCE 22ND .631286621
13404912 AGGRAVATED - OTHER DANGEROUS WEAPON ASHBURN SCHOOL - PUBLIC BUILDING 8TH .631286621
13405606 AGGRAVATED - OTHER DANGEROUS WEAPON AVALON PARK STREET 4TH .631286621
13509969 AGGRAVATED - OTHER DANGEROUS WEAPON AUBURN GRESHAM APARTMENT 6TH .631286621
13396440 AGGRAVATED - OTHER DANGEROUS WEAPON DUNNING STREET 16TH .631286621
13396497 AGGRAVATED - OTHER DANGEROUS WEAPON WOODLAWN APARTMENT 3RD .631286621
13405235 AGGRAVATED - OTHER DANGEROUS WEAPON NEAR WEST SIDE SIDEWALK 12TH .631286621
13396404 AGGRAVATED - OTHER DANGEROUS WEAPON NEAR NORTH SIDE CONVENIENCE STORE 18TH .631286621

10行が選択されました。

経過: 00:00:00.37
SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )


10行が選択されました。

経過: 00:00:00.32

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

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | 2 (50)| 00:00:01 |
| 1 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY | | 10 | 16400 | 2 (50)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| SEARCH_DATA | 10 | 16400 | 1 (0)| 00:00:01 |
| 6 | VECTOR INDEX HNSW SCAN | SEARCH_DATA_HNSW_IX | 10 | 16400 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)


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

 

ヒントで索引を利用できなくしてみます。新しいヒントが結構追加されていますが、それらについては、また、別の機会にでも。

Oracle Database 23ai / Oracle AI Vector Search User's Guide / Vector Index Hints

https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/vector-index-hints.html

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
NO_VECTOR_INDEX_SCAN(search_data)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH APPROX FIRST 10 ROWS ONLY
)
/


10行が選択されました。

経過: 00:00:03.84

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

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | | 51357 (1)| 00:00:03 |
| 1 | VIEW | | 10 | 1570 | | 51357 (1)| 00:00:03 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 125K| 18M| | 51357 (1)| 00:00:03 |
|* 4 | SORT ORDER BY STOPKEY| | 125K| 195M| 244M| 51357 (1)| 00:00:03 |
| 5 | TABLE ACCESS FULL | SEARCH_DATA | 125K| 195M| | 8543 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)


統計
----------------------------------------------------------
5635 recursive calls
0 db block gets
48201 consistent gets
46497 physical reads
0 redo size
1589 bytes sent via SQL*Net to client
108 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
119 sorts (memory)
0 sorts (disk)
10 rows processed

 

APPROXキーワードをEXACTに変えました。これもVECTOR INDEXを利用できなくなる条件の一つです。診てみましょう

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH EXACT FIRST 10 ROWS ONLY
)
/

 

実行計画とは関係ないですが、APPROXとEXACTの問い合わせ結果って結構違うのね。。。

SCOTT@localhost:1521/freepdb1> /

ID DESCRIPTION COMMUNITY LOCATION_DESC DISTRICT V_DISTANCE
---------- ---------------------------------------- -------------------- ---------------------------------------- ---------- ----------
13325717 OTHER CRIME AGAINST PERSON BRIDGEPORT APARTMENT 9TH .382904768
13331403 OTHER CRIME AGAINST PERSON HEGEWISCH APARTMENT 4TH .382904768
13329763 OTHER CRIME AGAINST PERSON NEAR NORTH SIDE SMALL RETAIL STORE 18TH .382904768
13330138 OTHER CRIME AGAINST PERSON ROSELAND STREET 5TH .382904768
13329559 OTHER CRIME AGAINST PERSON CHICAGO LAWN APARTMENT 8TH .382904768
13329389 OTHER CRIME AGAINST PERSON BELMONT CRAGIN ALLEY 25TH .382904768
13328588 OTHER CRIME AGAINST PERSON LOOP APARTMENT 1ST .382904768
13328610 OTHER CRIME AGAINST PERSON SOUTH CHICAGO APARTMENT 4TH .382904768
13325825 OTHER CRIME AGAINST PERSON WEST RIDGE PARKING LOT / GARAGE (NON RESIDENTIAL) 24TH .382904768
13326308 OTHER CRIME AGAINST PERSON PULLMAN STREET 5TH .382904768



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

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | | 51357 (1)| 00:00:03 |
| 1 | VIEW | | 10 | 1570 | | 51357 (1)| 00:00:03 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 125K| 18M| | 51357 (1)| 00:00:03 |
|* 4 | SORT ORDER BY STOPKEY| | 125K| 195M| 244M| 51357 (1)| 00:00:03 |
| 5 | TABLE ACCESS FULL | SEARCH_DATA | 125K| 195M| | 8543 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)

 

ということで、
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN
はここまで。

今回は一部だけ試してますが更新関連なども含めいろいろな仕組みがあるようで。。。ちょっと追っかけてみないと見えてこないところも多いです。マニュアルも多いですし。。。斜め読みしていて、まじか。。。。となっているところ。

ところで、前回explain plan 文でVector Indexサイズの見積もりを取得していたのですが、覚えてますでしょうか? 実はちょっとクセがあるみたいなんですよね。*_SEGMENTSビューからは索引名では見つからないので。。。


あれ、 SEARCH_DATA_HNSW_IX というVECTOR索引って、索引名で検索しても、*_SEGMENTSビューにでないのか。別の名前になってるのか。。。 セグメントサイズ見たかったのに。。。という謎だけ投げつけて、宿題としておきますw(わざとらしいw) うううーーーーむ、いろいろと知っておく必要なる事項は多そうですねぇ。。。。。

SCOTT@localhost:1521/freepdb1> select index_name,index_type,table_name from user_indexes where table_name='SEARCH_DATA';

INDEX_NAME INDEX_TYPE TABLE_NAME
------------------------------ --------------------------- ------------------------------
SYS_IL0000078074C00009$$ LOB SEARCH_DATA
SEARCH_DATA_HNSW_IX VECTOR SEARCH_DATA

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> select segment_name from user_segments where segment_name = 'SEARCH_DATA_HNSW_IX';

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

SCOTT@localhost:1521/freepdb1> r
1* SELECT * FROM vecsys.vector$index

IDX_OBJN IDX_OBJD IDX_OWNER# IDX_NAME IDX_BASE_TABLE_OBJN IDX_BASE_TABLE_OWNER# IDX_PARAMS IDX_AUXILIARY_TABLES
---------- ---------- ---------- ------------------------------ ------------------- --------------------- ------------------------------ ------------------------------
78797 153 SEARCH_DATA_HNSW_IX 78074 153 {"type":"HNSW","num_neighbors" {"rowid_vid_map_objn":78798,"s
:32,"efConstruction":200,"dist hared_journal_transaction_comm
ance":"COSINE","accuracy":90," its_objn":78800,"shared_journa
vector_type":"FLOAT32","vector l_change_log_objn":78803,"rowi
_dimension":384,"degree_of_par d_vid_map_name":"VECTOR$SEARCH
allelism":1,"pdb_id":3,"indexe _DATA_HNSW_IX$78074_78797_0$HN
d_col":"VECTOR_DESC"} SW_ROWID_VID_MAP","shared_jour
nal_transaction_commits_name":
"VECTOR$SEARCH_DATA_HNSW_IX$78
074_78797_0$HNSW_SHARED_JOURNA
L_TRANSACTION_COMMITS","shared
_journal_change_log_name":"VEC
TOR$SEARCH_DATA_HNSW_IX$78074_
78797_0$HNSW_SHARED_JOURNAL_CH
ANGE_LOG"}

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 JSON_SERIALIZE(idx_params RETURNING VARCHAR2 PRETTY) AS "INDEX PARAM"
3 ,JSON_SERIALIZE(idx_auxiliary_tables RETURNING VARCHAR2 PRETTY) AS "INDEX AUX"
4* FROM vecsys.vector$index

INDEX PARAM INDEX AUX
-------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------
{ {
"type" : "HNSW", "rowid_vid_map_objn" : 78798,
"num_neighbors" : 32, "shared_journal_transaction_commits_objn" : 78800,
"efConstruction" : 200, "shared_journal_change_log_objn" : 78803,
"distance" : "COSINE", "rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_ROWID_VID_MAP",
"accuracy" : 90, "shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"vector_type" : "FLOAT32", "shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
"vector_dimension" : 384, }
"degree_of_parallelism" : 1,
"pdb_id" : 3,
"indexed_col" : "VECTOR_DESC"
}

 

では、また!

Enjoin Execution Plans, SQLs, and AI Vector Search!

 


Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ!

実行計画は, 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
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)
実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID

 

AI Vector Search

Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編

 

 

| | | コメント (0)

2025年5月26日 (月)

スマートロジックの亜種のお話 / PostgreSQL, MySQL, Oracle Databaseそれぞれの影響

本題に入る前に、

祝! Mac De Oracle 20周年記念!!!!! Happy 20th Anniversary Mac De Oracle !!!

20年前の第一回目の記事はどんな内容だったでしょう!!!!
Panther De Oracle10g その1

MacOS (Mac OS X 10.3 / Panther ) に直接イントールして遊べる Oracle Database があったころの話ですw
なつかしい!
また、当時は失敗作と名高いw (今のMac Studio並の筐体)PowerMac G4 Cube 450Mhz改 Sonnet 1.2Ghz 832GMB RAM を使っていました。これまた懐かしいSonnetのCPUカードで。

そして最後に、最も大切なブログの名前 "Mac De Oracle" は、どう閃いたのか!!!
当時、Zopeネタのブログ、 "Cube De Zope" からG4 Cube繋がりでインスパイアされて、 de だけ頂いて (^^) 、
(知ってるかたどれぐらいいるのでしょうね)

付けたタイトルが、Mac De Oracle でした。

あ、忘れちゃいけない、そもそもブログ書いたら〜。Oracleの!!
と、背中を推してくれたのは妻なんですけどねw 
彼女の一言がなかったら Mac De Oracleも Oracle ACEにもなってなかったでしょうね。
ありがとう!

 

ということで、

これからも雑多なネタ織り交ぜて、楽しく書いていくことになるw
Mac De Oracle (まだ残してあるIntel MacのVirtalBoxで動いてたりするネタも多いのですけどもw) を、よろしくお願いいたします。 m(_ _)m

 



では、本日のおはなし。(ちょいと長めです)

 

WHERE句でCOALESCE関数を利用したスマートロジックの亜種って、Poor performanceを起こすってネタ、世間ではかなり書かれている印象なのですが、今日は、そんな中でも癖の強そうな、COALESCE関数を使ったスマートロジックの亜種の話。。

スマートロジックの亜種と書いた理由は、動的SQLで素直に書けばなんの問題のないもない比較的単純なSQL文を、COALESCE関数とNULLを組み合わせ動的SQLを回避したいという意図で書かれたものだから。。です。

また、この亜種は利用するデータベース(オプティマイザーだが)で影響の出方も異なるというところが、興味深いところだったりします。
なお、影響が小さそうに見える症状でも、膨大な量のデータも扱っている方達には、塵も積もれば。。。。という点には、むむむむ。。。となるはず。。

Oracle Database 21c, MySQL 8.0.36, PostgreSQL 16.3 いずれも、ちょい前のリリースにしてあります。前からそうだからってのも診てもらおうと。。。
(なお、今回利用している環境は、MacBook Intel 上のVirtualBoxのVMを利用しています。このためだけにIntel Macが残っているww)

piraruku ~ % sw_vers
ProductName: macOS
ProductVersion: 12.7.6
BuildVersion: 21H1320
piraruku ~ % /usr/sbin/system_profiler SPHardwareDataType | grep -E '(Processor|Cores|Memory|Chip|Model Name)'
Model Name: MacBook
Processor Name: Dual-Core Intel Core m5
Processor Speed: 1.2 GHz
Number of Processors: 1
Total Number of Cores: 2
Memory: 8 GB
piraruku ~ % VBoxManage -V
7.0.10r158379

 

 

Oracle Databaseの表と索引、データ件数ですが、MySQL, PostgreSQLでも同一の表、索引、データを登録してあります。

SCOTT@orclpdb1> desc customers
名前 NULL? 型
----------------------------------------- -------- ----------------------------------
CUSTOMER_ID NOT NULL 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 NOT NULL 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

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

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

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

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

 

 

 

まずは、問題のSQL文の例から診てもらいましょう。

SELECT
orders.order_id
, orders.order_date
, customers.first_name FROM orders INNER JOIN customers ON orders.customer_id = customers.customer_id WHERE customers.customer_id = COALESCE(:cust_id, customers.customer_id) AND orders.order_id = COALESCE(:order_id, orders.order_id);

 

みなさん, 上記SQL文のWHERE句で、なにをスマートにやりたい(と思っている)のか、わかりますか?

答え

バインド変数(cust_id, order_id)にどちも NULL がセットされた場合は、全顧客のオーダーをリスト.
特定のcust_idが指定されたら、該当顧客の前オーダーをリスト(このときorder_idはNULL).
特定のorder_idが指定された、該当オーダーをリスト(このときcust_idはNULL).
どちらの変数にも特定のオーダーと顧客が指定されたら該当するオーダーをリスト。

ようするに、全件取得と特定のデータの取得で個別のWHEREの条件に書きかける動的SQLにすればよいわけですけども、
動的SQLを回避したい、ただそこだけに着目してしまった"スマートロジックの亜種"になっています。

WHERE句のCOALESCE関数。SELECTリストならともかく、検索時の弊害が多くなる使い方ですよね。
スマートなように見えているのは机の上だけです。

実際にオプティマイザがどうするか次第ですが、大抵の場合、検索時のフィルタリングが諸悪の根源になる場合が多いいですよね。ご存知の方も多いと思います。
(データ量が多い場合は軽視できない部分です。それらについてもググると沢山ヒットするはずです)

で、
Oracle Database/MySQL/PostgreSQLのうち一つで、フィルタリング以外の影響を引き起こすことに気がついてしまったので、メモがわりに書いておきまっす”

(今回利用したスクリプトは後半に貼ってあります)


Oracle Database 21c から診てみましょう。

SCOTT@orclpdb1> select banner_legacy from v$version;

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

 

 

1) :cust_id, :order_id どちらも NULL にした場合(なお、バインドピークは有効のままです)

事前の想定通り、全データ取得のケースであるにもかかわらず、Table full scannの操作で、COALESCE関数を伴うフィルタリングが現れています。明らかに電力の無駄遣いw データが少ないと影響は見えにくいですが。
(HASH JOINで利用されている赤字にしている作業用メモリサイズは覚えておいてくださいね。改善後の実行計画の部分で必要になります)

SCOTT@orclpdb1> @testrun1 null null

CUST_ID
----------
[null]

ORDER_ID
----------
[null]

1 SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12 orders.customer_id = customers.customer_id
13 WHERE
14 customers.customer_id = COALESCE(:cust_id, customers.customer_id)
15* AND orders.order_id = COALESCE(:order_id, orders.order_id)

...略...

Plan hash value: 23084738

-------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 (100)| 105 |00:00:00.01 | 13 | | | |
|* 1 | HASH JOIN | | 1 | 6 (0)| 105 |00:00:00.01 | 13 | 1376K| 1376K| 1558K (0)|
|* 2 | TABLE ACCESS FULL| ORDERS | 1 | 5 (0)| 105 |00:00:00.01 | 2 | | | |
|* 3 | TABLE ACCESS FULL| CUSTOMERS | 1 | 1 (0)| 319 |00:00:00.01 | 11 | | | |
-------------------------------------------------------------------------------------------------------------------------

...略...

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

1 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
2 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
3 - filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

なお、opt_param('_optim_peek_user_binds', 'false')ヒントでbind peekをオフにしても同じ結果でした。

2) :cust_id, :order_idともに値をNULL以外に設定した場合(バインドピーク有効のままですがハードパースさせています)

実行計画が変化しました。ただ、ORDERSではTable Full Scanのままです。(データ量が少ない影響かもしれないですね。とは言え気になるw)

SCOTT@orclpdb1> @testrun1 144 2435

CUST_ID
----------
144

ORDER_ID
----------
2435

...略...

Plan hash value: 4191309613

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 6 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 1 | 30 | 6 (0)| 1 |00:00:00.01 | 4 |
| 2 | NESTED LOOPS | | 1 | 1 | 30 | 6 (0)| 1 |00:00:00.01 | 3 |
|* 3 | TABLE ACCESS FULL | ORDERS | 1 | 1 | 19 | 5 (0)| 1 |00:00:00.01 | 2 |
|* 4 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 1 | | 0 (0)| 1 |00:00:00.01 | 1 |
| 5 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 1 | 1 | 11 | 1 (0)| 1 |00:00:00.01 | 1 |
----------------------------------------------------------------------------------------------------------------------------

...略...

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

3 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
4 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

 

ORDERSの主キー索引をユニークスキャンさせるようにindexヒントを追加すると素直に効いてくれますね。ですが、ここがポイントです。index full scanしています。

ここ試験にでますよ。これCOALESCE関数の影響です。主キー索引からindex unique scanすればよいはずですが、できななくなるんですよ。

index full scan + table access by index rowid batched (1行だけ) ですが. 結局、無駄なデータにアクセスして捨てていることになりますね。ほとんどを。これじゃダメですよね。
実行計画は、全くスマートじゃなくなっていますww

SCOTT@orclpdb1> @testrun1 144 2435

...略...

1 SELECT
2 /*+
3 gather_plan_statistics
4 index(orders pk_orders)
5 */
6 orders.order_id

...略...

Plan hash value: 109917740

--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 3 (0)| 1 |00:00:00.01 | 4 |
| 2 | NESTED LOOPS | | 1 | 3 (0)| 1 |00:00:00.01 | 3 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 1 | 2 (0)| 1 |00:00:00.01 | 2 |
|* 4 | INDEX FULL SCAN | PK_ORDERS | 1 | 1 (0)| 1 |00:00:00.01 | 1 |
|* 5 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
| 6 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS | 1 | 1 (0)| 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------------------------------

...略...

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

4 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
5 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

 

3) 1)を動的SQLで回避した場合、全件取得はWHERE句のない文になりますよね!

最適な実行計画にするために動的SQL化し生成されたSQL文を実行して1)のケースがどう改善されるか診てみましょう

全行取得するのでWHERE句は不要。シンプルになりました。
余計なフィルタリングも消えました。

この例だとMERGE JOINになっていますが、データ量が少ない影響だと推測。
ソートに使うメモリを索引を使って回避している動きがあることから利用するメモリサイズが少なくなるためでしょうね。データ量が大きくなるとHash Joinに切り替わるかもしれないですね。
(理由は、1)の実行計画のHASH JOINが利用するメモリサイズとの比較で小さくなっていることがわかります)

  1  SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12* orders.customer_id = customers.customer_id

...略...

Plan hash value: 3079428679

---------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 9 (100)| 105 |00:00:00.01 | 20 | | | |
| 1 | MERGE JOIN | | 1 | 9 (12)| 105 |00:00:00.01 | 20 | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 2 (0)| 105 |00:00:00.01 | 16 | | | |
| 3 | INDEX FULL SCAN | FK_ORDERS_CUSTOMERS | 1 | 1 (0)| 105 |00:00:00.01 | 8 | | | |
|* 4 | SORT JOIN | | 105 | 7 (15)| 105 |00:00:00.01 | 4 | 20480 | 20480 |18432 (0)|
| 5 | TABLE ACCESS FULL | CUSTOMERS | 1 | 6 (0)| 319 |00:00:00.01 | 4 | | | |
---------------------------------------------------------------------------------------------------------------------------------------------

...略...

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

4 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")

 

4) 2)の改善

動的SQL化によりCOALESCE関数は不要でわかりやすいSQL文ですよね。こちらのほうが自然ですw

見ての通り、WHERE句に記述されているCOALESCE関数を含むフィルタリングが消え、スッキリしたPredicate Informationの内容に変化しました。これで安心!

  1  SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12 orders.customer_id = customers.customer_id WHERE
13 customers.customer_id = :cust_id
14* AND orders.order_id = :order_id

...略...

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 2 (0)| 1 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 1 | 1 (0)| 1 |00:00:00.01 | 2 |
|* 3 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
|* 4 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 1 (0)| 1 |00:00:00.01 | 2 |
|* 5 | INDEX UNIQUE SCAN | PK_ORDERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
-----------------------------------------------------------------------------------------------------------

...略...

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

3 - access("CUSTOMERS"."CUSTOMER_ID"=:CUST_ID)
4 - filter("ORDERS"."CUSTOMER_ID"=:CUST_ID)
5 - access("ORDERS"."ORDER_ID"=:ORDER_ID)

 

次は、MySQL 8.0.36

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+

1) :cust_id, :order_id どちらも NULL にした場合

Oracle Databaseでの症状同様に、無駄なフィルタリングが現れていますよね

mysql> set @cust_id = null;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+--------------------+
| @cust_id |
+--------------------+
| NULL |
+--------------------+v1 row in set (0.00 sec)

mysql> select @order_id;
+----------------------+
| @order_id |
+----------------------+
| NULL |
+----------------------+
1 row in set (0.01 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = COALESCE(@cust_id, customers.customer_id)
-> AND orders.order_id = COALESCE(@order_id, orders.order_id);
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=13.6 rows=10.5)
-> Filter: ((cast(orders.customer_id as double) = cast(coalesce(((@cust_id)),orders.customer_id) as double))
and (cast(orders.order_id as double) = cast(coalesce(((@order_id)),orders.order_id) as double))) (cost=2.05 rows=10.5)
-> Table scan on orders (cost=2.05 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1.01 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

2):cust_id, :order_idねともに値をNULL以外に設定した場合

このケースもOracle Databaseでの症状に類ていますよね。ふむふむ

mysql> set @cust_id = 144;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = 2435;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+----------+
| @cust_id |
+----------+
| 144 |
+----------+
1 row in set (0.00 sec)

mysql> select @order_id;
+-----------+
| @order_id |
+-----------+
| 2435 |
+-----------+
1 row in set (0.00 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = COALESCE(@cust_id, customers.customer_id)
-> AND orders.order_id = COALESCE(@order_id, orders.order_id);
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=13.6 rows=10.5)
-> Filter: ((orders.customer_id = coalesce(((@cust_id)),orders.customer_id))
and (orders.order_id = coalesce(((@order_id)),orders.order_id))) (cost=2.05 rows=10.5)
-> Table scan on orders (cost=2.05 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1.01 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

 

3) 1)を動的SQLにして適切なSQLになった対策後の結果

そもそも全行取得するのになんでフィルタリングさせちゃうようなWHERE句を書くのかと。。
フィルタリングはなくなり綺麗なものです。

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id;
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=127 rows=105)
-> Table scan on orders (cost=11.5 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

 

4) 3)同様に動的SQLにして、COALESCE関数を排除した結果。

無駄なフルタリングも一歳なし。こうでなくちゃw COAESCE関数を含むフィルタなんていらんのですよ。

mysql> set @cust_id = 144;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = 2435;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+----------+
| @cust_id |
+----------+
| 144 |
+----------+
1 row in set (0.00 sec)

mysql> select @order_id;
+-----------+
| @order_id |
+-----------+
| 2435 |
+-----------+
1 row in set (0.00 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = @cust_id
-> AND orders.order_id = @order_id;
+-------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------+
| -> Rows fetched before execution (cost=0..0 rows=1)
|
+-------------------------------------------------------+

 

 

そして、最後は、PostgreSQL 16.3 

とりを務めているぐらいなのでw、
Oracle Database, MySQLとはちょっと違った影響がありそうなのは、気づいている方が多そう。。。。
それがどのような違いなのか診てみましょう

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 行)

 

 

1) :cust_id, :order_id どちらも NULL にした場合

全行取得してINNER JOINするだけですが、Join Cardinarityが変です!!!!!

Rows Removed by Join Filter: 3129とあります。105rows*31rowsなので、なぜか直積しちゃってますね。わぉ! 細かいところだが一部のROWSなんとなく合わないようなきもするが、そこは見てないことにしておこう。。 フィルタリングが余計なのはOracle Database, MySQLと同じ。

症状としては重症の類ですよね。

データが多くなった場合の直積はフィルタリング以上にキツイですよーーーーっ!

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id NULL
perftestdb=> \set order_id NULL
perftestdb=> \echo :cust_id
NULL
perftestdb=> \echo :order_id
NULL
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = COALESCE(:cust_id, customers.customer_id)
perftestdb-> AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.33 rows=1 width=18) (actual time=0.025..1.416 rows=105 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
Inner Unique: true
Join Filter: (orders.customer_id = customers.customer_id)
Rows Removed by Join Filter: 3129
-> Seq Scan on public.orders (cost=0.00..2.31 rows=1 width=16) (actual time=0.016..0.041 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: (orders.order_id = COALESCE(orders.order_id))
-> Seq Scan on public.customers (cost=0.00..7.99 rows=2 width=10) (actual time=0.001..0.007 rows=31 loops=105)
Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
Filter: (customers.customer_id = COALESCE(customers.customer_id))
Planning Time: 0.456 ms
Execution Time: 1.453 ms

 

 

2):cust_id, :order_idともに値をNULL以外に設定した場合

主キーを利用するかと思いきや、COALESCE関数の影響で全表走査してフィルタリングしてほぼ捨ててますね なぜ(???)

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id 144
perftestdb=> \set order_id 2435
perftestdb=> \echo :cust_id
144
perftestdb=> \echo :order_id
2435
perftestdb=> explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.57 rows=1 width=18) (actual time=0.062..0.155 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Seq Scan on public.orders (cost=0.00..2.58 rows=1 width=16)
(actual time=0.038..0.064 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: ((orders.customer_id = 144) AND (orders.order_id = 2435))
Rows Removed by Filter: 104
-> Seq Scan on public.customers (cost=0.00..7.99 rows=1 width=10)
(actual time=0.020..0.085 rows=1 loops=1)

Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
Filter: (customers.customer_id = 144)
Rows Removed by Filter: 318
Planning Time: 0.253 ms
Execution Time: 0.225 ms

 

ちょいと気になるのでヒントでどうなるか追加で診ておきましょう。このケースでは改善。
pg_hint_planで索引スキャンでよりよくできますね。(データ量の影響だろうか。。)

perftestdb=> explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.29..16.34 rows=1 width=18) (actual time=0.039..0.043 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Index Scan using pk_orders on public.orders
(cost=0.14..8.16 rows=1 width=16) (actual time=0.027..0.028 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Index Cond: (orders.order_id = 2435)
Filter: (orders.customer_id = 144)
-> Index Scan using pk_customers on public.customers
(cost=0.15..8.17 rows=1 width=10) (actual time=0.008..0.010 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
, customers.address, customers.phone_number
Index Cond: (customers.customer_id = 144)
Planning Time: 0.290 ms
Execution Time: 0.083 ms

 

 

3) 1)の対策後。動的SQLにしてWHERE句を除外するだけです

無駄なWHERE句が消えて、スッキリ。こちらもOracle Databaseの改善後に類似したMerge Joinになってますね。直積が無くなってますねーーーー

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id NULL
perftestdb=> \set order_id NULL
perftestdb=> \echo :cust_id
NULL
perftestdb=> \echo :order_id
NULL
perftestdb=>
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Merge Join (cost=5.72..11.81 rows=105 width=18) (actual time=0.116..0.240 rows=105 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
Merge Cond: (customers.customer_id = orders.customer_id)
-> Index Scan using pk_customers on public.customers (cost=0.15..19.93 rows=319 width=10)
(actual time=0.012..0.040 rows=71 loops=1)
Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
-> Sort (cost=5.57..5.84 rows=105 width=16) (actual time=0.098..0.118 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.customer_id
Sort Key: orders.customer_id
Sort Method: quicksort Memory: 29kB
-> Seq Scan on public.orders (cost=0.00..2.05 rows=105 width=16)
(actual time=0.012..0.047 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.customer_id
Planning Time: 0.324 ms
Execution Time: 0.289 ms

 

 

4) 3)と同様の対策。動的SQLにしてCOALESCE関数を利用しない。一般的な条件を付加するだけです。

データ少ないからか、Seq Scanのままですね。
まじかw

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id 144
perftestdb=> \set order_id 2435
perftestdb=> \echo :cust_id
144
perftestdb=> \echo :order_id
2435
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = :cust_id
perftestdb-> AND orders.order_id = :order_id;

QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.57 rows=1 width=18) (actual time=0.037..0.097 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Seq Scan on public.orders (cost=0.00..2.58 rows=1 width=16)
(actual time=0.022..0.039 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
     , orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: ((orders.customer_id = 144) AND (orders.order_id = 2435))
Rows Removed by Filter: 104
-> Seq Scan on public.customers (cost=0.00..7.99 rows=1 width=10)
(actual time=0.011..0.053 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
     , customers.address, customers.phone_number
Filter: (customers.customer_id = 144)
Rows Removed by Filter: 318
Planning Time: 0.199 ms
Execution Time: 0.132 ms

 

pg_hint_planIndexScanを強制してみましょう。。すんなり改善。
プランナーの判断だと索引使ってくれなかったのですが、ヒントで改善できる状況は確認。状況に応じて人がアシストしてあげるのがいいですかね。

perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> /*+
perftestdb*> IndexScan(orders pk_orders)
perftestdb*> IndexScan(customers pk_customers)
perftestdb*> */
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = :cust_id
perftestdb-> AND orders.order_id = :order_id;

QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.29..16.34 rows=1 width=18) (actual time=0.039..0.043 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Index Scan using pk_orders on public.orders (cost=0.14..8.16 rows=1 width=16)
(actual time=0.027..0.028 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
     , orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Index Cond: (orders.order_id = 2435)
Filter: (orders.customer_id = 144)
-> Index Scan using pk_customers on public.customers (cost=0.15..8.17 rows=1 width=10)
(actual time=0.008..0.010 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
     , customers.address, customers.phone_number
Index Cond: (customers.customer_id = 144)
Planning Time: 0.290 ms
Execution Time: 0.083 ms
(11 行)

 

 

まとめると、
当初の想定通りいいことは全くない!! スマートなやりかたに見えているだけでしたよね
COALESCE関数をWHERE句で利用したスマートロジックはかなりリスキー。

今回試した全てのケースで、余分なフィルタリングが常に行われたりするリスクに加え、
PostgreSQLでは直積まで発生しちゃいました。やばいです。

 

ということで、

Mac De Oracle ブログ開設 20周年記念のエントリー。おわり。

では、まだ。

Enjoy SQLs! and Optimizers!

 

 


==== サンプルコード ====
Oracle Database側で簡易的に動的SQL化して生成した全件取得、キー指定版のSQLをPostgreSQL/MySQLで再利用しました。

Oracle database

SCOTT@orclpdb1> !cat testrun1.sql
set termout on
variable cust_id number
variable order_id number
set autop on
set null [null]
exec :cust_id := &1
exec :order_id := &2

SELECT
/*+
gather_plan_statistics
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id)
.
l
set termout off
/
set termout on
@show_actualplan.sql

-- 簡易的にSQL*PLus内で動的SQLを組み立てて実行
set feed off
set timi off
set head off
set termout off
set veri off
spool temp_sql.sql
SELECT
'SELECT
/*+
gather_plan_statistics
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id '
||
CASE
WHEN
:cust_id IS NOT NULL
AND :order_id IS NOT NULL
THEN
'WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id'
END
FROM
dual;
select '.' from dual;
select 'l' from dual;
select 'set termout off' from dual;
select '/' from dual;
select 'set termout on' from dual;
select '@show_actualplan.sql' from dual;
spo off
set feed on
set timi on
set head on
set termout on
set veri on
@temp_sql.sql

undefine 1
undefine 2
undefine cust_id
undefine order_id

 

show_actualplan.sql

set long 20000
set longchunk 400
select * from table(dbms_xplan.display_cursor(format=>'ALL ALLSTATS LAST +OUTLINE'));

 

 

MySQL

set @cust_id = null;
set @order_id = null;
select @cust_id;
select @order_id;


--スマートロジック版(すまーとじゃないけど)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(@cust_id, customers.customer_id)
AND orders.order_id = COALESCE(@order_id, orders.order_id);


--動的SQL(生成済み全件取得のSQL)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id;


set @cust_id = 144;
set @order_id = 2435;
select @cust_id;
select @order_id;


--スマートロジック版(すまーとじゃないけど)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(@cust_id, customers.customer_id)
AND orders.order_id = COALESCE(@order_id, orders.order_id);


--動的SQL(生成済みキー指定のSQL)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = @cust_id
AND orders.order_id = @order_id;

 

 

PostgreSQL

\pset null [null]
\set cust_id NULL
\set order_id NULL
\echo :cust_id
\echo :order_id

--スマートロジック版(すまーとじゃないけど)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--動的SQL(生成済み全件取得のSQL)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
;


\pset null [null]
\set cust_id 144
\set order_id 2435
\echo :cust_id
\echo :order_id


--スマートロジック版(すまーとじゃないけど)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--スマートロジック版(すまーとじゃないけど、キー指定版、SQL hint付き)
explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--動的SQL(生成済みキー指定のSQL、SQL hint付き)
explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id;


--動的SQL(生成済みキー指定のSQL)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id;

| | | コメント (0)

2025年4月28日 (月)

とあるJargon絡みのおはなし

久々のDatabaseネタですw

先日、ぐるぐる系がほんとうに嫌いなんだなぁ、というアトモスフィアを感じる会話に交ぜてもらいww、
ちょっと思い出したことがあったので書いてみようと思います。
(悪いとか良いとかそういうくくりの話に振るつもりはないので、どの方法をどう使うか、よーく考えて判断しましょうね、ぐらいの気持ちで書いいてますw)

 

世の中には、意図的にぐるぐる系バッチだけ、index unique scanや、access by rowidだけになることを狙い、統計情報も意図的に設定(e.g.  https://docs.oracle.com/cd/F82042_01/arpls/DBMS_STATS.html#GUID-F993ADFE-85A9-4939-84D1-CC7A6E5C56D2  など)したり、あたまを振り絞って考え抜かれた ぐるぐる系 の世界があったります。
待機イベントは、DB cpu, db file sequential readが主体で。(私は、やりたいとは思わないけどw)

 

ところで、

ぐるぐる系ってのは、いわゆるN+1問題に類する処理。
フレームワーク起因でそうなりやすいやつだけでなく、意図的にぐるぐるしちゃうというのもすべて含めたもの。基本的にネガティブな意味で使われていますw。
そんなJargonが存在する現場では、的確に意図が伝わるってのがメリットだったり。。。
N+1がさぁ〜より、ぐるぐるがさぁ〜ってほうが日本語の脳みそにはイメージしやすくてグっと来るw 方は多い感じはしますw。現場でうまれたJargonですのでw 英語3文字略語よりわかりやすくて良い(個人差ありw)

 

もう一つ、 ぐるぐる系の反対語のJargonって知ってます?

答え: ガツン系

ここまでの用語解説部分はツッコミいれるところではないので、かるくスルーしてくださいませ。 前説ですから。 m(_ _)m

(ちょっと脱線しすぎた感じはするけどもwwww)

 

ということで(どう言うことだ!w)、ながーーーーーーい前置きはこれぐらいにして本題へ。

ぐるぐる系とガツン系の予習をしていただいたところでww
ちょっとしたサンプルを紹介し、どちらを選ぶかは、アナタしだいです。という意味で、打ちっぱなしというか投げっぱなしの話を書いておこう思います。

Image-1

 

今回の例ではストアドプロシージャ化してあります。
ストアドプロシージャでぐるぐるしちゃった場合、ネットワークレイテンシーの影響は受けないのですが繰り返し部分のオーバーヘッド有無の差異は見えるはずです。
SQL文の繰り返しもそうですが、それ以外のオーバーヘッドも積もると山になるわけで。

なお、SQL文がものすごく軽くて速い、かつ、バッチ専用の別サーバーで実行される場合は繰り返し実行されるロジックだけでなく、サーバー間のネットワークレイテンシーの影響が見過ごせないぐらい(回避不能でもありますが)目立ってくることも思い出してくださいね。
以下の図をイメージしておくと危険な香りを感じ取れるのではないか思います。(特にクラウド上では)

パートの処理時間は記載の時間程度かかった場合のイメージで書いています。処理時間の延び具合も実際の処理だったり環境でも異なるのであくまで、そんなイメージにしてあります。

 

普通のぐるぐる系のイメージ(オンプレミスで隣接しているような環境だネットワークレイテンシーは短いのは言うまでもないですが)
20250420-161431

 

普通のぐるぐる系がネットワークレイテンシーの遅い環境に乗せられてしまった場合のイメージ(オンプレミス環境で熟成されたぐるぐる系がクラウドに乗ったらこうなったなんて話は、稀によく聞きますw。しかも自分たちではどうしようもないですよね。こればかりは。)
20250420-161442

 

普通のグルグル系のがネットワークレイテンシーの遅い環境に乗せられてしまった上に、実行計画がぶれて大変なことになってしまったイメージ
20250420-161450

 

最後に、がツン系にして回避したイメージ
20250420-161501

 

この辺り大切なので、試験にでるよ!(ないないw

 

利用したリリースは以下の通り。


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

 

差分マージを例ネタに、ぐるぐる系とガツン系の例、そして、ぐるぐる系のループ内で実行されるSQL文の実行計画が最悪の状態でぐるぐるしている例を載せておきますね。


準備

 

以下ディレクトリにcsvファイルを作成してあります。連番で100000行のデータが入っています。


[oracle@localhost ~]$ ll /home/oracle/rowbyrow_or_batch/*.csv
-rw-r--r--. 1 oracle oinstall 588894 1月 16 14:39 /home/oracle/rowbyrow_or_batch/data.csv
[oracle@localhost ~]$ cat /home/oracle/rowbyrow_or_batch/data.csv
1
2
3
4

...中略...

99996
99997
99998
99999
100000
[oracle@localhost ~]$

 

差分データを外部表で提供するためにディレクトリオブジェクト、対応するディレクトリを作成します。


SCOTT@orclpdb1> create directory rowbyrow_or_batch as '/home/oracle/rowbyrow_or_batch';

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

経過: 00:00:00.24
SCOTT@orclpdb1> select directory_name, directory_path from dba_directories where directory_name = upper('rowbyrow_or_batch');

DIRECTORY_NAME DIRECTORY_PATH
------------------------------ -------------------------------------------------
ROWBYROW_OR_BATCH /home/oracle/rowbyrow_or_batch

経過: 00:00:00.09
SCOTT@orclpdb1>

 

次に外部表を定義します。この時点では該当ディレクトリにcsvファイルは実在しなくても問題ありませんが、実行前には対応するファイルシステムのディレクトとcsvファイルを作成、配置しておく必要はあります。
(外部表作成時にそれらの存在は確認されていないだけです)


SCOTT@orclpdb1> DROP TABLE csv_data PURGE;
SCOTT@orclpdb1> CREATE TABLE csv_data
(
id NUMBER NOT NULL
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY rowbyrow_or_batch
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '|'
(
id
)
)
LOCATION (
'data.csv'
)
);

表が作成されました。

経過: 00:00:00.03

SCOTT@orclpdb1> desc csv_data
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER

SCOTT@orclpdb1> select min(id),max(id),count(1) from csv_data;

MIN(ID) MAX(ID) COUNT(1)
---------- ---------- ----------
1 100000 100000

SCOTT@orclpdb1> select count(distinct id) from csv_data;

COUNT(DISTINCTID)
-----------------
100000

 

差分データをマージする表を作成してデータを登録しておきます。(統計情報取得も忘れずに)


SCOTT@orclpdb1> CREATE TABLE tmp
(
ID NUMBER NOT NULL
, CONSTRAINT pk_tmp PRIMARY KEY (id) USING INDEX
);

表が作成されました。

経過: 00:00:00.17
SCOTT@orclpdb1> BEGIN
FOR i in 50001..200000 LOOP
INSERT INTO tmp VALUES(i);
IF MOD(i,100) = 0 THEN COMMIT; END IF;
END LOOP;
END;
/

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

経過: 00:00:08.57
SCOTT@orclpdb1> EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'TMP',cascade=>true, no_invalidate=>false);

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

経過: 00:00:01.86
SCOTT@orclpdb1> select min(id),max(id),count(1) from tmp;

MIN(ID) MAX(ID) COUNT(1)
---------- ---------- ----------
50001 200000 150000

経過: 00:00:00.03

 

はい! 準備完了。

上記表へ、csvファイルというか外部表との差分を追加する処理をぐるぐる系と、ガツン系。
そして、おまけで、荒れ狂う実行計画のうるぐるぐる系(ヒントを使って最悪の実行計画にしてるだけですが)w 実行してみましょう。

特に落ちはないですが、打ちっぱなしというか、投げぱなしにしますので、
みなさん、遠ーーーくを見ながら、あーだーこーだビール片手のおつまみにでもして考えてみるのも良いか、と。。

 

まず、ぐるぐる系から。


SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
50001 200000 150000

経過: 00:00:00.01
SCOTT@orclpdb1> exec rowbyrow_proc;

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

経過: 00:00:04.69
SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
1 200000 200000

経過: 00:00:00.01

 

次は、ガツン系で。


SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
50001 200000 150000

経過: 00:00:00.01
SCOTT@orclpdb1> exec allrows_batch_proc;

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

経過: 00:00:00.76
SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
1 200000 200000

 

最後に、おまけ。 荒れ狂うぐるぐる系w。(繰り返し実行されるSQL文の実行計画が最悪という設定)
こうなっちゃた経験のある方も少ないないはず。荒れ狂わないように最良の実行計画に固定しちゃう予防策を取っておく方が安全ではあります。


SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
50001 200000 150000

経過: 00:00:00.18
SCOTT@orclpdb1> exec badboy_rowbyrow_proc;

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

経過: 00:28:02.30
SCOTT@orclpdb1> select min(id),max(id),count(id) from tmp;

MIN(ID) MAX(ID) COUNT(ID)
---------- ---------- ----------
1 200000 200000

経過: 00:00:00.31

 

それぞれの実行計画も載せておきますね。

ぐるぐる系のrowbyrow_procプロシージャで実行したSQL文
(実際に3つの方法で確認してますが、monitor/gather_plan_statisticsヒントつけちゃってるのに、autotraceの結果だけのせているのでヒントが気になるかたは外しても問題ないです。多少処理時間は速くなるはずですが。)


MERGE
/*+
monitor
gather_plan_statistics
index(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
:l_id AS id
FROM
dual
) diff_data
ON
( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
)
;

SCOTT@orclpdb1> l
1 MERGE
2 /*+
3 monitor
4 gather_plan_statistics
5 index(tmp pk_tmp)
6 */
7 INTO tmp
8 USING (
9 SELECT
10 :l_id AS id
11 FROM
12 dual
13 ) diff_data
14 ON
15 ( tmp.id = diff_data.id )
16 WHEN NOT MATCHED THEN
17 INSERT VALUES
18 (
19 diff_data.id
20* )
SCOTT@orclpdb1> /

1行がマージされました。

経過: 00:00:00.02

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

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | 23 | 3 (0)| 00:00:01 |
| 1 | MERGE | TMP | | | | |
| 2 | VIEW | | | | | |
| 3 | NESTED LOOPS OUTER| | 1 | 23 | 3 (0)| 00:00:01 |
| 4 | VIEW | | 1 | 18 | 3 (0)| 00:00:01 |
| 5 | FAST DUAL | | 1 | | 3 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN| PK_TMP | 1 | 5 | 0 (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

6 - access("TMP"."ID"(+)=TO_NUMBER("DIFF_DATA"."ID"))

統計
----------------------------------------------------------
1 recursive calls
28 db block gets
3 consistent gets
3 physical reads
2432 redo size
204 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)
1 rows processed

 

ガツン系(allrows_batch_procプロシージャで実行)


MERGE
/*+
monitor
gather_plan_statistics
index_ffs(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
id
FROM
csv_data
) diff_data
ON ( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
)
;

SCOTT@orclpdb1>
1 MERGE
2 /*+
3 monitor
4 gather_plan_statistics
5 index_ffs(tmp pk_tmp)
6 */
7 INTO tmp
8 USING (
9 SELECT
10 id
11 FROM
12 csv_data
13 ) diff_data
14 ON ( tmp.id = diff_data.id )
15 WHEN NOT MATCHED THEN
16 INSERT VALUES
17 (
18 diff_data.id
19* )
SCOTT@orclpdb1> /

50000行がマージされました。

経過: 00:00:00.60

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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 100K| 1757K| 289 (15)| 00:00:01 |
| 1 | MERGE | TMP | | | | |
| 2 | VIEW | | | | | |
|* 3 | HASH JOIN OUTER | | 100K| 976K| 289 (15)| 00:00:01 |
|* 4 | EXTERNAL TABLE ACCESS FULL| CSV_DATA | 100K| 488K| 57 (20)| 00:00:01 |
| 5 | INDEX FAST FULL SCAN | PK_TMP | 150K| 732K| 214 (6)| 00:00:01 |
------------------------------------------------------------------------------------------

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

3 - access("TMP"."ID"(+)="ID")
4 - filter(SYS_OP_XTNN("CSV_DATA"."ID"))

統計
----------------------------------------------------------
44 recursive calls
101323 db block gets
593 consistent gets
0 physical reads
12691800 redo size
204 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)
50000 rows processed

 

荒れ狂うぐるぐる系badboy_rowbyrow_procプロシージャで実行したSQL文
計測回数が少ないので参考程度ですが、荒れ狂ってない場合の同一SQLと比較して、4倍も遅い状況。(一回当たりは 0.08秒でも積み上がると笑えないですね)


MERGE
/*+
monitor
gather_plan_statistics
index_ffs(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
:l_id AS id
FROM
dual
) diff_data
ON
( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
)
;

SCOTT@orclpdb1> l
1 MERGE
2 /*+
3 monitor
4 gather_plan_statistics
5 index_ffs(tmp pk_tmp)
6 */
7 INTO tmp
8 USING (
9 SELECT
10 :l_id AS id
11 FROM
12 dual
13 ) diff_data
14 ON
15 ( tmp.id = diff_data.id )
16 WHEN NOT MATCHED THEN
17 INSERT VALUES
18 (
19 diff_data.id
20* )
SCOTT@orclpdb1> /

1行がマージされました。

経過: 00:00:00.08

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

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | 23 | 226 (10)| 00:00:01 |
| 1 | MERGE | TMP | | | | |
| 2 | VIEW | | | | | |
|* 3 | HASH JOIN OUTER | | 1 | 23 | 226 (10)| 00:00:01 |
| 4 | VIEW | | 1 | 18 | 3 (0)| 00:00:01 |
| 5 | FAST DUAL | | 1 | | 3 (0)| 00:00:01 |
| 6 | INDEX FAST FULL SCAN| PK_TMP | 150K| 732K| 214 (6)| 00:00:01 |
----------------------------------------------------------------------------------

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

3 - access("TMP"."ID"(+)=TO_NUMBER("DIFF_DATA"."ID"))

統計
----------------------------------------------------------
1 recursive calls
8 db block gets
503 consistent gets
494 physical reads
780 redo size
204 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)
1 rows processed

 

 

本日利用したコードは以下の通り(csvファイルを作成したコードは略)


create directory rowbyrow_or_batch as '/home/oracle/rowbyrow_or_batch';
select directory_name, directory_path from dba_directories where directory_name = upper('rowbyrow_or_batch');

DROP TABLE csv_data PURGE;
CREATE TABLE csv_data
(
id NUMBER NOT NULL
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY rowbyrow_or_batch
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY ','
(
id
)
)
LOCATION (
'data.csv'
)
);

DROP TABLE tmp PURGE;
CREATE TABLE tmp
(
ID NUMBER NOT NULL
, CONSTRAINT pk_tmp PRIMARY KEY (id) USING INDEX
);

BEGIN
FOR i in 50001..200000 LOOP
INSERT INTO tmp VALUES(i);
IF MOD(i,100) = 0 THEN COMMIT; END IF;
END LOOP;
END;
/

EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'TMP',cascade=>true, no_invalidate=>false);


CREATE OR REPLACE PROCEDURE allrows_batch_proc AS
BEGIN
MERGE
/*+
monitor
gather_plan_statistics
index_ffs(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
id
FROM
csv_data
) diff_data
ON ( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/

CREATE OR REPLACE PROCEDURE rowbyrow_proc AS
C_DIR CONSTANT VARCHAR2(30) := 'ROWBYROW_OR_BATCH';
C_FILE_NAME CONSTANT VARCHAR2(30) := 'data.csv';
C_BUFFERSIZE CONSTANT BINARY_INTEGER := 32767;
C_OPENMODE CONSTANT VARCHAR2(2) := 'r';
fileHandle UTL_FILE.FILE_TYPE;
line VARCHAR2(32767);
l_id NUMBER;
rowcount NUMBER;
BEGIN
fileHandle := UTL_FILE.FOPEN(C_DIR, C_FILE_NAME, C_OPENMODE, C_BUFFERSIZE);
BEGIN
LOOP
UTL_FILE.GET_LINE(fileHandle, line, C_BUFFERSIZE);
l_id := TO_NUMBER(line);

MERGE
/*+
monitor
gather_plan_statistics
index(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
l_id AS id
FROM
dual
) diff_data
ON
( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
);

rowcount := rowcount + 1;
IF MOD(rowcount, 100) = 0 THEN
COMMIT;
NULL;
END IF;
END LOOP;

EXCEPTION
WHEN NO_DATA_FOUND THEN /* EOF */
NULL;
END;
COMMIT;
UTL_FILE.FCLOSE(fileHandle);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
IF UTL_FILE.IS_OPEN(fileHandle) THEN
UTL_FILE.FCLOSE(fileHandle);
END IF;
RAISE;
END;
/

CREATE OR REPLACE PROCEDURE badboy_rowbyrow_proc AS
C_DIR CONSTANT VARCHAR2(30) := 'ROWBYROW_OR_BATCH';
C_FILE_NAME CONSTANT VARCHAR2(30) := 'data.csv';
C_BUFFERSIZE CONSTANT BINARY_INTEGER := 32767;
C_OPENMODE CONSTANT VARCHAR2(2) := 'r';
fileHandle UTL_FILE.FILE_TYPE;
line VARCHAR2(32767);
l_id NUMBER;
rowcount NUMBER;
BEGIN
fileHandle := UTL_FILE.FOPEN(C_DIR, C_FILE_NAME, C_OPENMODE, C_BUFFERSIZE);
BEGIN
LOOP
UTL_FILE.GET_LINE(fileHandle, line, C_BUFFERSIZE);
l_id := TO_NUMBER(line);

MERGE
/*+
monitor
gather_plan_statistics
index_ffs(tmp pk_tmp)
*/
INTO tmp
USING (
SELECT
l_id AS id
FROM
dual
) diff_data
ON
( tmp.id = diff_data.id )
WHEN NOT MATCHED THEN
INSERT VALUES
(
diff_data.id
);

rowcount := rowcount + 1;
IF MOD(rowcount, 100) = 0 THEN
COMMIT;
NULL;
END IF;
END LOOP;

EXCEPTION
WHEN NO_DATA_FOUND THEN /* EOF */
NULL;
END;
COMMIT;
UTL_FILE.FCLOSE(fileHandle);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
IF UTL_FILE.IS_OPEN(fileHandle) THEN
UTL_FILE.FCLOSE(fileHandle);
END IF;
RAISE;
END;
/

Enjoy SQL!

 

今年は、桜吹雪を2回も見ることができたのですが、おそらく、20年ぶりw
では、また。

 


 

| | | コメント (0)

2025年1月30日 (木)

実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID

2025年最初のレントゲン写真ネタです。今日は、みなさん(私も忘れてましたw)、忘れちゃってると思う(あまり使うこともないので)ROWIDの話題で、2025年最初のSQLネタエントリーにしたいと思います。(他のネタを考えていたのですが、軽めのネタにしてみましたw)



ROWIDヒント、今現存公開されている最古のオンラインマニュアルでは、Oracle 7 Server 7.3.3 以降から9iのマニュアルに記載されています。(もっと前からあるよね〜。7.0のマニュアルとか流石に見つからないので確認できないですがw)
で、主役ではなくなりほぼ使われなくなったヒントとして、忘れ去られてしまうきっかけは、Oracle Database 10g以降のマニュアルに記載されなくなったことが大きいでしょうね。使うこともほぼ無いと言って良いヒントだからかなぁ
昔すぎて覚えてないですねw

他にも、使ってもらっては困る黒魔術的なヒントもマニュアルから隠されたりしたこともありました。v$sql_hintには残ってますが11g以降サポートされなくなりヒントを指定しても無視されるようになった有名なヒントですね。。。。w (まだ利用できたバージョンで、某氏が使おうとした黒魔術を必死で止めた思い出が蘇るw)
余談はこれぐらいで、本題へ。

現在確認できるオンラインマニュアルだと9iまで記載されていることが確認できます。
Oracle7 Server 7.3.3
Oracle7 Tuning, release 7.3.3 / Optimization Modes and Hints / Hints for Access Methods / ROWID

Oracle Database 8i 8.1.7
Oracle8i SQL リファレンス Vol.1 リリース 8.1 P.92 / ヒント / アクセス方法のヒント / ROWID

Oracle Database 9iR2
Oracle9i Database Performance Tuning Guide and Reference Release 2 (9.2) / ROWID


10g以降のマニュアルからは表記が消えています。ただし、v$sql_planには残っており、これまで通りに使えます(もそもそのアクセスパスを利用すること自体、かなりレアな状況ですよね :)
Oracle Database 10gR2
Oracle Database Online Documentation, 10g Release 2 (10.2) / Administration Database Performance Tuning Guide / 16 Using Optimizer Hints / 16.1.2.2 Hints for Access Paths

Oracle Database 11gR2
Oracle Database SQL言語リファレンス 11gリリース2 (11.2) / コメント / 表3-21 機能のカテゴリに分類したヒント


21cで確かめてみましょう。

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

SCOTT@orclpdb1> select * from v$sql_hint where name = 'ROWID';

NAME SQL_FEATUR CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OU CON_ID
---------- ---------- ---------- ---------- ------------ ---------- ---------- ---------- ----------
ROWID QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7 0

ROWID擬似列でそれぞれのROWIDを取り出してみますね。むかーし、ROWIDを使って、データブロック番号順にソートして性能問題を回避したことありましたっけw ( db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニング / バッファキャッシュ欠乏症 )

SCOTT@orclpdb1> select rowid,empno,ename from emp;

ROWID EMPNO ENAME
------------------ ---------- ------------------------------
AAASguAAMAAAACTAAA 7369 SMITH
AAASguAAMAAAACTAAB 7499 ALLEN
AAASguAAMAAAACTAAC 7521 WARD
AAASguAAMAAAACTAAD 7566 JONES
AAASguAAMAAAACTAAE 7654 MARTIN
AAASguAAMAAAACTAAF 7698 BLAKE
AAASguAAMAAAACTAAG 7782 CLARK
AAASguAAMAAAACTAAH 7839 KING
AAASguAAMAAAACTAAI 7844 TURNER
AAASguAAMAAAACTAAJ 7900 JAMES
AAASguAAMAAAACTAAK 7902 FORD
AAASguAAMAAAACTAAL 7934 MILLER

12行が選択されました。


まずROWIDヒントを使わず、単純に、ROWID疑義列を使って一本引きw

すごいですよね。ピンポイントでアクセスするので1blockだけしか読みこんんでないですよね。
ROWID関する注意などはマニュアルを見てくさだい。特にROWIDのリスキーな使い方などがわかると思うので。むかーーーーし、むかーーーし、ROWIDに仕様変更がありそういう使い方をしていたシステムの移行はかなり大変だったらしい。私は幸いなことにそういつ使い方をしていたシステムに出会わなかったのでラッキーではありましたが。。)

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.05

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

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 39 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------


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


さて、意地悪な実験ですw TABLE ACCESS BY USER ROWIDとなるところをですが、INDEXヒントやFULLヒントで抑止してみるととうなるか見ておきましょう。(想像通りだと思いますけども)

SCOTT@orclpdb1> select /*+ FULL(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.01

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

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

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

1 - filter(ROWID='AAASguAAMAAAACTAAA')


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
1142 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

SCOTT@orclpdb1> select /*+ INDEX(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.07

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

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 39 | 2 (0)| 00:00:01 |
|* 2 | INDEX FULL SCAN | PK_EMP | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

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

2 - filter(ROWID='AAASguAAMAAAACTAAA')


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

最後に、ROWIDヒントが使えることをSQL Hint Reportも含めて確認してみましょう。

SCOTT@orclpdb1> set autot off
SCOTT@orclpdb1> select /*+ gather_plan_statistics rowid(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20

経過: 00:00:00.00
SCOTT@orclpdb1> @show_actualplan

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
SQL_ID 69yuykdp7c1za, child number 0
-------------------------------------
select /*+ gather_plan_statistics rowid(emp) */ * from emp where rowid
= 'AAASguAAMAAAACTAAA'

Plan hash value: 1116584662

-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 1 (100)| | 1 |00:00:00.01 | 1 |
| 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 1 | 39 | 1 (0)| 00:00:01 | 1 |00:00:00.01 | 1 |
-----------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$1 / "EMP"@"SEL$1"

Outline Data
-------------

/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('21.1.0')
DB_VERSION('21.1.0')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
ROWID(@"SEL$1" "EMP"@"SEL$1")
END_OUTLINE_DATA
*/

Column Projection Information (identified by operation id):
-----------------------------------------------------------

1 - "EMP"."EMPNO"[NUMBER,22], "EMP"."ENAME"[VARCHAR2,10], "EMP"."JOB"[VARCHAR2,9], "EMP"."MGR"[NUMBER,22],
"EMP"."HIREDATE"[DATE,7], "EMP"."SAL"[NUMBER,22], "EMP"."COMM"[NUMBER,22], "EMP"."DEPTNO"[NUMBER,22]

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1

---------------------------------------------------------------------------

1 - SEL$1 / "EMP"@"SEL$1"
- rowid(emp)

Enjoy SQL

では、また。



Related article on Mac De Oracle

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

| | | コメント (0)

2024年12月27日 (金)

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

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

 

さて、本題。

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

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

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

 

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

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

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

 

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

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

 

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

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

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

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

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

 

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

Sql_20250105102401

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

 

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

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

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

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

 

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

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



 

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

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

 

SCOTT@orclpdb1> select banner_full from v$version;

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

SCOTT@orclpdb1> show parameter optimizer_mode

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

SCOTT@orclpdb1> show parameter optimizer_dynamic_sampling

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

SCOTT@orclpdb1> show parameter optimizer_adaptive_plans

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

前提条件は以下の通り。

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

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

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

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

 

SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

表が作成されました。

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

表が作成されました。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

100000行が選択されました。

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

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

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

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

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

SCOTT@orclpdb1> set autot off

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

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

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

100000行が選択されました。

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

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

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

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

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

SCOTT@orclpdb1> set autot off

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

 

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

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

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

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

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

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

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

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

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

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

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

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

 

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

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

100000行が選択されました。

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

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

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

100000行が選択されました。

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

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

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

 

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

...略...

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

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

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

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

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

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

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

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

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

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

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

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

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

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

100000行が選択されました。

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

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

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

100000行が選択されました。

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

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

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

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

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

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

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

 

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

 

SCOTT@orclpdb1> drop table hoge_tmp purge;

表が削除されました。

SCOTT@orclpdb1> drop table hoge_tmp2 purge;

表が削除されました。

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

表が作成されました。

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

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

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

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

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

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

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

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

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

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

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

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

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

100000行が選択されました。

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

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

100000行が選択されました。

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

100000行が選択されました。

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

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

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

100000行が選択されました。

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

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

 

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

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

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

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

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

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

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

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

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

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

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

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

 

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

 

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

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

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

100000行が選択されました。

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

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

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

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

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

SCOTT@orclpdb1> set autot off

 

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

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

 

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

では、また。

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

Enjoy! SQL and Optimizer Features! :)

 



今回利用したSQLなど

 

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


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


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

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


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

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

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


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

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

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

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



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

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

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


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


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


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


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




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

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


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


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

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


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


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

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


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


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


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

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

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


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

 

 

| | | コメント (0)

2024年12月13日 (金)

実行計画は, SQL文のレントゲン写真だ! No.65 / JPOUG Advent Calendar 2024 / JSON-Relational Duality Views

本エントリーは、13日の金曜日のエントリーです。
昨日のポストは、Takayuki Nishio (Nisshii0)さんの「[Oracle] ここが違うよ Autonomous Database! 初めてデータ移行して気づいたこと」でした。




ということで、私のターン。

皆様の期待通り?、13日の金曜日なので、JSON を取り上げておきたいと思います。(実は元々別のながーーーいネタを用意していたのですが、13日の金曜日であることに気づきwww 以下略)
Oracle Database のJSON関連機能について、すでに多くの方がブログ等で書かれていることもあり、本エントリーでは、Mac De Oracleっぽくw、実行計画という名のレントゲン写真はどうなのかw を診ておきたいと思います。


Stable Diffusion WebUI
20241206-131544

対象とするのは、JSON-Relational Duality Views です。
この機能、名前の通り、リレーショナル表のままで、JSONにマッピングするVIEWを通して使えるようにしたものでOracle Database 23aiの新機能の一つです。(雑に解説すると。。)

気になりますよね。JSON-Relational Duality Viewsを介してアクセスした場合の実行計画!!!!

ちょっとわき道に逸れるのですが、Oracle Databaseには、RDFView という機能があります。リレーショナル表のままで、RDF Graphのトリプルとして参照する機能ですよね。これまた雑に解説すると。

実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 おまけ#3
トリプルとして扱うために、対象の列毎にSELECT文を作る必要がありかつ3列なのでUNIONも必要で、という想像通りのなかなかのレントゲン写真(実行計画でした)。
Rdfview


果たして、JSON-Relational Duality Views ではどうなるのか。。。。

早速、試してみましょう。

23aiを利用しています。

COTT@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


まず、JSON-Relational Duality ViewsはViewなので元になるリレーショナル表を決めちゃいます。dept表をもとにして department表を作って使いましょう。 dept表のままでも良いのですけどもw

SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc dept
Name Null? Type
----------------------------------------- -------- ----------------------------
DEPTNO NOT NULL NUMBER(2)
DNAME VARCHAR2(14)
LOC VARCHAR2(13)

SCOTT@localhost:1521/freepdb1> select * from dept;

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

SCOTT@localhost:1521/freepdb1> create table department as select * from dept;

Table created.

SCOTT@localhost:1521/freepdb1> alter table department add constraint pk_department primary key (deptno) using index;

Table altered.


department表の列をJSONのキーにマップしちゃうだけ(おまけで, UPDATE/INSERT/DELETEも許可していますが、今回はSELECTしかしませんw)
JSON RELATIONAL DUALITY VIEWを作成します。(見ると リレーショナル表の列とJSONのキーをマップしているだけですね。シンプル)

SCOTT@localhost:1521/freepdb1> l
1 CREATE JSON RELATIONAL DUALITY VIEW department_dv
2 AS
3 SELECT
4 JSON { '_id' : d.deptno,
5 'departmentName' : d.dname,
6 'location' : d.loc }
7 FROM
8 department d
9* WITH UPDATE INSERT DELETE
SCOTT@localhost:1521/freepdb1> /

View created.

SCOTT@localhost:1521/freepdb1> select collection_name,collection_type from user_json_collections;

COLLECTION_NAME COLLECTION_T
------------------------------ ------------
DEPARTMENT_DV DUALITY VIEW

SCOTT@localhost:1521/freepdb1> set linesize 400
COTT@localhost:1521/freepdb1> select * from user_json_duality_views;

VIEW_NAME JSON ROOT_TABLE_NAME ROOT_TABLE_OWNER ALLOW_INSER ALLOW_UPDAT ALLOW_DELET READ_ONLY JSON_SCHEMA STATUS
------------------------------ ---- ------------------------------ ------------------------------ ----------- ----------- ----------- ----------- ------------------------------ -------
DEPARTMENT_DV DATA DEPARTMENT SCOTT TRUE TRUE TRUE FALSE {"title":"DEPARTMENT_DV","dbOb VALID
ject":"SCOTT.DEPARTMENT_DV","d
bObjectType":"dualit


特に何も指定せず問い合わせると、まんまのJSONが返されます。

SCOTT@localhost:1521/freepdb1> set long 4000
SCOTT@localhost:1521/freepdb1> set longchunk 4000
SCOTT@localhost:1521/freepdb1> set linesize 400
SCOTT@localhost:1521/freepdb1> select * from department_dv;

DATA
--------------------------------------------------------------------------------------------------------------------------------------------------
{"_id":10,"_metadata":{"etag":"66F269F721C734BAE74D56D6A948D0F6","asof":"0000000001799313"},"departmentName":"ACCOUNTING","location":"NEW YORK"}
{"_id":20,"_metadata":{"etag":"8A0701C115BFECAB64C34E2FF406FFDA","asof":"0000000001799313"},"departmentName":"RESEARCH","location":"DALLAS"}
{"_id":30,"_metadata":{"etag":"A7CDA588F9052B35B56E00BB22B6EC6F","asof":"0000000001799313"},"departmentName":"SALES","location":"CHICAGO"}
{"_id":40,"_metadata":{"etag":"93AE902896310C0DFFCE3FC70E0479F6","asof":"0000000001799313"},"departmentName":"OPERATIONS","location":"BOSTON"}


さて、table full scanになると思いますが、とりあえず見てみましょう。

SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> select * from department_dv;

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------
Plan hash value: 826413278

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

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

SCOTT@localhost:1521/freepdb1> set autot off
SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc department_dv
Name Null? Type
----------------------------------------- -------- ----------------------------
DATA JSON

SCOTT@localhost:1521/freepdb1> set linesize 400
SCOTT@localhost:1521/freepdb1> col _id for a30
SCOTT@localhost:1521/freepdb1> col departmentName for a30
SCOTT@localhost:1521/freepdb1> col location for a30

...略...

リレーショナル表っぽいクエリーにもできます。

SCOTT@localhost:1521/freepdb1> r
1 select d.data."_id",
2 d.data."departmentName",
3 d.data."location"
4 from department_dv d
5* order by 1

_id departmentName location
------------------------------ ------------------------------ ------------------------------
10 "ACCOUNTING" "NEW YORK"
20 "RESEARCH" "DALLAS"
30 "SALES" "CHICAGO"
40 "OPERATIONS" "BOSTON"

では、次に、主キーにマップした _id 列でアクセスしてみましょう。おおおおお、普通に、INDEX UQNIQE SCANでしたね。(想像していた通りですがw)
Predicate Information に現れるアクセスパスを見ると、 access("D"."DEPTNO"=30) となっており、内部的にはリレーショナル表を問い合わせるSQL文に書き換えられているように見えますよね(まだ調べていないですがw)

SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> select json_serialize(d.data pretty) from department_dv d where d.data."_id" = 30;

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3132674683

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

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

2 - access("D"."DEPTNO"=30)


Statistics
----------------------------------------------------------
8 recursive calls
0 db block gets
5 consistent gets
0 physical reads
0 redo size
782 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

SCOTT@localhost:1521/freepdb1> set autot off
SCOTT@localhost:1521/freepdb1> r
1* select json_serialize(d.data pretty) from department_dv d where d.data."location" = 'CHICAGO'

JSON_SERIALIZE(D.DATAPRETTY)
----------------------------------------------------------------------------------------------------
{
"_id" : 30,
"_metadata" :
{
"etag" : "A7CDA588F9052B35B56E00BB22B6EC6F",
"asof" : "0000000001799BF8"
},
"departmentName" : "SALES",
"location" : "CHICAGO"
}

...略...

SCOTT@localhost:1521/freepdb1> r
1 select
2 d.data."_id" as deptno
3 , d.data."departmentName" as department_name
4 , d.data."location" as location
5 from
6 department_dv d
7 where
8* d.data."location" = 'CHICAGO'

DEPTNO DEPARTMENT_NAME LOCATION
------------------------------ ------------------------------ ------------------------------
30 "SALES" "CHICAGO"


JSON-Relational Duality Viewsから問い合わせてもリレーショナル表を直接問い合わせるのと同じなんですねぇ。なんとなく安心w

13日の金曜日のJSONネタなので、恐ろしーい結果を期待していた方、ごめんなさいwwww JSON怖くないですw


明日のJPOUG Advent Calendar 2024は、ketsujiさんです。




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

| | | コメント (0)

2024年10月 2日 (水)

ミックさんの「SQL緊急救命室 ──非効率なコードを改善せよ! 」を査読でお手伝いした話

SQL緊急救命室 ──非効率なコードを改善せよ! - ミック(著)が9月14日に発売されました。
Photo_20241002094401

緊急治療wが必要なレベルのSQLと格闘する若手とベテラン医師(じゃなくて、DBエンジニア)のやり取り、覚えているだろうか。。
そう、2011-2012年にWeb+DB Pressに掲載されていた妙に親近感を覚えるやり取りw (それに加え、時代に合わせた新ネタもあり)

気楽に読めるSQL本なので、ワイン片手にニヤニヤしながら楽しめると思います(若手の立場でも、ベテラン、いずれの目線でもw)


余談
日頃から打ちっぱなしでタイポ多めの私が査読をお願いされまして、長ーい付き合いもあり断りきれずにw(嘘、喜んでぇ〜!)
お手伝いさせていただきました。今まで気づけなかった細かいSQLの癖に気づいたり、ちょっと得した気分もありましたw

ミックさん、新型コロナ後遺症で一時は心配な状況でしたが、見事に復活、勢い余って書籍のリリースラッシュ、体調大丈夫なの? 
と余計な心配したりしてますが、完全復活したみたいで、マジで、よかった、よかった:)

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

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

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

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

20240710-141853


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

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

SCOTT@orclpdb1> select * from dept;

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

SCOTT@orclpdb1> select * from emp;

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

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

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

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

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

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

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

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

経過: 00:00:00.18

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

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

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

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


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

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


MySQL(8.0.36)

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

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


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

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

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

SCOTT@orclpdb1> @scalar_subquery_unnest.sql

経過: 00:00:00.17

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

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

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

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


Enjoy SQL! and 癖

ではまた。






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

| | | コメント (0)

2024年5月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 #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年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)

2023年12月22日 (金)

PostgreSQLのexplain analyze中にCTRL+Cでキャンセルすると、途中まで実行されているActual Planの結果は出力されないわけだが、その挙動を、ほんの少し追ってみた件


本エントリーは、PostgreSQL Advent Calendar 2023 シリーズ2の Day 22向けエントリーです


 

今回は、PostgreSQLオンリーで、投げっぱなしだったやつをちょっと掘り下げた時のメモ的なエントリーです。。

 

以前、

MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る

というエントリーを書いたのですが、

explain (analyze) 実行中、CTRL+C した時、どのようにハンドングしてるんだろう?
Hookで拾って、extensionで、explain (analyze) でも途中までは結果を書き出せるようにできちゃったりするのだろうか? と。 

気になりすぎてしまったのでw、該当ソースコードを追い、象が生息する森の奥地に入ってみることにした。。。。

https://github.com/postgres/postgres

 

ただ、あてもないのに森林の奥地入り込むのは、迷子になる危険を感じたのでw、ちょっとだけ、生態を調べてみたw

すると、幸運なことに、先人が足を踏み入れた痕跡と思われるメモ見つけることができた。

PostgreSQLのシグナル周り
https://qiita.com/KazuyaTomita/items/6feb708d73190032dfed

 

ふむふむ、なんとなくわかった気持ちになる。。(理解したとは言ってない)

 

PostgreSQLのActual Plan取得のログ。
見ての通り、PostgreSQLは、explain (analyze)実行中にCTRL+Cでキャンセルすると、シンプルなキャンセルメッセージを返すだけ。
(MySQL/Oracleでは、キャンセルされるまでのActual Planを返してくれる。冒頭のリンク参照)

 

この表示されているメッセージは手がかりになりそうだなぁ。。。。


perftestdb=> explain (analyze, buffers, verbose) 
WITH RECURSIVE gen_nums(v)
AS
(
SELECT 1
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 <= 100000000
)
SELECT v from gen_nums;
^Cキャンセル要求を送信しました
ERROR: canceling statement due to user request
perftestdb=>

 

 

シグナル周りのハンドリング部分のコードを追って、このメッセージを出力しているところを特定すると良さげな気がしますよね。

ただ、explain を実行している箇所とは違いそうではある。。。ざっくり目だが。

postgres.cのPostgresMain()に以下の文がありますな〜

 

postgres.c

pqsignal(SIGINT, StatementCancelHandler);   /* cancel current query */

 

そして、至る所で、 CHECK_FOR_INTERRUPTS ()マクロが呼ばれてます〜〜。

CHECK_FOR_INTERRUPTS()マクロで、呼ばれているのが、 ProcessInterrupts() ですね。

 

miscadmin.h

 /* Service interrupt, if one is pending and it's safe to service it now */
#define CHECK_FOR_INTERRUPTS() \
do { \
if (INTERRUPTS_PENDING_CONDITION()) \
ProcessInterrupts(); \
} while(0)


ProcessInterrupts()をみると、以下に見覚えのあるエラーメッセージ "canceling statement due to user request"を出力している箇所を発見。

 

postgres.c

/*
* If we are reading a command from the client, just ignore the cancel
* request --- sending an extra error message won't accomplish
* anything. Otherwise, go ahead and throw the error.
*/
if (!DoingCommandRead)
{
LockErrorCleanup();
ereport(ERROR,
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling statement due to user request")));
}

 

explain (analyze)に関わりそうな以下のコードでは、CHECK_FOR_INTERRUPTSはないのだけど、executer配下のコードには沢山ある。。。

explain.c

execMain.c

 

... extensionでなんとか、、というのは雰囲気的に難しそうな。。。

 

explain (actual plan取得含む)実行中に CTRL+C キャンセルすると、

postgres.c の PostgresMain() -> CHECK_FOR_INTERRUPTS() -> ProcessInterrupts() -> ereport() -> errmsg("canceling statement due to user request") を出力して実行キャンセル
と言う流れっぽい。斜め読みした限りでは。

 

ぱっと見だが、

 

errmsg("canceling statement due to user request")の前に、

1. commandが実行されているか
2. 1.であれば、commandは、explain かつ analyzeか?
3. 2.であれば、explain analyzeのキャンセルされる前に取得されたActual Planの結果を出力

みたいなことを、ProcessInterrupts()内でやらないと難しそうだよなぁ〜と。extensionでやれそうな感じにも見えないし。。。
一旦、そっとGitHubを開いていたブラウザのタブを閉じたww

 

と言うことで、冬の夜長に、PostgreSQLのコードをちょっと追ってみた話はここまで。

 

PostgreSQL Advent Calendar 2023 ですがw 恒例?の、Oracle Pipelined Table Function で Christmas ASCII Art(クロスポストに続き2回目)

 



関連エントリー
MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る

| | | コメント (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月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年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
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
0 sorts (memory)
0 sorts (disk)
5 rows processed



本検証で利用したスクリプト( hinting_tech1.sql )

drop table table_a purge
/
create table table_a
(
id number primary key
, dummy_str varchar2(2000)
)
/

drop table table_b purge
/
create table table_b
(
id number primary key
, dummy_str varchar2(2000)
)
/


BEGIN
FOR i IN 1..50000 LOOP
INSERT INTO table_a VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/


BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO table_b VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
END;
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
END;
.
l
/


SELECT COUNT(1) FROM table_a
.
l
/
SELECT COUNT(1) FROM table_b
.
l
/

REM **** Original - No Hint - 1-0 ****
VARIABLE s NUMBER
VARIABLE e NUMBER
BEGIN
:s := 1;
:e := 5;
END;
.
/


SELECT
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 1-1 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a)
USE_HASH(b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 1-2 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off



truncate table table_a
.
l
/

truncate table table_b
.
l
/


BEGIN
FOR i IN 1..50000 LOOP
INSERT INTO table_b VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/


BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO table_a VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
END;
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
END;
.
l
/

SELECT COUNT(1) FROM table_a
.
l
/
SELECT COUNT(1) FROM table_b
.
l
/

REM **** Original - No Hint - 2-0 ****
SELECT
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 2-1 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a)
USE_HASH(b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 2-2 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


やっと、涼しくなってきたけど、涼しくなるのが急すぎて、まじで季節の変化が急激になってきたなぁと。

では、また。

| | | コメント (0)

2023年9月23日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる

Previously on Mac De Oracle
前回は、MySQLのHash Join を取り上げました。MySQL 8.0.32では NLJに使えるINDEXが存在していても、Hash Joinをヒントで強制することができる!(オプティマイザが選択することがある!)
(MySQLの生い立ちを考えると、Hash Joinとか言われると、MySQLもHash Joinが必要な時代になったのか〜。と遠くを見る自分がいるw)

ということで、今回は、前々回とりあげた、
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)で思い出したネタで、オプティマイザがパースに悩むというか、考えすぎている姿を、時間ではなく、オプティマイザトレースのサイズから見える化して比較!!! してみたいと思います。


実は、
10億年以上前に、Oracle Databaseで同じことやってましたw

MySQLへ話を戻すと、
MySQLのオプティマイザトレースは、Oracle Databaseのそれとは少々ことなり、パラメータで指定されたメモリー上にJSON形式で記録され、INFORMATION_SCHEMA経由で問い合わせて確認します。
パラメータで指定されたメモリーサイズを超えるトレースは切り捨てられてしまいます。Oracle Databaseのオプティマイザトレースとは異なる注意点ですね。

また、面白い特徴もあります。
オプティマイザトレースの出力サイズを事前に予測することはできないわけですが、メモリー内に記録しきれず切り捨てたバイト数をレポートしてくれます。
その機能を利用し、切り捨てたれたトレースのバイト数とトレースを記録するために指定したメモリーのバイト数の合計からトレースのサイズを確認することはできます! 面白い仕組みを提供してくれてますよね。

余談
Oracle/MySQLは、オプティマイザトレースを明示的に取得できますが、PostgreSQLって、オプティマイザトレースだけを取得するOracleの10053トレースMySQLのoptimizer_trace=onに類似する方法はなかったはず。
(DB側の機能の一部としてはないという認識なので、もし勘違いしていたら、ツッコミ🙇よろしくお願いします)



細けーことはこれぐらいにして、比較してみましょう。

まず、前回もネタにした、パース時間のながーーーーーーい、クエリー。
explainだけで、29秒 です!!  11! の組み合わせをみているわけですから、デフォルト設定のMySQL、ものすごーく考えてますよね。
OracleとかPostgreSQLならとっくに諦めている数です。

参考)
検証で利用しているMySQLの表及び索引定義とSQLスクリプトは以下ブログ参照のこと。
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る


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


mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (29.05 sec)

MySQLでオプティマイザトレースを取得するには、optimizer_trace を on 、optimizer_trace_max_mem_size に オプティマイザトレース記録に必要なメモリーサイズを指定します。
その後、explain文でオプティマイザトレースを取得します。なお、今回は、explainでパース部分だけをトレースして比較します。
https://dev.mysql.com/doc/refman/8.0/en/information-schema-optimizer-trace-table.html


optimizer_trace_max_mem_size を 100 bytesにして、確実に切り捨てが起きるように設定します。

mysql> show variables like 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name | Value |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.18 sec)

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.01 sec)

mysql> show variables like 'optimizer_trace';
+-----------------+-------------------------+
| Variable_name | Value |
+-----------------+-------------------------+
| optimizer_trace | enabled=on,one_line=off |
+-----------------+-------------------------+
1 row in set (0.00 sec)

mysql> show variables like 'optimizer_trace_max_mem_size';
+------------------------------+---------+
| Variable_name | Value |
+------------------------------+---------+
| optimizer_trace_max_mem_size | 1048576 |
+------------------------------+---------+
1 row in set (0.00 sec)

mysql> SET optimizer_trace_max_mem_size = 100;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'optimizer_trace_max_mem_size';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| optimizer_trace_max_mem_size | 100 |
+------------------------------+-------+
1 row in set (0.00 sec)

オプティマイザトレースの準備ができたので、explainしてみましょう!

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (7 min 3.67 sec)

次に、INFORMATION_SCHEMA.OPTIMIZER_TRACEからオプティマイザトレースを取得してみます。

2147483647 bytes (切り捨てられたトレースのサイズ)+ 100 bytes (optimizer_trace_max_mem_sizeパラメータに指定したサイズ)= 2,147,483,747 bytes およそ 2GB です

mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: explain format=tree
select
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `master`.`id` AS `id`,`master`.`dummya` AS `dummya`,`detail`.`id` AS `id`,`detail`.`subid` AS `subid`,`detail`.`dummya` AS `dummya`,`t2`.`id` AS `id`,
`t2`.`subid` AS `subid`,`t2`.`dummya` AS `dummya`,`t3`.`id` AS `id`,`t3`.`subid` AS `subid`,`t3`.`dummya` AS `dummya`,`t4`.`id` AS `id`,`t4`.`subid` AS `subid`,`t4`.`dummya` AS `dummya`,
`t5`.`id` AS `id`,`t5`.`subid` AS `subid`,`t5`.`dummya` AS `dummya`,`t6`.`id` AS `id`,`t6`.`subid` AS `subid`,`t6`.`dummya` AS `dummya`,`t7`.`id` AS `id`,`t7`.`subid` AS `subid`,
`t7`.`dummya` AS `dummya`,`t8`.`id` AS `id`,`t8`.`subid` AS `subid`,`t8`.`dummya` AS `dummya`,`t9`.`id` AS `id`,`t9`.`subid` AS `subid`,`t9`.`dummya` AS `dummya`,`t10`.`id` AS `id`,
`t10`.`subid` AS `subid`,`t10`.`dummya` AS `dummya` from ((((((((((`master` join `detail` on((`master`.`id` = `detail`.`id`))) join `detail` `t2` on((`t2`.`id` = `detail`.`id`)))
join `detail` `t3` on((`t3`.`id` = `t2`.`id`))) join `detail` `t4` on((`t4`.`id` = `t3`.`id`))) join `detail` `t5` on((`t5`.`id` = `t4`.`id`))) join `detail` `t6` on((`t6`.`id` = `t5`.`id`)))
join `detail` `t7` on((`t7`.`id` = `t6`.`id`))) join `detail` `t8` on((`t8`.`id` = `t7`.`id`))) join `detail` `t9` on((`t9`.`id` = `t8`.`id`))) join `detail` `t10` on((`t10`.`id` = `t9`.`id`)))"
},
{
"transformations_to_nested_joins": {
"transformations": [
"JOIN_condition_to_WHERE",
"parenthesis_removal"
],
"expanded_query": "/* select#1 */ select `master`.`id` AS `id`,`master`.`dummya` AS `dummya`,`detail`.`id` AS `id`,`detail`.`subid` AS `subid`,`detail`.`dummya` AS `dummya`,
`t2`.`id` AS `id`,`t2`.`subid` AS `subid`,`t2`.`dummya` AS `dummya`,`t3`.`id` AS `id`,`t3`.`subid` AS `subid`,`t3`.`dummya` AS `dummya`,`t4`.`id` AS `id`,`t4`.`subid` AS `subid`,
`t4`.`dummya` AS `dummya`,`t5`.`id` AS `id`,`t5`.`subid` AS `subid`,`t5`.`dummya` AS `dummya`,`t6`.`id` AS `id`,`t6`.`subid` AS `subid`,`t6`.`dummya` AS `dummya`,`t7`.`id` AS `id`,
`t7`.`subid` AS `subid`,`t7`.`dummya` AS `dummya`,`t8`.`id` AS `id`,`t8`.`subid` AS `subid`,`t8`.`dummya` AS `dummya`,`t9`.`id` AS `id`,`t9`.`subid` AS `subid`,
`t9`.`dummya` AS `dummya`,`t10`.`id` AS `id`,`t10`.`subid` AS `subid`,`t10`.`dummya` AS `dummya` from `master` join `detail` join `detail` `t2` join `detail` `t3`
join `detail` `t4` join `detail` `t5` join `detail` `t6` join `detail` `t7` join `detail` `t8` join `detail` `t9` join `detail` `t10`
where ((`t10`.`id` = `t9`.`id`) and (`t9`.`id` = `t8`.`id`) and (`t8`.`id` = `t7`.`id`) and (`t7`.`id` = `t6`.`id`) and (`t6`.`id` = `t5`.`id`) and (`t5`.`id` = `t4`.`id`)
and (`t4`.`id` = `t3`.`id`) and (`t3`.`id` = `t2`.`id`) and (`t2`.`id` = `detail`.`id`) and (`master`.`id` = `detail`.`id`))"
}
}
]
}
},


....中略....

},
"condition_filtering_pct": 100,
"rows_for_plan": 10240,
"cost_for_plan": 7163,
"pruned_by_cost": true
}

MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 2147483647
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.26 sec)

mysql>


次に、考え過ぎているオプティマイザに、
JOIN_ORDERヒントを利用し、結合順を 考えるな! 感じろ! 作戦でオプティマイザに考えさせないように。。。パースも速いし、オプティマイザトレースのJSONのサイズも小さい!

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


オプティマイザトレース結果のJSONサイズは、

57379 bytes (切り捨てられたトレースのバイト数) + 100 bytes(optimizer_trace_max_mem_sizeパラメータに指定したサイズ) = 57,479 bytes 、約 56 KBとなりました。

2GB vs 56KB

オプティマイザトレースのJSONサイズからオプティマイザの仕事量を覗いてみるのも面白いですよね。(俺だけか喜んでるのw)

mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
TRACE:
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 57379
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

ということで、 MySQLのオプティマイザトレースはメモリー中に記録され、メモリーに記録できないトレースは切り捨てられる。
また、切り捨てられたサイズ+オプティマイザトレース向けメモリーサイズからトレースを完全に取得するためのメモリーサイズを確認できる。
ただし、必要がメモリーの空き次第ではありそうですね。ということで、Oracle Databaseの 10053トレースとはちょっと違う注意点もあるな。

という知見を得た :)

Oracle Database の 10053トレースでのトレースによる考えている仕事量の見える化同様に、MySQLのオプティマイザトレースでも、オプティマイザが考えすぎていると様子をトレースサイズで見える化してみるという検証はここまで。
MySQLのオプティマイザトレースをなんとなく眺めて、なぜ、その判断をしたのだなどを追うのはまた別の機会に;)


10月も近いのに、muggy な気候の続く、東京より。

ではまた。





関連エントリー
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)
悩ませ過ぎは及ばざるがごとし #7 - おまけ



標準はあるにはあるが癖の多い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 オプティマイザの結合順評価テーブル数上限にも癖が出る

| | | コメント (0)

2023年9月 7日 (木)

MySQL 8.0.32では NLJに使えるINDEXが存在していても、Hash Joinをヒントで強制することができる!(オプティマイザが選択することがある!)

Previously on Mac De Oracle
前回は悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)で、パース時間が長い場合、ヒントを使うことでパース時間を短縮させることが可能なケースもあるというという話でした。Oracleでも同様のケースがあることもお話ししました。が、その中で、MySQL 8.0.32 ではオプティマイザが、NLJで利用するINDEXが存在していても、HJを選択することがあることに気づいた!(どうやら、該当表がバッファープールに乗っているとNLJを選択しやすくなる傾向もありそう)という、予告編までw

ということで、今日は、MySQL 8.0.32 では、NLJに利用するINDEXが存在していても、オプティマイザは、HJを選択することもあるし、ヒントによってHJを矯正できるのか? ということを確認してみました。


これまでの、MySQL 8.0.x台のHash Joinの適用範囲および挙動は以下のブログで取り上げられています。(非常に参考になりました。ありがたいですね。本当に ;)

MySQL 8.0.18のHASH JOINを試した(tom__bo’s Blog)
https://tombo2.hatenablog.com/entry/2019/10/14/212100

MySQL 8.0.20 のハッシュジョイン(Hash Join)を INDEX があるテーブルで試してみる(Qiita @hmatsu47)
https://qiita.com/hmatsu47/items/e473a3e566b910d61f5b

MySQL 8.0.20 でHASH JOINが効くケースが拡大した (mita2 database life)
https://mita2db.hateblo.jp/entry/2020/05/03/174101

と、NLJ可能な索引があるとなぜか、HJにならなかった。ついでに、HASH_JOINというヒントは、8.0.18でしか効果がない!
https://dev.mysql.com/doc/refman/8.0/ja/optimizer-hints.html#optimizer-hints-table-level


MySQL 8.0 Manual
https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html

"8.2.1.4 Hash Join Optimization By default, MySQL (8.0.18 and later) employs hash joins whenever possible. It is possible to control whether hash joins are employed using one of the BNL and NO_BNL optimizer hints, or by setting block_nested_loop=on or block_nested_loop=off as part of the setting for the optimizer_switch server system variable.

Note
MySQL 8.0.18 supported setting a hash_join flag in optimizer_switch, as well as the optimizer hints HASH_JOIN and NO_HASH_JOIN. In MySQL 8.0.19 and later, none of these have any effect any longer.

Beginning with MySQL 8.0.18, MySQL employs a hash join for any query for which each join has an equi-join condition, and in which there are no indexes that can be applied to any join conditions, such as this one:"


ななな、なーーーん、だってーーーーーーっ!。


だが、しかし、8.0.32 になるとマニュアルの記載では特に見かけなかったが、NLJに使える索引がある場合でも等価結合でHash Joinになるではありませんか。みなさん!

まじでーーーというか、実際、そうなるもん!

以下、前回のブログ参照のこと。
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編) (Mac De Oracle)
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2023/09/post-bee50f.html


ただし、 HASH_JOINヒントは使えないので、どのようにヒントを書けば Hash Joinを強制できるのだろう? 8.0.32編 ということで、試してみた!

結論から言ってしまうと

MySQL 8.0.32 では NLJに使えるINDEXが存在していても、Hash Joinを強制することができる! (いつから変わったかは不明だが、8.0.32では可能!)
MySQL 8.0.32 では NLJに使えるINDEXが存在していても、Hash Joinを強制することができる! (いつから変わったかは不明だが、8.0.32では可能!)


詳細は、以下の検証を。比較的気楽にヒントでHJを強制できるようです(だたしちょっと分かりずらいね。Oracleとかと比べると)

表定義等は前々回のエントリー参照のこと。帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る

起動直後、バッファキャッシュが空だと(未検証)Hash Joinが選択されるので、まずは、オプティマイザが Hash Join (等価結合でNLJに利用できる索引が存在する状況でも)が選択されている(前回のブログのおさらい)

[master@localhost ~]$ sudo service mysqld restart
Redirecting to /bin/systemctl restart mysqld.service
[master@localhost ~]$ mysql -u scott -p
Enter password:
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>
mysql> \. test1.sql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t10.id = `master`.id) (cost=20489.43 rows=10240)
-> Table scan on t10 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t9.id = `master`.id) (cost=10238.74 rows=5120)
-> Table scan on t9 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t8.id = `master`.id) (cost=5113.38 rows=2560)
-> Table scan on t8 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t7.id = `master`.id) (cost=2550.45 rows=1280)
-> Table scan on t7 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t6.id = `master`.id) (cost=1268.60 rows=640)
-> Table scan on t6 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t5.id = `master`.id) (cost=627.24 rows=320)
-> Table scan on t5 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t4.id = `master`.id) (cost=306.09 rows=160)
-> Table scan on t4 (cost=0.02 rows=20)
-> Hash
-> Inner hash join (t3.id = `master`.id) (cost=145.03 rows=80)
-> Table scan on t3 (cost=0.03 rows=20)
-> Hash
-> Inner hash join (t2.id = `master`.id) (cost=64.01 rows=40)
-> Table scan on t2 (cost=0.06 rows=20)
-> Hash
-> Inner hash join (detail.id = `master`.id) (cost=23.00 rows=20)
-> Table scan on detail (cost=0.12 rows=20)
-> Hash
-> Index scan on master using ix_master (cost=2.00 rows=10)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

関連する表をアクセスしてバッファープールに乗った状態にすると。オプティマイザは、INDEXを利用したNLJを選択するようになった。(前回のブログのおさらい)

mysql> 
mysql> select * from master;
+----+--------+
| id | dummya |
+----+--------+
| 1 | 1 |
| 10 | 10 |

....中略....

| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
+----+--------+
10 rows in set (0.01 sec)

mysql> select * from detail;
+----+-------+--------+
| id | subid | dummya |
+----+-------+--------+
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
| 2 | 2 | 22 |

....中略....

| 9 | 1 | 91 |
| 9 | 2 | 92 |
| 10 | 1 | 101 |
| 10 | 2 | 102 |
+----+-------+--------+
20 rows in set (0.01 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


オプティマイザがNLJを選択するようになった状況で、Hash Join となるヒントを追加(ヒント等は後半のスクリプト test1_hj.sql を参照のこと)すると、NLJで利用可能なINDEXが存在していたとしても、HJを強制できる。(8.0.20ごろまでは、HJは選択されなかったようなので、8.0.21から8.0.32の間でHJの適用範囲がさらに拡大されたのでしょうね。想像ですけども)

mysql> 
mysql> \. test1_hj.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t10.id = `master`.id) (cost=20468.86 rows=10240)
-> Table scan on t10 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t9.id = `master`.id) (cost=10226.19 rows=5120)
-> Table scan on t9 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t8.id = `master`.id) (cost=5104.85 rows=2560)
-> Table scan on t8 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t7.id = `master`.id) (cost=2544.11 rows=1280)
-> Table scan on t7 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t6.id = `master`.id) (cost=1263.65 rows=640)
-> Table scan on t6 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t5.id = `master`.id) (cost=623.31 rows=320)
-> Table scan on t5 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t4.id = `master`.id) (cost=303.02 rows=160)
-> Table scan on t4 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t3.id = `master`.id) (cost=142.76 rows=80)
-> Table scan on t3 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t2.id = `master`.id) (cost=62.50 rows=40)
-> Table scan on t2 (cost=0.02 rows=20)
-> Hash
-> Inner hash join (detail.id = `master`.id) (cost=22.25 rows=20)
-> Table scan on detail (cost=0.05 rows=20)
-> Hash
-> Table scan on master (cost=2.00 rows=10)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)


ついでなので、NLJになるようにがっつりヒントで固めてみた場合はどうなるか? 想定通り、NLJに倒せますね。(前回のブログのおさらい)

mysql> 
mysql> \. test1_nlj.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7173.00 rows=10240)
-> Nested loop inner join (cost=3589.00 rows=5120)
-> Nested loop inner join (cost=1797.00 rows=2560)
-> Nested loop inner join (cost=901.00 rows=1280)
-> Nested loop inner join (cost=453.00 rows=640)
-> Nested loop inner join (cost=229.00 rows=320)
-> Nested loop inner join (cost=117.00 rows=160)
-> Nested loop inner join (cost=61.00 rows=80)
-> Nested loop inner join (cost=33.00 rows=40)
-> Nested loop inner join (cost=19.00 rows=20)
-> Index scan on master using ix_master (cost=12.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>


検証で利用した 表と索引定義は前回のブログ参照のこと。

以下、今回検証で利用したスクリプト

オリジナル。結合順だけ JOIN_ORDERで制御。オプティマイザが、NLJ/HJをその時の気分で(嘘w)、その時の状況で判断して、NLJ/HJのいずれかを選択する(ことがわかっている 8.0.32 では)

[master@localhost ~]$ cat test1.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;


JOIN_ORDER/NO_INDEX/NO_JOIN_INDEXヒントを利用して、結合順と索引スキャンの抑止、そして、NLJで利用する索引の利用を抑止することで HJ になるようにしたスクリプト

[master@localhost ~]$ cat test1_hj.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
NO_INDEX(master ix_master)
NO_JOIN_INDEX(detail primary)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
NO_JOIN_INDEX(t5 primary)
NO_JOIN_INDEX(t6 primary)
NO_JOIN_INDEX(t7 primary)
NO_JOIN_INDEX(t8 primary)
NO_JOIN_INDEX(t9 primary)
NO_JOIN_INDEX(t10 primary)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;


最後に、前回も利用した NLJ に倒すためのガチガチヒントバージョンのスクリプト

[master@localhost ~]$ cat test1_nlj.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
INDEX(master ix_master)
JOIN_INDEX(detail primary)
JOIN_INDEX(t2 primary)
JOIN_INDEX(t3 primary)
JOIN_INDEX(t4 primary)
JOIN_INDEX(t5 primary)
JOIN_INDEX(t6 primary)
JOIN_INDEX(t7 primary)
JOIN_INDEX(t8 primary)
JOIN_INDEX(t9 primary)
JOIN_INDEX(t10 primary)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

ということで、まだまだまだ、蒸し暑いし、台風直撃しそうな、東京からお送りしました。台風に注意しつつ週末を過ごすしかなさそう。

では、また。





悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)

| | | コメント (0)

2023年9月 3日 (日)

悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)

Mac De OracleでMySQLとかPostgreSQLネタやるのって、2006年ごろの、異機種間接続サービス(Oracle Generic Connectivity)以来のような感覚になりますなw
あの頃は、

MySQL4.0.25 (MacOSX 10.4 Tiger)
MySQL4.0.26 (WindowsXP Pro)
MySQL4.1.13a (MacOSX 10.4 Tiger Server付属のもの)
PostgreSQL7.4.9 (MacOSX 10.4 Tiger)
Oracle10g R1 EE 10.1.0.3 for MacOSX

で遊んでた頃なので、みんな、それぞれ、バージョンが育ったりしてますよねwwww

では本題

Previously on Mac De Oracle
前回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る でした。

MySQLで結合数が多いとオプティマイザが考え過ぎてパース時間が伸びるケースとシステムパラメータでそれを制御する例でした。前回のエントリーでもちょっと書きましたが、似たようなケースで、Oracleではオプティマイザにそもそも頭を使わせないでパース時間を短縮させちゃうなんて、荒技でもな、テクニックがあります。それは単純、ヒントを指定してオプティマイザに該当部分で頭を使わせないようにするw

 

という方法です。

 

悩ませ過ぎは及ばざるがごとし #7 - おまけ

 

上記の例は、結合ではなくINリストとヒントでオプティマイザに考えさせないという方法です。
(なお、Oracleのヒントの指定方法は長年の研究によるちょっと特徴のあるヒントの指定方法ですがw 大丈夫ですよ。ちゃんと機能しますのでw 最近、OracleのSQLヒントレポートが出力できるようになったので、その辺り気にする人もいるのですけどもね。引用先のOracle SQLヒントの利用方法は、パース時間短縮専用ということではなく、一般的に私がよく使う方法でもあります。その辺のネタは次回にでもw)

 

では、MySQLでも効果があるのか、試してみましょう! というのが今日のお題。

 

MySQL 8.0.32で、パースに時間を要するケースの再現。optimizer_search_depthパラメータはデフォルトのまま

 

MySQL : optimizer_search_depth optimizer_search_depth


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

 


mysql> show variables like 'optimizer_search_depth';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| optimizer_search_depth | 62 |
+------------------------+-------+
1 row in set (0.00 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7162.25 rows=10240)
-> Nested loop inner join (cost=3578.25 rows=5120)
-> Nested loop inner join (cost=1786.25 rows=2560)
-> Nested loop inner join (cost=890.25 rows=1280)
-> Nested loop inner join (cost=442.25 rows=640)
-> Nested loop inner join (cost=218.25 rows=320)
-> Nested loop inner join (cost=106.25 rows=160)
-> Nested loop inner join (cost=50.25 rows=80)
-> Nested loop inner join (cost=22.25 rows=40)
-> Nested loop inner join (cost=8.25 rows=20)
-> Index scan on master using ix_master (cost=1.25 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (21.37 sec)

 

パースに 21.37 sec 要していますね。

というところまでは、前回のエントリーと同じですが、次は、パラメータで調整するのではなく、結合順を考えさせないためにヒントで結合順を指定みましょう。どうなりますかね。。。楽しみです。

 

JOIN_ORDERヒントで結合順を全て指定します。OracleのLEADINGヒントに似てますよね。


[master@localhost ~]$ cat test1.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

MySQLを起動して。。。試してみます。


[master@localhost ~]$ sudo service mysqld start
[sudo] master のパスワード:
Redirecting to /bin/systemctl start mysqld.service
[master@localhost ~]$ mysql -u scott -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
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> \. test1.sql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t10.id = `master`.id) (cost=20489.43 rows=10240)
-> Table scan on t10 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t9.id = `master`.id) (cost=10238.74 rows=5120)
-> Table scan on t9 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t8.id = `master`.id) (cost=5113.38 rows=2560)
-> Table scan on t8 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t7.id = `master`.id) (cost=2550.45 rows=1280)
-> Table scan on t7 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t6.id = `master`.id) (cost=1268.60 rows=640)
-> Table scan on t6 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t5.id = `master`.id) (cost=627.24 rows=320)
-> Table scan on t5 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t4.id = `master`.id) (cost=306.09 rows=160)
-> Table scan on t4 (cost=0.02 rows=20)
-> Hash
-> Inner hash join (t3.id = `master`.id) (cost=145.03 rows=80)
-> Table scan on t3 (cost=0.03 rows=20)
-> Hash
-> Inner hash join (t2.id = `master`.id) (cost=64.01 rows=40)
-> Table scan on t2 (cost=0.06 rows=20)
-> Hash
-> Inner hash join (detail.id = `master`.id) (cost=23.00 rows=20)
-> Table scan on detail (cost=0.12 rows=20)
-> Hash
-> Index scan on master using ix_master (cost=2.00 rows=10)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

パースめちゃめちゃ早くなりました! w オプティマイザよ、考えるな! 感じろ! いや、でも考えて! オプティマイザ!w

ただ、妙なのは、見積もりだからでしょうか?????? 等価結合なのにHash Joinになってます! MySQLってOracleとかPostgreSQLっぽくHash Join行ったりするんでしたっけ?????? 謎。。。。。

統計情報をとってあります。(前回の通りですが。。。。。。)

 

ちょっと気になるところがあります。前々回もそうだったので、MySQL起動直後だけ、該当表を一度もアクセスしていない場合には、なぜか、Hash Joinになるというのが再現しています!!!!!! なになになにこれ。。。。

 

簡単な実験、analyzeとかしませんよ。単純に、利用する表を全表走査してみますね。そのあとで、実行計画がどうなるかみてみましょう。ヒントは結合順の指定だけですよ。


mysql> 
mysql>
mysql> select * from master;
+----+--------+
| id | dummya |
+----+--------+
| 1 | 1 |
| 10 | 10 |

...中略...

| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
+----+--------+
10 rows in set (0.00 sec)

mysql> select * from detail;
+----+-------+--------+
| id | subid | dummya |
+----+-------+--------+
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
| 2 | 2 | 22 |
| 3 | 1 | 31 |

...中略...

| 8 | 2 | 82 |
| 9 | 1 | 91 |
| 9 | 2 | 92 |
| 10 | 1 | 101 |
| 10 | 2 | 102 |
+----+-------+--------+
20 rows in set (0.01 sec)

 

どうなるか変化するかどうか、乞うご期待!!!!!

 

きたーーーーーー。NLJに変化しました。。。。。MySQL的に、現時点ではこの実行計画の方が自然なのでは????? 教えて、マイエスキューエルの人!

 

本題である、パース時間は見事に早くなってますよね!!! 結合順を考えなくて済むわけですから、早いに決まってます!

考えるな! 感じろ! (でも考えて、オプティマイザ! 大切なので2度目の雄叫びw)


mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

MySQL 8.0.32、色々と楽しませてくれますねー。。 Oracle 7.0の挙動と戯れてたときを思い出しますねぇ〜。



 

おまけ、念の為ヒントバリバリで固めてみましたw 結果は同じです。 NLJになってくれてます


[master@localhost ~]$ cat test1.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
INDEX(master ix_master)
JOIN_INDEX(detail primary)
JOIN_INDEX(t2 primary)
JOIN_INDEX(t3 primary)
JOIN_INDEX(t4 primary)
JOIN_INDEX(t5 primary)
JOIN_INDEX(t6 primary)
JOIN_INDEX(t7 primary)
JOIN_INDEX(t8 primary)
JOIN_INDEX(t9 primary)
JOIN_INDEX(t10 primary)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

 


mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=22518.00 rows=10240)
-> Nested loop inner join (cost=11254.00 rows=5120)
-> Nested loop inner join (cost=5622.00 rows=2560)
-> Nested loop inner join (cost=2806.00 rows=1280)
-> Nested loop inner join (cost=1398.00 rows=640)
-> Nested loop inner join (cost=694.00 rows=320)
-> Nested loop inner join (cost=342.00 rows=160)
-> Nested loop inner join (cost=166.00 rows=80)
-> Nested loop inner join (cost=78.00 rows=40)
-> Nested loop inner join (cost=34.00 rows=20)
-> Index scan on master using ix_master (cost=12.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=2.02 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=2.01 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=2.00 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

なんか、Oracle7.0とか、8iとかのドキドキ感があるMySQL 8.0.x と遊ぶのもいいのですが、こればかりやってもいられない。Oraclerですし。PostgreSQLもありますしw ネタは色々湧いてきそうではありまする。。;)

では、また。

 



関連エントリー
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
悩ませ過ぎは及ばざるがごとし #7 - おまけ


yoku0825さんより、MySQL 8.0.xではoptimizerがバッファーブールに乗ってるデータを意識するようになったとの情報が寄せられました。
そこは見逃していた! とはいえ、以前よりHash Joinの適用範囲がさらに拡大?しているような挙動もあり、色々ドキドキしますね。
とはいえ、マニュアルによれば、HASH_JOINは、 (MySQL 8.0.18 のみ。MySQL 8.0.19 以降では無効です) との記載があり現状制御しにくいですね。索引を使えなくしたら倒れてくれるだろうか。。。別途試してみよう(9/5追記)

https://twitter.com/yoku0825/status/1698140599722110976


| | | コメント (0)

2023年9月 2日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る

Previously on Mac De Oracle前回の癖はw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
でした。
実は、今日の癖は、その検証時に気づいた、MySQLで結合表数が多いと、パース時間長くなってね? というか長いよね! という点。

オプティマイザは、皆さんの難しい(いや、難しい必要はないですが)SQLを無駄なく、そのSQLに見合った最短の時間で結果を返そうと努力して実行計画を立てています。これを解析フェーズとかパース時間とか、ハードパース時間とかいくつかのバリエーションで表現していたりします。
今回は、中をとってw パース時間としますね。SQL文の実行計画を立てる、オプティマイザがあれやこれや考えて実行計画を立て終えるまでの時間のことです。

先日の検証で、10表結合したのですが、MySQLでは、20秒以上要していました。実行計画でオプティマイザ考えすぎて帰ってこない。SQL文の実行ではなく実行計画を立てるところで時間がかかるケースはいくつか有名なのがありますが。
そうならないように各データベースは閾値を設けています。今回は、結合順評価テーブル数上限 ってやつです。結合する表の順序の組み合わせをどこまで検討するかということなのですが、これが、票数が増えると鰻登りに増加します。
今回のケースだと、1011表なので、10!11!ですね。2表をを繰り返し結合していてもしっかり考えてくれちゃいます。

ただ、何も考えずにオプティマイザに時間を与えていると、パース時間だけでとんでもない時間になったり、性能要件に見合わないといったケースが出てきます。なので、適当なところで手を打って、オプティマイザに考えさせ過ぎないようしています。
その閾値のデフォルト値が、MySQLとOracle/PostgreSQLとでは結構違うみたい。というところが今回の癖!

ちなみに、Oracleの例ですが、結合以外にINリストに大量にリテラルがあり、かつ考慮する必要のある索引も多いケースでは、悩ませ過ぎは及ばざるがごとし #7 - おまけなんてことも起こりますw オプティマイザを悩ませすぎると大変なので、そんな時は、SQLヒントなどを使って、オプティマイザに考えさせないという治療で回避するのが特効薬ですw

余談はこれぐらいにして、本題へ。

 

Note:
(MySQL/PostgreSQL/Oracle 23c freeそれぞれ、同一Virtualbox VMを利用して検証しているため、CPUリソース等の差異はありません)

最初は、ネタの発端になったMySQL 8.0.32から


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

mysql> show tables;
+----------------------+
| Tables_in_perftestdb |
+----------------------+
| detail |
| master |
+----------------------+
2 rows in set (0.00 sec)

mysql> desc detail;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| subid | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> show indexes from detail;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| detail | 0 | PRIMARY | 1 | id | A | 10 | NULL | NULL | | BTREE | | | YES | NULL |
| detail | 0 | PRIMARY | 2 | subid | A | 20 | NULL | NULL | | BTREE | | | YES | NULL |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.02 sec)

mysql> desc master;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | MUL | NULL | |
+--------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> show indexes from master;
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| master | 0 | PRIMARY | 1 | id | A | 10 | NULL | NULL | | BTREE | | | YES | NULL |
| master | 1 | ix_master | 1 | dummya | A | 10 | NULL | NULL | YES | BTREE | | | YES | NULL |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.02 sec)

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

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

mysql>

 

 

表定義とデータは以前のエントリーも参考に
MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?

 

では、

MySQL 8.0.32で、パースに時間を要するケースの再現。optimizer_search_depthパラメータはデフォルトのまま

 

MySQL : optimizer_search_depth optimizer_search_depth


mysql> show variables like 'optimizer_search_depth';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| optimizer_search_depth | 62 |
+------------------------+-------+
1 row in set (0.00 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7162.25 rows=10240)
-> Nested loop inner join (cost=3578.25 rows=5120)
-> Nested loop inner join (cost=1786.25 rows=2560)
-> Nested loop inner join (cost=890.25 rows=1280)
-> Nested loop inner join (cost=442.25 rows=640)
-> Nested loop inner join (cost=218.25 rows=320)
-> Nested loop inner join (cost=106.25 rows=160)
-> Nested loop inner join (cost=50.25 rows=80)
-> Nested loop inner join (cost=22.25 rows=40)
-> Nested loop inner join (cost=8.25 rows=20)
-> Index scan on master using ix_master (cost=1.25 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (21.37 sec)

パースに 21.37 sec 要していますね。 62!まで結合順を探査する設定ですから、このケースだと、10!まで行っているということなのでしょうね。

では、Oracleなどで設けられてる閾値と同等の 6 にせってしてみた場合のパース時間はどうでしょうね。このケースで実行計画を間違うことはない(単純なので)ですが、複雑な結合だと、実行計画をミスする率は多くなりそうですけども、Oracleとかだとその程度で使ってますし。。この程度でも良いのかもしれません。時間かかってもいいよというケースも当然あるので、非機能要件次第ではありますけどね。


mysql> set optimizer_search_depth = 6;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'optimizer_search_depth';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| optimizer_search_depth | 6 |
+------------------------+-------+
1 row in set (0.00 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7162.25 rows=10240)
-> Nested loop inner join (cost=3578.25 rows=5120)
-> Nested loop inner join (cost=1786.25 rows=2560)
-> Nested loop inner join (cost=890.25 rows=1280)
-> Nested loop inner join (cost=442.25 rows=640)
-> Nested loop inner join (cost=218.25 rows=320)
-> Nested loop inner join (cost=106.25 rows=160)
-> Nested loop inner join (cost=50.25 rows=80)
-> Nested loop inner join (cost=22.25 rows=40)
-> Nested loop inner join (cost=8.25 rows=20)
-> Index scan on master using ix_master (cost=1.25 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.27 sec)

はい! パース時間は、一気短縮され、 0.27 sec になりました。Oracleのハードパースでもこれぐらいなこんな程度だとは思います。

 

次に、PostgreSQLで確認してみましょ。 PostgreSQLの場合は、Oracleに近かったはず。表定義とデータ量は同じです。

PostgreSQLでは、結合順序評価上限は以下join_collapse_limitパラメータで制御するようですね。あまり気にしてなかったので脳のしわが一つ増えた! defaultは、8 でOracleより多少、多いというところですね。
join_collapse_limit

 


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=> show join_collapse_limit;
join_collapse_limit
---------------------
8
(1 行)

perftestdb=> \d+ master
テーブル"public.master"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
--------+-----------------------+----------+---------------+------------+------------+----------+------
id | integer | | not null | | plain | |
dummya | character varying(10) | | | | extended | |
インデックス:
"master_pkey" PRIMARY KEY, btree (id)
アクセスメソッド: heap

perftestdb=> \d+ detail
テーブル"public.detail"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
--------+-----------------------+----------+---------------+------------+------------+----------+------
id | integer | | not null | | plain | |
subid | integer | | not null | | plain | |
dummya | character varying(10) | | | | extended | |
インデックス:
"pk_detail" PRIMARY KEY, btree (id, subid)
アクセスメソッド: heap

perftestdb=> analyze verbose master;
INFO: analyzing "public.master"
INFO: "master": scanned 1 of 1 pages, containing 10 live rows and 0 dead rows; 10 rows in sample, 10 estimated total rows
ANALYZE
perftestdb=> analyze verbose detail;
INFO: analyzing "public.detail"
INFO: "detail": scanned 1 of 1 pages, containing 20 live rows and 0 dead rows; 20 rows in sample, 20 estimated total rows
ANALYZE
perftestdb=>

 

では試してみましょう。


perftestdb=> \timing on
タイミングは on です。
perftestdb=>
perftestdb=>
perftestdb=> \i ./test1_pg.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------
Hash Join (cost=19.53..156.40 rows=10240 width=116)
Hash Cond: (master.id = t8.id)
-> Hash Join (cost=12.93..31.40 rows=1280 width=83)
Hash Cond: (master.id = detail.id)
-> Hash Join (cost=6.33..10.00 rows=160 width=50)
Hash Cond: (master.id = t4.id)
-> Hash Join (cost=2.67..4.45 rows=40 width=28)
Hash Cond: (master.id = t7.id)
-> Hash Join (cost=1.23..2.50 rows=20 width=17)
Hash Cond: (t6.id = master.id)
-> Seq Scan on detail t6 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.10..1.10 rows=10 width=6)
-> Seq Scan on master (cost=0.00..1.10 rows=10 width=6)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t7 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=3.15..3.15 rows=40 width=22)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (t4.id = t5.id)
-> Seq Scan on detail t4 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t5 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=5.60..5.60 rows=80 width=33)
-> Hash Join (cost=2.90..5.60 rows=80 width=33)
Hash Cond: (detail.id = t3.id)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (detail.id = t2.id)
-> Seq Scan on detail (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t2 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t3 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=5.60..5.60 rows=80 width=33)
-> Hash Join (cost=2.90..5.60 rows=80 width=33)
Hash Cond: (t8.id = t10.id)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (t8.id = t9.id)
-> Seq Scan on detail t8 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t9 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t10 (cost=0.00..1.20 rows=20 width=11)
(41 行)

時間: 29.117 ミリ秒
perftestdb=>

 

MySQLで上限を制限した時間と似たような時間でパースが行われていますね。ただ、HASH JOINが選択されていますがw この辺りも、MySQLの生い立ちと、PostgreSQL、そしてOracleの違いとして現れてきます。MySQL 8のHash Joinって、MySQL 8.0.20 でHASH JOINが効くケースが拡大したという元々、Block Nested Loopの置き換えとしての意味が強かったという生い立ちが影響しているようにも見えます。なので、等価結合のINNER JOIN(今回の例)の場合、MySQL 8.0.32でもNLJが選択されているということのようですね。似てるようで似てないRDBMSの世界。まだまだ、私も学びが必要ですね。ちょっと曲のあるMySQLのHash Joinの発動条件。。いずれPostgreSQLやOracleっぽくHash Joinをカジュアルに使えちゃう日が来るのだろうか。。。

 

最後に真打w Oracle Database

 

今回は、23c Freeで試します。ただし、前回のエントリーにあるように、23c freeでは、"_optimizer_max_permutations" = 300と少なく設定されています。
今回の確認では、正式リリースされれば、通常の 2000 になるだろう。という想定で、 2000 にしてハードパース時間を確認しておきます。


SCOTT@freepdb1> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 23c Free, Release 23.0.0.0.0 - Developer-Release

 

デフォルトの設定はいかですが、.sqlスクリプト内で、 _optimizer_max_permutations を 2000 に設定して実行します!


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


SCOTT@freepdb1> desc master
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
DUMMYA VARCHAR2(10)

SCOTT@freepdb1> desc detail
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
SUBID NOT NULL NUMBER
DUMMYA VARCHAR2(10)

SCOTT@freepdb1> select table_name,index_name,column_name from user_ind_columns where table_name in ('MASTER','DETAIL') order by table_name,index_name,column_position;

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
DETAIL PK_DETAIL ID
DETAIL PK_DETAIL SUBID
MASTER SYS_C008265 ID

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

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

経過: 00:00:01.08
SCOTT@freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'DETAIL',cascade=>true,no_invalidate=>false);

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

経過: 00:00:00.13

では、試してみましょう!


SCOTT@freepdb1> @test1_ora

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

経過: 00:00:00.01

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_optimizer_max_permutations integer 2000
1* alter system flush shared_pool

システムが変更されました。

経過: 00:00:00.68

解析されました。

経過: 00:00:00.27

 

一般的なOracleの_optimizer_max_permutationsパラメータの設定で、 0.27 sec となりました. こんなもんでしょうね。- this is an adaptive plan となっているので、ACTUAL PLANを見ないと確定できないケースですが、多分
Hash joinが選ばれていることでしょうw(なので確認まではしませんが)

 

ついでなので、実行計画(見積もり)を見ておきましょう。 hash joinになってますね。一部、SORT MERGE JOINになってますが、


SCOTT@freepdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------
Plan hash value: 3620033460

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10240 | 950K| 33 (4)| 00:00:01 |
|* 1 | HASH JOIN | | 10240 | 950K| 33 (4)| 00:00:01 |
| 2 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 5120 | 430K| 30 (4)| 00:00:01 |
| 4 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 2560 | 192K| 27 (4)| 00:00:01 |
| 6 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 7 | HASH JOIN | | 1280 | 87040 | 24 (5)| 00:00:01 |
| 8 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 9 | HASH JOIN | | 640 | 37760 | 21 (5)| 00:00:01 |
| 10 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 11 | HASH JOIN | | 320 | 16000 | 18 (6)| 00:00:01 |
| 12 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 13 | HASH JOIN | | 160 | 6560 | 15 (7)| 00:00:01 |
|* 14 | HASH JOIN | | 80 | 2560 | 12 (9)| 00:00:01 |
|* 15 | HASH JOIN | | 40 | 920 | 9 (12)| 00:00:01 |
| 16 | MERGE JOIN | | 20 | 280 | 6 (17)| 00:00:01 |
| 17 | TABLE ACCESS BY INDEX ROWID| MASTER | 10 | 50 | 2 (0)| 00:00:01 |
| 18 | INDEX FULL SCAN | SYS_C008265 | 10 | | 1 (0)| 00:00:01 |
|* 19 | SORT JOIN | | 20 | 180 | 4 (25)| 00:00:01 |
| 20 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------


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

174行が選択されました。

経過: 00:00:00.71

MySQL/PostgreSQL/Oracleそれぞれ、ハードパースの結合順評価テーブル数上限にも癖というか違いがあって、かつ、それが、パース時間の差になってみたり。癖が多いですよね。それぞれの特徴というか。
ついでに、実行計画、MySQLのHash Joinの発動ケース。現状OracleやPostgreSQLのようには使えないので、その辺りは、認識しておいた方が良いですよね。癖として ;)

8月末に東北に居たのですが、例年だと朝晩は涼しくて、過ごしやすいのに、今年は、ダメですね。東京と同じです。農作物への影響が気になりますね。寒暖の差が美味しさに影響するのもありますからね。。。

 

では、また。

 

次回は、OracleのSQLヒントネタでもしようかと思ってます。Oracleのヒントレポートが出力されるようになって、そこ気になる、なぜ。みたいな方も多かったり、それにより無用な議論があったりと聞いているのでw 一応書いておこうかなと。

 



MySQL/PostgreSQL/Oracleで利用したスクリプト(テーブル、索引定義およびデータ取得と統計取得コマンドは本文参照のこと)

MySQL


[master@localhost ~]$ cat test1.sql
use perftestdb;


explain
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

PostgreSQL


[master@localhost ~]$ cat test1_pg.sql
explain
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

Oracle


[master@localhost ~]$ cat test1_ora.sql
alter session set "_optimizer_max_permutations"=2000;
show parameter "_optimizer_max_permutations"

alter system flush shared_pool
.
l
/


explain plan for
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 



関連エントリー
Oracle Database 23c Free Developer Releaseの”_optimizer_max_permutations” parameterの設定値について

 


標準はあるにはあるが癖の多い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取得中のキャンセルでも癖が出る

| | | コメント (0)

2023年9月 1日 (金)

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

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

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


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

これ

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


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

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

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



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

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


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

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


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

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


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

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


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

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

では、また。

| | | コメント (0)

2023年8月29日 (火)

帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る

前々回のエントリーで、MySQL 8.0.32のActual Plan取得中にキャンセルしてもキャンセルされるまでのActual Planを取得できることを確認した。

では、OracleやPostgreSQLではどうなのだろう?(Oracleは皆さんご存知だと思いますが、確認の意味も含めて)

Oracle Database 21c と PostgreSQL 13.6 (私の環境の都合上w)で確認しておこうと思います。

Actual Plan欲しいけど、本番環境でしか試せないとか、実行に数時間以上要するような状況と大人の事情で、泣く泣くキャンセルしなければいけない。
でも、途中まででもいいからActual Plan返してくれたら、原因特定できる。。。かもしれないし。。。と思いますよね。

MySQL 8.0.32では、キャンセルしても途中まで取得できました。
(SQLパースフェーズの場合を除く。これはOracleでも、PostgreSQLでも同じでしょう。だって、実行前の解析フェーズでキャンセルされたらActual Planなんて返しようがないですからね)

 

では、Oracle Database 21cから見てみましょう!
(ご存知の方も多いと思いますので最初に答えを言っちゃうと、Oracleの場合、Actual Plan取得でも、SQLモニターの場合でもキャンセルしても途中までの結果を見ることができます!)

 

再帰問合せを利用した連番生成を行う方法で試してみます。


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

 

gather_plan_statisticsヒントを利用した、Actual Plan取得(キャンセルせず最後まで実行した例です)


SCOTT@freepdb1> @actual
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ gather_plan_statistics */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums

 


SCOTT@freepdb1>  @actual_plan 6n3sc0n82t9dq

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 6n3sc0n82t9dq, child number 0
-------------------------------------
WITH gen_nums(v) AS ( SELECT /*+ gather_plan_statistics */ 1 FROM
dual UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <=
10000000 ) SELECT v from gen_nums

Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Writes | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 4 (100)| | 10M|00:07:57.97 | 105M| 28831 | | | |
| 1 | VIEW | | 1 | 2 | 26 | 4 (0)| 00:00:01 | 10M|00:07:57.97 | 105M| 28831 | | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | 1 | | | | | 10M|00:07:53.30 | 105M| 28831 | 2048 | 2048 | 97M (0)|
| 3 | FAST DUAL | | 1 | 1 | | 2 (0)| 00:00:01 | 1 |00:00:00.01 | 0 | 0 | | | |
| 4 | RECURSIVE WITH PUMP | | 10M| | | | | 9999K|00:00:16.81 | 0 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

同SQLを途中でキャンセルした例です。
A-Rows/A-Timeが途中まで実行されたことを示しています。


SCOTT@freepdb1> @actual
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ gather_plan_statistics */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums
^CSCOTT@freepdb1>

 


SCOTT@freepdb1> @actual_plan 6n3sc0n82t9dq

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 6n3sc0n82t9dq, child number 0
-------------------------------------
WITH gen_nums(v) AS ( SELECT /*+ gather_plan_statistics */ 1 FROM
dual UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <=
10000000 ) SELECT v from gen_nums

Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Writes | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 4 (100)| | 7479K|00:06:29.48 | 76M| 21535 | | | |
| 1 | VIEW | | 1 | 2 | 26 | 4 (0)| 00:00:01 | 7479K|00:06:29.48 | 76M| 21535 | | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | 1 | | | | | 7479K|00:06:25.85 | 76M| 21535 | 2048 | 2048 | 2048 (0)|
| 3 | FAST DUAL | | 1 | 1 | | 2 (0)| 00:00:01 | 1 |00:00:00.01 | 0 | 0 | | | |
| 4 | RECURSIVE WITH PUMP | | 7479K| | | | | 7479K|00:00:12.40 | 0 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

次に、SQL監視の例を見てみます。まず、キャンセルせず、最後まで実行した場合の例


Global Information
------------------------------
Status : DONE (ALL ROWS)
Instance ID : 1
Session : SCOTT (379:17125)
SQL ID : 8m5nwydj0rk2t

...中略...

Global Stats
============================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
============================================================================
| 294 | 195 | 3.56 | 95 | 100K | 105M | 1126 | 225MB |
============================================================================

SQL Plan Monitoring Details (Plan Hash Value=1492144221)
============================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
============================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 411 | +0 | 1 | 10M | | | . | . | | |
| 1 | VIEW | | 2 | 4 | 411 | +0 | 1 | 10M | | | . | . | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 411 | +0 | 1 | 10M | 1126 | 225MB | 98MB | 226MB | 95.44 | Cpu (248) |
| | | | | | | | | | | | | | | direct path write temp (3) |
| 3 | FAST DUAL | | 1 | 2 | 1 | +0 | 1 | 1 | | | . | . | | |
| 4 | RECURSIVE WITH PUMP | | | | 411 | +0 | 10M | 10M | | | . | . | 1.90 | Cpu (5) |
============================================================================================================================================================================================

 

次に途中でキャンセルした場合の例。 410sec要していたので、10sec後ぐらいにキャンセルしました。どちらの方法でもキャンセルした場合でも途中までのActual Planを取得できました。MySQLは、Actual Plan取得時のキャンセルの挙動をOracle Database側にあわせたのでしょうかね?

 

SQLモニターの場合、Global informationのStatusにも着目してください。全行取得できたのか、途中で止められたのか、エラーで途中終了したのかなどの情報も確認できます。


  1  WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ MONITOR */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums
^CSCOTT@orclpdb1>

 


Global Information
------------------------------
Status : DONE (FIRST N ROWS)
Instance ID : 1
Session : SCOTT (379:17125)
SQL ID : 8m5nwydj0rk2t

...中略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 10 | 5.49 | 4.45 | 7216 | 2M |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1492144221)
=========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 19 | +0 | 1 | 722K | . | | |
| 1 | VIEW | | 2 | 4 | 19 | +0 | 1 | 722K | . | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 19 | +0 | 1 | 722K | 34MB | 83.33 | Cpu (5) |
| 3 | FAST DUAL | | 1 | 2 | 1 | +0 | 1 | 1 | . | | |
| 4 | RECURSIVE WITH PUMP | | | | 19 | +0 | 722K | 722K | . | | |
=========================================================================================================================================================

 

次にPostgreSQL


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=> explain (analyze, buffers, verbose) 
perftestdb-> WITH RECURSIVE gen_nums(v)
perftestdb-> AS
perftestdb-> (
perftestdb(> SELECT 1
perftestdb(> UNION ALL
perftestdb(> SELECT v + 1
perftestdb(> FROM
perftestdb(> gen_nums
perftestdb(> WHERE v + 1 <= 100000000
perftestdb(> )
perftestdb-> SELECT v from gen_nums;

QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
CTE Scan on gen_nums (cost=3.21..3.83 rows=31 width=4) (actual time=0.007..98357.448 rows=100000000 loops=1)
Output: gen_nums.v
Buffers: temp written=170898
CTE gen_nums
-> Recursive Union (cost=0.00..3.21 rows=31 width=4) (actual time=0.005..67023.784 rows=100000000 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=1)
Output: 1
-> WorkTable Scan on gen_nums gen_nums_1 (cost=0.00..0.26 rows=3 width=4) (actual time=0.000..0.000 rows=1 loops=100000000)
Output: (gen_nums_1.v + 1)
Filter: ((gen_nums_1.v + 1) <= 100000000)
Rows Removed by Filter: 0
Planning Time: 0.090 ms
Execution Time: 103968.625 ms
(13 行)

 

キャンセルした例。
Planning Time: 0.090 ms なので、パースフェーズ中にキャンセルする方が難しいですね。103秒ほど要するので、60秒ぐらいでキャンセルしましたが、PostgreSQLの場合は、Actual Plan取得中のキャンセルでは何も出力されません。
XにPostしたところ、篠田さんから、本体側で対応しないと返せなようだ、とコメントをもらいコードを見始めたり() 機能として、あったら便利かもしれないですね。PostgreSQLでも。キャンセルしたところまでのActual Planを返してくれたら問題解決の糸口になることもあるだろうし。


perftestdb=> explain (analyze, buffers, verbose) 
WITH RECURSIVE gen_nums(v)
AS
(
SELECT 1
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 <= 100000000
)
SELECT v from gen_nums;
^Cキャンセル要求を送信しました
ERROR: canceling statement due to user request
perftestdb=>

 


 

温泉三昧と、いつも満席で諦めてた蕎麦屋のいくつかは食べ歩けた遅いお盆休みも終わり。もう9月が目の前ですね。

 

では、また。

 

 



標準はあるにはあるが癖の多い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

| | | コメント (0)

2023年8月 4日 (金)

MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?

本年度一発目のw Databaseネタは、なんと、MySQLネタからのスタートですw

MySQL 8.0.18以降、Actualプランが取得できるようになってた。
また、MySQL 8.0.20 以降、KILL QUERY または CTRL-C を使用してこのステートメントを終了できるとある。
ただ、マニュアルに記載されているのはここまでで、キャンセルできることは記載されているが、キャンセルした場合のActual Planはどこまで出力されるのだろう? or 全く出力されない?

OracleのリアルタイムSQL監視だとキャンセルすると不完全ではあるけども、なんとなーく判断できる程度の情報は途中まで取得できるが、
GATHER_PLAN_STATISTICSヒントなどを使ったActualプランは最後まで実行しないとアcつあlはしゅとくできない。どちらのタイプに似ているのでしょうね?

長時間(数日とかw)かかってしまうようになったSQLのActualプラン取得するの意外と難しいケースも場合によってはあったりするわけで。。。そんなとき、Actualみたいけど、キャンセルしたらどうなるのだろうと。。
途中まででも取得できるのか、それとも、Nothingなのか。。。

MySQLのActualプランの取得はどうなんだろう。。。ということで、試してみた。

EXPLAIN ANALYZE による情報の取得
https://dev.mysql.com/doc/refman/8.0/ja/explain.html#explain-analyze





結論から書くと、
MySQL 8.0.32での検証だが、

explain analyzeで取得できるActual Planは、対応しているSQL文の実行中にCTRL-Cでキャンセルしても、その時点までの、Actual Planを表示してくれる!!!!!!

ただし、パース時間が異常に長めのSQL文(結合数めちゃ多いとか)だと、実行以前に、パース時間が長いため、パースフェーズでキャンセルしてしまうの何も表示されない。。と(最初はこちらを引いてしまったので、常に表示されないものかと思い込んでしまった)
他の方法で実行をキャンセルした場合も同じだろう。とは思う。

いや、他にも出力されないケースがあるとか、その検証方法だから出力されているだけどか、MySQLのexplain analyzeのディープなツッコミがありましたら、よろしくお願いします。:)

MySQLの8.0台ってマイナー番号変わっても機能追加されたり変化しているので、一応。 MySQL 8.0.32 上では。ということにしておく。




以下、検証の記録的なもの。

確認に使用した MySQLのバージョン

[master@localhost ~]$ mysql -u scott -p
Enter password:
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)

事前に準備したクエリ向け表と索引など

Database changed
mysql> use perftestdb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql>
mysql> desc master;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

mysql> desc detail;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| subid | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> show indexes from master;
+--------+------------+----------+--------------+-------------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Index_type |
+--------+------------+----------+--------------+-------------+------------+
| master | 0 | PRIMARY | 1 | id | BTREE |
+--------+------------+----------+--------------+-------------+------------+
1 row in set (0.02 sec)

mysql> show indexes from detail;
+--------+------------+----------+--------------+-------------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Index_type |
+--------+------------+----------+--------------+-------------+------------+
| detail | 0 | PRIMARY | 1 | id | BTREE |
| detail | 0 | PRIMARY | 2 | subid | BTREE |
+--------+------------+----------+--------------+-------------+------------+
2 rows in set (0.02 sec)

mysql> select * from master;
+----+--------+
| id | dummya |
+----+--------+
| 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.01 sec)

mysql> select * from detail;
+----+-------+--------+
| id | subid | dummya |
+----+-------+--------+
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
| 2 | 2 | 22 |
| 3 | 1 | 31 |
...略...
| 7 | 1 | 71 |
| 7 | 2 | 72 |
| 8 | 1 | 81 |
| 8 | 2 | 82 |
| 9 | 1 | 91 |
| 9 | 2 | 92 |
| 10 | 1 | 101 |
| 10 | 2 | 102 |
+----+-------+--------+
20 rows in set (0.01 sec)

検証用に作ったSQL。どのぐらいのElapsed Timeかanaluyzeをつけて実行して、キャンセルしやすい程度のElapsed Timeか確認しておきます。

mysql> explain analyze
-> select *
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.115..37.938 rows=10240 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.108..18.747 rows=5120 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.102..9.267 rows=2560 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.097..4.689 rows=1280 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.091..2.336 rows=640 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.086..1.203 rows=320 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.080..0.630 rows=160 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.075..0.337 rows=80 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.069..0.189 rows=40 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.063..0.110 rows=20 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.041..0.047 rows=10 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.005..0.006 rows=2 loops=10)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.003..0.004 rows=2 loops=20)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.002..0.003 rows=2 loops=40)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=80)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=160)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=320)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=640)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=1280)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=2560)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=5120)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (21.18 sec)


では、explain analyseを途中でキャンセルしてみます。elapsed timeが21秒ほどなので、20秒後にキャンセルしてみます。

mysql> explain analyze
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on t2.id = master.id
inner join detail t3
on t3.id = master.id
inner join detail t4
on t4.id = master.id
inner join detail t5
on t5.id = master.id
inner join detail t6
on t6.id = master.id
inner join detail t7
on t7.id = master.id
inner join detail t8
on t8.id = master.id
inner join detail t9
on t9.id = master.id
inner join detail t10
on t10.id = master.id
;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql>


! やはり、実行途中のキャンセルだとActual Planは何も出力されないみたい。。。。

??ん? いや、なんか違うな。これw!

SQL文の実行時間はたいしたことない。人間がこの時間内にピンポイントでキャンセルするには短すぎるよね。
SQL文の実行時間は、約38ミリ秒!!! こっちだw 最終に表示されている21秒には、SQLのパース時間も含まれているはず。。。。見逃した。俺としたことが。。。

| -> Nested loop inner join  (cost=5729.85 rows=8192) (actual time=0.115..37.938 rows=10240 loops=1)

キャンセルしたのは、SQL実行後、おおよそ、20秒後。SQLのActualタイムを見ると38ミリ秒なので、ギリギリ、パース時間中に被ってそう。

1 row in set (21.00 sec)

だとしたら、まず、そこから確認。パースにどの程度要しているか、explainだけにすれば、良さそうだよね。Oracle Databaseでもそうだし。(PostgreSQLはパース時間も表示してくれたりするけども。あれ意外に便利なんだよ)

mysql> explain format=tree    -> select *
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192)
-> Nested loop inner join (cost=2862.65 rows=4096)
-> Nested loop inner join (cost=1429.05 rows=2048)
-> Nested loop inner join (cost=712.25 rows=1024)
-> Nested loop inner join (cost=353.85 rows=512)
-> Nested loop inner join (cost=174.65 rows=256)
-> Nested loop inner join (cost=85.05 rows=128)
-> Nested loop inner join (cost=40.25 rows=64)
-> Nested loop inner join (cost=17.85 rows=32)
-> Nested loop inner join (cost=6.65 rows=16)
-> Table scan on master (cost=1.05 rows=8)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (21.84 sec)


お! 予想通り、explainでパースだけさせたら、explain analyze(SQL文の実行を含む)とほぼ同じ 21秒台。想定は正しそう。

だとすると、今回確認したいことを実現するには、..... SQLの実行時間をもっと長くして、人がw キャンセルしやすい程度に実行時間を間延びさせておく必要がある。。
とはいえ、大量のデータを用意する時間もVMのストレージもないと。。。

さて、どうするか。。。。

***** ここで、 一休さん、考え中。。。 木魚の音。ポク、ポク、ポク。。。。w *****


***** ここで、 一休さん、閃いた。。。 鐘の音、チーーーーーん *****
 
sleep()関数を使って、眠らせると時間稼ぎできるのでは?  やってみよう!
おおおおお、いい感じだ。

mysql> explain analyze
-> select *, sleep(0.01)
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.170..232.399 rows=10240 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.164..75.457 rows=5120 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.159..37.659 rows=2560 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.154..17.709 rows=1280 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.149..9.612 rows=640 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.143..4.658 rows=320 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.138..2.285 rows=160 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.132..1.174 rows=80 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.126..0.625 rows=40 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.120..0.347 rows=20 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.097..0.122 rows=10 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.017..0.021 rows=2 loops=10)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.010..0.013 rows=2 loops=20)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.010..0.013 rows=2 loops=40)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.013 rows=2 loops=80)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.014 rows=2 loops=160)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.011..0.015 rows=2 loops=320)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.009..0.012 rows=2 loops=640)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.011..0.015 rows=2 loops=1280)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.014 rows=2 loops=2560)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.028 rows=2 loops=5120)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (2 min 47.64 sec)


Elapsed Timeがパース時間より長くなったので、できそう。 60秒後にキャンセルすれば余裕はありそう。

1 row in set (2 min 47.64 sec)

explain analyzeを実行して、60秒後にキャンセルした!

mysql> explain analyze
-> select *, sleep(0.01)
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;

^C^C -- query aborted

+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.113..92.382 rows=2639 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.106..29.480 rows=1320 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.101..14.323 rows=660 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.096..7.161 rows=330 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.090..3.507 rows=165 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.085..1.624 rows=83 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.079..0.867 rows=42 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.073..0.352 rows=21 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.068..0.210 rows=11 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.062..0.122 rows=6 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.040..0.044 rows=3 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.021..0.024 rows=2 loops=3)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.011..0.014 rows=2 loops=6)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.009..0.012 rows=2 loops=11)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.020..0.024 rows=2 loops=21)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.014..0.017 rows=2 loops=42)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.022 rows=2 loops=83)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.014..0.021 rows=2 loops=165)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.016..0.021 rows=2 loops=330)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.022 rows=2 loops=660)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.044 rows=2 loops=1320)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (1 min 3.57 sec)


SQLのActualタイムは、92ミリ秒、処理した行数が、2629行。

| -> Nested loop inner join  (cost=5729.85 rows=8192) (actual time=0.113..92.382 rows=2639 loops=1)

以下を見ると、loopsが、1320回となっている。

    -> Index lookup on t10 using PRIMARY (id=`master`.id)  (cost=0.50 rows=2) (actual time=0.015..0.044 rows=2 loops=1320)

やった〜〜〜。 MySQLの explain analyzeは、"SQLの実行途中"(パース時間が長い割に、実行時間が短い場合は、実行中にキャンセルするのが難しいので表示されないケースはあるので注意)でキャンセルしても、途中までのActual Planを返してくれる!


念の為、sleep()関数を使ったトリッキーな再現方法ではなく、実際にSQLの実行中にキャンセルする方法を思いついたので、さらに、試してみる!


お分かりだろうか。 みなさん大好き(?)、 再起問い合わせ再帰問合せでシーケンス番号を生成するクエリーだ。100000000ぐらい生成すれば、いい感じの実行時間になるだろうと、思われるので、まず、このSQLのパース時間だけ確認。


mysql> SET SESSION cte_max_recursion_depth = 100000000;
Query OK, 0 rows affected (0.00 sec)

パース時間はほぼかかってないですね。先ほどの例は、結合する表が多いので、パース時間は長くなる傾向があるので、シンプルなSQLだけど、実行時間は、なげ〜〜〜〜ぞ〜というのにしてみた。

mysql> explain format=tree
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3)
-> Rows fetched before execution (cost=0.00..0.00 rows=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2)
-> Scan new records on gen_nums (cost=2.73 rows=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


explain analyzeの実時間を確認

mysql> explain analyze
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3) (actual time=139082.095..178091.268 rows=100000000 loops=1)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (actual time=139081.959..139081.959 rows=100000000 loops=1)
-> Rows fetched before execution (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2) (actual time=0.003..29453.367 rows=50000000 loops=2)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..21852.139 rows=50000000 loops=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (3 min 8.46 sec)

Actual timeは、178秒なので、60秒後にキャンセルすればよさそうですね。

| -> Table scan on gen_nums  (cost=3.87..5.56 rows=3) (actual time=139082.095..178091.268 rows=100000000 loops=1)

では、キャンセルでどうなるか検証!!

mysql> explain analyze
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
^C^C -- query aborted
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3) (never executed)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (never executed)
-> Rows fetched before execution (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2) (actual time=0.004..27789.355 rows=47924088 loops=1)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..20555.788 rows=47924088 loops=1)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (1 min 3.94 sec)


60行でキャンセルしたので、ほぼ同じ。

1 row in set, 1 warning (1 min 3.94 sec

以下の行ソースを見ると、Actual timeが21秒ほど、rowsも47924088行。


-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..20555.788 rows=47924088 loops=1)

興味深い部分は、以下の2行の行ソース。never execute と表示されている。

| -> Table scan on gen_nums  (cost=3.87..5.56 rows=3) (never executed)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (never executed)
¥


これを見る限り、MySQLのexplain analyzeは、SQL文の実行フェース中にキャンセルすると、その時点までの Actual Planを出力してくれる。これ、結構嬉しいよね。長時間実行で、仕方なくキャンセルするにしても途中の状態。運が良ければ、詰まっている部分が見えるかもしれないわけで。。。


いや、他にも出力されないケースがあるとか、その検証方法だから出力されているだけどか、MySQLのexplain analyzeのディープなツッコミがありましたら、よろしくお願いします。:) (大事なので、2度書いておくw)


以上、explain analyze のブラックボックステスト。 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの? の巻。終わり。


似てるようで、似てない。それぞれのRDBMSの世界w。 ではまた。

 

参考)
EXPLAIN ANALYZE による情報の取得
https://dev.mysql.com/doc/refman/8.0/ja/explain.html#explain-analyze


| | | コメント (0)

2023年2月 2日 (木)

join elimination(結合の排除)のバリエーション / FAQ

Previously on Mac De Oracle
SQL*Plus -Fastオプション / FAQでした。

 

今日は、結合排除のバリエーションをいくつか紹介しておこうと思います。
なんどか説明していた無駄に結合してないですよね? の例は、参照整合性制約を頼りに結合排除が行われるものでした。

今日はそれに加えて、典型的な例を2つ紹介しておきたいと思います。(ここ” ”、試験に出ませんよ!w でないけど大切:)

一つ目は、結合列がそれぞれユニークキーかプライマリキーで一意で、1 : 0..1 で外部結合されるケース
結合の排除は、その結合を排除しても結果に影響しなことが自明な場合に発動するので、条件を満たしています。ただ、参照整合性制約のパターン同様に、SQL文を見ただけでは気づけないですよね。
あれ、実行計画にSQL文に記載されている表がない!? で制約を見てみて、あ”〜〜〜〜理解。みたいなw

実行計画ではSQLモニターも含め、Join Eliminationしたことを明示的に示すコメント等はありません。結合が消えていることで気づくことが多いわけですw(よーく考えたら、その結合イラねーじゃんというわけですけどもw0

  1* CREATE TABLE foo (id NUMBER PRIMARY KEY, note VARCHAR2(100))

表が作成されました。

1* CREATE TABLE bar (id NUMBER PRIMARY KEY, memo VARCHAR2(100))

表が作成されました。

1 SELECT
2 foo.id
3 , foo.note
4 FROM
5 foo
6 LEFT OUTER JOIN bar
7 ON
8* foo.id = bar.id

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

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

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

 

この手の制約による結合の排除は、前提になる制約が結合排除の条件からはずれると排除されなくなります。
たとえば、前述の例で、それぞれの表を主キーで 1:0..1 で外部結合していましたよね?
それが仕様変更で、片方の方の主キーが複合主キーになってしまい。1:* の外部結合になってしまうと。。。。結合排除できなくなります。排除した場合、クエリー結果に影響するから。

  1  CREATE TABLE foo2 (
2 id NUMBER
3 , note VARCHAR2(100)
4 , PRIMARY KEY (id)
5* )

表が作成されました。

1 CREATE TABLE bar2 (
2 id NUMBER
3 , sq NUMBER NOT NULL
4 , memo VARCHAR2(100)
5 , PRIMARY KEY (id, sq)
6* )

表が作成されました。

1 SELECT
2 foo2.id
3 , foo2.note
4 FROM
5 foo2
6 LEFT OUTER JOIN bar2
7 ON
8* foo2.id = bar2.id

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

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 78 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 1 | 78 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| FOO2 | 1 | 65 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SYS_C009222 | 1 | 13 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------

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

3 - access("FOO2"."ID"="BAR2"."ID"(+))

 

では、結合の排除のもう一つの例。これは制約というより、排除を狙った意図的な方法です。
動的なSQlが利用されているアプリケーションで使われていることが多い印象ですが、この方法は好き嫌い激しいかもしれないですね。私は好きじゃないですw が偶にみます。

このクエリは、1:* の外部結合なのですが、WHERE 1=0 によって結果を返しません。なのでクエリー結果に影響しないことが自明なので排除されている。ということになります。

   1  SELECT
2 foo2.id
3 , foo2.note
4 FROM
5 foo2
6 LEFT OUTER JOIN
7 (
8 SELECT * FROM bar2 WHERE 1=0
9 ) bar2
10 ON
11* foo2.id = bar2.id

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

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

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

 

ということで、今年も残すところあと11ヶ月ですねw
長いことやってたプロジェクトが一つ形になったみたいな、ならないような、hear through the grapevine.

 

では、また。

 



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

 

| | | コメント (0)

2023年1月27日 (金)

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

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

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

軽めですが。

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

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

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

...略...

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

1000001行が選択されました。

経過: 00:02:18.83

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

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

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

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


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


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

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

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

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

...略...

Duration : 173s

...略...

Fetch Calls : 66668

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

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


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

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

...略...

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

1000001行が選択されました。

経過: 00:01:55.03

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

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

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

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


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


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

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

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

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

...略...

Duration : 146s

...略...

Fetch Calls : 10001

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

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


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

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

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

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

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

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

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

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

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

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

と思う寒い日々。

では、また。

| | | コメント (0)

2023年1月16日 (月)

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

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

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

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

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

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

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

10001行が選択されました.

経過: 00:00:01.41

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

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

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

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

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


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

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

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

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

...略...

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

...略...

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

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

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

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

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

10001行が選択されました.

経過: 00:00:01.28

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

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

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

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

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


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

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

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

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

...略...

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

...略...

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

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

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

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

表が削除されました.

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

表が作成されました.

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

表が削除されました.

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

表が作成されました.

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

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

経過: 00:04:37.66


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

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

1000001行が選択されました.

経過: 00:02:23.03

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

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

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

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

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


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

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

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

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

...略...

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

...略...

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

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


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

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

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

1000001行が選択されました.

経過: 00:02:24.20

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

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

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

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

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


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

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

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

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

...略...

Duration : 223s

...略...

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

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


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

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

...略...

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

...略...

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

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

SYS@orclpdb1> alter user scott temporary tablespace hogetemp;

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


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

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

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

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

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

...略...

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

...略...

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

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


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

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

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

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

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

...略...

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

...略...

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

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


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

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

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

では, また.



Related article on Mac De Oracle

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

| | | コメント (0)

2022年12月26日 (月)

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

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

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

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

 

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

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

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

...略...

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

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

 

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

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

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

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

 

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


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

 

| | | コメント (0)

2022年12月25日 (日)

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

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

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

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

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

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

Sql_20221224173301

 

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

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

 

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

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

表が削除されました.

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

表が作成されました.

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

表が削除されました.

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

表が作成されました.

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

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

経過: 00:00:02.83

 

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

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

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

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

SCOTT@orclpdb1> select banner from v$version;

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

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

10001行が選択されました.

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

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

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

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

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

 

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

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

 

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

SCOTT@ORCL> select banner from v$version;

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

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

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

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

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

 

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

SCOTT> select banner from v$version;

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

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

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

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

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

 

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

orcl@SCOTT> select banner from v$version;

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

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

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

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

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

 

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

SCOTT@pdborcl12c> select banner from v$version;

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

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

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

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

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

 

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

orcl@SCOTT> select banner from v$version;

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

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

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

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

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

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

 

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

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

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

参考) Internal Views / Oracle Scratchpad

 

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

ではまた ;)

 


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

 

| | | コメント (0)

2022年12月24日 (土)

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


Previously on Mac De Oracle...

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

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

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

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

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

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

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


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

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

SCOTT@orclpdb1> @day24
1* DROP FUNCTION sq_macro_sample

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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


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

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


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

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

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

ではまた.



Related article on Mac De Oracle

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

| | | コメント (0)

2022年12月23日 (金)

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

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

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

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

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

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

参考)

Oracle Database 21c SQL Tuning Guide / 9.3.4 Antijoins

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

SCOTT@orclpdb1> select banner from v$version;

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


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

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

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

...略...

240 Government Sales
250 Retail Sales
260 Recruiting
270 Payroll

16行が選択されました.

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

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

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

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

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

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

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

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

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

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

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


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

SCOTT@orclpdb1> @day23

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

...略...

7369 SMITH
7566 JONES
7902 FORD

12行が選択されました.

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

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

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

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


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

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

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

...略...

7369 SMITH
7566 JONES
7902 FORD

12行が選択されました.

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

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

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

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


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

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

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

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

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

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

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

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


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

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

残り2日だ.

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



Related article on Mac De Oracle

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

| | | コメント (0)

2022年12月21日 (水)

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

Previously on Mac De Oracle...

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

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

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

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

SCOTT@orclpdb1> select banner from v$version;

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

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

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

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

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

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

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

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


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

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

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

6行が選択されました.

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

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

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

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

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

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

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

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

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

Plan hash value: 4207756064

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

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

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

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

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

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

では, また :)



Related article on Mac De Oracle

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

| | | コメント (0)

2022年12月20日 (火)

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

Previously on Mac De Oracle... 
Day 19は, かなり地味にデビューしていた機能ですが、変なところで目立った Group by Elimination にフォーカスを当てました。この手のEliminationは、実行計画という名のレントゲンからは見えなくなってしまう。何か悪さをしているときの調査は難易度高めです。基本的に操作が行われなくなるわけですが、例としてお見せしたような単純なSQLなら別ですが、クソデカクエリーだと、キッツイですよねw 結果不正って。

と言うことで、Day 20の窓を開けましょう!

 

今日は、 DISTINCT Eliminationです。またか! と。そういうEliminationもある。ということ知っておいた方が良いと思うので、eliminationネタの最後として、ちょっと飽きてきた感じはありますが取り上げてみました。この機能の提供も古く、機能が提供されていなかった時代を知る人の方が少ないかもしれませんw

ちなみに、今回も該当機能をを局所的に無効化する例を書いていますが、ヒントが提供されていない最適化もあるので、そう言う場合は、隠しパラメータからそれらしいのを探してして、検証して効果の有無を確かめると良いと思います。MOSに書かれてないケースも多いのでBlogなどから情報を集めたりして、最終的には動作確認。もし不具合などと関連しているようであればサポートへ問い合わせても対応してくれるでしょう。コミュニティーにこれどうよ?と投げてみるのもありだと思います。Jonathanもネタもとは、コミュニティーでのやりとりだったりすることも多く、調べてみたら、そうだった!という記事も多く、本当に助かった!って経験は何度もあります!!!。

parameterはこんなあたりから見つけると楽ですよん。
Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden params
Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0)

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

前回と同じデータを使います。(こんな使いまわせるテストデータを作るスクリプト用意しておくと何かと便利ですよ!)


SCOTT@orclpdb1> @day20
1* DROP TABLE business_day_calendar

表が削除されました。

1 CREATE TABLE business_day_calendar AS
2 WITH
3 FUNCTION get_num_of_dates
4 RETURN NUMBER IS
5 l_dummy_date DATE;
6 --
7 eORA01839 exception;
8 pragma exception_init(eORA01839, -1839);
9 BEGIN
10 -- validate leap year
11 l_dummy_date := TO_DATE(TO_CHAR(SYSDATE, 'YYYY') || '0229', 'YYYYMMDD');
12 RETURN 366;
13 EXCEPTION
14 WHEN eORA01839 THEN
15 RETURN 365;
16 END;
17 SELECT
18 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1) AS business_date
19 , CASE
20 WHEN TO_CHAR(
21 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1)
22 , 'DY'
23 , 'NLS_DATE_LANGUAGE=AMERICAN'
24 ) IN ('SUN','WED')
25 THEN '1'
26 ELSE '0'
27 END AS is_holiday
28 FROM
29 dual
30 CONNECT BY
31* level <= get_num_of_dates

表が作成されました。

1 ALTER TABLE business_day_calendar
2 ADD CONSTRAINT pk_business_day_calendar PRIMARY KEY
3 (
4 business_date
5 )
6* USING INDEX

表が変更されました。

 

はい、主キー列にDISTINCTを使ってますが、無駄ですよね!(いきなり本題w)

DISTINCT操作は見事に実行計画から排除されています!(簡単ですね。とは言っても実行計画を見ただけでは、DISTINCT Eliminationが行われていることには気付けないわけですけども)


  1  SELECT
2 DISTINCT business_date
3 FROM
4* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

では、元栓というか、Optimizer自体を、とーーーく昔の状態にしてみましょう(DISTINCT Eliminationがバックポートもされてない時代の10g 10.2.0.3。こういうことができるOracle面白いですよね)
このようなことを本番でやるのはかなりレアで、よっぽど新しい機能を使いたくないか、めちゃめちゃキツイ大人の事情があるんだと思います(知らんけど)

Optimizerを10g R2ぐらいに戻したことで、DISTINCT操作として、SORT UNIQUE NOSORTが合わられましたが、INDEX FULL SCANでユニークキーをアクセスしているのでNOSORTとなり、SORT UNIQUE操作はスキップされていることがわかります。ソート順に索引を全捜査する INDEX FULL SCANだからこそできる動きですね。


  1  SELECT
2 /*+
3 OPTIMIZER_FEATURES_ENABLE('10.2.0.3')
4 */
5 DISTINCT business_date
6 FROM
7* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 311283176

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT| | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

Optimizerの能力を10gに戻してしまうのも勿体ないので、よく使う局所的な方法。特定のSQLだけが影響するのであれば、この方法がおすすめです。
元々の機能に影響を受けていない。逆に恩恵を受けているものあるかもしれません。
前回の結果不正などもそうですが、どの方法で治療するというかチューニングかは、対応するエンジニア考え方や、該当する患者さん(システムや、お客様の大人の事情w)にもよりますが、私は、基本的に局所的な対処で済むのなら、そちらを選ぶ方針です。
なるべく狭い範囲、SQL、セッションあたりで無効、有効にして、経過観察、副作用有無、対処した範囲外で、同一理由による問題が発生していないか。もし狭い範囲の対処では無理なら徐々に広げる。最終系がインスタンス全体で。みたいな流れにすることで、無駄に全て止めてしまうということを避けたい(恩恵を受けているのもあるはずということを前提にしています)。この辺りは考え方次第なので、絶対、こうするのが良いとか悪いという話では無いですが。長い目で考えるとそれが良いのでは無いかと個人的には思います。
この機能、直接利用できるヒントはないので、隠しパラメータでon/offできます。冒頭で紹介したパラメータ一覧には隠しパラメータもリストしているので、こういう時はクエリ投げずに該当ページを検索すると楽ですよん:)


  1  SELECT
2 /*+
3 OPT_PARAM('_optimizer_distinct_elimination','false')
4 */
5 DISTINCT business_date
6 FROM
7* business_day_calendar

BUSINESS
--------
22-01-01
22-01-02
22-01-03

...略...

22-12-29
22-12-30
22-12-31

365行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 311283176

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT| | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
8855 bytes sent via SQL*Net to client
316 bytes received via SQL*Net from client
26 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
365 rows processed

 

別テストケースで確認してみましょう。


  1* DROP TABLE case2

表が削除されました。

1 CREATE TABLE case2
2 (
3 id NUMBER PRIMARY KEY
4 , col2 NUMBER NOT NULL
5 , col3 NUMBER
6 , col4 VARCHAR2(10) NOT NULL
7 , CONSTRAINT uix_case2 UNIQUE (col2,col3) USING INDEX
8* )

表が作成されました。

1 BEGIN
2 FOR i IN 1..2000 LOOP
3 INSERT INTO case2 VALUES(i, i, NULL, LPAD(TO_CHAR(i),10,'*'));
4 IF MOD(i,100) = 0 THEN COMMIT; END IF;
5 END LOOP;
6* END;

PL/SQLプロシージャが正常に完了しました。

 

このテストケースでは、複合一意キーかつ、第二キーに null を許しています。
Oraleの索引は、null が含まれない!という都市伝説がありましたが、そんなことはないのは以前解説していた通りです。一部の列にnullを許可した一意キーでもDISTINCT Eliminationは発動することを確認するテストケースです)

この例では、第二キー列がnullableで、この状態では、第二キー列全てを null にしています。DISTINCT Eliminationの条件を満たすため、DISTINCT 捜査が排除されていることがわかります!


  1  SELECT
2 DISTINCT col2, col3
3 FROM
4* case2

COL2 COL3
---------- ----------
541 [null]
542 [null]
543 [null]

...略...

1998 [null]
1999 [null]
2000 [null]

2000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1081843087

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 5 (0)| 00:00:01 |
| 1 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
----------------------------------------------------------------------------------

 

OPT_PARAM('_optimizer_distinct_elimination','false')でDISTINCT Eliminationを無効化すれば、DISTINCT 操作は現れますが、INDEX FAST FULL SCANとなっているため HASH UNIQUE操作が行われていることがわかります。
実際には、ユニーク索引なので、不要ではあるのですが:)


  1  SELECT
2 /*+
3 OPT_PARAM('_optimizer_distinct_elimination','false')
4 */
5 DISTINCT col2, col3
6 FROM
7* case2

COL2 COL3
---------- ----------
555 [null]
585 [null]
586 [null]

...略...

1986 [null]
1989 [null]
1990 [null]

2000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 523836292

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 6 (17)| 00:00:01 |
| 1 | HASH UNIQUE | | 2000 | 52000 | 6 (17)| 00:00:01 |
| 2 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

 

では、ちょっと初歩的な確認で、1, nullというすでに存在する値をINSERTしてみましょう。当然エラーです。UIX_CASE2は一意キーなので。


  1* INSERT INTO case2 VALUES(2001,1,NULL,'test')
INSERT INTO case2 VALUES(2001,1,NULL,'test')
*
行1でエラーが発生しました。:
ORA-00001: 一意制約(SCOTT.UIX_CASE2)に反しています

 

第二キーがnullではない値を登録しておきます。


  1* INSERT INTO case2 VALUES(2002,1,1,'test')

1行が作成されました。

1* COMMIT

コミットが完了しました。

 

特にどうだということもないのですがw 正しく、DISTINCT EliminationされINDEX FAST FULL SCANだけの実行計画という名のレントゲンが現れています。ニッコリ(どちらかというと、索引に null 入らないという本当の意味を知らずにいる方もいるのではないだろうかという余計な心配をしただけのお節介なテストケース。というだけだったかもしれません)


  1  SELECT
2 DISTINCT col2, col3
3 FROM
4* case2

COL2 COL3
---------- ----------
541 [null]
542 [null]
543 [null]

...略...

1 1
1 [null]
2 [null]
3 [null]

...略...

538 [null]
539 [null]
540 [null]

2001行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1081843087

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 52000 | 5 (0)| 00:00:01 |
| 1 | INDEX FAST FULL SCAN| UIX_CASE2 | 2000 | 52000 | 5 (0)| 00:00:01 |
----------------------------------------------------------------------------------

 

ということで、 Day 20はここまで。

 

残り5日。追い込みだーーーーっ
明日も、私が担当なので、よろしくお願いします。


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination

| | | コメント (0)

2022年12月19日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination

このポストは, JPOUG Advent Calendar 2022 Day 19帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022へクロスポストしています.

 

JPOUG Advent Calendar 2022 Day 18は, tomoさんのSPM使ってる?でした. 使ってますか?ヒント埋込派, パッケージなんでSPMっす!とか大人の事情で色々ありそうですね.

Previously on Mac De Oracle... Day 18は, Join Elimination にフォーカスをあてました. Join Eliminationも他のElimination系書き換えは, 実行計画上存在が消されてしまうので, 実際行われたかどうか気づきにくいケースが多いです. 以前も書きましたが, Explain plan等で, Elimination Informationとかリストしてくれると便利かもしれません.

ということで, 今日, Day 19は, そんなElimination InformationがExplain plan等で表示されたら嬉しいかもね, という気持ちが強くなるネタにしました.

 

では, Day 19のお題, Group by Eliminationです.
この機能, 目立たないですが, 変なところで目立ったので意外に有名かもしれません. 機能として登場したのは, 12cR2ごろでした. Jonathanも書いてるから間違いない!

変なところで目立ってしまった. そんなに目立たない存在だった, Group by Eliminationの確認
目立ってしまった理由は, 結果不正.  できれば7445の方が分かりやすくて良かったわけですが, 出会いたくないですよねー. 結果不正.
(なお, すでに修正されており, 想定する結果が返されるようになっているようです. 詳細は本ページ末の参考リンクのJonathanのエントリー参照のこと)

まず, 検証用のデータ準備から(ちょいと凝ったことやってますが, 単純に当年のカレンダーで, その日が営業日か休業日かをフラグで持たせるだけのでデータで, 2022年の一年分のデータを作りました. day18.sqlを実行していますが, こちらの都合w公開日を入れ替えた影響なので, 気にしないでくださいw

SCOTT@orclpdb1> @day18
1* DROP TABLE business_day_calendar

表が削除されました.

1 CREATE TABLE business_day_calendar AS
2 WITH
3 FUNCTION get_num_of_dates
4 RETURN NUMBER IS
5 l_dummy_date DATE;
6 --
7 eORA01839 exception;
8 pragma exception_init(eORA01839, -1839);
9 BEGIN
10 -- validate leap year
11 l_dummy_date := TO_DATE(TO_CHAR(SYSDATE, 'YYYY') || '0229', 'YYYYMMDD');
12 RETURN 366;
13 EXCEPTION
14 WHEN eORA01839 THEN
15 RETURN 365;
16 END;
17 SELECT
18 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1) AS business_date
19 , CASE
20 WHEN TO_CHAR(
21 TO_DATE(TRUNC(SYSDATE,'YYYY') + level - 1)
22 , 'DY'
23 , 'NLS_DATE_LANGUAGE=AMERICAN'
24 ) IN ('SUN','WED')
25 THEN '1'
26 ELSE '0'
27 END AS is_holiday
28 FROM
29 dual
30 CONNECT BY
31* level <= get_num_of_dates

表が作成されました.

1 ALTER TABLE business_day_calendar
2 ADD CONSTRAINT pk_business_day_calendar PRIMARY KEY
3 (
4 business_date
5 )
6* USING INDEX

表が変更されました.

 

今回は, この Order BY Elimination をちょっと有名にしてしまった, 結果不正から. 12cR2で試してみます. CASE 2の結果が想定と異なっています.

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

CASE 1特に問題は起きていません. 想定通りの結果が帰り, Group By Eliminationの発動条件にはなっていないため, Group By操作が行われています.

  1  SELECT
2 EXTRACT(MONTH from business_date)
3 , COUNT(
4 CASE
5 WHEN TO_CHAR(
6 business_date
7 , 'DY'
8 , 'NLS_DATE_LANGUAGE=AMERICAN'
9 ) IN ('SUN','WED')
10 THEN 1
11 END
12 ) AS holidays
13 FROM
14 business_day_calendar
15 GROUP BY
16* EXTRACT(MONTH from business_date)

EXTRACT(MONTHFROMBUSINESS_DATE) HOLIDAYS
------------------------------- ----------
1 9
6 9
11 9
2 8
4 8
5 9
8 9
3 9
7 9
9 8
10 9
12 8

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2 おやおやおや, おかしなことになってます. 実行計画をみると, 動作対象ではないはずなのに, GROUP BY 操作が排除されているのが分かります! 結果不正です. HASH GROUP BYが排除されています! その影響で, GROUP BYが行われていません!!!!

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202201 1
202201 1
202201 1

...略...

202212 1
202212 1
202212 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2 の結果不正回避 では, 結果不正回避策を取ってみましょう. 何度か利用している, no_elim_groupbyヒントで回避できるか試します!
結果不正の理由は, 本来発動しない条件であるにもかかわらず発動してしまった Group By Eliminationにる影響であることが分かります.

これをみると, なおさら, Explain Plan等でどのような書き換えが動作したのかしないのか一覧できるような情報が欲しいなと思ったりするわけです. .

orcl@SCOTT> @day18-2
1 SELECT
2 /*+
3 no_elim_groupby
4 */
5 TO_CHAR(business_date, 'YYYYMM')
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202205 31
202209 30
202211 30
202212 31
202202 28
202208 31
202207 31
202210 31
202201 31
202204 30
202203 31
202206 30

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 3 こちらは正しく GROUP BY Eliminationが発動していますね. GROUP BYしなくても問題ないクエリですから!

  1  SELECT
2 business_date
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* business_date

BUSINESS_ COUNT(1)
--------- ----------
01-JAN-22 1
02-JAN-22 1
03-JAN-22 1

...略...

29-DEC-22 1
30-DEC-22 1
31-DEC-22 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 4 GROUP BY Eliminationを無効化しても他のバージョンと結果同じで, 想定した結果通りです. CASE 3と結果は同じなのは当たり前.

  1  SELECT
2 /*+
3 no_elim_groupby
4 */
5 business_date
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* business_date

BUSINESS_ COUNT(1)
--------- ----------
01-JAN-22 1
02-JAN-22 1
03-JAN-22 1

...略...

29-DEC-22 1
30-DEC-22 1
31-DEC-22 1

365 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 3672056694

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 365 | 2920 | 1 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

 

では, 私が確認したテストケースの範囲で結果不正が起きていない, 21cは全テストケース, それ以外のバージョンでは, 結果不正の発生していた CASE 2 の実行計画という名のレントゲンを並べておきたいと思います. 頭に浮かんだイメージは, レントゲン写真を光るボードに沢山貼り付けてる感じw

Oracle Database 21cのCASE 1 - CASE 4まで全て

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

CASE 1:Group By Eliminationは発生しないケース

  1  SELECT
2 EXTRACT(MONTH from business_date)
3 , COUNT(
4 CASE
5 WHEN TO_CHAR(
6 business_date
7 , 'DY'
8 , 'NLS_DATE_LANGUAGE=AMERICAN'
9 ) IN ('SUN','WED')
10 THEN 1
11 END
12 ) AS holidays
13 FROM
14 business_day_calendar
15 GROUP BY
16* EXTRACT(MONTH from business_date)

EXTRACT(MONTHFROMBUSINESS_DATE) HOLIDAYS
------------------------------- ----------
1 9
2 8
3 9
4 8
5 9
6 9
7 9
8 9
9 8
10 9
11 9
12 8

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 2:Group By Eliminationは発生しないケース(12cR2でGroup By Eliminationの誤作動で結果不正となったテストケース)

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHAR(BUSINESS_D COUNT(1)
------------------ ----------
202201 31
202202 28
202203 31
202204 30
202205 31
202206 30
202207 31
202208 31
202209 30
202210 31
202211 30
202212 31

12行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 3:Group By Eliminationが発生するケース

  1  SELECT
2 business_date
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* business_date

BUSINESS_DATE COUNT(1)
--------------- ----------
20220101 000000 1
20220102 000000 1
20220103 000000 1

...略...

20221228 000000 1
20221229 000000 1
20221230 000000 1
20221231 000000 1

365行が選択されました.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1786497156

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

CASE 4:Group By Eliminationが発生するケースで, no_elim_groupby ヒントでGroup By Eliminationを抑止したケース

  1  SELECT
2 /*+
3 no_elim_groupby
4 */
5 business_date
6 , COUNT(1)
7 FROM
8 business_day_calendar
9 GROUP BY
10* business_date

BUSINESS_DATE COUNT(1)
--------------- ----------
20220101 000000 1
20220102 000000 1
20220103 000000 1
20220104 000000 1

...略...

20221228 000000 1
20221229 000000 1
20221230 000000 1
20221231 000000 1

365行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 3672056694

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 1 (0)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 365 | 2920 | 1 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

 

Oracle Database 19cです. CASE 2だけ確認します.

SCOTT@orcl> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

CASE 2:このリリースでも問題は無さそうですね

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202206 30
202203 31
202210 31
202212 31
202202 28
202211 30
202204 30
202208 31
202209 30
202201 31
202205 31
202207 31

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

Oracle Database 18c です. 同様に, CASE 2のみ確認しています.

SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production

CASE 2:このリリースでも問題は無さそう

  1  SELECT
2 TO_CHAR(business_date, 'YYYYMM')
3 , COUNT(1)
4 FROM
5 business_day_calendar
6 GROUP BY
7* TO_CHAR(business_date, 'YYYYMM')

TO_CHA COUNT(1)
------ ----------
202206 30
202203 31
202210 31
202212 31
202202 28
202211 30
202204 30
202208 31
202209 30
202201 31
202205 31
202207 31

12 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 100882575

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 365 | 2920 | 2 (50)| 00:00:01 |
| 1 | HASH GROUP BY | | 365 | 2920 | 2 (50)| 00:00:01 |
| 2 | INDEX FULL SCAN| PK_BUSINESS_DAY_CALENDAR | 365 | 2920 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

 

ちょっと思ったのですが, 現状, 10053トレース等をみるしかないタイプの書き換えでも, OTHER_XML列からOUTLINEを抜き出すと比較的簡単に発動有無がわかるものもあるかもしれないですね. 今回のように固有のヒントでON/OFFできるタイプだとOUTLINEには, elim_groupby のようなヒントが含まれているだろうし. (全てではないとは思いますが)

ふ〜〜〜っ. JPOUG Advent Calendar 2022 の自分のターンも終わって, 自分の分をなんとかするだけだーーーーー.

 

ということで,
帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022は, 明日も私が担当です. よろしくお願いします.
そして, JPOUG Advent Calendar 2022 Day 20は, 凌直孝さんの担当です. よろしくお願いします!

 

では, また:)

参考)

Group by Elimination / Oracle Scratchpad
A Look at the Oracle Group-by Bug / Database Journal

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination

 

| | | コメント (0)

2022年12月18日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination

Previously on Mac De Oracle...
Day 17は, order by Elimination にフォーカスをあてました. チューニングの現場で気づいたのが最初だったと思いますが, SQL文を見ていて, これ無駄なソートだなぁなんて思いながら実行計画という名のレントゲンをみていたら, おお! NOSORTとかではなく, ORDER BY自体が消されてる! 賢い! と.

では, Day 18のお題, Join Eliminationです. (登場したのは10gR2 ぐらいのはずですが, 間違ってたらツッコミ歓迎)
この排除系書き換えも, 実行計画という名のレントゲンシリーズでは, まだ紹介していなかったので, 今回は, ヒントで無効化する例も含め, 軽めの内容でw, 診ていきたいと思います. (参照整合性制約アレルギーネタを思い出すw)


いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

対象となる表には外部参照整合制約があることが前提です.
20190321-144842

customers表を結合していますが, 参照整合性制約でcustomers表に存在する顧客しか注文できないという制約があります. つまり, この参照整合性制約があるため, INNER JOINやEXISTSを利用して存在チェックする必要はないということを意味しています.
Join EliminationによりSQLが書き換えられ結合が排除されていることがわかります. 実行計画には, ORDER_PKをIndex Only Scanしているだけで, customers表やcustomersの索引を結合しているOperationは含まれていない!!!
結合しないので, 結合のコスト及び, customers表やcustomersの索引へのアクセスコストが削減されています. 実行統計からは, Pysical Readや, Buffer Getsの低下という形で現れてきます.

とはいえ, 参照整合性制約アレルギーのお持ちの方も多く, 一生目にすることのない方々も, 残念ながら多いのも事実です.
参照整合性制約アレルギーが発症してしまうと, 一生付き合っていくことになちゃいますからね(大抵の場合)


Day19の内容とDay18のネタを入れ替えたので, day19.sqlを実行しているところは気にしないでくださいw

OE@orclpdb1> @day19
1 SELECT
2 order_id
3 FROM
4 orders o
5 INNER JOIN customers c
6 ON
7 o.customer_id = c.customer_id
8 WHERE
9* order_id < 2400

ORDER_ID
----------
2354
2355
2356
2357
2358

...略...

2396
2397
2398
2399

実行計画
----------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("O"."ORDER_ID"<2400)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
0 physical reads
0 redo size
1482 bytes sent via SQL*Net to client
85 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
46 rows processed
/code>


NO_ELIMINATE_JOINヒントを利用し, Join Eliminationを抑止してみましょう.
NO_ELIMINATE_JOINは, 使うことがあるのか? と思う方もいるかもしれませんが, 例えば, 不具合で7445とか, その他結果不正などに当たった時, かつ, 局所的対応で回避できそうなケースでは, 該当SQLにヒントを埋め込み, Join Eliminationの抑止で回避したりします.
どこで起きるかわからんので, インスタンスレベルで止めるケースもなくなないですが, そういう場合は, 隠しパラメータで無効化するのが一般的です. (ほぼ使わないと思いますが, ELIMINATE_JOIN でJoin Eliminationを利用できます. インスタンスレベルで無効化している状態で, 特定のSQLだけはJoin Eliminationしたいという場合に使うぐらいですね. 滅多にないと思いますが)

  1  SELECT
2 /*+
3 NO_ELIMINATE_JOIN(c)
4 */
5 order_id
6 FROM
7 orders o
8 INNER JOIN customers c
9 ON
10 o.customer_id = c.customer_id
11 WHERE
12* order_id < 2400

ORDER_ID
----------
2354
2355
2356
2357
2358

...略...

2392
2393
2394
2395
2396
2397
2398
2399

実行計画
----------------------------------------------------------
Plan hash value: 875022219

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 552 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 46 | 552 | 2 (0)| 00:00:01 |
|* 2 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 46 | 368 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ORDER_PK | 46 | | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | CUSTOMERS_PK | 1 | 4 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("O"."CUSTOMER_ID">0)
3 - access("O"."ORDER_ID"<2400)
4 - access("O"."CUSTOMER_ID"="C"."CUSTOMER_ID")

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
1482 bytes sent via SQL*Net to client
85 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
46 rows processed
/code>

アドベントカレンダー終わったら, もう, 今年も残りわずか....

ということで, 出口が見えつつある, アドベントカレンダー全部俺, 明日も, 俺が書きますw
では, また.


参考)
Join Elimination(結合の排除)と 参照整合性制約 / FAQ
db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ, SQLチューニングの「参照整合性制約アレルギー」を参照のこと



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination

| | | コメント (0)

2022年12月17日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination

Previously on Mac De Oracle... Day 16は, Concurrent Execution of Union All and Union にフォーカスをあてました. 使う機会があったら試してみてくださいね. 私は, この機能が登場した頃, Spatialな機能でRDFを使った際, Exadataでバリバリ活用してたというか, されていたという記憶があります.

では, Day 17のお題, Order by Eliminationです. 文字通り, Order byが排除される, 書き換え機能の一種です. だからと言って, 無駄なOrder by句書いても大丈夫ってことではないですから, 注意しましょうw

 

10gR2ぐらいから実装されている最適化の一つですね. 多少無駄なOrder byを書いちゃっても無駄だと判断されれば, 行われません. (だからと言って, 気にしないでOrder By書いちゃってもいいという話ではありませんがw)

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

emp表の索引などはいかの通り(scottのemp表を知らない方向け. いるのか?)

SCOTT@orclpdb1> desc emp
名前 NULL? 型
----------------------------------------- -------- ----------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

SCOTT@orclpdb1> select index_name,column_name from user_ind_columns where
2 table_name = 'EMP' order by index_name,column_position;

INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------
IX01_EMP DEPTNO
PK_EMP EMPNO

 

SQL中に ORDER BY句がありますが, NOSORT操作もないのにソートされていません. そもそも書き換えられて, ORDER BY句が削除されているんですよーーーー.
このケースでは, empno順にソートされた結果返ってきているかのう様に見えますが, それは索引をempno順に読無ことで, ソートを排除できるからなんですよね. 索引がなければ, ソートは必要なのです. ちなみに, INDEX FULL SCAN はソートされたキー順に索引を全てスキャンするところがポイント, INDEX FAST FULL SCANはとは違うのはみなさんご存知のはずなのでコマけーことは省略.

SCOTT@orclpdb1> @day17
1 SELECT
2 *
3 FROM
4 emp
5 ORDER BY
6* empno

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 4170700152

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 468 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 12 | 468 | 2 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | PK_EMP | 12 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
1669 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
12 rows processed

 

こちらも, 無用なORDER BY句が削除されています. 賢い. オプティマイザー!
以下のORDER BYは, みるからに無駄なソートですが, オプティマイザーは気づいて排除しています.

  1  SELECT
2 COUNT(ename)
3 FROM
4 (
5 SELECT
6 *
7 FROM
8 emp
9 ORDER BY
10 ename
11* )

COUNT(ENAME)
------------
12

実行計画
----------------------------------------------------------
Plan hash value: 2083865914

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
| 2 | TABLE ACCESS FULL| EMP | 12 | 72 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
595 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

EXPLAIN等改善してほしいなと思うのはこれらのElimination関連情報, 今回のように無駄なORDER BYを排除したという情報を, Elimination Informationとしてリストしたら良いのではないかと思っています. 最近, SQLヒントに関する情報をリストするようになったのでできるのではないだろうかと. ただ, 諸々内部でSQL文を書き換えたりするから, 実は面倒なのかもねw  10053トレースなら見える訳ですが, 毎回取得するわけにもいかないしね.

ついに,  Day 17 done. あと少しだ頑張れ, 俺w

 

ということで, 残念ですが, 明日も私です.
ではまた.

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union

 

| | | コメント (0)

2022年12月16日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union

Previously on Mac De Oracle... Day 15は, 分散, リモートクエリーにフォーカスをあてました. 好き嫌い多いネタだったかもしれませんねぇw

では, Day 16のお題, Concurrent Execution of Union All and Union

これは何かというと, 12c R1以降だったか?(定かでないw)に, UNION/UNION ALLの各分岐がパラレルで実行されるという機能です. それまではシリアルに処理されていたので, 分岐が多いほど処理時間も増加していた訳ですが, 分岐がそれぞれパラレルで実行される分処理時間は短くなるというやつですね.

Concurrent Execution of Union All and Unionが実行されている時の特徴は, PX SELECTOR があるかどうかで見分けます.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

準備として, 12c R1以前ではシリアルに処理される代表格, リモート表を用意します. (昨日のネタはこの準備のためでもあったり)



SCOTT@orclpdb2> @day16
1* CREATE DATABASE LINK remote_scott CONNECT TO scott IDENTIFIED BY tiger USING 'orclpdb1'

データベース・リンクが作成されました.

1 CREATE TABLE local_emp
2 AS
3 SELECT *
4 FROM
5 emp@remote_scott
6 ORDER BY
7 empno
8* FETCH FIRST 5 ROWS ONLY

表が作成されました.

1* INSERT INTO local_emp(empno, ename) VALUES(1, 'NULL')

1行が作成されました.

 

クエリーの結果、たまたま、empno順に並んでいるように見える結果もありますが、ソートしたい場合は、必ず!!! ORDER BY句が必要ですからねw(誰となくw)

シリアルで実行してみます. よく見る実行計画という名のレントゲンです. リモート表は Operationが REMOTE として現れています. 2つある分岐は, シリアルに実行されます.

  1  SELECT
2 *
3 FROM
4 local_emp
5 UNION ALL
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 1607333107

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | |
| 1 | UNION-ALL | | | | | | | |
| 2 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | | |
| 3 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

3 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

次に, パラレルクエリーにして, かつ, Concurrent Execution を抑止する NO_PQ_CONCURRENT_UNION  を付加して実行してみました.
PX SELECTOR は現れていません. パラレルクエリーではありますが, 分岐は, 順に処理されていきます.

  1  SELECT
2 /*+
3 PARALLEL(3)
4 NO_PQ_CONCURRENT_UNION
5 */
6 *
7 FROM
8 local_emp
9 UNION ALL
10 SELECT
11 *
12 FROM
13* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 3305250800

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | | |
| 1 | UNION-ALL | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM)| :TQ10000 | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 4 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 5 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 6 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
----------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

6 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

本日の主役 PX SELECTORREMOTE 操作の上に出てきました. この状態であれば, 各分岐はパラレルに処理されることになり, 分岐が順に処理されるより処理時間は短縮されることになります(REMOTE処理が重かったら, その処理時間に引きづら訳ですが, 順に処理するよりは早く終わると予想できますよね)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 UNION ALL
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
1 NULL
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 2359762278

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 5 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | | | | | Q1,00 | P->S | QC (RAND) |
| 3 | UNION-ALL | | | | | | Q1,00 | PCWP | |
| 4 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 5 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 6 | PX SELECTOR | | | | | | Q1,00 | PCWP | |
| 7 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | Q1,00 | PCWP | |
----------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

7 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing ':Q1000' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

以下, UNIONでも同様に, 実行計画というレントゲンを確認しておきましょう. まずはシリアル.


  1  SELECT
2 *
3 FROM
4 local_emp
5 UNION
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
1 NULL
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画
----------------------------------------------------------
Plan hash value: 2189749816

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | |
| 1 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | | |
| 2 | UNION-ALL | | | | | | | |
| 3 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | | |
| 4 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
-------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

4 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

NO_PQ_CONCURRENT_UNIONでConcurrent Execution を抑止してみると, 順に処理されていることがわかります.

  1  SELECT
2 /*+
3 PARALLEL(3)
4 NO_PQ_CONCURRENT_UNION 5 */
6 *
7 FROM
8 local_emp
9 UNION
10 SELECT
11 *
12 FROM
13* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7902 FORD ANALYST 7566 81-12-03 3000 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
1 NULL
7900 JAMES CLERK 7698 81-12-03 950 30
7934 MILLER CLERK 7782 82-01-23 1300 10
7839 KING PRESIDENT 81-11-17 5000 10
7782 CLARK MANAGER 7839 81-06-09 2450 10
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30

実行計画
----------------------------------------------------------
Plan hash value: 622056383

-----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,01 | PCWP | |
| 8 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 9 | TABLE ACCESS FULL | LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 10 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 11 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | PX SEND ROUND-ROBIN| :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | RND-ROBIN |
| 13 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-----------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

13 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP" "EMP"
(accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

UNIONでもPX SELECTORが現れ, 分岐がパラレルに処理されていることが見えます. :)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 UNION
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7369 SMITH CLERK 7902 80-12-17 800 20
1 NULL
7900 JAMES CLERK 7698 81-12-03 950 30
7934 MILLER CLERK 7782 82-01-23 1300 10
7839 KING PRESIDENT 81-11-17 5000 10
7782 CLARK MANAGER 7839 81-06-09 2450 10

実行計画
----------------------------------------------------------
Plan hash value: 3647789528

--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 4 | PX RECEIVE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,01 | PCWP | |
| 5 | PX SEND HASH | :TQ10000 | 18 | 990 | 7 (29)| 00:00:01 | Q1,00 | P->P | HASH |
| 6 | HASH UNIQUE | | 18 | 990 | 7 (29)| 00:00:01 | Q1,00 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,00 | PCWP | |
| 8 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 9 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 10 | PX SELECTOR | | | | | | Q1,00 | PCWP | |
| 11 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | Q1,00 | PCWP | |
--------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

11 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing ':Q1000' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

ちなみに, Concurrent Execution of Union All and Union と書いている通り, MINUS/EXCEPT/INTERSECTでは今まで通りです.  EXCEPTとINTERSECTの実行計画で見てみます. PX SELECTOR は現れていません. (MINUSとEXCEPTはどちらもMINUSとして扱われているので片方だけで十分です)

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 EXCEPT
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
1 NULL

実行計画
----------------------------------------------------------
Plan hash value: 331225933

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | | | | | Q1,02 | P->S | QC (RAND) |
| 3 | MINUS HASH | | | | | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | | | | | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | | | | | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 6 | 522 | 3 (34)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 8 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 9 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 10 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 11 | PX SEND HASH | :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | HASH |
| 12 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

12 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

  1  SELECT
2 /*+
3 PARALLEL(3)
4 */
5 *
6 FROM
7 local_emp
8 INTERSECT
9 SELECT
10 *
11 FROM
12* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30

実行計画
----------------------------------------------------------
Plan hash value: 1417135745

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ/Ins |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 990 | 7 (29)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | | | | | Q1,02 | P->S | QC (RAND) |
| 3 | INTERSECTION HASH | | | | | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | | | | | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | | | | | Q1,01 | P->P | HASH |
| 6 | HASH UNIQUE | | 6 | 522 | 3 (34)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX BLOCK ITERATOR | | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWC | |
| 8 | TABLE ACCESS FULL| LOCAL_EMP | 6 | 522 | 2 (0)| 00:00:01 | Q1,01 | PCWP | |
| 9 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 10 | PX RECEIVE | | 12 | 468 | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 11 | PX SEND HASH | :TQ10000 | 12 | 468 | 3 (0)| 00:00:01 | | S->P | HASH |
| 12 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S | |
-------------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

12 - SELECT /*+ SHARED (3) */ "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 3 because of hint

 

ということで, Day 16 done

残り9日だ. ふ〜〜〜〜っ. 明日も絶対, 担当は私です.

ではまた.

 

参考) Oracle Database 21c - VLDB and Partitioning Guide / 8.5.3.14 Concurrent Execution of Union All


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE

 

| | | コメント (0)

2022年12月15日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE

Previously on Mac De Oracle...
Day 14は, the DUAL Table にフォーカスをあてました. 23cでは省略できるようになる. やっと, と言う感じもしなくないですが. やっとと言う言葉しかないわけです. はいw

帰ってきた! 実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022も遂に, 残り10回. 頑張ってネタ考えようw
それでは, Day 15 の窓を開けましょう!.

REMOTE と聞いて, ピンと来た方w 多いと思いますが, そうです, 変なおじ, いや, 変なSQL, 違うw,  リモートクエリーか, 分散クエリーのどちらかで現れるOperationです.  リモートクエリーと分散クエリーの違いは, この辺り, とかググっていただくとして.

リモートクエリーとか分散クエリー嫌いな方も多いとは思いますがw とにかく簡単な例で診てみましょう.


いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

検証を簡単に行うため, 2つのPDBを用意し, 一方のPDBのSCOTTへDatabase Linkを作成しています.

SCOTT@orclpdb2> @day15
1* CREATE DATABASE LINK remote_scott CONNECT TO scott IDENTIFIED BY tiger USING 'orclpdb1'

データベース・リンクが作成されました.

経過: 00:00:00.01
1 CREATE TABLE local_emp
2 AS
3 SELECT *
4 FROM
5 emp@remote_scott
6 ORDER BY
7 empno
8* FETCH FIRST 5 ROWS ONLY

表が作成されました.

経過: 00:00:00.09


今回の例は分散クエリーです(複数のデータベースを問い合わせてるので). Database Linkを介してアクセスされる表へのOperationは, REMOTE となり, REMOTEデータベースへSQL文の実行がリクエストされることになります.
リクエストされるSQLは, 最近では, AUTO TRACEのRemote SQL Informationセクションでも確認できるようになっています(便利ですよね). 該当SQLをリモートデータベースで実行して実行計画を確認することができます.

  1  SELECT
2 *
3 FROM
4 local_emp
5 INTERSECT
6 SELECT
7 *
8 FROM
9* emp@remote_scott

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30

実行計画
----------------------------------------------------------
Plan hash value: 63876577

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 903 | 7 (29)| 00:00:01 | | |
| 1 | INTERSECTION HASH | | | | | | | |
| 2 | TABLE ACCESS FULL| LOCAL_EMP | 5 | 435 | 2 (0)| 00:00:01 | | |
| 3 | REMOTE | EMP | 12 | 468 | 3 (0)| 00:00:01 | REMOT~ | R->S |
------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

3 - SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM "EMP"
"EMP" (accessing 'REMOTE_SCOTT' )

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1352 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

9年ほど前, クエリー分裂症の治療の様子を見たい方はこちらのスライド見てね. :)


ということで, 明日も私です:)




Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table

| | | コメント (0)

2022年12月14日 (水)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table

Previously on Mac De Oracle... Day 13は, MULTI-TABLE INESRT にフォーカスをあてました. 癖は多いですけども, 便利ですよねーと思っている文の一つでした.

 

それでは, Day 14 の窓を開けましょう!.

Oracle Databaseでは有名すぎる, Selecting from the DUAL Tableですが, 遂に, 21c23cで省略できるようになる!!らしい. まだ, 23cは触ったことはないですが, リリースされたら試してみたいですよね. というか, 多分, みんな, 間違いなく, ブログに書くネタだろうと思いますw

今日は, そんな消えゆく, dual 表, ありがとう. という意味も込めて, 魚拓として, 実行計画という名のレントゲンを残しておこうと思います. (23cリリースされた時の引用にもなるのでw)

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

dual表って実在する表なんですよねw.
昔, 誰かが, dual表へ行をINSERTして, dual表が複数行返すようにしたらどうなるか? なんていう, おイタをしていたような...誰だろうなぁ, あれやってたのw 



SCOTT@orclpdb1> @day14
1* SELECT * FROM dual

DUM
---
X

経過: 00:00:00.01

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
587 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

Oracle Database 10g R1以降では, DUMMY列にアクセスしない場合には, dual表へのconsistent getsの発生が抑止されるようになりました. その場合 FAST DUAL としてOperationに現れます


  1* SELECT null FROM dual

N
-

経過: 00:00:00.00

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1388734953

-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
584 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

SELECT * FROM dual の動きで面白いところの一つは, 前回のエントリーで, Multi Table Insertのような構文では, SELECT * FROM dual を使っていますが, SELECT * FROM dual 単体で使う時とは異なり, 実行計画中では, しっかり, コストの低い FAST DUAL が現れている点です. 21cまでは, 文法上必須なわけですが, SELECT * FROM dual と書いた場合でも, ただのお約束上の話なので, よしなに FAST DUAL してくれていると言うことになります. ただ、23cになったら記述不要になるようなので, SQL文には書かなくなりそうな気がします(下位互換で残るのはあるでしょうけども).

と, 言うことで, 本日はここまで.

 

はぁ. アドベントカレンダー全部俺も折り返しましたが, まだ, 先は長いw
バテ気味な感じが, しつつも, 明日も私が担当します!
では, また.

 


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT

 

| | | コメント (0)

2022年12月13日 (火)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT

Previously on Mac De Oracle... Day 12は, TEMP TABLE TRANSFORMATIONにフォーカスをあてました. WITH句以外でも内部的に行われますが, 一時表へのマテリアライズを意図した話ではありますが, クソでかSQLの可読性向上のために利用されることも少なくなく, そのようなケースではインラインビューとして展開されるとうお話しでした. オプティマイザが間違えなければ:)

それでは, Day 13 の窓を開けましょう!.

今日は, MULTI-TABLE INSERT です. え!, INSERT文なのに? みたいに思った方もいるかもしれませんが, MULTI-TABLE INSERTは特徴的なOperationgが現れるので, 知っていた方が良いですよ. ただ, この構文自体はOracleの方言なので, なかなかお目にかからない(マルチテーブルインサート自体は他のRDBMSにもありますが, 癖の多い部分の一つなので)のですがねw 良い機能だと思いますけどね!
では, 診ていこうと思います.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

Oracle Database 23cからdualを省略できるようになるらしいですが! 21cまでは, MULTI TABLE INSERTを利用する場合, SELECT * FROM dual が必須です. 不要になるとかなり便利ですよね.
以下では, 文字通り複数表へINSERTしていますが, a href="https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2020/12/post-10aa61.html">同一表へ複数行INSERTすることもできます. 何気に便利でしょこれ.


SCOTT@orclpdb1> @day13
1 INSERT ALL
2 INTO emp(empno, ename) VALUES(7788, 'Lucky')
3 INTO dept(deptno, dname) VALUES(88, 'QA')
4* SELECT * FROM dual

2行が作成されました.

 

さて, 実行計画は, どうでしょうか. MULTI-TABLE INSERTに加えて, INTOが複数現れます. 現状, FAST DUALも出ますけど:)


実行計画
----------------------------------------------------------
Plan hash value: 2939908344

--------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
--------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | MULTI-TABLE INSERT | | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
| 3 | INTO | EMP | | | |
| 4 | INTO | DEPT | | | |
--------------------------------------------------------------------

統計
----------------------------------------------------------
6 recursive calls
11 db block gets
10 consistent gets
4 physical reads
1288 redo size
188 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
2 rows processed

この手の方言って, SQL標準で使用禁止みたいに書かれてたりすることが多くて, なんで?という気はします. 方言でも性能面で有利に働くのであれば例外は設けておくべきだと, 個人的は思うんですよね.
昔. 階層問合せ使わずに(典型的なSQLアンチパターンで)ヒーヒー言ってた現場を思い出したり. かなり昔ですけどね ー> その現場へ有用性説明し階層問い合わせに書き換えたことで, 皆さん幸せな感じになってましたよーと:)

 

調子が出てきたので, また明日も, 担当は, 私です!

ではでは.

 


Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION

| | | コメント (0)

2022年12月12日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION

Previously on Mac De Oracle... Day 11は, GROUPING SETS, ROLLUP, CUBEでした. ROLLUP, CUBEが登場したのは, Oracle Database 8i なので, 1999年あたりだったと思います. それまでUNIONを使ったクソ重, クソクエリーが多かった記憶はありますw ROLLUP, CUBEが神様に見えましたものw 

それでは, Day 12 の窓を開けましょう!.

今日の実行計画という名のレントゲンは, TEMP TABLE TRANSFORMATION です. 内部的に変換されて行われて現れることもあるこのOperationですが, 意図的にTEMP TABLE TRANSFORMATIONに行う場合で有名なのはCTEではないでしょうか?
類似する話題LOAD AS SELECT (CURSOR DURATION MEMORY)を取り上げていました, 内容としては11g,12c,18c,19cの差異の話。今回はその元となるTEMP TABLE TRANSFORMATIONにフォーカスをあてます。

では診ていこうと思います.

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

ちなみに, WITH句でCTEを使ったら必ず, TEMP TABLE TRANSFORMATION でマテリアライズされるということでもなく, マテリアライズするよ理, インラインビューのままでいいよねとオプティマイザが判断してインラインビューとして展開する場合もあります. それを強制する INLINE というヒントもあります.
WITH句が利用されるケースの1つとして, 可読性向上だけを目的としている場合WITH句で定義されたクエリーが一度しか参照されていないようなケースでは, マテリアライズして一時表作るよりインラインビューで十分という判断でそのような最適化が行われます:)
逆に, マテリアライズして, 一時表に変換してほいしケースもあります. (複数回参照しているのに....みたいな場合ですね. 場合は, MATERIALIZE ヒントでオプティマイザヒントに教えてあげましょう. 最近ミスらなくなった気がしないでもない. オプティマイザも進化してますからね)

 

以下, オプティマイザはCTEと認識して, 一時表へマテリアライズした上で, 一時表を再利用しています.


SCOTT@orclpdb1> @day12
1 WITH temp_emp AS
2 (
3 SELECT
4 emp.*
5 ,dept.dname
6 FROM
7 emp
8 INNER JOIN dept
9 ON
10 emp.deptno = dept.deptno
11 )
12 SELECT
13 *
14 FROM
15 temp_emp
16 WHERE
17 temp_emp.empno = 7369
18 UNION
19 SELECT
20 *
21 FROM
22 temp_emp
23 WHERE
24* temp_emp.deptno = 20

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DNAME
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ---------- ------------------------------------------
7369 SMITH CLERK 7902 80-12-17 800 20 RESEARCH
7902 FORD ANALYST 7566 81-12-03 3000 20 RESEARCH
7566 JONES MANAGER 7839 81-04-02 2975 20 RESEARCH

実行計画
----------------------------------------------------------
Plan hash value: 1235767800

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 768 | 6 (34)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D665A_DDA815 | | | | |
| 3 | MERGE JOIN | | 4 | 208 | 6 (17)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPT | 4 | 52 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 4 | 156 | 4 (25)| 00:00:01 |
|* 7 | TABLE ACCESS FULL | EMP | 4 | 156 | 3 (0)| 00:00:01 |
| 8 | HASH UNIQUE | | 8 | 768 | 6 (34)| 00:00:01 |
| 9 | UNION-ALL | | | | | |
|* 10 | VIEW | | 4 | 384 | 2 (0)| 00:00:01 |
| 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D665A_DDA815 | 4 | 196 | 2 (0)| 00:00:01 |
|* 12 | VIEW | | 4 | 384 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D665A_DDA815 | 4 | 196 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
filter("EMP"."DEPTNO"="DEPT"."DEPTNO")
7 - filter("EMP"."DEPTNO"=20 OR "EMP"."EMPNO"=7369)
10 - filter("TEMP_EMP"."EMPNO"=7369)
12 - filter("TEMP_EMP"."DEPTNO"=20)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1362 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
3 rows processed

 

CTEの一時表へのマテリアライズを INLINE ヒントでインラインに展開するようにした例です. 一時表へのマテリアライズが抑止され, SQL本文に同一クエリーが展開されている様子が見えます.
WITH句がサポートされていなかった頃は, 事前に一時表を作成して使ったり苦労していたことを思うと, これも非常に便利な機能ですよね.


  1  WITH temp_emp AS
2 (
3 SELECT
4 /*+
5 inline
6 */
7 emp.*
8 ,dept.dname
9 FROM
10 emp
11 INNER JOIN dept
12 ON
13 emp.deptno = dept.deptno
14 )
15 SELECT
16 *
17 FROM
18 temp_emp
19 WHERE
20 temp_emp.empno = 7369
21 UNION
22 SELECT
23 *
24 FROM
25 temp_emp
26 WHERE
27* temp_emp.deptno = 20

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO DNAME
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ---------- ------------------------------------------
7369 SMITH CLERK 7902 80-12-17 800 20 RESEARCH
7566 JONES MANAGER 7839 81-04-02 2975 20 RESEARCH
7902 FORD ANALYST 7566 81-12-03 3000 20 RESEARCH

実行計画
----------------------------------------------------------
Plan hash value: 119758422

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 208 | 5 (20)| 00:00:01 |
| 1 | HASH UNIQUE | | 4 | 208 | 5 (20)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | NESTED LOOPS | | 1 | 52 | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | EMP | 1 | 39 | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 3 | 156 | 2 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 3 | 117 | 1 (0)| 00:00:01 |
|* 12 | INDEX RANGE SCAN | IX01_EMP | 3 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("EMP"."EMPNO"=7369)
7 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
10 - access("DEPT"."DEPTNO"=20)
12 - access("EMP"."DEPTNO"=20)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1362 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed

 

サッカーの判定に利用されているVAR(Video Assistant Referee)ですが, 人の判断だと微妙なところとは人それぞれのブレが現れるので, テニスでもそうですがわかりやすいので良いんじゃないかと思いますが. ね. (ここ数日そういうブレによる判定へ意義申し立てしたりしてるw しかも見落としだし原因は. )

 

でちょいと, めんどくせーなーとなっていたりしているわけですけども.

それはさておき.

 

明日も, アドベントの担当は私ですw

ではまた.

 

参考)


SCOTT@orclpdb1> select name,inverse, sql_feature from v$sql_hint where name in ('INLINE');

NAME INVERSE SQL_FEATURE
-------------------- -------------------- ------------------------------
INLINE MATERIALIZE QKSFM_TRANSFORMATION

 

 


Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE

 

| | | コメント (0)

2022年12月11日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE

Previously on Mac De Oracle... Day 10は, MAT_VIEW REWRITE ACCESS FULLでした. Materialized Viewアクセスへ内部でRewriteする機能は随分前からありますが, Oracle Databaseらしい機能の一つだなぁ. と思います. 6.7 Using Real-time Materialized Viewsのように既存機能のブラッシュアップなど継続的に細かい改善が行われていたりします.

それでは, Day 11 の窓を開けましょう!.

まだ紹介していない既存の実行計画多いんですよね. SQLもそうですが, 実行計画になるとますます奥が深いですといいますか, このシリーズ, ネタは沢山あるので当分持ちそうですw

まず, 2011年の面倒くさい大人の事情縛りシリーズのネタですが, 考え方は今でも同じなので, 一度, 軽く眺めておいてください. その間に準備しますのでw.. (違
なお, SQLヒントの書き方で, 最近, ヒントが利用されたかどうかレポートされるようになったことで, USE_HASH, USE_NLのヒントの書き方が云々とかいう話をたまに聞きますが, エラーではなくて無視されるというのが仕様なので, それをうまく利用した使い方を, 面倒くさい大人の事情だらけの現場で考えて今に至った結果であるということは, いずれどこかで説明したいとは思います. (ポイントはチューニングする際の手間削減だったり, どこまで固定するべきか, しないほうが良いかという判断だったりします. 経年で変化もしますからね. ただ, それが手癖になっているという点は否めないわけですけども)
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #2
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #3
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #4
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #5
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #7 おまけ
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング8 おまけのおまけ
・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング番外編

 

昔の2011年ぐらいの頃の Oracle Database の実行計画を見たことない年齢のエンジニア多くなってきているとは思いますが, どうでしたか? 上記の過去エントリー. 今でも考え方の基本のキです.

ということで, やっと, 今日の本題です.

データはなくても想定した実行計画のキーワードは取得できるので, 表(データなし)と索引だけ作っておきます. (データ登録するのが面倒だったということでもありますが, 影響はないのでw)
なお, 以下表は, ・いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1で利用したものと同一です.

SCOTT@orclpdb1> @day11
1* drop table test1

表が削除されました.

経過: 00:00:00.04
1 create table test1
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test1 primary key (starting_date,item_code,shop_code) using index nologging
8 )
9* nologging

表が作成されました.

経過: 00:00:00.02
1* drop table test2

表が削除されました.

経過: 00:00:00.05
1 create table test2
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test2 primary key (starting_date,item_code,shop_code) using index global nologging
8 )
9 partition by hash(starting_date)
10 (
11 partition test201,
12 partition test202,
13 partition test203,
14 partition test204
15 )
16* nologging

表が作成されました.

経過: 00:00:00.02
1* drop table test3

表が削除されました.

経過: 00:00:00.07
1 create table test3
2 (
3 starting_date char(8) not null,
4 shop_code char(4) not null,
5 sales_figure number not null,
6 item_code char(10) not null,
7 constraint pk_test3 primary key (starting_date,item_code,shop_code) using index local
8 (
9 partition test301idx,
10 partition test302idx,
11 partition test303idx,
12 partition test304idx,
13 partition test305idx,
14 partition test306idx,
15 partition test307idx,
16 partition test308idx,
17 partition test309idx,
18 partition test310idx,
19 partition test311idx,
20 partition test312idx,
21 partition testmaxidx
22 )
23 nologging
24 )
25 partition by range(starting_date) (
26 partition test301 values less than ('20110201') ,
27 partition test302 values less than ('20110301') ,
28 partition test303 values less than ('20110401') ,
29 partition test304 values less than ('20110501') ,
30 partition test305 values less than ('20110601') ,
31 partition test306 values less than ('20110701') ,
32 partition test307 values less than ('20110801') ,
33 partition test308 values less than ('20110901') ,
34 partition test309 values less than ('20111001') ,
35 partition test310 values less than ('20111101') ,
36 partition test311 values less than ('20111201') ,
37 partition test312 values less than ('20120101') ,
38 partition testmax values less than (maxvalue)
39 )
40* nologging

表が作成されました.

経過: 00:00:00.05

GROUPING SETSから.
GROUPING SETSというキーワードは実行計画には現れまん. 内部で後半で説明するROLLUPが含まれる一時表が生成されるように書き換えられる挙動が特徴です. 2011の頃の実行計画と大きく違うのは, CURSOR DURATION MEMORYなんて操作が行われてるあたりですね.
GROUPING SETSというキーワードはないですが, 内部的には, CTEが利用されてROLLUP等と併用されている挙動なので, CTEによる一時表の生成コストがポイントになります. 最近はSSDがほとんどだろうと思うので, HDDだった頃ほど, 一時表の生成コストは気にならないケースも多いかもしれないですけども, 状況次第, どこまでチューニングするか次第というところはありますよね.

  1  SELECT
2 CASE
3 WHEN quarter IS NULL THEN month
4 ELSE quarter
5 END AS month
6 ,CASE
7 WHEN grouping_id = 1 THEN 'ALL'
8 ELSE shop_code
9 END AS shop_code
10 ,sales_figure
11 FROM (
12 SELECT
13 grouping_id(shop_code) as grouping_id
14 ,quarter
15 ,month
16 ,shop_code
17 ,SUM(sales_figure) AS sales_figure
18 FROM
19 (
20 SELECT
21 CASE
22 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
23 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
24 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
25 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
26 END AS quarter
27 ,SUBSTR(starting_date,1,6) AS month
28 ,shop_code
29 ,sales_figure
30 FROM
31 test2
32 WHERE
33 SUBSTR(starting_date,1,6) BETWEEN '201101' AND '201103'
34 )
35 GROUP BY GROUPING SETS (
36 (month, shop_code),
37 (quarter, shop_code),
38 (month),
39 (quarter)
40 )
41 )
42 WHERE
43 shop_code = '1000'
44 OR grouping_id = 1
45 ORDER BY
46 month
47* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.03

レコードが選択されませんでした.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 2567395266

----------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 50 | 11 (28)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 50 | 11 (28)| 00:00:01 | | |
| 2 | VIEW | | 1 | 50 | 10 (20)| 00:00:01 | | |
| 3 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 4 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6620_DD3617 | | | | | | |
| 5 | PARTITION HASH ALL | | 1 | 29 | 2 (0)| 00:00:01 | 1 | 4 |
|* 6 | TABLE ACCESS FULL | TEST2 | 1 | 29 | 2 (0)| 00:00:01 | 1 | 4 |
| 7 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6621_DD3617 | | | | | | |
| 8 | HASH GROUP BY ROLLUP | | 1 | 29 | 3 (34)| 00:00:01 | | |
| 9 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6620_DD3617 | 1 | 29 | 2 (0)| 00:00:01 | | |
| 10 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6621_DD3617 | | | | | | |
| 11 | HASH GROUP BY ROLLUP | | 1 | 23 | 3 (34)| 00:00:01 | | |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6620_DD3617 | 1 | 23 | 2 (0)| 00:00:01 | | |
| 13 | VIEW | | 1 | 50 | 2 (0)| 00:00:01 | | |
|* 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6621_DD3617 | 1 | 29 | 2 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - filter(SUBSTR("SYS_TBL_$1$"."STARTING_DATE",1,6)>='201101' AND SUBSTR("SYS_TBL_$1$"."STARTING_DATE",1,6)<='201103')
14 - filter("SYS_TEMP_0FD9D6621_DD3617"."C1"='1000' OR BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D6621_DD3617"."D
0"),1))=1)

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

13 - SEL$80FD2AB9
U - NO_MERGE

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
2 db block gets
0 consistent gets
0 physical reads
384 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed

次に, ROLLUP. GROUPPING SETSで内部的に行われていますが, 実際にROLLUPを実行するとこうなります. (最初のSQL分とは集計内容が異なるので結果は同一ではないことはお気づきだと思います. データが無い状態で実行しているのでわかりづらいですが, ご注意くさだい)

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 ROLLUP(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.02

レコードが選択されませんでした.

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 3343531542

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
|* 2 | FILTER | | | | | | | |
| 3 | HASH GROUP BY ROLLUP | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 4 | VIEW | | 1 | 33 | 2 (50)| 00:00:01 | | |
| 5 | HASH GROUP BY | | 1 | 29 | 2 (50)| 00:00:01 | | |
| 6 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| TEST2 | 1 | 29 | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 7 | INDEX RANGE SCAN | PK_TEST2 | 1 | | 1 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
7 - access("STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed

最後は, CUBEです. これは, GROUPING SETSで行っていたことと同じことをCUBEを使って実現しています. 同じ結果を得るのであれば, CUBEの方が有利そうだ, という点に気づいた方は多いだろう思います:)

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

レコードが選択されませんでした.

経過: 00:00:00.01

レコードが選択されませんでした.

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 2588666537

------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 1 | SORT ORDER BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
|* 2 | FILTER | | | | | | | |
| 3 | SORT GROUP BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 4 | GENERATE CUBE | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 5 | SORT GROUP BY | | 1 | 33 | 4 (75)| 00:00:01 | | |
| 6 | VIEW | | 1 | 33 | 2 (50)| 00:00:01 | | |
| 7 | HASH GROUP BY | | 1 | 29 | 2 (50)| 00:00:01 | | |
| 8 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| TEST2 | 1 | 29 | 1 (0)| 00:00:01 | ROWID | ROWID |
|* 9 | INDEX RANGE SCAN | PK_TEST2 | 1 | | 1 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
9 - access("STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
542 bytes sent via SQL*Net to client
41 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
0 rows processed

 

アドベントカレンダー書いているだけで, 12月が終わってしまう気がするw

ということを, 言っても, やはり, 明日も担当は, 私ですw

では, また.

 

参考) Oracle Database 21c - Data Warehousing Guide / 21 SQL for Aggregation in Data Warehouses


Related article on Mac De Oracle ・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL

| | | コメント (0)

2022年12月10日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL

Previously on Mac De Oracle...
Day 9は, COLLECTION ITERATOR PICKLER FETCHでした. パイプラインテーブルファンクションだけに限りませんが, 利用者定義プロシージャやファンクション内から実行されるSQL文の有無, そして, 実行計画の把握という一手間多くなるタイプです. 特にパイプラインテーブルファンクションについては, 実行計画に特殊な操作として現れるのが特徴というお話しでした.

では, Day 10 の窓を開けましょう.

今日も, 紹介済みだと勘違いして忘れていた, MAT_VIEW REWRITE ACCESS FULL です!

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

データ作りが面倒なので, 再びサンプルスキーマのSHの力を借りましょうw (アドベントカレンダー, 全部俺は, ある意味体力, 気力の勝負w でもあるので, 楽できるところは楽に行きましょう :)


SH@orclpdb1> @day10
1 CREATE MATERIALIZED VIEW LOG ON sales
2 WITH
3 SEQUENCE
4 , ROWID
5 (
6 prod_id
7 , quantity_sold
8 , amount_sold
9 )
10* INCLUDING NEW VALUES

マテリアライズド・ビュー・ログが作成されました.

1 CREATE MATERIALIZED VIEW LOG ON products
2 WITH
3 ROWID
4 (
5 prod_id
6 , prod_name
7 , prod_category
8 , prod_subcategory)
9* INCLUDING NEW VALUES

マテリアライズド・ビュー・ログが作成されました.

1 CREATE MATERIALIZED VIEW mv_sales_4_day10
2 REFRESH FAST ON DEMAND
3 ENABLE QUERY REWRITE
4 AS
5 SELECT
6 prod_name
7 , SUM(quantity_sold) AS sum_quantity
8 , SUM(amount_sold) AS sum_amount
9 FROM
10 sales
11 INNER JOIN products
12 ON
13 sales.prod_id = products.prod_id
14 GROUP BY
15* prod_name

マテリアライズド・ビューが作成されました.


マテビューの準備ができたので, 本日の主役. Query Rewriteが働いで, sales, products表ではなく, materialize viewが full scan されるように書き換えられ, MAT_VIEW REWRITE ACCESS FULLが現れています!
処理時間もいい感じですよね.  経過: 00:00:00.02 となっています.

  1  SELECT
2 prod_name
3 , SUM(quantity_sold)
4 , SUM(amount_sold)
5 FROM
6 sales
7 INNER JOIN products
8 ON
9 sales.prod_id = products.prod_id
10 GROUP BY
11* prod_name

PROD_NAME SUM(QUANTITY_SOLD) SUM(AMOUNT_SOLD)
-------------------------------------------------- ------------------ ----------------
5MP Telephoto Digital Camera 6002 6312268.4
17" LCD w/built-in HDTV Tuner 6010 7189171.77
Envoy 256MB - 40GB 5766 5635963.08
Y Box 6929 2082330.3
Mini DV Camcorder with 3.5" Swivel LCD 6160 8314815.4
Envoy Ambassador 9591 15011642.5

...略...

Smash up Boxing 7844 260436.75
Martial Arts Champions 6711 148558.92
Comic Book Heroes 4572 101214.6
Fly Fishing 4091 34547.82
Finding Fido 6168 78881.08
Adventures with Numbers 12742 175563.92
Extension Cable 7576 60713.47
Xtend Memory 15191 366858.31

71行が選択されました.

経過: 00:00:00.02

71行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 3048942819

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 71 | 2627 | 3 (0)| 00:00:01 |
| 1 | MAT_VIEW REWRITE ACCESS FULL| MV_SALES_4_DAY10 | 71 | 2627 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
4356 bytes sent via SQL*Net to client
96 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
71 rows processed

ついでなの, Query Rewirteを無効にしてみるとどうなるでしょうか? 当然, オリジナルのsales, products表が結合されます! 
処理時間も大幅に増加して,  経過: 00:00:00.28 ですね! かなりの差ですよねこれ. このあたりもうまく使いたい, Oracleの便利な機能の一つではありますね.

  1  SELECT
2 /*+
3 NO_REWRITE
4 */
5 prod_name
6 , SUM(quantity_sold)
7 , SUM(amount_sold)
8 FROM
9 sales
10 INNER JOIN products
11 ON
12 sales.prod_id = products.prod_id
13 GROUP BY
14* prod_name

PROD_NAME SUM(QUANTITY_SOLD) SUM(AMOUNT_SOLD)
-------------------------------------------------- ------------------ ----------------
5MP Telephoto Digital Camera 6002 6312268.4
17" LCD w/built-in HDTV Tuner 6010 7189171.77
Envoy 256MB - 40GB 5766 5635963.08
Y Box 6929 2082330.3
Mini DV Camcorder with 3.5" Swivel LCD 6160 8314815.4

...略...

Martial Arts Champions 6711 148558.92
Comic Book Heroes 4572 101214.6
Fly Fishing 4091 34547.82
Finding Fido 6168 78881.08
Adventures with Numbers 12742 175563.92
Extension Cable 7576 60713.47
Xtend Memory 15191 366858.31

71行が選択されました.

経過: 00:00:00.28

71行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 504757596

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 71 | 4260 | 574 (11)| 00:00:01 | | |
| 1 | HASH GROUP BY | | 71 | 4260 | 574 (11)| 00:00:01 | | |
|* 2 | HASH JOIN | | 72 | 4320 | 573 (11)| 00:00:01 | | |
| 3 | VIEW | VW_GBC_5 | 72 | 2160 | 570 (11)| 00:00:01 | | |
| 4 | HASH GROUP BY | | 72 | 864 | 570 (11)| 00:00:01 | | |
| 5 | PARTITION RANGE ALL| | 918K| 10M| 523 (3)| 00:00:01 | 1 | 28 |
| 6 | TABLE ACCESS FULL | SALES | 918K| 10M| 523 (3)| 00:00:01 | 1 | 28 |
| 7 | TABLE ACCESS FULL | PRODUCTS | 72 | 2160 | 3 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ITEM_1"="PRODUCTS"."PROD_ID")

Note
-----
- this is an adaptive plan

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1641 consistent gets
0 physical reads
0 redo size
4356 bytes sent via SQL*Net to client
96 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
71 rows processed

1* DROP MATERIALIZED VIEW LOG ON sales

マテリアライズド・ビュー・ログが削除されました.

1* DROP MATERIALIZED VIEW LOG ON products

マテリアライズド・ビュー・ログが削除されました.

1* DROP MATERIALIZED VIEW mv_sales_4_day10

マテリアライズド・ビューが削除されました.


さてさて, やっと, Day 10です. まだ半分も終わってないのかと, 遠ーーーーーーーーーーくをみるなど.

そんなこと, してても, 明日も私が担当なので, 何か考えますw

参考)
Oracle Database 21c / Data Warehousing Guide - 6 Advanced Materialized Views



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH

| | | コメント (0)

2022年12月 9日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH

Previously on Mac De Oracle...
Day 8は, レントゲンで紹介済みだと, すっかり勘違いして紹介し忘れていた TABLE ACCESS BY INDEX ROWID BATCHED でした. 索引スキャンはしているけど, 実行回数が多かったり, 実行回数は少ないが, 一回あたりのBuffer gets, Physical Readsが多いケースでは, ボディーブローのような感じで結構影響出るタイプであるケースも少なくないので, もし必要があれば, Index Only ScanなどでIO数削減して(重箱の隅を突くような)治療に繋がることも多い身近なOperationでもあるので知っておくと何かの時には助けになりますよ. きっと.

ということで, Day 9 の窓を開けましょう.

今日の主役は, COLLECTION ITERATOR PICKLER FETCHです.
どのような時に現れるOperationか, 既にググってる方はお気づきだと思いますがw パイプラインテーブルファンクションを利用してコレクションを返している場合です. これ意外に多くなってきているようにも思いますが, PJ次第なのかなとは思います. 上手く使えば味方になったり. .

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


Pipelined table function で ascii artで使ったパイプラインテーブルファンクションで実行計画を見てみましょう. (ちなみに, ASCII ARTのYouTube動画には無音です:)


前述のパイプラインテーブルファンクションでは, 表データをアクセスしていませんが, 表をアクセスしている場合でも, パイプラインテーブルファンクション内部から実行されているSQLは表面上現れません.

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from tree(50, 0.2);

4651行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 1806254315

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| TREE | 8168 | 16336 | 29 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
23 recursive calls
0 db block gets
26 consistent gets
0 physical reads
0 redo size
311383 bytes sent via SQL*Net to client
3462 bytes received via SQL*Net from client
312 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4651 rows processed


以下, サンプルスキーマであるshのsales表をアクセスするパイプラインテーブルファンクションですが, 実行計画には, COLLECTION ITERATOR PICKLER FETCHが現れるだけであることが分かります.
通常このように, PL/SQLなどのUDFやプロシージャ内部から実行されるSQL文はAWR等で別途確認していく必要があるという点は, 21cでも同様です. この辺りは, 仕方ないかなという感じはしますが, もう少し楽に該当SQL文の実行計画が確認できたら楽になるかな. という気はします.

データを確認し, 引数に利用する値を選んでいるようす.

SH@orclpdb1> desc sales
名前 NULL? 型
----------------------------------------- -------- ----------------------------
PROD_ID NOT NULL NUMBER
CUST_ID NOT NULL NUMBER
TIME_ID NOT NULL DATE
CHANNEL_ID NOT NULL NUMBER
PROMO_ID NOT NULL NUMBER
QUANTITY_SOLD NOT NULL NUMBER(10,2)
AMOUNT_SOLD NOT NULL NUMBER(10,2)

SH@orclpdb1>
SH@orclpdb1> select * from sales order by time_id desc fetch first 10 rows only;

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
14 1472 01-12-31 3 351 1 1193.02
20 3042 01-12-31 3 351 1 628.89
20 8182 01-12-31 2 999 1 628.89
20 7231 01-12-31 2 999 1 628.89
20 5745 01-12-31 2 999 1 628.89
20 3973 01-12-31 2 999 1 628.89
20 1978 01-12-31 3 999 1 628.89
20 118 01-12-31 3 999 1 628.89
20 1978 01-12-31 2 999 1 628.89
16 4958 01-12-31 3 999 1 298.11


パイプラインテーブルファンクションの作成中

SH@orclpdb1> @day9
1 CREATE OR REPLACE PACKAGE day9_pkg AS
2 CURSOR cur_2_latest_sales (
3 in_channel_id sales.channel_id%TYPE
4 ,in_prod_id sales.prod_id%TYPE
5 ,in_cust_id sales.cust_id%TYPE
6 ) IS
7 SELECT
8 *
9 FROM
10 sales
11 WHERE
12 channel_id = in_channel_id
13 AND prod_id = in_prod_id
14 AND cust_id = in_cust_id
15 ORDER BY
16 time_id DESC
17 FETCH FIRST 2 ROWS ONLY;
18
19 TYPE outtable_type IS TABLE OF sales%ROWTYPE;
20
21 FUNCTION list_2_latest_sales (
22 in_channel_id IN sales.channel_id%TYPE
23 ,in_prod_id IN sales.prod_id%TYPE
24 ,in_cust_id IN sales.cust_id%TYPE
25 )
26 RETURN outtable_type PIPELINED;
27* END day9_pkg;

パッケージが作成されました.

経過: 00:00:00.00
エラーはありません.


1 CREATE OR REPLACE PACKAGE BODY day9_pkg AS
2 FUNCTION list_2_latest_sales (
3 in_channel_id IN sales.channel_id%TYPE
4 ,in_prod_id IN sales.prod_id%TYPE
5 ,in_cust_id IN sales.cust_id%TYPE
6 )
7 RETURN outtable_type PIPELINED IS
8 sales_rec outtable_type;
9 BEGIN
10 FOR sales_rec IN cur_2_latest_sales(in_channel_id, in_prod_id, in_cust_id) LOOP
11 PIPE ROW(sales_rec);
12 END LOOP;
13 RETURN;
14 END list_2_latest_sales;
15* END day9_pkg;

パッケージ本体が作成されました.

経過: 00:00:00.00
エラーはありません.

以下, テーブルファンクションでsales表をアクセスしていますが, 見える範囲は21cになっても同じで, テーブルファンクションの場合は, COLLECTION ITERATOR PICKLER FETCHという形で表に見える状態なんですよ.
内部で, SQLが実行されているかどうかは, 実行計画からだけでは判断できない例の一つでもあります. このOperationを見つけたら, ファンクション内部で利用されているSQL文を特定しておくと後々役に立つこともあります.

  1  SELECT
2 *
3 FROM
4* day9_pkg.list_2_latest_sales(2, 20, 5745)

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
20 5745 01-12-31 2 999 1 628.89

経過: 00:00:00.00

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 457385954

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| LIST_2_LATEST_SALES | 8168 | 16336 | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
2 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
1093 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

テーブルファンクションから実行されるSQLは以下のような感じです. 実際にはバインド変数が利用されるため, バインドピークによる影響も合わせて見る必要もあります(バインドピーク無効にしているところって, まだありそうですしね. 昔からの大人の事情に縛られまくっているところとか)

  1  SELECT
2 *
3 FROM
4 sales
5 WHERE
6 channel_id = 2
7 AND prod_id = 20
8 AND cust_id = 5745
9 ORDER BY
10 time_id DESC
11* FETCH FIRST 2 ROWS ONLY

PROD_ID CUST_ID TIME_ID CHANNEL_ID PROMO_ID QUANTITY_SOLD AMOUNT_SOLD
---------- ---------- -------- ---------- ---------- ------------- -----------
20 5745 01-12-31 2 999 1 628.89

実行計画
----------------------------------------------------------
Plan hash value: 3545264548

-------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 218 | 59 (2)| 00:00:01 | | |
|* 1 | VIEW | | 2 | 218 | 59 (2)| 00:00:01 | | |
|* 2 | WINDOW SORT PUSHED RANK | | 1 | 29 | 59 (2)| 00:00:01 | | |
| 3 | PARTITION RANGE ALL | | 1 | 29 | 58 (0)| 00:00:01 | 1 | 28 |
|* 4 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| SALES | 1 | 29 | 58 (0)| 00:00:01 | 1 | 28 |
| 5 | BITMAP CONVERSION TO ROWIDS | | | | | | | |
| 6 | BITMAP AND | | | | | | | |
|* 7 | BITMAP INDEX SINGLE VALUE | SALES_CUST_BIX | | | | | 1 | 28 |
|* 8 | BITMAP INDEX SINGLE VALUE | SALES_PROD_BIX | | | | | 1 | 28 |
-------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=2)
2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("SALES"."TIME_ID") DESC )<=2)
4 - filter("CHANNEL_ID"=2)
7 - access("CUST_ID"=5745)
8 - access("PROD_ID"=20)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
1093 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

v$sqlareaビューからはこんな感じでSQL_IDが取得できるので, AWRなどから問題になっていそうならば. こいつの実行計画をおっていく感じ. まあ, ファンクションやプロシージャで実装されている場合だと一手間増える感じなのは21cでも変わらんですね. (この例ではplan_hash_valueを指定していますが、バインド変数が利用されているケースでは同一とは限らないのでご注意ください)
SH@orclpdb1> select sql_id,plan_hash_value,sql_text from v$sqlarea where sql_text like '%SALES%' and plan_hash_value = 3545264548;

SQL_ID                                  PLAN_HASH_VALUE SQL_TEXT
--------------------------------------- --------------- ----------------------------------------------------------------------------------------------------
5ang5upk282ga 3545264548 SELECT * FROM SALES WHERE CHANNEL_ID = :B3 AND PROD_ID = :B2 AND CUST_ID = :B1 ORDER BY TIME_ID DESC
FETCH FIRST 2 ROWS ONLY


さて, さて, 続きのネタ考えてると, 睡眠不足になりそうな週にw突入してきたぞw

参考)
Oracle Database 21c / 13.5 Chaining Pipelined Table Functions for Multiple Transformations


眠くても, 明日の担当は, 私しかいませんので, やりますw はい.



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED

| | | コメント (0)

2022年12月 8日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED

Previously on Mac De Oracle...
Day 7は, 実行計画という名のレントゲンにもしっかり現れる安心感のあるJINDEX RANGE SCAN (MULTI VALUE)という操作というJSON絡みの機能のお話でした. やはり, レントゲンだけで診れた方が楽ですねw
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022もやっと1/3ぐらいw 毎年思うけど, 大変. 全部俺だとw

ということで, Day 8 の窓を開けましょう.

今日は, すでに紹介済みと勘違いして, すっかり忘れていた TABLE ACCESS BY INDEX ROWID BATCHED について診ていきたいというか, 改めて確認しておきましょう.

TABLE ACCESS BY INDEX ROWID BATCHEDが登場したのは12cの頃です. 2014年に本ブログでも扱っていました. その時の解説で復習しておきましょう.

実は, この, TABLE ACCESS BY INDEX ROWID BATCHED, 実行計画のOperationに現れるようになったのは, 12cからですが, 内部的には, 11gでも同様の挙動を示していました.
11gの頃は, Operation上は, TABLE ACCESS BY INDEX ROWIDとして現れていましたが, 内部的には, 待機イベント, db file parallel readとして起きている、ということが分かる程度でした. そう, 実行計画という名のレントゲンだけでは判別できない部類の動きでした. その後, 12cになってからOperationとして簡易に判断できるようになりました.

以下, 2014年の記事ですが, SQLトレースを取得して待機イベントも含め調べてた思い出.
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #3


では, レントゲンを見てみましょう. (索引レンジスキャンするケースでは, TABLE ACCESS BY INDEX ROWID BATCHEDになる場合が圧倒的に多くなったようにも感じるので, 比較的よく見るOperationだと思います)
INDEX RANGE SCAN + TABLE ACCESS BY INDEX ROWID BATCHEDが行われており, かつ、索引のクラスタリングファクターが大きめ(行数に近い)である場合, Index Only Scanによって, ギリギリまでチューニングできる可能性が高いケースが多いのも, このタイプのOperationあ現れた時の特徴だったりします.

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

データの準備は以下.

SCOTT@orclpdb1> @day8
1* DROP TABLE day8

表が削除されました.

1 CREATE TABLE day8
2 (
3 id NUMBER PRIMARY KEY
4 ,string_data VARCHAR2(500)
5* )

表が作成されました.

1 DECLARE
2 i NUMBER(4) := 0;
3 num_of_rows CONSTANT NUMBER(4) := 1000;
4 done BOOLEAN := false;
5 BEGIN
6 WHILE NOT done LOOP
7 BEGIN
8 INSERT INTO day8 VALUES(TRUNC(DBMS_RANDOM.VALUE(1,3001)), LPAD(TO_CHAR(i),500,'*'));
9 i := i + 1;
10 IF i >= num_of_rows THEN EXIT; END IF;
11 EXCEPTION
12 WHEN DUP_VAL_ON_INDEX THEN
13 NULL;
14 END;
15 IF MOD(i,100) = 0 THEN COMMIT; END IF;
16 END LOOP;
17* END;

PL/SQLプロシージャが正常に完了しました.

1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>UPPER('day8'),no_invalidate=>false,cascade=>true);
3* END;

PL/SQLプロシージャが正常に完了しました.

1 SELECT id
2 FROM
3 day8
4 ORDER BY id
5* FETCH FIRST 5 ROWS ONLY

ID
----------
4
8
10
12
15

良かったw 綺麗に, TABLE ACCESS BY INDEX ROWID BATCHEDが出てますね.

SCOTT@orclpdb1> @day8-2 4 15
1 SELECT
2 id
3 ,substr(string_data,1,10)
4 FROM
5 day8
6 WHERE
7* id BETWEEN &1 AND &2
旧 7: id BETWEEN &1 AND &2
新 7: id BETWEEN 4 AND 15

ID SUBSTR(STRING_DATA,1,10)
---------- -----------------------------------
4 **********
8 **********
10 **********
12 **********
15 **********

旧 7: id BETWEEN &1 AND &2
新 7: id BETWEEN 4 AND 15


実行計画
----------------------------------------------------------
Plan hash value: 145644201

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 2525 | 7 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| DAY8 | 5 | 2525 | 7 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | SYS_C008604 | 5 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID">=4 AND "ID"<=15)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
779 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

やっと, 実行計画は, SQL文のレントゲン写真だ!というタイトルっぽいネタになったような気がしたところで, 本日はこれまで.

明日の担当は, 私しかいないので, 私が書きますw (全部俺)



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)

| | | コメント (0)

2022年12月 7日 (水)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)

Previously on Mac De Oracle...
Day 6は, 実行計画という名のレントゲンにも現れないタイプ。SQLモニターアクティブレポート(html)という名の内視鏡を利用してなんとか見ることができました。また、v$sysstatからシステム統計という名の血液検査を利用してもある判断できることを確認しました。最近、レントゲンだけで確定できないタイプの多くなってきて遠ーくを見ることが多くなってきたかもねーw

さて、帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022もなんとか1/3ほどです。持つのかネタというより、検証するのツレーのが多すぎて、記事アップが間に合うのか? これw(レントゲンに映したいだけのシリーズのはずなのにw)

ということで、 Day 7の窓を開けましょう。今日は、レントゲンで分かるタイプです(一安心w)

Multi-Value Functional Indexです。21cより前は、JSON_VALUE()を利用する関数索引は、Single-Valueだけが利用できました。Single-Valueだけだと辛い場面は多いわけで、その課題への答えが、Multi-Value Functional Indexですね。
JSONへの対応を追っていると、XMLへの対応が始まった頃のOracle Databaseを思い出すお年頃の私なので、色々当時のことを思い出してしまいます(ところで、XML Masterっていう資格知ってます?w みなさん?)

余談はこれぐらいにして、本題へ。

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

table、データを用意します。


SCOTT@orclpdb1> @day7
1* DROP TABLE groups

表が削除されました。

経過: 00:00:00.04
1* CREATE TABLE groups (group_list_json JSON)

表が作成されました。

経過: 00:00:00.02
1 INSERT INTO
2 groups(group_list_json)
3 SELECT
4 JSON_OBJECT
5 (
6 'group_id' VALUE deptno
7 ,'object_type' VALUE JSON_ARRAYAGG
8 (
9 JSON_OBJECT
10 (
11 'member_id' VALUE empno
12 ,'member_name' VALUE ename
13 ) RETURNING CLOB
14 ) RETURNING CLOB
15 )
16 FROM
17 emp
18 GROUP BY
19* deptno

3行が作成されました。

経過: 00:00:00.01
1* COMMIT

コミットが完了しました。

経過: 00:00:00.01

multi-value functional indexを作成! group_id毎に配列を持っており、配列に含まれている member_idに対して、MULTIVALUE INDEXを作成しています!


  1* CREATE MULTIVALUE INDEX groups_mvix ON groups t1( t1.group_list_json.object_type.member_id.number() )

索引が作成されました。

経過: 00:00:00.01

 

本日の主役はこれです。
JSON_EXISTS()でMULTIVALUE INDEXを作成した、 member_id で指定したメンバーが含まれているメンバー全員の名前を取得しています。(その元となっている、JSONも)

想定通り、MULTIVALUE INDEXをアクセスしており、実行計画にも分かりやすく現れています:)
ついでに、Predicate Information には興味深い情報も現れていますね。これもアドベントカレンダー以外でネタになりそうです。

JSONTABLE EVALUATIONというオペレーションは、SQL/JSON Function である、JSON_TABLE() の操作を示しています。(なぜ、PIVOTが実行計画のOperationに現れないのか、ますます、謎ですなw)


  1  SELECT
2 member_name
3 , group_list_json
4 FROM
5 groups
6 ,JSON_TABLE
7 (
8 group_list_json, '$.object_type[*]'
9 COLUMNS
10 (
11 member_name VARCHAR2(10) PATH '$.member_name'
12 )
13 )
14 WHERE
15 JSON_EXISTS
16 (
17 group_list_json
18 ,'$.object_type?(@.member_id == 7499)'
19* )

MEMBER_NAME GROUP_LIST_JSON
------------------------------ ----------------------------------------------------------------------------------------------------
ALLEN {"group_id":30,"object_type":[{"member_id":7499,"member_name":"ALLEN"},{"member_id":7900,"member_nam
e":"JAMES"},{"member_id":7844,"member_name":"TURNER"},{"member_id":7698,"member_name":"BLAKE"},{"mem
ber_id":7654,"member_name":"MARTIN"},{"member_id":7521,"member_name":"WARD"}]}

JAMES
TURNER
BLAKE
MARTIN
WARD

6行が選択されました。

経過: 00:00:00.02

6行が選択されました。

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1011849735

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 32M| 31 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 8168 | 32M| 31 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| GROUPS | 1 | 4115 | 2 (0)| 00:00:01 |
| 3 | HASH UNIQUE | | 8168 | 32M| | |
|* 4 | INDEX RANGE SCAN (MULTI VALUE) | GROUPS_MVIX | 1 | | 1 (0)| 00:00:01 |
| 5 | JSONTABLE EVALUATION | | | | | |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access(JSON_QUERY("GROUP_LIST_JSON" /*+ LOB_BY_VALUE */ FORMAT OSON ,
'$.object_type.member_id.number()' RETURNING NUMBER ASIS WITHOUT ARRAY WRAPPER ERROR ON
ERROR NULL ON EMPTY NULL ON MISMATCH MULTIVALUE)=7499)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
3446 bytes sent via SQL*Net to client
532 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed

 


久々に、実行計画という名のレントゲンだけで完結した記事で、ホットしたw (なんだこの気持ちはw
ということで、スッキリしたわけですが、 JSONの構文に、XMLの構文と同じような、思い出を思い出しつつw  明日は、やっぱり、私が担当です:)

 

参考) Oracle Database 21c / 28.8 Creating Multivalue Function-Based Indexes for JSON_EXISTS


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

 

| | | コメント (0)

2022年12月 6日 (火)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join

Previously on Mac De Oracle...
Day 5は, 実行計画というレントゲンにも現れない PIVOT の謎。みたいなオチですが、UNPIVOTは現れるんだよねー。ということでした。

今日は、さらに、このタイトルでいいのか! という結末になっております (乞うご期待w

ということで、 Day 6の窓を開けましょう。

今回、データを準備するのは、面倒だな、と思ったので、 サンプルスキーマである SH スキーマの sales表とcustomer表を利用します。ただ、少しだけ準備は必要ですが.
(なお、INMEMORY_DEEP_VECTORIZATION パラメータのデフォルト値は、TRUEなので, INMEMORY_SIZE パラメータが設定されているのであれば、追加の設定は不要です)

SH@orclpdb1> show parameter INMEMORY_DEEP_VECTORIZATION

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_deep_vectorization boolean TRUE


では、準備を。
各表をIn-Memory化してポピュレーションする必要があります。v$im_segmentsビューからすべて載ったことを確認します。

SH@orclpdb1> alter table customers inmemory priority high memcompress for capacity low;

表が変更されました。

SH@orclpdb1> alter table sales inmemory priority high memcompress for capacity low;

表が変更されました。

SH@orclpdb1> select /*+ full(s) no_parallel(s) */ count(*) from sales s;

count(*)
----------
918843

SH@orclpdb1> select /*+ full(c) no_parallel(c) */ count(*) from customers c;

count(*)
----------
55500

SH@orclpdb1> select segment_name,inmemory_size,bytes_not_populated from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED
------------------------------ ------------- -------------------
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
DAY4 15990784 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
CUSTOMERS 2359296 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0

18行が選択されました。

実行計画という名のレントゲンがテーマのシリーズですがw 実行計画を見ただけでは、はっきりわからないというPIVOTに続き、2つ目の事例が!!!!!
実行時に実施するかどうかを判断しているとのことで、実行計画を立てている時点では判断できず、実行後または、実行中にのみ確認できるということらしい(なんとかならんのか。ん〜〜〜〜)

ということで、SQLモニターアクティブレポート(html)という名の内視鏡を利用して診る必要のある事例であることがわかりました!!!!!!!
(各表はin-memory fullでアクセスされてます。前提はクリアしているHASH JOINになっています)

SH@orclpdb1> @day6

1 SELECT
2 /*+
3 NO_STAR_TRANSFORMATION
4 NO_VECTOR_TRANSFORM
5 FULL(c)
6 FULL(s)
7 MONITOR
8 */
9 count(*)
10 FROM
11 sales s
12 INNER JOIN customers c
13 ON
14* s.cust_id = c.cust_id


実行計画
----------------------------------------------------------
Plan hash value: 3219640484

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 56 (24)| 00:00:01 | | |
| 1 | SORT AGGREGATE | | 1 | 10 | | | | |
|* 2 | HASH JOIN | | 918K| 8973K| 56 (24)| 00:00:01 | | |
| 3 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 270K| 17 (6)| 00:00:01 | | |
| 4 | PARTITION RANGE ALL | | 918K| 4486K| 34 (21)| 00:00:01 | 1 | 28 |
| 5 | TABLE ACCESS INMEMORY FULL| SALES | 918K| 4486K| 34 (21)| 00:00:01 | 1 | 28 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("S"."CUST_ID"="C"."CUST_ID")


統計
----------------------------------------------------------
4 recursive calls
0 db block gets
36 consistent gets
0 physical reads
0 redo size
593 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

...略...

内視鏡で撮影したスナップショットにもメモしてありますが、HASH JOIN操作の行のInfo列の双眼鏡アイコンをクリックすることで、In-Memory Vectorized Joinが行われたか否かを確認することができます。
DeepVec Hash Joins が重要なキーワードです。見逃さないようにしてくださいね。

今の所、この方法が最も確実で分かりやすい方法です。
Vectorized-join1
Vectorized-join11

次に、通常のHASH JOINとどう違うのかも見ておきましょう。
sales表と、customers表を非インメモリー化します。

SH@orclpdb1> alter table customers no inmemory;

表が変更されました。

SH@orclpdb1> alter table sales no inmemory;

表が変更されました。

SH@orclpdb1> select segment_name,inmemory_size,bytes_not_populated from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED
------------------------------ ------------- -------------------
DAY4 15990784 0


1  SELECT
2 /*+
3 NO_STAR_TRANSFORMATION
4 NO_VECTOR_TRANSFORM
5 FULL(c)
6 FULL(s)
7 MONITOR
8 */
9 count(*)
10 FROM
11 sales s
12 INNER JOIN customers c
13 ON
14* s.cust_id = c.cust_id

実行計画
----------------------------------------------------------
Plan hash value: 3219640484

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 947 (2)| 00:00:01 | | |
| 1 | SORT AGGREGATE | | 1 | 10 | | | | |
|* 2 | HASH JOIN | | 918K| 8973K| 947 (2)| 00:00:01 | | |
| 3 | TABLE ACCESS FULL | CUSTOMERS | 55500 | 270K| 423 (1)| 00:00:01 | | |
| 4 | PARTITION RANGE ALL| | 918K| 4486K| 518 (2)| 00:00:01 | 1 | 28 |
| 5 | TABLE ACCESS FULL | SALES | 918K| 4486K| 518 (2)| 00:00:01 | 1 | 28 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("S"."CUST_ID"="C"."CUST_ID")

統計
----------------------------------------------------------
178 recursive calls
0 db block gets
3444 consistent gets
1586 physical reads
220 redo size
593 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
21 sorts (memory)
0 sorts (disk)
1 rows processed

...略...


SQLモニターアクティブレポート(html)というなの内視鏡のスナップショット画像から、例の双眼鏡アイコンをクリックして結果をみると、In-Memory Vectorized Joinが行われていないことが分かります。また、In-memory table full も抑止したのでその差は物凄い差になって現れますね。
In-Memory Vectorized Join 19.39 ms
Hash Join / In-Memory Off 0.13 s


20221205-85808
20221205-85820

....

SQLモニターアクティブレポート(html)以外に、システム統計からも確認できるようですね。慣れないと読みにくいかも知れないですが、特定のSQL実行前後の変化を見る必要があります。ちょっと面倒ですね。セッション統計でも拾えるのだろうか。。。。(要追跡調査)

SOURCE     NAME                                     Before                After
---------- ---------------------------------------- -------------------- --------------------
sysstat IM simd KV add rows 610500 666000
sysstat IM simd KV probe calls 176 192
sysstat IM simd KV probe keys 393888 429696
sysstat IM simd KV probe rows 393888 429696
sysstat IM simd decode symbol calls 352 384
sysstat IM simd decode unpack calls 176 192
sysstat IM simd decode unpack selective calls 176 192

最後に、インメモリーにポピュレートされた状態で、In-Memory Vectorized Joinを無効化して差分をみてみましょう。

SH@orclpdb1> alter session set INMEMORY_DEEP_VECTORIZATION = false;

セッションが変更されました。

SH@orclpdb1> show parameter INMEMORY_DEEP_VECTORIZATION

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_deep_vectorization boolean FALSE


該当表はポピュレートされている状態です

SH@orclpdb1> select segment_name,inmemory_size,bytes_not_populated from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED
------------------------------ ------------- -------------------
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
DAY4 15990784 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
CUSTOMERS 2359296 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0
SALES 1310720 0


実行計画は、In-Memory Vectorized Joinが行われたものと同じ(Plan hash valueが同一)ですが、前後で取得したシステム統計値で、In-Memory Vectorized Joinに関連するsimd関連の数値に変化がみられません!!!!
システム統計をみるとIn-Memory Vectorized Joinは動作してないことが分かります。とはいえ、SQLモニターレポート(アクティブレポート)が明らかに見やすいとは思います。

  1  SELECT
2 /*+
3 NO_STAR_TRANSFORMATION
4 NO_VECTOR_TRANSFORM
5 FULL(c)
6 FULL(s)
7 MONITOR
8 */
9 count(*)
10 FROM
11 sales s
12 INNER JOIN customers c
13 ON
14* s.cust_id = c.cust_id

経過: 00:00:00.03

実行計画
----------------------------------------------------------
Plan hash value: 3219640484

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 56 (24)| 00:00:01 | | |
| 1 | SORT AGGREGATE | | 1 | 10 | | | | |
|* 2 | HASH JOIN | | 918K| 8973K| 56 (24)| 00:00:01 | | |
| 3 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 270K| 17 (6)| 00:00:01 | | |
| 4 | PARTITION RANGE ALL | | 918K| 4486K| 34 (21)| 00:00:01 | 1 | 28 |
| 5 | TABLE ACCESS INMEMORY FULL| SALES | 918K| 4486K| 34 (21)| 00:00:01 | 1 | 28 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("S"."CUST_ID"="C"."CUST_ID")

統計
----------------------------------------------------------
33 recursive calls
0 db block gets
67 consistent gets
0 physical reads
0 redo size
593 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
8 sorts (memory)
0 sorts (disk)
1 rows processed

...略...

SOURCE NAME Before After
---------- -------------------------------------------------- ---------- ----------
sysstat IM simd KV add rows 0 0
sysstat IM simd KV probe calls 0 0
sysstat IM simd KV probe keys 0 0
sysstat IM simd KV probe rows 0 0
sysstat IM simd decode symbol calls 0 0
sysstat IM simd decode unpack calls 0 0
sysstat IM simd decode unpack selective calls 0 0


In-Memory Vectorized Joinが、利用されたかどうかを判断しやすいというわけではないですが、システム統計の差分で判断するのも、ありといえばアリですかね。この結果をみると。
In-Memory Vectorized Joinを無効化すると、IM simd ***という複数の統計値が変化しなくなりました!
ただ、血液検査みたいなものなので、これは、一般的には、判断の敷居が上がる気がします。

もう少し簡易に動作有無が判断できるようになると良いのではないかと思います。

例えば、実行後の NOTE) 等のテキストで、実行されたことが判断できる情報を付加してもらうとか。auto explainでもいいし、AWR SQLレポートや実行計画をリストするときに、追加で特定のビューを問い合わせると分かるとか、とか。

実行時間の差にも注目してみてください。

どちらもIn-Memory table full scanが行われた場合、このケースでIn-Memory Vectorized Joinを利用した場合、10%ほど改善しています、より効果的な場面で利用すれば大きな改善につながりそうではありますね。
In-Memory Vectorized Join 有効化 19.39 ms
In-Memory Vectorized Join 無効化 22.54 ms
20221205-82613
20221205-82624

明日も、やはり、担当は、私ですw



参考)
Oracle Database 21c / 8.3 Optimizing Joins Using In-Memory Deep Vectorization




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

| | | コメント (0)

2022年12月 5日 (月)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT

Previously on Mac De Oracle...
Day 4は, SQLというより, in-memory operationに追加された, In-Memory Hybrid Scans の実行計画という名のレントゲンをみながら, どのような状況で発動するのかを簡単に確認しましたー.

 

なかな面白い動きでしたね. 別途時間を取って深掘りしないとね, と. :)

 

 

ということで, Day 5 の窓を開けましょう. 今日は, 新機能というより以前からあったのにレントゲン取ってなかったよね? ということで PIVOT and UNPIVOT を診てみたいと思います.

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

 

最初は, pivot 向けデータの準備から


SCOTT@orclpdb1> @day5

表が削除されました.

1 CREATE TABLE for_pivot
2 AS
3 SELECT
4 stat_name
5 , value
6 FROM
7 v$sys_time_model
8 WHERE
9 stat_name IN (
10 'DB time'
11 , 'DB CPU'
12 , 'background elapsed time'
13 , 'background cpu time'
14* )

表が作成されました.

1* CREATE INDEX ix_for_pivot ON for_pivot(stat_name)

索引が作成されました.

1 SELECT
2 *
3 FROM
4* for_pivot

STAT_NAME VALUE
------------------------------ ----------
DB time 143415979
DB CPU 127540525
background elapsed time 7381483
background cpu time 6340262
/code>

 

 

 

PIVOTで行持ちを列持ちに変換してみます.

 

おお, 実行計画には, PIVOT とか出ないのですよーーーー!! (知ってましたか!
こいつも実行計画を見ただけでは, PIVOTが行われているかは読み取れないですねw アクセスパス以外は注意する箇所はあまりないわけですけども:)


  1  SELECT
2 db_time
3 ,db_cpu
4 FROM
5 (
6 SELECT
7 stat_name
8 , value
9 FROM
10 for_pivot
11 WHERE
12 stat_name IN ('DB time', 'DB CPU')
13 )
14 PIVOT
15 (
16 MAX(value)
17 FOR stat_name IN
18 (
19 'DB time' AS db_time
20 ,'DB CPU' AS db_cpu
21 )
22* )

DB_TIME DB_CPU
---------- ----------
143415979 127540525


実行計画
----------------------------------------------------------
Plan hash value: 1690715989

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 2 (0)| 00:00:01 |
| 1 | VIEW | | 1 | 26 | 2 (0)| 00:00:01 |
| 2 | SORT AGGREGATE | | 1 | 22 | | |
| 3 | INLIST ITERATOR | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| FOR_PIVOT | 2 | 44 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IX_FOR_PIVOT | 2 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("STAT_NAME"='DB CPU' OR "STAT_NAME"='DB time')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
677 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
/code>

 

次に, UNPIVOT 向けデータの準備


表が削除されました. 

1 CREATE TABLE for_unpivot
2 AS
3 SELECT
4 db_time
5 ,db_cpu
6 ,bg_time
7 ,bg_cpu
8 FROM
9 (
10 SELECT
11 stat_name
12 ,value
13 FROM
14 v$sys_time_model
15 )
16 PIVOT
17 (
18 MAX(value)
19 FOR stat_name IN
20 (
21 'DB time' AS db_time
22 ,'DB CPU' AS db_cpu
23 ,'background elapsed time' AS bg_time
24 ,'background cpu time' AS bg_cpu
25 )
26* )

表が作成されました.

1* COMMIT

コミットが完了しました.

1 SELECT
2 *
3 FROM
4* for_unpivot

DB_TIME DB_CPU BG_TIME BG_CPU
---------- ---------- ---------- ----------
143522885 127634777 7381483 6340262
/code>

 

 

UNPIVOT では, PIVOT とは異なり, 実行計画上 UNPIVOT であることが読み取れます!!! ここ試験に出ますよ!(嘘w


  1  SELECT
2 stat_name
3 , value
4 FROM
5 for_unpivot
6 UNPIVOT
7 (
8 value FOR stat_name IN
9 (
10 db_time
11 , db_cpu
12 , bg_time
13 , bg_cpu
14 )
15 )
16* ORDER BY stat_name

STAT_NAME VALUE
------------------------------ ----------
BG_CPU 6340262
BG_TIME 7381483
DB_CPU 127634777
DB_TIME 143522885

実行計画
----------------------------------------------------------
Plan hash value: 3659757171

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 72 | 6 (17)| 00:00:01 |
| 1 | SORT ORDER BY | | 4 | 72 | 6 (17)| 00:00:01 |
|* 2 | VIEW | | 4 | 72 | 5 (0)| 00:00:01 |
| 3 | UNPIVOT | | | | | |
| 4 | TABLE ACCESS FULL| FOR_UNPIVOT | 1 | 26 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("unpivot_view_005"."VALUE" IS NOT NULL)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
781 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
4 rows processed
/code>

 

ということで,  Operation には, なぜ, UNPIVOT は現れるのに, PIVOT が現れないのだろう. . . その謎を求め, 一向は洞窟の奥深くへ進んでいくのであった. . . .

 

昔からサポートされている PIVOT / UNPIVOT ですが, 改めて実行計画というレントゲンを診てみると, 新しい気づきとかあって良いですね.

 

参考 SQL Language Reference / PIVOT and UNPIVOT

 

 

Oracle Database 11g:Oracle ACEディレクター, Arup Nanda - PIVOT and UNPIVOT

 

 

ということで, 明日の担当は, またまた私ですw

 

 

 


Related article on Mac De Oracle ・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans

 

| | | コメント (0)

2022年12月 4日 (日)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans

reviously on Mac De Oracle...
Day3は, Oracleの場合の EXCEPT/EXCEPT ALLは, 内部で MINUS/MINUS ALLになってるね. やってること同じだし. SQLのキーワードが違うだけなので, まあ, そりゃそうだという感じの実装になっているようだ.
というところを, 実行計画という名のレントゲンで確認しましたw

 

それでは, Day 4 の窓を開けましょう!

タイトルの通り, SQL単体ではなく, 実行計画上の新機能を診てみたいと思います. 狙い通りのレントゲンが取れるでしょうか. . . .

 

いつもと同じように 21c で確認します.


SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

なお, 今回は, in-memory機能を利用するのでお約束の inmemory_size パラメータを確認しておきますよ.
以前, 何度かセットし忘れで in-memory 発動せず!
みたいなこと何度かやらかしてたのことを思い出しますw
ちなみに, この inmemory_size パラメータのデフォルトは 0


SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 112M

 

まず, 準備から. データサイズが大したことないのと, データのカーディナリティもあまり深く考えてなかったのでイマイチかも知れません.

in-memory columnsと no in-memory columnsを持つ表を作成します. In-Memory Hybrid Scansは, 同一表の列がインメモリーだったり非インメモリーだったという状況で効果のある操作なので. :)


SCOTT@orclpdb1> @day4

表が削除されました.

経過: 00:00:04.17

1 CREATE TABLE day4
2 (
3 no_im_id NUMBER
4 , im_subid NUMBER
5 , no_im_str VARCHAR2(1000)
6 , im_str VARCHAR2(1000)
7 )
8 INMEMORY PRIORITY HIGH
9 MEMCOMPRESS FOR CAPACITY LOW
10 NO INMEMORY (
11 no_im_id
12 , no_im_str
13* )

表が作成されました.

経過: 00:00:00.02


1 BEGIN
2 FOR i IN 1..100000 LOOP
3 INSERT
4 INTO day4
5 VALUES(i, i+1000, LPAD(TO_CHAR(i),1000,'*'), LPAD(TO_CHAR(i+1000),1000.,'*'));
6 IF MOD(i,100) = 0
7 THEN
8 COMMIT;
9 END IF;
10 END LOOP;
11* END;

PL/SQLプロシージャが正常に完了しました.

経過: 00:00:16.88

 

定義を確認しておきましょうね.


  1  SELECT
2 table_name
3 , inmemory_compression "compression"
4 , inmemory_priority "priority"
5 FROM
6 user_tables
7 WHERE
8* table_name = 'DAY4'

TABLE_NAME compression priority
---------- --------------------------------------------------- ------------------------
DAY4 FOR CAPACITY LOW HIGH

経過: 00:00:00.00


1 SELECT
2 obj_num
3 , segment_column_id
4 , inmemory_compression
5 FROM
6 v$im_column_level im
7 INNER JOIN dba_objects do
8 ON
9 im.obj_num = do.object_id
10 WHERE
11* do.object_name = 'DAY4'

OBJ_NUM SEGMENT_COLUMN_ID INMEMORY_COM
---------- ----------------- ------------
77251 1 NO INMEMORY
77251 2 DEFAULT
77251 3 NO INMEMORY
77251 4 DEFAULT

経過: 00:00:00.01

 

データのPOPULATEを行います. 必要な列はすべてインメモリーにPOPULATEされたようです:)


  1  SELECT
2 /*+
3 FULL(day4)
4 NO_PARALLEL(day4)
5 */
6 COUNT(*)
7 FROM
8* day4

COUNT(*)
----------
100000

経過: 00:00:00.08


1 SELECT
2 segment_name
3 , inmemory_size
4 , bytes_not_populated
5 FROM
6* v$im_segments

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED
------------ ------------- -------------------
DAY4 15990784 0

経過: 00:00:00.01

 

準備運動からw SELECT LISTにある2列は, どちらも非インメモリー列なので, 結果としてはよく見る table full scanになります. 当然の結果ですね.


  1  SELECT
2 MAX(no_im_id)
3 , COUNT(no_im_str)
4 FROM
5* day4

MAX(NO_IM_ID) COUNT(NO_IM_STR)
------------- ----------------
100000 100000

経過: 00:00:00.09

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 515 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 515 | | |
| 2 | TABLE ACCESS FULL| DAY4 | 87887 | 43M| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
33544 consistent gets
0 physical reads
0 redo size
685 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

ベタな検証その2. 今度は, SELECT LISTの2列はどちらもインメモリー列です. 結果はみなさんの想像の通り, TABLE ACCESS INMEMORY FULLです.
consistent getsが非常に少なくなっています. (処理時間的なところはまあ, データ量次第なので大きな差にはなってないですね. 深く分析していないので, 別の機会にデータ量などを変えつつ検証しておきたいですよね, この辺り)


  1  SELECT
2 MAX(im_subid)
3 , COUNT(im_str)
4 FROM
5* day4

MAX(IM_SUBID) COUNT(IM_STR)
------------- -------------
101000 100000

経過: 00:00:00.10

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 515 | 382 (13)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 515 | | |
| 2 | TABLE ACCESS INMEMORY FULL| DAY4 | 87887 | 43M| 382 (13)| 00:00:01 |
------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
683 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

次に, in-memory hybrid scan発動の条件に該当しない例をみてみましょう.
このクエリーでは, SELECT LISTにインメモリー列のみ. WHERE句に非インメモリー列のみがありますが, これは, in-memory hybrid scan発動条件に一致しません.

結果として, 通常のTABLE ACCESS FULLとなってしまいますので注意しましょうね.


  1  SELECT
2 MAX(im_subid)
3 , COUNT(im_str)
4 FROM
5 day4
6 WHERE
7* no_im_id < 5000

MAX(IM_SUBID) COUNT(IM_STR)
------------- -------------
5999 4999

経過: 00:00:00.08

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 528 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 528 | | |
|* 2 | TABLE ACCESS FULL| DAY4 | 4794 | 2471K| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("NO_IM_ID"<5000)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
33544 consistent gets
0 physical reads
0 redo size
684 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

最後に, 本日の主役の登場です!

SELECT LISTには, 非インメモリー列のみ. WHERE句には, インメモリー列のみが利用されています. これが発動条件です.
結果として, これまで, みたことのないOperationである,  TABLE ACCESS INMEMORY FULL (HYBRID) が現れています. ふむふむ.
consistent getsもかなり少ないですし, このケースでは, 処理時間も早くなっていますね. ますます別途詳しく調査したくなりまし:)

なんとなくですが, この in-memory hybrid scan を見て, MySQL Heatwaveが浮かんできますよね. 諸々応用しているのでしょうかね...


  1  SELECT
2 MAX(no_im_id)
3 , COUNT(no_im_str)
4 FROM
5 day4
6 WHERE
7* im_subid < 5000

MAX(NO_IM_ID) COUNT(NO_IM_STR)
------------- ----------------
3999 3999

経過: 00:00:00.02

実行計画
----------------------------------------------------------
Plan hash value: 2217228964

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 528 | 9104 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 528 | | |
|* 2 | TABLE ACCESS INMEMORY FULL (HYBRID)| DAY4 | 1598 | 823K| 9104 (1)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("IM_SUBID"<5000)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1342 consistent gets
0 physical reads
0 redo size
687 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 


アドベントカレンダー, 全部俺をやらなきゃ, 師走は走り回るほど忙しいわけではないはずだったのになぁw と, 遠ーーーくを見てるw

では, また,

明日の担当も! 私です.


参考 Database In-Memory Office Hours / In-Memory Hybrid Scans

 

そして, Ask Tomがこんな感じになるとは:) いい感じですよね.
Database In-Memory Office Hours / ASK TOM

Database In-Memory Guide / 21c


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL

| | | コメント (0)

2022年12月 3日 (土)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL

Day 2は, Oracleの方言である集合演算 MINUS ALL の実行計画というレントゲンを確認しました. MINUSでも19cまでは, ALLはサポートされていませんでした. サポートされるようになった理由は, 今日レントゲンを見ていくEXCEPT/EXCEPT ALLのサポートが影響してそうですね. やることは同じですので:)

では, お約束のw
なお, 今回のSQL文の結果セットはソートされているように並んでいますが, たまたま昇順に並んでいるだけなので, ソートが必要な場合は. 必ず. ORDER BY句を付けてくださいね.

ということで, Day 3の窓を開けましょう!

MINUSは方言ですが, 標準SQLに合わせた形で同様の機能を持つEXCEPTがサポートされました. また, 方言であるMINUSも同様にサポートは継続されています. 下位互換の意味もあると思いますが, MINUS/MINUS ALLの実行計画というレントゲンと比較しながら見ると良いと思います.

 

いつもと同じように 21c で確認します.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

Day 1と同じ表とデータを利用します(再掲). 意図的に, 該当表には索引を作成していません.

SCOTT@orclpdb1> @day3
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.

 

最初は, EXCEPT ALL から診てみます 実行計画を見て〜〜〜〜〜〜くださ〜〜〜〜い, 驚きますよ!!!! 

 

内部では, MINUS ALL に書き換えられている!! これが MINUS ALLをサポートした理由ですねw やってることが同じなので, まあ無駄が無いのは確かだw

EXCEPT ALLが内部で, MINUS ALLに書き換えられているのでしょうか. 今回はトレースを取得して追いかける余力はないので, その辺りは別の機会にでも.
また, このケースでもHASH操作が現れています.

  1  SELECT
2 num
3 FROM
4 groupa
5 EXCEPT ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
2
5

実行計画
----------------------------------------------------------
Plan hash value: 895579533

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
648 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed

 

次は, EXCEPT 同様に, HASH操作を伴うMINUSとして現れています. やっている集合演算も同じですから, 実行計画だけ見せられたら, EXCEPTなのかMINUSなのかは区別できないでしょうね. 区別する必要もないわけですけども.
プロジェクトのSQL標準がどちらを使うか定めていれば, どちらかなのかは想像できるかも知れませんがw

  1  SELECT
2 num
3 FROM
4 groupa
5 EXCEPT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5

実行計画
----------------------------------------------------------
Plan hash value: 2352437695

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
640 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed

 

次に, 索引アクセスがある場合はどうなるかを確認します. 利用するデータや表はDay1と同じです.(再掲) なお, 索引等の情報は前回のエントリーをご覧ください

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.

1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.

まずは, EXCEPT ALLから Table Full Scanであろうが, Index Range Scanであろうが, HASH操作になる点も含め, MINUS ALLそのものですね:)

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

27行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 381125746

---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
|* 2 | INDEX RANGE SCAN| TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
3 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)v
統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
1040 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
27 rows processed

 

EXCEPTの実行計画という名のレントゲンはどうでしょう?

 

思った通り MINUS と同じ実行計画が現れています

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

24行が選択されました.

実行計画
----------------------------------------------------------
Plan hash value: 2829086308

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
993 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
24 rows processed

 

19cでは, EXCEPT/EXCEPT ALLともに未サポートなので, どちらもシンタックスエラーです. 当然と言えば当然の結果です:)

SCOTT@orcl> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75
EXCEPT ALL
*
ERROR at line 7:
ORA-00933: SQL command not properly ended

 

1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 EXCEPT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75
EXCEPT
*
ERROR at line 7:
ORA-00933: SQL command not properly ended

 


まだまだ, Day 3 ですねーーーーw

 

意外に多くの, ITアドベントカレンダー全部俺, または, 準全部俺(タイトルに全部俺って書いてないだけで, 多分, 全部俺になっちゃうでしょうーーっなカレンダー立てた方含め,
楽しんでやってきましょう.  w

 

明日の Day 4の担当は, なんとーーーっ, 私ですw

 

では, また.


参考)

19c UNION [ALL], INTERSECTおよびMINUS演算子

21c The Set Operators


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL

 

| | | コメント (0)

2022年12月 2日 (金)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL

Previously on Mac De Oracle...
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022始まっちゃいましたw

Day 1は, 19cまでのINTERSECTに加え, 21c(正確には20c)からサポートされたINTRESECT ALLの実行計画というレントゲンはどう見えるのか?を確認しました.
21cからはHASH操作が加わっているとことは興味深いので, HASH操作について観点を変えて実行計画というレントゲンを見ておかねば, という気がしていますね. 今のところ, おまけとして考えているところですw(ネタがキツくなったら普通にやるかもw)


ということで, Day 2の窓を開けましょう!

なお, 今回のSQL文の結果セットはソートされているように並んでいますが, たまたま昇順に並んでいるだけなので, ソートが必要な場合は, 必ず, ORDER BY句を付けてくださいね.


INTERSECTと同様に, 19cまでは, ALLがサポートされていなかったOracleの方言である集合演算 MINUS にも MINUS ALLがサポートされました.
21c以降で利用できます. 実際には20cですが世に出てくることは無かったわけで, 事実上21c以降といってもいいですよね
(おそらく)

21cでみてみましょう.

SCOTT@orclpdb1> select banner from v$version;

BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


Day 1と同じ表とデータを利用します(再掲). 意図的に, 該当表には索引を作成していません.

SCOTT@orclpdb1> @day2
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.


今回の主役, MINUS ALLの実行計画という名のレントゲンから診てみます.

OperationにMINUS ALLと現れています. わかりやすいですね. また, ここでもHASH操作が現れています. 21cの特徴でもあるので頭の片隅に置いといた方が良さそうです.

  1  SELECT
2 num
3 FROM
4 groupa
5 MINUS ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
2
5

実行計画
----------------------------------------------------------
Plan hash value: 895579533

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
648 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3 rows processed


次にこれまでもサポートされていた, MINUSの実行計画という名のレントゲンから診てみます.

これも判断しやすいですね. キーワードとなるMINUSが, Operationに現れています. 同様に, HASHも.

  1  SELECT
2 num
3 FROM
4 groupa
5 MINUS
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5


実行計画
----------------------------------------------------------
Plan hash value: 2352437695

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
640 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed


次に, 索引アクセスがある場合はどうなるかを確認します. 利用するデータや表はDay1と同じです.(再掲)
なお, 索引等の情報は前回のエントリーをご覧ください

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.

1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.


まずは, MINUS ALLから

Table Full Scanであろうが, Index Range Scanであろうが, HASH操作になるというのも特徴のようですね. 興味深いです.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 MINUS ALL
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

27行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 381125746

---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS ALL HASH | | | | | |
|* 2 | INDEX RANGE SCAN| TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
3 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
1040 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
27 rows processed

MINUSはどうでしょう?

お! やはり, 挙動が変わりますね. これは, 索引を利用することを重要視した結果と言えそうです. SORT UNIQUE NOSORTなので, ソートなしでUNIQUE操作を行っています. ソートのバイバスを重要視したということになりますね.
ここも非常に興味深い動きですね.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 MINUS
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

24行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 2829086308

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
993 bytes sent via SQL*Net to client
63 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
24 rows processed


では, 最後に, 19cでも実行計画という名のレントゲンを診ておきたいと思います.

SYS@orclcdb> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production


MINUS ALLは未サポートなので, シンタックスエラーになります.

SCOTT@orcl> @day2
1 SELECT
2 num
3 FROM
4 groupa
5 MINUS ALL
6 SELECT
7 num
8 FROM
9* groupb
MINUS ALL
*
ERROR at line 5:
ORA-00928: missing SELECT keyword


以前からサポートされているMINUS, やはり, SORT UNIQUE操作が利用されています. 21cのMINUSとは異なる動きのあることが見えてきます.

1  SELECT
2 num
3 FROM
4 groupa
5 MINUS
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
1
5

Execution Plan
----------------------------------------------------------
Plan hash value: 2070324559

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | MINUS | | | | | |
| 2 | SORT UNIQUE | | 7 | 21 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 8 | 24 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
599 bytes sent via SQL*Net to client
421 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
2 rows processed

アドベントカレンダー, 今のところ順調だが, 25回分全てのネタを書き終わったわけではないので, 先はナゲーーーーーーーなーーーと, 遠ーーーくを見てるw

Day3の担当は, 私です. よろしくお願いしますw

では, また, 明日.



参考)

19c
UNION [ALL], INTERSECTおよびMINUS演算子

21c
The Set Operators



Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL

| | | コメント (0)

2022年12月 1日 (木)

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL

さあ, 今年もやってきましたよ. 12月の風物詩ITアドベントカレンダーの季節が loL. 今年は久々に, 復活

 

実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺

 

最後まで体力とネタが持つでしょうかww (いや, なんとしてもネタを持たせないと
帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022

 


 

Day 1の窓は, 他のデータベースでも話題になっている標準SQLへの対応ネタから.
Oracle 19cまでは未対応だった, いくつかの集合演算子が, 20c以降でサポートされるようになりました.

 

簡単なとこからですよー!(最初から飛ばしすぎると後半息切れするのでw)

 

前述の集合演算子の拡張で特徴的だなと感じたのは, 今までALLがサポートされていなかった演算子でALLが使えるようになった事ですよね!

 

今回は, ALLがサポートされたINTERSECTの実行計画を見て見ましょう.

 

INTERSETのレントゲンは以前も紹介していました(Oracle 19c以前の実行計画)
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2019/12/post-9e4acc.html

 

今回のアドベントカレンダーでは, Oracle Database 21c Enterprise Edition を使います. (比較の為に, 旧リリースを使う場合もあります)

SCOTT@orclpdb1> select banner from v$version;

BANNER
------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

groupa表と, groupbh表を用意しました. num列だけの単純な表です(なお, 索引は作っていません. 理由は単純な集合演算を行いたかったから:)

SCOTT@orclpdb1> @day1
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7行が選択されました.

1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

8行が選択されました.

 

上記のようなデータがあったとして, INTERSECT ALLするとどうなるか.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT ALL
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4
4

 

INTERSECTION ALL HASH と出てますね. 21cではこれを手がかりに, INTERSECT ALL行われていると判断できますね.

INTERSECTION ALL, このケースだとHASH操作を組み合わせてる. ふむむむ 21cだからですかね. 以前まではなかったはずなので, 後半で19cを利用して試してみましょう.

実行計画
----------------------------------------------------------
Plan hash value: 2581184127

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION ALL HASH| | | | | |
| 2 | TABLE ACCESS FULL | GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------

 

では, 以前のリリースからあったALL無しだとどうなるか. HASH操作も出てますね. 興味深い. 大量データを扱う場合は有利でしょうね.

  1  SELECT
2 num
3 FROM
4 groupa
5 IINTERSECT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4

実行計画
----------------------------------------------------------
Plan hash value: 3344747026

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION HASH | | | | | |
| 2 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------

 

では, HASHが選ばれない場合の簡単な例を.

表定義はこんな感じ. (削除フラグ!!!があるけど気にしないでね. 他のネタでも使いたいのでw)

名前                                    NULL?    型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

名前 NULL? 型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

 

索引関連はこんな感じ(索引スキャンしてもらうことを狙っています)

TABLE_NAME                     INDEX_NAME                     INDEX_TYPE                     UNIQUENESS
------------------------------ ------------------------------ ------------------------------ ---------------------------
TAB311 TAB311_PK NORMAL UNIQUE

TAB311 TAB311_IX_SUB_ITEM_CODE NORMAL NONUNIQUE

TAB312 IX01_TAB312 NORMAL NONUNIQUE

TAB312 IX02_TAB312 NORMAL NONUNIQUE


TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
TAB311 TAB311_IX_SUB_ITEM_CODE SUB_ITEM_CODE

TAB311 TAB311_PK UNIQUE_ID
TAB311 TAB311_PK SUB_ITEM_CODE

TAB312 IX01_TAB312 UNIQUE_ID
TAB312 IX02_TAB312 SUB_ITEM_CODE

TAB312 IX01_TAB312 SUB_ITEM_CODE

 

以下のようなデータを用意しました.

  1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6* unique_id BETWEEN 1 AND 50

UNIQUE_ID
----------
1
1
1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

53行が選択されました.


1 SELECT
2 unique_id
3 FROM
4 tab312
5 WHERE
6* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

51行が選択されました.

 

INTERSECにしてみました. 索引が利用できる程度の取得件数なので索引を利用したSORT UNIQNE NOSORTが実行されています.
状況によっては, このようなケースもあることを思えておきましょうね. 索引利用が妥当なのかは個別の条件に応じて判断していく必要があります.
この場合は問題はないでしょう. ポイントはIndex only scanで必要最小限の範囲をアクセスして, かつ, 索引を利用してソートを回避しているところですね.

 1  SELECT
2 unique_id
3 FROM
4 tab311
5 WHERE
6 unique_id BETWEEN 1 AND 50
7 INTERSECT
8 SELECT
9 unique_id
10 FROM
11 tab312
12 WHERE
13* unique_id BETWEEN 25 AND 75

UNIQUE_ID
----------
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

26行が選択されました.


実行計画
----------------------------------------------------------
Plan hash value: 547955931

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50 | 612 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE NOSORT| | | | | |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 50 | 300 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE NOSORT| | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TAB312 | 52 | 312 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=50)
5 - access("UNIQUE_ID">=25 AND "UNIQUE_ID"<=75)

 

おまけ, 19cでは INTERSECT ALLはサポートされいませんが, 実行計画の違いを見ておきましょう.

SCOTT@ORCL> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

 

SCOTT@ORCL> @day1
1 SELECT
2 num
3 FROM
4* groupa

NUM
----------
1
2
2
3
4
4
5

7 rows selected.


1 SELECT
2 num
3 FROM
4* groupb

NUM
----------
0
2
3
3
4
4
4
7

 

19cでは当然, シンタックスエラーです.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT ALL
6 SELECT
7 num
8 FROM
9* groupb
INTERSECT ALL
*
ERROR at line 5:
ORA-00928: missing SELECT keyword

 

21cでは, HASH操作が行われてましたが, 19cでは, HASHの代わりに, SORT UNIQUEが現れていますね. 興味深い違いです. それぞれのリリースで同一統計情報で操作が変化する点は, しっかり押さえておきましょう. 今後のチューニングに役立つかもしれませんよー.

  1  SELECT
2 num
3 FROM
4 groupa
5 INTERSECT
6 SELECT
7 num
8 FROM
9* groupb

NUM
----------
2
3
4


Execution Plan
----------------------------------------------------------
Plan hash value: 2012227029

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 45 | 8 (25)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE | | 7 | 21 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS FULL| GROUPA | 7 | 21 | 3 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 8 | 24 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| GROUPB | 8 | 24 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------

 

参考)

19c UNION [ALL], INTERSECTおよびMINUS演算子

21c The Set Operators

 

ひとまず, Day 1の窓をあけた. 明日に続く.

 

最後に、今回は、ORDER BY句を付加していませんが、付けてくださいよ。ソートが必要な場合は!!!!

今回の例ではたまたま昇順に並んでいるだけですからね。


 

Related article on Mac De Oracle



・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー

 

| | | コメント (0)

2022年11月10日 (木)

帰ってきた! 実行計画は、SQL文のレントゲン写真だ! Oracle Database (全部俺) Advent Calendar 2022

Oracle Databaseの実行計画を中心にしていますが、他のDatabaseの実行計画も合わせて載せるかもしれません。

基本的に、全部俺で予定していますが、どうーーーしても、どうーーーしても、俺、私にも書かせろ!

という方は、ご連絡ください。:)

20221110-70010

| | | コメント (0)

2022年4月17日 (日)

Mac Studio 2022 と故障もほぼ無く長生きで良いマシンだった Mac Pro mid2012のGeekbench比較

かなーーーり、久々にMacオンリーのネタですw

左がMac Studio 2022、右がMac Pro mid2012 (ストレージはALL SSDで、GPUもMojave向けに入れ替えたものなのでそれなりに手を入れてありますがw)
真打で登場するMac Proがどうなるのか不明ですが、今まで通りのモジュラー型でパーツを入れ替えつつ使うというのはなさそうな気はしますね。

20220417-92704

20220417-92539

20220417-92623

20220420-101624
(Disk Speet Test / Stress = 1GBで実施)

| | | コメント (0)

2022年4月11日 (月)

実行計画は、SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー

Previously on Mac De Oracle
前回は、その前のエントリーの流れから、標準はあるにはあるが癖の多いSQL - #27 LNNVL is 何? と思った方向けでちょいと脱線してました。
今日は、話を元に戻しますw

USE_CANTATとOR_EXPAND、レントゲン(実行計画)をみて、どこがどう違うのかは理解できたのではないかと思います。ではなぜ、今後使うとしたら、OR_EXPANDなのかは、USE_CONCATとより言うことを聞いてくれやすいという他にもう一つあるのですが、それは何かわかりますか?
大人の事情で、しばらく関わりが薄かった時期(w にこのヒントの効果を知ったのですが、もう一つのメリットまでは知らなかったんですよw。 斜め読みだけしてると取りこぼしちゃいますねw

答えはパラレルクエリーにした場合の違い。

OR_EXPANDによる書き換えとUNION-ALLへの内部的な書き換えの効果で、パラレルクエリーとの相性が良くなっているんですよね。

早速、レントゲンをみてみましょう :)
(あ、書き忘れてましたが、Oracle Database 21cを使ってます)

USE_CONCATを使ってCONCATENATION(Id=1のoperation)を強制してかつパラレルクエリーにしています。PX COORDINATOR が Id=2とId=9に現れているのでUNIONの各SELECT文はシリアルに実行されているようですね。この挙動は変わってなさそうです。

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 use_concat
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.44

実行計画
----------------------------------------------------------
Plan hash value: 1305058436

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 8 (0)| 00:00:01 | | | |
| 1 | CONCATENATION | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ20001 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 6 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | | | |
| 9 | PX COORDINATOR | | | | | | | | |
| 10 | PX SEND QC (RANDOM) | :TQ10001 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 13 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 14 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 15 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | | | |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

8 - access("SUB_ITEM_CODE"='0001000000')
15 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

OR_EXPANDでU内部的にUNION-ALLに書き換えてパラレルクエrーにすると。。。。。。おーーーーー。違う!!! Id=1にあるPX COORDINATOR だけになってますね。各SELECT文もパラレル化されているようです。:)
結構違いますね。やはり、使うなら、USE_CANTATよりOR_EXPANDのようが良さそうですね。これで思い出した! ORDERED と LEADINGヒントのような感じですかねー。同じ機能を持つ後発ヒントの方が色々と使い勝手が良くなってることって意外に多いです!

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 or_expand
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.14

実行計画
----------------------------------------------------------
Plan hash value: 3317360125

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 8 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | VW_ORE_5F0E22D2 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 1 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


前々回手動でunionに書き換えたSQLをパラレルにするとどうなるだろう。。。
ほう。

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

実行計画
----------------------------------------------------------
Plan hash value: 3983264199

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (19)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10003 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 5 | PX SEND HASH | :TQ10002 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | P->P | HASH |
| 6 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 8 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 10 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 11 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 12 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 13 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
---------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

12 - access("UNIQUE_ID"=1)
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

同じく、前々回手動でunion all + フィルタ条件追加に書き換えたSQLをパラレルにするとどうなるだろう。。。
おおおおおーーーーーーっと。これはCONCATENATIONの実行計画にそっくりですね。CONCATENATIONの部分がUNION-ALLになっている程度の違い。2つのPX COORDINATOR がある点も共通しています。。。むむ。

このSQLをOR_EXPANDの実行計画と同じようにするには......あ! あれだ!

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

実行計画
----------------------------------------------------------
Plan hash value: 1844591072

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 9 (0)| 00:00:01 | | | |
| 1 | UNION-ALL | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10001 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 6 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 8 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 9 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 10 | PX COORDINATOR | | | | | | | | |
| 11 | PX SEND QC (RANDOM) | :TQ20001 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 2 | | 3 (0)| 00:00:01 | Q2,00 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q2,00 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q2,00 | SCWP | |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

9 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


ということで、
前々回手動でunion all + フィルタ条件追加に書き換えたSQLを単純にパラレルクエリーにしてもイマイチだったので、OR_EXPANDのような実行計画にするために、インラインビューにしてみました!!! どうでしょう? OR_EXPANDの実行計画と同じようになりました。
ポイントは、前々回のOR_EXPANDの実行計画中に現れるインラインビュー VW_ORE_5F0E22D2 です。内部的にインラインビューを追加してるんですよね! OR_EXPANDのUNION ALL書き換え。
インラインビュー化したことで、Id=4にビューが登場しています。OR_EXPANDでは、VW_ORE_* と名付けられるOR_EXPANDトランスフォームにより追加されるインラインビューと同じ役割を持っていますが、内部的に書き換えられて追加されるインラインビューとは異なり動的に名称が付加されません。

インラインビューが決めて! というか、意外と忘れがちなので注意しないとね。

select
/*+
parallel(4)
*/
*
from
(
select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1)
);

経過: 00:00:00.03

実行計画
----------------------------------------------------------
Plan hash value: 3706965944

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 9 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

4月はじめだと言うのに、夏日とか、北の方面の友人からは31度だとか、最近の異常気象ほんとに農家泣かせな感じ。最近は天気予想が細かい範囲ででるので以前より対応しやすいのかもしれないけど。
こんな、陽気だとぶらりと湘南あたりからリモートワークしたいw

ではまた。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

| | | コメント (0)

2022年4月 9日 (土)

実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

Previously on Mac De Oracle
前回のレントゲンは、BITMAP CONVERSION TO ROWIDSでした。複数の索引を同時に使うという昔からあるオペレーションでした。

今回は単にレントゲンを見ていくだけではなく、同じ問い合わせ結果(よくある間違いなどもいれてありますw)になるものの微妙に違うレントゲンをみつつ、元のSQL文、それに今回ヒントになにが使われているか、見ていきたいと思います。

これが前回のエントリで使ったSQL分です. 問い合わせ結果と実行計画(前回のエントリで取り上げたBITMAP CONVERSIONです。この問い合わせ結果と実行計画という名のレントゲンをよーーーーーーーーーく、覚えておいてくださいね。

いくつかのレントゲンを使って、これなーーーーーーーんだ? wみたいなw

これが原型なので、覚えておいてください。

select
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1248 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
4 rows processed

 

前回のエントリでも少し書いたのですが、CONCATENATION がレントゲンに現れるときは、BITMAP CONVERTなど効率が悪いOR条件の実行計画を改善するため、OR条件部分を分離排除し、2つの索引それぞれを有効に利用させるためのヒントによるチューニングで行なった場合が多いです。オプティマイザが選択するケースもありますが。

ヒントで強制的にすることもありますが、ヒントが効かないケースは多も多いのは事実です。理由は内部的に2つのクエリーに分解しているわけですが、それぞれで利用する索引のアクセス効率が悪いオプティマイザに見えている場合にはヒントが効かない場合が多いように思います。
なんとなーーーくざっくりなイメージですが、UNIONのような形に内部的に書き換えていると思うとわかりやすいかもしれないですね。。UNIONとでてないのでUNIONのようなものとしかかけないのですがW
とにかく、CONCATENATIONを見つけたら USE_CONCAT ヒントでチューニングされてるね!
と脊髄反応できるようになっているとよいですね!

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
2 0001000000 fooooooo2 0
1 0001000000 2**** 0
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1228 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

上記のレントゲンの元はこれです。USE_CONCATヒント使われてますよね。このケースではオプティマイザは言うことを聞いてくれたようですね。w

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

 

では、オプティマイザが言うことを聞いてくれなかった場合、Oracle Database 12c R1まではどうやって、治療していたか。知りたく無いですか?
USE_CONCATでUNIONのように内部的書き換えてくれると、SQLへはヒントの追加だけで済むので、同値検証等も不要で患者さんの痛みは少なくてすむわけですが、先にも買いたように必ず効くわけでもないという、ちょっと癖のあるヒントなんです。で、12c R1までは、しかたないの、SQL構文変更という中程度の難易度の手術(SQL書き換えw)が必要でした。

 

先ほと、UNIONのようにと書きましたが、まさに、それで、UNIONまたは、UNION ALLに書き換えてしまうという手術ですw

 

どちらでやってもよいのですが、重複データの排除がどれだけの負荷になるかというところかなと思います。重複排除するデータ量が多いのであれば UNION にしてHASH UNIQUEによる重複行排除の方がよいかもしれませんし、少量なら UNION ALLでフィルタリングによる重複行排除のほうがよいかもしれません。HASH UNIQUEにしてもPGA不足でTEMP落ちしてしまうようなことがあるのならフィルタリングのほうがよさそうですし、その時の状況次第かと思います。

 

では、ずは、UNION を使った書き換えから。

 

ソートしていないので並びが変わってますが、あえてソートしていません。Id=2のUNION-ALLとId=1にHASH UNIQUEというoperationがありますが、これが現在のUNIONの典型的なoperationです。HASH UNIQUEがなかったころは、SORT UNIQUEだったわけですが、その影響でデータがソートされていたので、諸々勘違いしてデフォルトでソートされるんだー、みたいな勘違いしている方も一定数存在していた時期があり、HASH UNIQUEがなって順序通りになってない! と勝手にザワザワしていたこともありましたね。それ知ってる方々はOracleにながーーーーいこと関わっている方だと思いますw 注意しましょうね。思った通りの並びにしたい場合は、ちゃんとORDER BY句でソートしましょうね。(これ言いたかっただけw)
あと、最近は、UNIONをパラレル実行できるようになったので、その場合も、順序はバラバラになります。シリアルに実行している場合は上位にあるクエリから処理されるのでその順序で行が戻されていましたが、パラレルだと何が来るかはその時々ですね。

 

脇道にそれましたが、手書きでSQLを UNION に書き換えた場合のレントゲンはこんな感じです。UNION-ALLのオペレーションの後に重複行排除のUNIQUE操作が必ず入るので覚えやすいと思います。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

もともとOR条件でしたが、それぞれの索引を有効に活用させるため、2つの文に分解し、それらを UNIONしています。UNIONで重複排除も行なっているわけです。

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

 

 

次に、UNION ALLへ手動で書き換えた場合はどうなるかレントゲンを見てみましょう。

 

結果も正しいです。レントゲンで見える UNION と UNION-ALLとの違いは、重複行排除のUNIQUEオペレーションが無いところです。Id=1にあるUNION-ALL だけで、 HASH UNIQUEがありません。
これ大丈夫なのでしょうか? 重複行を排除するオペレーションがないなんで、たまため結果が正しいだけでしょうか???

 

実は、実行計画に現れない違いが述語部分にあります。 Predicate Information (identified by operation id):セクションに 4 - filter(LNNVL("UNIQUE_ID"=1)) とあるのに気づきましたか?

 

4は、実行計画の Id = 4を示しています。これは Id = 4の TAB311のアクセス時に、"UNIQUE_ID"=1 であれば falseとして該当行をフィルタリングして捨てていることを意味しています。
つまり、UNIONで HASH UNIQUEを行なっていた重複行を排除と同様の効果をえるフィルター条件なんです。この条件にで、 Id = 2 と Id = 3で取得されたUNIQUE_ID=1の行を捨てています

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

UNION ALLでの書き換えは、以下のようなSQL文でした。and LNNVL(unique_id=1)という条件追加がポイントです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

 

では、つい忘れそうな、フィルター条件追加をわすれてUNION ALLにしてしまった場合はどうなるでしょう? もう想像できますよねw

 

SQLの違いからみてみましょう。 and LNNVL(unique_id=1) と言う条件が無いこと以外前述のSQLと同じです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000';

 

実行してみると。。。。。あららららら、ちゃっしゃいましたな感じの結果ですねw Predicate Information (identified by operation id): には重複排除のフィルター条件は見当たりません。(当然ですね。書き忘れているわけですから)

UNIQUE_ID = 1 でもあり、SUB_ITEM_CODE = '0001000000' である行が2回リストされています。ざんねーーーん。注意しましょうね。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0


----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID"=1)
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1239 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

 

よーーーーし、バグになるのが怖いから、常に UNION だーーーと安全策に流れそうですが、性能面ではフィルタリングと HASH UNIQUE、または、SORT UNIQUEかということであれば、データ量次第でどちらが良いか判断した方がよいだろうと。個人的には思っています。フィルターしたほうが有利なケースも当然ありますし、しないほうが良い場合もあるでしょう。

最後に、今日のタイトルにも書いた NO_EXPAND これ少々気難しい、USE_CONCATの後継として 12c R2 で登場したヒントです。内部の書き換えもそれまでのCONCATENATIONではなく、UNION ALLとフィルタリングにより重複行排除が行われるように、内部的に書き換えられるのが特徴です。なんでこれもっと早く実装してくれなかったんですかね? 強強ですね。USE_CONCATの気まぐれ感は消えてる感じがします。

事実、某所で、USE_CONCAT効かなくて、しぬーーーーーみたいな状況で、たまたま 12c R2だったので、NO_EXPAND で回避したーーーーなんてこともありました。そういことで、USE_EXPANDをUSE_CONCATの代わりにUSE_EXPANDを使うことをおすすめしますw (それ以外にもメリットも多いですし、それはまた、次回にでも)

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

 

レントゲンを見ると、手書きで書いた UNION ALLへの書き換えと微妙に違うの気づきますか? これまで紹介してきた手書きでの書き換えとヒントに夜書き換えは4つありますが、それぞれ実行計画に特徴があるんです。(もしかしたら将来は区別しにくくなるかもしれませんが、現状は区別できます!!!

OR_EXPANヒントでUNION ALL変換した場合 Id = 1にあるような、インラインビューがは登場します。VW_ORE_5F0E22D2 とオプティマイザが動的に名称をつけますが、ポイントは VW_ORE_* というprefixが作ろころですね。VWはびゅー。OREは、OR_Expand の大文字部分みたいですねw (そのうち内部的に生成されるインラインビュー名もまとめて紹介したいですね。すでに誰かやってそうな気もしますがw)

実行計画
----------------------------------------------------------
Plan hash value: 3148130991

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

長くなったので、まとめです。
ヒントによる OR条件のUNIONのような書き換えや、UNION ALLへの書き換え、または、人が UNION や UNION ALLへ手書きで書き換えたSQLのレントゲン、それぞれに特徴があり、4つとも、レントゲンから元のSQLがイメージできるんですよ!!!

USE_CONCATによる書き換え Oracle 8i 8.1以降〜

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

 

OR_EXPANDによる書き換え Oracle 12cR2以降〜

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION ALL + 重複排除フィルター条件追加

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

 

 

レントゲンから諸々読み取るスキルは大切だと思っているので、みんなもレントゲンというなの実行計画は読んでみるといいよーーーっ。おすすめ。

 

では、次回へつづく

 

 



Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS

| | | コメント (0)

2022年4月 7日 (木)

実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS


Previously on Mac De Oracle
前回は外部表特有のoperationであるEXTERNAL TABLE ACCESS FULL / INMEMORY FULL のレントゲンでした。

今日は、昔からあるBITMAP CONVERSION TO ROWIDSを見てみたいと思います。

SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換
SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換 / 21c


このオペレーションは、複数の索引からbitmapを生成しその結果のrowidを用いて表をアクセスするところにあります。通常一つの索引が利用されますが、この場合は複数の索引が利用されるところが特徴です。
ただ、bitmapに変換コストより、unionに書き換えたり(内部的な書き換えも含む)したほうが効率が良かったりします。なので意外と嫌われてたりw なので、STAR TRANSFORM などで見るぐららいで、結構それ以外の方向へチューニングされているケースのほうが多いかもしれません。でもこれで問題なければそのままでも問題はないわけですが。

あ、そういえば、以前、CONCATENATIONのレントゲンを紹介していましたね。
ちょうどよいので、CONCATENATIONのレントゲン撮影時と同じ表とSQL文を使って BITMAP CONVERSION TO ROWIDS のレントゲンを見てみましょう :)

SCOTT@orclpdb1> desc tab311
名前 NULL? 型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

SCOTT@orclpdb1> select count(1) from tab311

COUNT(1)
----------
2000000

経過: 00:00:00.09

実行計画を見てわかると思いますが、 2つの索引(TAB311_PK, TAB311_IX_SUB_ITEM_CODE)のROWIDからBITMAPを作り(Id=3,7)、それを BITMAP OR (SQL文の7行目 Id=3)した結果をROWIDへ変換(Id=2)、複数のROWIDをまとめ、IOリクエストを少なくするための ROWID BATCHED(Id=1)で表(TAB311)をアクセスしていことが読み取れます。
ROWIDでアクセスするので、基本的に少量の行にアクセスする場合には有利ではあります。ただ、BITMAPへの変換コスト次第というところではあるわけです。なので、BITMAPの変換のないタイプのトランスフォームを狙ったHINTを利用したり、SQL文自体を書き換えたりするケースは少なくありません。意外に嫌いな方が多くてw 大抵チューニングされてしまい、あまり見かけることはないかもしれませんw 

SCOTT@orclpdb1> r
1 select
2 *
3 from
4 tab311
5 where
6 unique_id= 1
7* or sub_item_code = '0001000000'

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1263461875

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
1103 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

ちなみに、CONCATENATIONのエントリーを見ていただくのがよいとは思いますが、これも比較的古くからある、CONCATENATIONを使ったSQL変換のレントゲンも改めて載せておきます。
(USE_CONCATヒントで強制しています。みなさん、知っているとは思いますが、NO_EXPANDヒントが逆のヒントです)

実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 Day 17 / CONCATENATION
UNION のような実行計画ですが、UNIONとは出てませんw これはまた別の機会に。ただ、ほぼ同等の意味で、OR条件でそれぞれに最適な索引を使うことでindex range scanやindex unique scanを効かせて高速にアクセスしようとしています。
BITMAPとの相互変換などが無い分、安定して早いケースは経験的にも多いのは確かです。どちらを選ぶかはやはり、登録されているデータの傾向と検索条件次第ではあります。ただ一般的BITMAP変換を避ける傾向が強いのは確かではありますね。

SCOTT@orclpdb1> r
1 select
2 /*+
3 use_concat
4 */
5 *
6 from
7 tab311
8 where
9 unique_id= 1
10* or sub_item_code = '0001000000'

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 1344230703

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1091 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

GWも間近だ。そろそろ予定考えないとな。その前にACEのKPIはクリアしておかないと。追い込み追い込みw


ということで、次回へつづく。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

| | | コメント (0)

2022年4月 1日 (金)

実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

Previously on Mac De Oracle
前回は、ルーティーンとなったw、19cと20cのパラメータ差分チェックでした。

今日は、元の路線に戻り、「実行計画は、SQL文のレントゲン写真だ!」シリーズです :)

前回のパラメータ差分チェックで、外部表を利用していたので、18c以降で変更された In-Memory External Tables とそれまでの External Tableのレントゲンを見ておこうと思います。

利用するのはOaracle Database 21cですが、In-Memory External Tablesは、18c以降であれば使える機能なので使えるはず!


12cまでのnon In-Memory External Tablesなころのレントゲンからです。

外部表は EXTERNAL TABLE ACCESS FULL というオペレーションになっています。CSVファイルを全て読み込んでいることを表ています。ここ大切なので、覚えておきましょう。

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 2727K| | 629 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 8168 | 2727K| 2984K| 629 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 8168 | 2727K| | 30 (4)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 8168 | 2727K| | 30 (4)| 00:00:01 |
| 4 | VIEW | | 838 | 139K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 838 | 58660 | | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_11_1_0_7_0 | 8168 | 1363K| | 29 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

外部表をフルスキャンしている。実際にはcsvファイルをまるっと読んで、ですよねーー。という感じ。外部表を利用されたことのある方であれば、ふむふむというところだと思います。

では21cの環境に切り替えて、前回Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden paramsを行った sysユーザーに接続して違いを見ていきましょう。

まずは、インメモリーを使わない状態で見てみます。(rpmでインストールしてconfigureしただけの21cデフォルトの状態。。のはずw。 こちらではカスタマイズしてないので)

ビルド表とプローブ表が12cR1の実行計画と逆になってますが、まあ気にしないw
外部表は、12cR1と同様に EXTERNAL TABLE ACCESS FULL ですね。

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 Version 21.3.0.0.0

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5997 | 2002K| | 457 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 5997 | 2002K| 2192K| 457 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 5997 | 2002K| | 15 (7)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 5997 | 1399K| | 15 (7)| 00:00:01 |
| 4 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 5412 | 359K| | 14 (0)| 00:00:01 |
| 5 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 6 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
----------------------------------------------------------------------------------------------------------


では、外部表をインメモリー対応に作り変えます。
INMEMORY句を追加してる箇所がポイントですね。

DROP TABLE ksppi_19_3_0_0_0;
CREATE TABLE ksppi_19_3_0_0_0 (
ksppinm VARCHAR2(80)
,ksppdesc VARCHAR2(255)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '|'
(
ksppinm
,ksppdesc
)
)
LOCATION (
'19.3.0.0.0.ksppi.csv'
)
)
INMEMORY MEMCOMPRESS FOR CAPACITY
;


おおお?? ビルド表とプローブ表が入れ替わりましたが、外部表は、EXTERNAL TABLE ACCESS FULL のままですね。
なにか設定し忘れているようです。

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

ん、外部表自体のインメモリー定義は問題なさそうですね。

SYS@ORCLCDB> r
1 select table_name, inmemory, inmemory_compression
2* from user_tables where EXTERNAL = 'YES'

TABLE_NAME INMEMORY INMEMORY_COMPRESSION
------------------------------ ------------------------ ---------------------------------------------------
OPATCH_XML_INV DISABLED
KSPPI_19_3_0_0_0 ENABLED FOR CAPACITY LOW


ポピュレーションされてないのか?

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


あ”〜〜っ! Oracle ACEのKPI入力期限まであと3ヶ月ある。あせるなwwww 大丈夫だw(謎

では、手動で。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


ん? なんで?
あれか!

SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 0


やはり、だよね〜w デフォルトのままだもの。そりゃそうだ。

では、変更しましょう。inmemory_sizeは、最低 100MBは指定しないといけないので、今回は最低サイズ(十分だと思うので)に。

SYS@ORCLCDB> create pfile from spfile;

ファイルが作成されました。

SYS@ORCLCDB> alter system set inmemory_size = 100m scope=spfile;

システムが変更されました。

SYS@ORCLCDB> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@ORCLCDB> startup
ORACLEインスタンスが起動しました。

Total System Global Area 5586811432 bytes
Fixed Size 9697832 bytes
Variable Size 956301312 bytes
Database Buffers 4496293888 bytes
Redo Buffers 7077888 bytes
In-Memory Area 117440512 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 112M


よし!! これで大丈夫なはず。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED POPULATE_STATUS IS_EXTERNAL
------------------------------ ------------- ------------------- --------------------------------------- ---------------
KSPPI_19_3_0_0_0 1179648 0 COMPLETED TRUE

では、こんどこそ!!

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

あれ〜〜〜〜〜〜〜っ。 もう一つ忘れてた。query_rewrite_integrity.

Database Reference 2.294 QUERY_REWRITE_INTEGRITY
https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/QUERY_REWRITE_INTEGRITY.html

SYS@ORCLCDB> alter session set query_rewrite_integrity=stale_tolerated;

セッションが変更されました。


外部表のオペレーションが EXTERNAL TABLE ACCESS INMEMORY FULL に変化しました。

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------------


まとめ

18cで登場した In-Memory External Tables のレントゲンは以下の通り。
EXTERNAL TABLE ACCESS INMEMORY FULL というオペレーションがポイントです。
また、INMEMORY_SIZEパラメータの設定や、QUERY_REWRITE_INTEGRITYパラメータをstale_toleratedにする必要あります。忘れがち?!


21cでの non in-memory external tables と in-memory external tables のレントゲンをSQL MONITORの実行計画を。

non in-memory external tables : EXTERNAL TABLE ACCESS FULL

Global Stats
=====================================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
=====================================================================================================
| 0.06 | 0.05 | 0.00 | 0.00 | 0.00 | 406 | 121 | 1 | 512KB | 16 | 683 |
=====================================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (%) | (# samples) |
========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 6066 | | | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 1 | +1 | 1 | 6066 | | | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | 2MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 6 | EXTERNAL TABLE ACCESS FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 1 | +1 | 1 | 5412 | 1 | 512KB | 16 | | . | | |
========================================================================================================================================================================================

in-memory external tables : EXTERNAL TABLE ACCESS INMEMORY FULL

Global Stats
======================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
======================================================================================
| 0.04 | 0.04 | 0.00 | 0.00 | 0.00 | 406 | 48 | 16 | 683 |
======================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
==================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 2 | +0 | 1 | 6066 | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 2 | +0 | 1 | 6066 | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +0 | 1 | 6066 | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +0 | 1 | 6066 | | | 3MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 2 | +0 | 1 | 5412 | 16 | | . | | |
==================================================================================================================================================================================

レントゲンは大切 !!!! :)


東京は一気に葉桜になりつつある。。。ではまた。





Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

| | | コメント (0)

2022年3月26日 (土)

実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

さて、実行計画のバリエーションの数だけ、レントゲンはありますよー(まだネタには余裕があるw)

ということで、今回は、12c前後で変化したところを見ておきたいと思います。

タイトルにも書いた

TEMP TABLE TRANSFORMATION
LOAD AS SELECT (CURSOR DURATION MEMORY)

は、12c以降のリリースから見られるWITH CTEで繰り返し利用されることが自明で性能改善につながると想定される場合に、一時表にマテリアライズされたときのオペレーションですよね。
(みなさんご存知だとおもいます。

12c以降のリリースしか利用したことのない方は、気づかないと思いますが、11gまで少々違いました。

TEMP TABLE TRANSFORMATION
  LOAD AS SELECT


違いといっても、CURSOR DURATION MEMORY があるかないかなのですがw、細かい改善の一つでだよね。と。


ちょうど良いネタなので、昨年末のAdvent CalendarのSQL文と実行計画でバージョン間のレントゲン写真の差を確認しておきましょう:)

まず、一つまえの 19cから。
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Id=2のオペレーションは、LOAD AS SELECT (CURSOR DURATION MEMORY) ですね。

もう一点、Predicate Informationセクションには、/*+ CACHE ("T1") */ というヒントが内部的に利用されていますね。ふーむ。
また、アダプティブな挙動もレポートされていないようですね。計画自体もindex only scanですし、まあ、想定通りというところですね。

参考までに、OPTIMIZER_DYNAMIC_SAMPLING = 2 と。デフォルトのままです。

CURSOR DURATION MEMORYの挙動については、
SQLチューニング・ガイド cursor-duration一時表

にあるように、シリアル実行では、PGAを利用するようですね。パラレルだと違うのか... でいずれもメモリ上に乗らなくなると、一時セグメントがストレージ上に確保されると記載されているのでTEMP表領域が利用されそうですね。
メモリに余裕があり、かつ、繰り返し参照されるケースでは効果はありそうですよね。ストレージに落ちてしまうと、direct path read from tempが発生するでしょうし、ストレージを繰り返しアクセスするかしないかの違いは大きいかも。
とはいえ、WITH句のCTEで性能改善を狙うケースでポイントになるところは同じなので、その点は忘れないようにしておきたいところですね。(例外は巨大なSQLで可読性向上を目的とした場合ぐらい)

Execution Plan
----------------------------------------------------------
Plan hash value: 3964084889

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 553 | 12 (0)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6640_6FF953 | | | | |
| 3 | WINDOW SORT | | 7 | 119 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 119 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 7 | 77 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0012896 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0012900 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0012898 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6641_6FF953 | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | VIEW | | 7 | 553 | 2 (0)| 00:00:01 |
| 19 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 20 | FILTER | | | | | |
| 21 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 23 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 24 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 25 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 26 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - filter("NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "NAME"='Steve' AND "ANIMALS"."KIND"<>'Dog' AND
"ANIMALS"."KIND"<>'Snake' OR "NAME"='Hiro' AND "ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR
"NAME"='Larry' AND "ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("NAME"='Wendy' AND "KIND"='Dog' OR "NAME"='Tiger' AND "KIND"<>'Dog' AND
"KIND"<>'Turtle' OR "NAME"='Lisa' AND "KIND"<>'Snake' AND "KIND"<>'Dog' OR "NAME"='Taro' AND "KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND
NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2"
"PET_NAME","C3" "ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
20 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT
EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
23 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
922 bytes sent via SQL*Net to client
2090 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed

では次、12R1の場合です。(12R2は19cと同じなので省略)
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

基本的にデフォルト設定のままなので、 OPTIMIZER_DYNAMIC_SAMPLING = 2 でもろもろ止めたりしていませんw

12cR2以降との違いの1つめ!
12cR1では、CURSOR DURATION MEMORY オペレーションがありません!! 

12cR2以降との違いの2つめ!
/*+ CACHE ("T1") */ではなく、/*+ CACHE_TEMP_TABLE ("T1") */ という一時表専用のヒントが担っている点。
CACHE_TEMP_TABLEヒントって解説がないヒントなのですが、12cR2以降はCACHEヒントという解説のある通常の表と同じヒントに置き換えられいますね。一時表だけ特別なわけではないので統一したのでしょうか(中の人のみぞ知るw)

Note部分にもでてますが、動的統計とプランディレクティブが動いてますね。再起コールが減らないのもその影響のようです。19cの挙動とは興味深い違いですね。データ量が多くなった場合にどう変化するのかなという気はしますがw

ちなみに、この挙動は、11cR2とくらべて、プランディレクティブが無い以外の挙動は同じ。


Execution Plan
----------------------------------------------------------
Plan hash value: 3787387246

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6647_566AED | | | | |
| 3 | WINDOW SORT | | 8 | 136 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 8 | 136 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 8 | 88 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0014925 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0014929 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0014927 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT | SYS_TEMP_0FD9D6648_566AED | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 19 | FILTER | | | | | |
| 20 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 24 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 25 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |

--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR
"PETS"."NAME"='Tiger' AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR
"PETS"."NAME"='Lisa' AND "ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR
"PETS"."NAME"='Taro' AND "ANIMALS"."KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
19 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1
AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
22 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
24 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- 5 Sql Plan Directives used for this statement

Statistics
----------------------------------------------------------
4 recursive calls
36 db block gets
126 consistent gets
2 physical reads
1160 redo size
811 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


そして、最後は、私の環境で最も古いOracle 11.1.0.7

Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production

わかっちゃいたけど、オプティマイザの進化が見えて嬉しいですねw

なかなか苦しい実行計画ですね。これは。。。w

Index only Scanにはなっていますが、マテリアライズする部分の実行計画が.. オプティマイザありがとう。(主に最新版のほうですけどw)

本題にもどると、このリリースではCURSOR DURATION MEMORYがないのは当然ですが、CACHE_TEMP_TABLEヒントが利用されているという点に関しては、12cR1と同じ
そして、アダプティブな挙動も一切ない自体の実行計画ですね。ここには戻りたくないですよね。みなさんw

アダプティブな挙動はないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw

Plan hash value: 3067991639

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 14 (15)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 8 | 240 | 9 (12)| 00:00:01 |
| 4 | CONCATENATION | | | | | |
| 5 | NESTED LOOPS | | 7 | 210 | 7 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 9 | 225 | 7 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 5 | 100 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 9 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 10 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 11 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 12 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 13 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 14 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 15 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 16 | NESTED LOOPS | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | NESTED LOOPS | | 2 | 50 | 1 (0)| 00:00:01 |
| 18 | NESTED LOOPS | | 1 | 20 | 0 (0)| 00:00:01 |
| 19 | NESTED LOOPS | | 1 | 15 | 0 (0)| 00:00:01 |
| 20 | NESTED LOOPS | | 1 | 10 | 0 (0)| 00:00:01 |
|* 21 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 22 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
|* 23 | INDEX UNIQUE SCAN | SYS_C009962 | 4 | 20 | 0 (0)| 00:00:01 |
|* 24 | INDEX UNIQUE SCAN | SYS_C009960 | 4 | 20 | 0 (0)| 00:00:01 |
|* 25 | INDEX FULL SCAN | SYS_C009964 | 2 | 10 | 1 (0)| 00:00:01 |
|* 26 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
| 27 | LOAD AS SELECT | | | | | |
| 28 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 29 | FILTER | | | | | |
| 30 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 32 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 33 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 34 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 35 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 36 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 37 | FILTER | | | | | |
| 38 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 39 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 40 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 41 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 42 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
12 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
13 - access("PETS"."NAME"="PETS"."NAME")
filter("PETS"."NAME"<>'Tiger')
14 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
15 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"<>'Hiro')
21 - access("PETS"."NAME"='Tiger')
22 - access("OWNERS"."NAME"='Hiro')
filter(LNNVL("OWNERS"."NAME"<>'Hiro') OR LNNVL("PETS"."NAME"<>'Tiger'))
23 - access("PETS"."NAME"="PETS"."NAME")
24 - access("OWNERS"."NAME"="OWNERS"."NAME")
25 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
26 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
29 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
32 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
34 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
37 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
40 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
42 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
85 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


アダプティブな挙動がないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw
NO_EXPANDヒントでCONCATENATIONを抑止してみましたw
それでもイマイチだ。古いオプティマイザとの挙動の違いをみると、ほんと、最新版のオプテマイザの良さが身にしみますw

Plan hash value: 2841796482

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 7 | 210 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 210 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 12 | 300 | 8 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 7 | 140 | 4 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 9 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 11 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 12 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 13 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 14 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 15 | LOAD AS SELECT | | | | | |
| 16 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 17 | FILTER | | | | | |
| 18 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 19 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 20 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 24 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 25 | FILTER | | | | | |
| 26 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 28 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 30 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

10 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
11 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
12 - access("PETS"."NAME"="PETS"."NAME")
13 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
14 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger')
17 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
20 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
22 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
28 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
30 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
84 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


SQLのレントゲン写真、バージョンと共に基本形wが変化していくので、常に差分と最新の内容を把握しておきたいですよね。いざ、診療するとなったときには役に立つ、はず!!

今回利用したSQL文や表定義およびデータは、誰がどんな名前のペットを飼っているのかな? 解答編 / JPOUG Advent Calendar Day 23を参照ください。

では、また。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT

| | | コメント (0)

2022年3月21日 (月)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #13

随分間が空いてしまったのです、覚えてますかねw 前回のネタ。私もほぼ忘れてました orz. w

思い出しながら進めましょう:)

Previously on Mac De Oracle
前回は、CTASだと、内部的にdirect path writeされてしまうのでクリーンアウトが必要な状況にはならないという動きを確認しました。

scattered read同様に、sequential readだとどうなの?(という遠くから声が聞こえたきがしたのでw)

とはいえ、遅延ブロッククリーンアウトって、自分の理解だと、全表走査時の動きだと思っているので、多分、起きないよなーと。頭の上に雲型の吹き出した出た状態でイメージしているところ。


では、試してみましょう。

これまでと異なる箇所は、sequential read でぐるぐるループさせたいので、該当表に主キー索引を作成します。また、table full scanではなく、index unique scan で全行アクセスさせてみます。(この動きが大きく異なる部分です)





strong>0) 対象表のdrop/create と主キー作成
対象表のHOGE2は削除して作り直し。このテストケースでは主キーアクセスさせるため、合わせて主キーも作成しておきます。
SCOTT@orcl> @droppurge_create_hoge2_with_pk.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.51
1* create table hoge2 (id number, data varchar2(2000), constraint pk_hoge2 primary key (id) using index)

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.13


1) 統計をクリアするためOracle再起動


$ sudo service oracle restart

[sudo] password for oracle:

Restarting oracle (via systemctl): [ OK ]



2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略! (思い出したいかたはこの辺りを参照ください。。


4) PDBのSCOTTユーザーでNOAPPENDヒント付きIASを実行(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ)/ 未コミット

NOAPPENDヒントを利用している理由は前回までの内容を見ていただければわかりますが、direct path writeさせたないためです。これによりコミットクリーンアウトではクリーンアウトできない状況、つまり、遅延ブロッククリーンアウトが必要となる状態を作っています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:19.63
14:32:40 SCOTT@orcl>


5) CDBのSYSで統計取得(未コミット)

検証中に統計値が変動したもののみ記載

狙い通りdirect path writeは抑止できていることは確認できます。
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22,918
sysstat DBWR checkpoints 72
sysstat DBWR thread checkpoint buffers written 22,918
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 983
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 1,601
sysstat commit cleanouts successfully completed 1,601
sysstat consistent gets 122,604
sysstat db block changes 564,554
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 70,607
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 1,752
sysstat no work - consistent read gets 67,108
sysstat physical reads 66,750
sysstat physical reads direct 66,709
sysstat physical writes 22,918
sysstat physical writes from cache 22,918
sysstat physical writes non checkpoint 22,918
sysstat redo blocks written 967,108
sysstat redo size 479,412,088
sysstat redo writes 378

(PDB) SCOTTのセッション統計
こちらから見てもdirect path writeは発生していません

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1,589
sesstat commit cleanouts successfully completed 1,589
sesstat consistent gets 122,204
sesstat db block changes 564,506
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 70,595
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 1,748
sesstat no work - consistent read gets 66,884
sesstat physical reads 66,740
sesstat physical reads direct 66,709
sesstat redo size 478,945,336


該当期間の待機イベント記録されていないですよね。狙い通りなので準備としては問題なしです。

EVENT                                                            WAIT_CLASS
---------------------------------------------------------------- -----------------
SQL*Net message from client Idle
log buffer space Configuration
events in waitclass Other Other
direct path read User I/O
log file switch (private strand flush incomplete) Configuration
log file switch completion Configuration
log file sync Commit
db file sequential read User I/O
SQL*Net message to client Network
Disk file operations I/O User I/O
db file scattered read User I/O

6) CDB or PDBのSYSユーザで接続し、buffer cacheをflushする

SYS@orclcdb> @flush_buffercache.sql
1* alter system flush buffer_cache

System altered.

Elapsed: 00:00:09.04
14:36:26 SYS@orclcdb>


7) CDBのSYSで統計取得(buffer cacheをflush後)
strong>(CDB)システム統計

検証中に統計値が変動したもののみ記載

バッファキャッシュをフラッシュしたのでキャッシュから書き出されたことを示す physical writes from cache および関連する統計値が上昇しています。これも想定通りの挙動ですね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 8
sysstat DBWR undo block writes 1912
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 43
sysstat commit cleanouts successfully completed 43
sysstat consistent gets 20793
sysstat db block changes 429
sysstat deferred (CURRENT) block cleanout applications 21
sysstat free buffer requested 1013
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 9
sysstat no work - consistent read gets 12321
sysstat physical reads 977
sysstat physical reads direct 0
sysstat physical writes 47900
sysstat physical writes from cache 47900
sysstat physical writes non checkpoint 47900
sysstat redo blocks written 2017
sysstat redo size 994652
sysstat redo writes 23

(PDB) SCOTTのセッション統計

キャッシュをフラッシュしただけなので、該当セッションの統計には変化がありません。(まあ、そうですよねw)


SOURCE NAME VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistentread gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 0
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 0
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 0
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 0
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 0

8) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.

Elapsed: 00:00:00.01


9) CDBのSYSで統計取得(コミット後)
strong>(CDB)システム統計
コミット時点ではクリーンアウト仕切れないほどの更新ブロック数かつ、コミット前にバッファキャッシュをフラッシュしているので、コミット時のクリーンアウトもできない状態なっています(これも事前の準備の通りの結果。なので問題なし)
commit cleanouts と commit cleanout failures: block lost がポイントです。コミット時にクリーンアウトできる量(過去の記事を参照のこと)の閾値以下の量ですら、事前にキャッシュからフラッシュしまっているので、クリーンアウトに失敗(できないようにしたので当然ですが)しています。両方の統計値が同一であるとくことは、コミットクリーンアウトしようとした全てでくりアウトできなかった。理由はキャッシュに該当ブロックがキャッシュから落ちていたから(意図的に落としたわけですが)ということを意味します。予定通りですね。

コミットの時点で最低現おこなうクリーンアウトもさせていないですが、このケースでは 11140 ブロックをコミット時にクリーンアウトしようとしているので、コミット時のクリーンアウトが行われていたとしても 50,000 ブロック以上は遅延されていたことになりますね。今回は、 66,667 ブロックがク後続のSQLで参照されるついでにクリーンアウトされることになります。

検証中に統計値が変動したもののみ記載

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 0
sysstat DBWR undo block writes 0
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 11140
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 11140
sysstat commit cleanouts successfully completed 0
sysstat consistent gets 34
sysstat db block changes 1
sysstat deferred (CURRENT) block cleanout applications 0
sysstat free buffer requested 8
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 0
sysstat no work - consistent read gets 5
sysstat physical reads 8
sysstat physical reads direct 0
sysstat physical writes 0
sysstat physical writes from cache 0
sysstat physical writes non checkpoint 0
sysstat redo blocks written 3
sysstat redo size 456
sysstat redo writes 3

(PDB) SCOTTのセッション統計
セッションレベルでみるよりはっきり見るとことができます。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 11140
sesstat commit cleanouts 11140
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 1
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 1
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 1
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 176


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表を主キーによる一意検索で全行アクセス)

さて、事前に想定している実行計画になっているかプランだけ確認しておきましょう。主キーによる索引ユニークスキャンであることが確認できます。想定通りです。この実行計画で、200,000 行をグルグル参照した場合、遅延されたブロッククリーンアウトが行われるでしょうか。。。。(してくれないと思いますが。。。試してみましょう)

SCOTT@orcl> set autot trace exp
14:41:47 SCOTT@orcl> select * from hoge2 where id = 1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3319133482

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1015 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE2 | 1 | 1015 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_HOGE2 | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

SCOTT@orcl> set autot off
SCOTT@orcl>
SCOTT@orcl> @table_unique_scan_hoge2.sql
1 begin
2 for i in 1..&1 loop
3 for hoge2_rec in (select * from hoge2 where id = i) loop
4 null;
5 end loop;
6 end loop;
7* end;
Enter value for 1: 200000
old 2: for i in 1..&1 loop
new 2: for i in 1..200000 loop

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.94
14:39:36 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

クリーンアウトが行われていれば、データブロックの総数 66,667 ブロックは少なくともクリーンアップの対象になっているはずですが。。。。。
cleanout系の統計値に動きはありませんよね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 21
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 5
sysstat DBWR undo block writes 5
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 366
sysstat commit cleanouts successfully completed 351
sysstat consistent gets 632775
sysstat db block changes 7848
sysstat deferred (CURRENT) block cleanout applications 169
sysstat free buffer requested 76662
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 169
sysstat no work - consistent read gets 21822
sysstat physical reads 69501
sysstat physical reads direct 0
sysstat physical writes 21
sysstat physical writes from cache 21
sysstat physical writes non checkpoint 10
sysstat redo blocks written 5132
sysstat redo size 2536084
sysstat redo writes 28

(PDB) SCOTTのセッション統計

physical reads は、67706 ブロックなのでほ、HOGE2表の索引と表のセグメントサイズほどは物理読み込みされたということになります。ただ、cleanouts only - consistent read gets などのクリーンアウトを示す統計値はほぼ動いていません。ようするに、db file sequential readとなる index unique scan ではブロッククリーンアウトは発生しないということを意味しています。

おおおーーー。だったら、グルグル系の処理で一行ごとに処理するのが最高じゃん! という単純な話ではないので要注意ですからねw

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 4
sesstat commit cleanouts successfully completed 4
sesstat consistent gets 600005
sesstat db block changes 14
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 74540
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 0
sesstat physical reads 67706
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 2152


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(sacattered read)

single block readを行うindex unique scanではクリーンアウトが発生しなかったということは、全表走査させる table full scan かつ scattered readさせれば発生するのではないでしょうか? という確認です。
auto traceで統計情報をみると、なにやら、REDOログが大量に生成されてますよね。。。これは!!!

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.55

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
10 recursive calls
1 db block gets
147157 consistent gets
377 physical reads
5866968 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

14:56:56 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 1121
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 36
sysstat DBWR undo block writes 347
sysstat cleanouts only - consistent read gets 66676
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 806
sysstat commit cleanouts successfully completed 806
sysstat consistent gets 174296
sysstat db block changes 80773
sysstat deferred (CURRENT) block cleanout applications 383
sysstat free buffer requested 2095
sysstat immediate (CR) block cleanout applications 66676
sysstat immediate (CURRENT) block cleanout applications 75
sysstat no work - consistent read gets 27953
sysstat physical reads 1842
sysstat physical reads direct 0
sysstat physical writes 1121
sysstat physical writes from cache 1121
sysstat physical writes non checkpoint 981
sysstat redo blocks written 16550
sysstat redo size 8074260
sysstat redo writes 351

(PDB) SCOTTのセッション統計
HOGE2の表のクリーンアウト対象ブロック数、66,667 きっちり同じ数字が現れました。しかも cleanoutに関わる統計でかつ、大量のREDOログも生成されています。でました〜〜〜w クリーンアウト。全表走査かつ、scattered readで。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 66667
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1
sesstat commit cleanouts successfully completed 1
sesstat consistent gets 153731
sesstat db block changes 66718
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 690
sesstat immediate (CR) block cleanout applications 66667
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 16317
sesstat physical reads 680
sesstat physical reads direct 0
sesstat redo size 5877252




まとめ

index unique scanでグルグルさせつつ、全行アクセスしても発生しないですよね!
single block readになるunique index scanでは、遅延ブロッククリーンアウトは発生しない。(想像通りでよかったw)
で、そのあとで全表走査(scattered read)させてみると、やはり、遅延ブロッククリーンアウトは発生するわけですよ。面白いです。ほんと。



さあ、5月末まで追い込みだw ブログ書かないとw (頑張って、アドベントカレンダー全部俺やっとくほうが実は楽なのかもしれないw)

では、また。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12



| | | コメント (0)

2021年9月25日 (土)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12

Previously on Mac De Oracle
前回、前々回とIASでdirect path writeであるか否かで、そのコミットおよび遅延ブロッククリーンアウトの有無が決まるという動きを確認しました。

今回は、DDLですが、親戚みたいな挙動のCTASではどうなるか確認しておきます。CTASはDDLなのでコミットは不要ですよね。また、 direct path writeが前提になっていることも皆さんご存知の通り。

ということは、IAS + APPENDヒントで、direct path write させた時と同じような挙動になるはず。。。。ですよね。

手順はいつもの図でご確認ください。(DDLなので手順も単純になっています)
Ctas_steps




0) 対象表のdrop
対象表のHOGE2は削除しておきます。CTASで作成することになるので。

SCOTT@orcl> @droppurge_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:02.79


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでCTAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ

SCOTT@orcl> @ctas_from_hoge.sql
1* create table hoge2 as select * from hoge

Table created.

Elapsed: 00:00:07.41


5) CDBのSYSで統計取得(CTAS後)
DDLなのでコミットはありませんが、念の為に確認すると、commit cleanouts、commit cleanouts successfully completed はほんの少しだけ。このテストケースではノイズ程度の量です。
DDL終了時にクリーンアウトは発生していないと読み取れます。(この後の手順で遅延クリーンアウトも発生していなければ。direct path writeではクリーンアウトが必要な状態にはならないということは間違いないと判断するできますよね)

参考程度ですが、physical writes 、physical writes direct、physical writes non checkpoint が同数です。物理書き込みが発生し、かつ、direct path writeでチェックポイントで書き出されたものではないということが確認できます。想定通り、CTASは direct path writeで書き出されているということがわかります。
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは以前確認した通りの値です)

また、physical reads と physical reads direct は、HOGE表のブロック数以上あり、HOGE表は direct path readでお読み込まれていることがわかります。このときのfree buffer requested は非常に低いことは、バッファキャッシュを介さず、ストレージへ書き出されていることを示しています。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 21
sysstat commit cleanouts successfully completed 21
sysstat consistent gets 72654
sysstat db block changes 3898
sysstat deferred (CURRENT) block cleanout applications 7
sysstat free buffer requested 819
sysstat immediate (CURRENT) block cleanout applications 8
sysstat no work - consistent read gets 69569
sysstat physical reads 67150
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 21
sesstat commit cleanouts successfully completed 21
sesstat consistent gets 72366
sesstat db block changes 3898
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 818
sesstat immediate (CURRENT) block cleanout applications 8
sesstat no work - consistent read gets 69415
sesstat physical reads 67149
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

IASで direct path writeさせた場合同様に、direct path writeで書き出された場合、クリーンアウトするブロックは存在しないため、遅延ブロッククリーンアウトも発生しないという状況になります。
REDOは多少生成あれていますが、recursive call 等によるものと考えられ、このテストケースではノイズの類程度です。システム/セッション統計値をみることでその点も確認できます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.66

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
14 recursive calls
12 db block gets
80022 consistent gets
66669 physical reads
2108 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


7) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
deferred (CURRENT) block cleanout applications や、immediate (CURRENT) block cleanout applicationsなど遅延ブロッククリーンアウトで動く統計に極わずかに動きがありますが、数ブロックなので気にする程度ではないです。遅延ブロッククリーンアウトは発生していないと読み取れます。

また、physical reads は該当表のブロック数程度のブロック数となっており、physical reads direct が発生していないことから、 scattered read でバッファキャッシュに載せられたことが確認できます。free buffer requested もほぼ同じ値になっていることからも同様のことが言えます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105725
sysstat db block changes 255
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 67608
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 94138
sysstat physical reads 67567

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86719
sesstat db block changes 43
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66791
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82976
sesstat physical reads 66783




まとめ

CTASはDDLで、 direct path write を伴うため、IASのAPPEND同様、対象データブロックはクリーンアウトが必要な状態にはならず、コミットおよび遅延ブロッククリーンアウトは発生しない!
ことが確認できました。 :)

次回は、一旦中締めにしますか。

つづく。


目がショボショボしてるから、なにか浮遊物の影響を受けてる気がする。ブタクサやヨモギの季節だもんな。(目だけなんだよなー。アレルギーの酷方のように鼻水でたりとかではなくて。疲れ目用の目薬では、効かないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11



| | | コメント (0)

2021年9月24日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11

Previously on Mac De Oracle
前回は、クリーンアウトの後始末をさせられる側ではなく、そもそも、後始末不要な状況もあるという確認でした。
IAS(Insert as Select)でdirect path writeで書き込まれたブロックは、クリーンアウトする必要がない状態なので、コミットクリーンアウトも遅延ブロッククリーンアウトも発生していないことはシステム統計やセッション統計からも明らかでした。

今回は、今一度確認ということで、IASでも非direct path writeだったら、やはりコミットクリーンアウトや遅延ブロッククリーンアウトは発生するよね! というところだけは見ておこうと思います。

手順は前回と同じ。前回と異なる点は、4) の部分。IASで NOAPPEND ヒントを使い direct path writeを抑止している部分のみ。
Iasstep





0) 対象表のdrop/create

SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.67
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.05
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.16


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは 非direct path write でINSERTしたいので NOAPPEND ヒントで direct path write を抑止しています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:16.79


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へ非direct path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは 非direct path write していることが読み取れます。
free buffer requested で読み込みブロック相当のブロックがバッファキャッシュへ載せられているように見えますが、読み込みは direct path read なのでバッファキャッシュには載りません。ようするに、HOGE2表向けのデータと考えることができますよね。このキャッシュされたブロックが本当にHOGE2表向けブロックであるかどうかは、これ以降の操作で物理読み込みが発生しないということで確認することができます。


(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前々回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 19158
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 19158
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 299
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 110098
sysstat db block changes 443304
sysstat deferred (CURRENT) block cleanout applications 5
sysstat free buffer requested 67998
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 66823
sysstat physical reads 66730
sysstat physical reads direct 66709
sysstat physical writes 19158
sysstat physical writes from cache 19158
sysstat physical writes non checkpoint 19158
sysstat transaction tables consistent reads - undo records applied 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 109971
sesstat db block changes 443304
sesstat deferred (CURRENT) block cleanout applications 5
sesstat free buffer requested 67996
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 66781
sesstat physical reads 66730
sesstat physical reads direct 66709


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
APPENDモードのIASと異なり、NOAAPENDモードでは、はやり、コミットクリーンアウトが発生しています。バッファキャッシュの10%-15%の範囲のブロックがコミット時にクリーンアウトされている状況が commit cleanouts successfully completed および、commit cleanouts から確認することができます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 55709
sysstat commit cleanouts successfully completed 55709
sysstat consistent gets 18547
sysstat db block changes 289
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 791
sysstat no work - consistent read gets 10937
sysstat physical reads 758
sysstat transaction tables consistent reads - undo records applied 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

direct path writeと非direct path writeの違いはハッキリでました。REDOログは大量に生成され、遅延ブロッククリーンアウトは発生しているようです。
また、物理読み込みは発生していないので、バッファキャッシュにヒットしているという状況も読み取れます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:05.74

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
19 recursive calls
13 db block gets
91462 consistent gets
2 physical reads
967388 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed
¥
set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
実行統計の示す通り、遅延ブロッククリーンアウトが発生していることは、immediate (CR) block cleanout applications、cleanouts only - consistent read getsのブロック数からもハッキリ確認することができます!(コミットクリーンアウトできなかったブロック数です)

physical readsはほんの少しありますが、発生していないとみなしても良い程度です。このケースはscattered readが発生していない。つまり、非direct path writeでバッファキャッシュに載ったブロックにヒットしていることで、scattered readの必要がなかった! ということを意味しています。もし、この時、バッファキャッシュから該当表のブロックがある程度落ちていれば、physical readsが表のブロック数程度まで増加していたはずです。(多数の同時実行トランザクションが存在する状況であればキャッシュからエージアウトされ、物理読み込みが大量に発生するというケースは珍しくありません。その分処理時間も長くなるわけで、程度とデータサイズサイズしだいで処理時間も延びることが予想できます。場合よっては、処理時間がかかりすぎて、ザワザワしたり。。。w)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 19
sysstat commit cleanouts successfully completed 19
sysstat consistent gets 98619
sysstat db block changes 11065
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 180
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 72340
sysstat physical reads 169

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98015
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 121
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72013
sesstat physical reads 113




まとめ

IASで 非direct path write してINSERTされた場合、ブロッククリーンアウトが通常通り発生する(コミット時でも遅延でも)
direct path write時との統計値の違いからも明らかですね。

前回のIAS(APPEND)時の遅延ブロッククリーンアウトがなかったケースの統計を再掲しておきます。統計値の違いをよーく確認してみてください。(試験に。。。でないですけどw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770

次回は、CTAS ( create table as select ) ではどうなるか確かめます。(まだいくつかの関連統計を動かせていないケースありw)

次回へつづく。


東京は、自転車での移動や買い物のほうが渋滞や混雑のストレスがなくていいな。久々に coutry side側をドライブしてたが、ノーストレレスだった。一転機能は、買い物でドライブするも、いちいち渋滞w
WfHも年単位になると、すでに都心の満員電車や渋滞に耐えられない感じになってる気がしないでもないw (渋滞でスタックしている時間がもったいないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10



| | | コメント (0)

2021年9月22日 (水)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10

Previously on Mac De Oracle
前回は、SELECT文であっても遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログも生成される。ただし、direct path read で読み込まれた場合を除く。
という動きをみました。

今回もdirect pathがらみです。といっても direct path write だったら、コミットクリーンアウトや、遅延ブロッククリーンアウトはどういう扱いになるのだろう。。。と
(このシリーズの初回で紹介したいろいろなブログに答えはあるのですがw)

システム統計やセッション統計の統計値から、それをどう読み取るか。γGTP高いから肝臓あたりに問題があるか、検査前日に酒飲んじゃったでしょ! 的なところを診て、なにがおきているか診察していくシリーズなので、それぞれの統計と、auto traceによる実行計画と実行統計を診ていくわけですが。。w


ということで、今回は、前回利用していた表を元にIASで別表を作成し、コミット前後の状況を診ていきたいとおもいます。IASと言っても direct path write になるケースと従来型のロードがあるのは皆さんご存知だと思いますが、まずは、 direct path writeの方から挙動を診ていくことにします。手順はざっとこんな感じ
Iasstep

そういえば、以前、IASで、direct path write かどうか判別しやすくなったよねーというネタをやってましたね。実行計画だけでも違いがわかりやすくなっているので便利になりました。:)

実行計画は、SQL文のレントゲン写真だ! No.30より、LOAD TABL CONVENTIONAL vs. LOAD AS SELECTの実行計画の違い。
20210213-150833


20210213-150416





0) 対象表のdrop/create
SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.74
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.15

1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは direct path write でINSERTしたいので APPEND ヒントで direct path write を強制しています。 direct path read とは異なり、ヒントで制御できるのは楽w

SCOTT@orcl> @ias_from_hoge.sql
1* insert /*+ append */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:10.42


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へdirect path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは direct path write している状況がはっきりでていてわかりやすい結果を得られました :)
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 72460
sysstat db block changes 3131
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 792
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 69328
sysstat physical reads 67121
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 16
sesstat commit cleanouts successfully completed 16
sesstat consistent gets 72373
sesstat db block changes 3131
sesstat deferred (CURRENT) block cleanout applications 12
sesstat free buffer requested 792
sesstat immediate (CURRENT) block cleanout applications 3
sesstat no work - consistent read gets 69291
sesstat physical reads 67121
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
direct path writeでバッファキャッシュを経由せず書き出されたブロックはコミット時にはクリーンアウトの対象にはなっていないようですね。。。。統計をみる限りノイズ程度ですね。
ということは全てのブロックが遅延ブロッククリーンアウト対象になってしまうのか、または、その逆で、最初からクリーンアウト対象にもなっていないかということになります。次の全表走査の結果でどちらであるか、わかるはずです!!!!(ニヤニヤ、それ、ねらってやってるので、答えは知っているわけですがw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 25
sysstat commit cleanouts successfully completed 25
sysstat consistent gets 12104
sysstat db block changes 1557
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 714
sysstat immediate (CURRENT) block cleanout applications 4
sysstat no work - consistent read gets 7192
sysstat physical reads 705

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 864
sesstat db block changes 1474
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 37
sesstat no work - consistent read gets 351
sesstat physical reads 32


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

REDOログは多少生成されていますが、実際に遅延ブロッククリーンアウトが発生した場合REDOログ量この程度では少なすぎますよね。
今回の検証目的からするとノイズの類ですね。むむむ。これは。。。。
(物理読み込みは発生しているので、direct path read か、scattered readのどちらかということにはなります。期待している動きは、Scattered read 。)

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.84

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
13 recursive calls
12 db block gets
80022 consistent gets
66668 physical reads
2284 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
physical reads は想定通りのブロック以上になっていますが、physical reads direct は発生していないので、scattered readによる全表走査であると読み取ることができます。
ただ、この状態でも、遅延ブロッククリーンアウトの発生を示す統計値はノイズ程度の値です。

つまり、direct path write でINSERTされたデータブロックはクリーンアウトが必要な状態だということになりますね。興味深い動きですよね。メモしておいたほうが良さそうです :)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770




まとめ

IASで direct path write してINSERTされた場合、ブロッククリーンアウトは発生しない!(コミット時でも遅延でも)
そもそもクリーンアウトが必要な状態になっていないというのが正しいのでしょうね。統計値としては全く動いてないに等しいので。

これも試験にでますよ(嘘w

次回は、同じIASもdirect path write ではないケースではどうなるでしょう。。。(ここまでのエントリーを読んでいる方は、結果は想像できそうではありますが)

次回へつづく。


遅めの夏休みですが、まあ、普段とちがうのは、いろいろなタスクの締め切りに追われずに、マイペースな時間の過ごし方になるぐらいだな。この状況下ではw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9



| | | コメント (0)

2021年9月21日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9

Previously on Mac De Oracle
バッファキャッシュから溢れ出る程度のデータを登録しコミットした場合は、コミットクリーンアウトがどうなるか、遅延ブロッククリーンアウトされるブロック数はどの程度になるかという、寄り道でしたw

今回は、寄り道し過ぎて忘れるところだった、前回まで、scattered readを伴うtable full scanで発生していた遅延ブロッククリーンアウトが direct path read だったらどうなるか、というシリーズ :) 
(まだ続くのかーーーっ! はいw いろんなケースがありますから。シンプルなケースであっても。それぞれの基本的な挙動を知っいて損はないとおもいます。 複雑なケースだと脳汁出過ぎるくらい複雑なので考えたくもなくなるのでw)

では、早速再現してみましょう。手順はこれまで行なってきたとおりで、違いは遅延ブロッククリーンアウトを発生させるためのtable full scanでdirect path readさせるという部分のみ。手順はscattered readとの比較も入れるので長くなってしまうので、追加ステップを追記した図を見てもらうと何やっているか、流れは理解しやすいかもしれません。Steps


事前準備
バッファキャッシュのサイズは元のサイズに戻してあります

SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

200,000行登録したデータ(セグメントサイズ農地純粋にデータが乗っているブロック数)が乗っているブロック数はこんなところ。セグメントサイズはこれより多いですよ。行データが載っているブロックだけカウントしているので。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ということで、セグメントサイズも確認。

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS
------------------------------ ----------
HOGE 67584

バッファキャッシュの10-15%程度はコミットクリーンアウトされるので、間をとってこれぐらいはコミットクリーンアウトされる。。。

SCOTT@orcl> select 3472883712 / 8192 * 0.13 from dual;

3472883712/8192*0.13
--------------------
55111.68

残りは遅延ブロッククリーンアウトする。だいだいこんなもん。

SCOTT@orcl> select 66667 - 55112 from dual;

66667-55112
-----------
11555

direct path read狙いの全表走査(シリアル実行で発動させることを意図していますが、言うこときいてくれるかあなぁ)

$ cat table_full_scan_with_dpr.sql
alter session set "_serial_direct_read" = always
.
l
/
alter session set "_very_large_object_threshold" = 512
.
l
/

!echo set autot trace exp stat
set autot trace exp stat

select * from hoge
.
l
/

!echo set autot off
set autot off

Scattered Read狙いの全表走査のスクリプト

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 20400
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




さて、うまく再現できるかどうか。。(今回もやったことをほぼすべて載せているので長いです)


0) 対象表をdrop/create
オブジェクトを作り直し前提合せ

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでINSERT(データ量2倍、コミットなし)
データサイズはバッファキャッシュに載るサイズ、コミットクリーンアウトではクリーンアウト仕切れないサイズで、ある程度の遅延ブロッククリーンアウトが発生するサイズになっているのは以前と同じ。

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)
insertしただけなので、insertしたデータ量に応じたブロック数がバッファキャッシュに確保されたという程度の情報( free buffer requested = 68766 なので事前に確認していた 66667 以上になっています)は確認できます。
(バッファキャッシュに収まるデータ量ですし)ただ、checkpointの発生でいくらか書き出されているのも見えますよね( DBWR checkpoint buffers written )

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22909
sysstat DBWR checkpoints 3
sysstat DBWR thread checkpoint buffers written 22902
sysstat DBWR transaction table writes 6
sysstat DBWR undo block writes 573
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 49682
sysstat db block changes 744727
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 68766
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 82
sysstat physical reads 9
sysstat physical writes 22909
sysstat physical writes from cache 22909
sysstat physical writes non checkpoint 22892

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49561
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat free buffer requested 68766
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 40
sesstat physical reads 9


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
コミットクリーンアウトされているブロック数を見ると、事前に計算していた バッファキャッシュの13%( 55112 blocks )に近い 55700 ブロックがコミットのタイミングでクリーンアウトされていることがわかります。ここまでは想定通りの動きです。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 56077
sysstat commit cleanouts successfully completed 56062
sysstat consistent gets 50494
sysstat db block changes 7894
sysstat deferred (CURRENT) block cleanout applications 158
sysstat free buffer requested 3217
sysstat immediate (CURRENT) block cleanout applications 154
sysstat no work - consistent read gets 32764
sysstat physical reads 2962

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)
全表走査させてコミットクリーンアウトされなかったブロックがクリーンアウトを確認します。ただし、全表走査ではありますが、direct path read で読み込ませるように工夫しています。

さて狙い通りになるかどうか。。。パラレルクエリーでない場合の強制はちょいとむずいのですが、見る限り、REDOログは生成されています。

ただ、以前のscattered read ( db file sequential read )で発生させた遅延ブロッククリーンアウトの検証結果に比べると明らかに少ない。。
なにかが違いますね。。。。むむむ。なんだろう?
scattered readでほぼ同じバッファキャッシュサイズで、遅延ブロッククリーンアウトさせた際、967432 redo size というサイズが生成されていたのを思い出してみてください!!! 明らかに少ないです。。。。

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:10.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
23 recursive calls
13 db block gets
113559 consistent gets
66712 physical reads
2996 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)
direct path read、遅延ブロッククリーンアウトの有無をシステム統計およびセッション統計から読み取ってみます!

物理読み込み( physical writes )とダイレクトパスリードを示す( physical writes from cache )が同じであることから、間違いなく direct path readが発生しています。ブロック数も 66667 ブロックを超えていることは確認できます。
ただ、immediate (CR) block cleanout applications が想定している量の3倍ぐらいあります:)
遅延ブロッククリーンアウトは行われているのは間違いないですが、前述の通りREDOサイズが異常に少ない。どういうことだろう。。(想定通りの結果に、ニヤニヤなわけですがw)

真相を探るため、われわれはアマゾンの奥深くへ入っていくのであった。。。W

少々本題からそれますが、DBWR parallel query checkpoint buffers written で 44793 ブロックほど書き出されています。これが発生するのは direct path readの影響です。direct path read バッファキャッシュを介ず、常にストレージからデータを読み込む必要があります。この検証では、INSERTでバッファキャッシュに載っているデータであるため一旦書き出す必要があります。書き出されたデータを direct path read で読み込むのでこんな動きになっているというわけですね。。。。。

これ、よくよく考えると、コミットクリーンアウトされていないブロックもそのままの状態で書き出されてますよね。。。ここ試験にでますよ(嘘ですw

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 44793
sysstat DBWR checkpoints 2
sysstat DBWR object drop buffers written 2
sysstat DBWR parallel query checkpoint buffers written 44793
sysstat cleanouts only - consistent read gets 33057
sysstat commit cleanouts 18
sysstat commit cleanouts successfully completed 18
sysstat consistent gets 120519
sysstat db block changes 97
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 127
sysstat immediate (CR) block cleanout applications 33057
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 50145
sysstat physical reads 66827
sysstat physical reads direct 66710
sysstat physical writes 44795
sysstat physical writes from cache 44795
sysstat physical writes non checkpoint 44795

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 33057
sesstat commit cleanouts 5
sesstat commit cleanouts successfully completed 5
sesstat consistent gets 120131
sesstat db block changes 48
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 120
sesstat immediate (CR) block cleanout applications 33057
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 49952
sesstat physical reads 66823
sesstat physical reads direct 66710


10) Oracle再起動
Oracleを再起動して、諸々綺麗にした状態で、今一度、direct path readで全表走査させてみましょう。

$ sudo service oracle restart


11) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


12) CDBのSYSで統計取得(再起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


13) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)2回目
お!!! REDOが生成されていないですね。Scattered Readの場合でも、コミット時でも一度クリーンアウトされたブロックはクリーンアウト済みなので、クリーンアウトされるような挙動は発生しませんでしたが、 direct path read でもおなじかなーー。

と。。。。とりあえず、システム統計とセッション統計も確認しておきましょう!

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:03.90

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91159 consistent gets
66938 physical reads
0 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


14) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)2回目
セッション統計のphysical readsとhysical reads direct は同一であることから direct path readになっていることは間違いありません。また、ブロック数も 66667 ブロック以上にはなっているので全ブロック読み込まれているようですね。
ただ、妙な値を示している統計があります。

immediate (CR) block cleanout applications  10959 

遅延ブロッククリーンアウトが行われている時に上がる統計です。しかも、コミットクリーンアウトされたブロック数を差し引いたブロック数にほぼ一致します。(1回目の実行ではこの3倍ぐらいに跳ね上がっていましたが。。一度クリーンアウトされたのでは??)

さらに不思議なことに、REDO生成されないんですね。。。。

ん? ちょっと待ってください。一度、クリーンアウトされたブロックがなぜ、再度クリーンアウトされているのでしょう? scattered readで遅延ブロッククリーンアウトされたケースと動きが違います!!!!!!

Oracleを再起動する前のステップでREDOログが異常に少ないにも関わらず、遅延クリーンアウトされていた統計値が高くなった。Oracleを再起動した後でも、同様に、direct path read で読み込み、遅延ブロッククリーンアウト発生。しかもREDOログはありません。。。これって、クリーンアウト行われているようですが、実際にはメモリー上だけで実祭のブロックはクリーンアウトされずに残っているということですよね。なんども発生しているわけですから。
(ブロックダンプしなくても統計値から状況は見えてきましたよね!!)

direct path readはその名の通り、バッファキャッシュを介さず、常にストレージからデータブロックを読み込み、PGAへ。このケースだとSELECT文なので単純にPGAへ直接読み込み、メモリ上ではクリーンアップは行なっているようですが、クエリーが終了すれば、単に捨てられるのみ。。。なので、クリーンアウトの結果は永続化されない。。。ということになりますよね!

ということは、SELECT文の場合は、scattered read等でバッファキャッシュを経由させないと、遅延ブロッククリーンアウトは、ずーっと先延ばしされる。。。direct path readのSELECT文を2回実行してクリーンアウトさせたわけだが、この後、scattered readで全表走査させれば、遅延ブロッククリーンアウトが発生して、大量のREDOログが生成されるて、完全にクリーンアウトされる。。。。。。はず。。。ですよね。

試してみよう!!!!

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat DBWR object drop buffers written 2
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 36
sysstat commit cleanouts successfully completed 36
sysstat consistent gets 106766
sysstat db block changes 166
sysstat deferred (CURRENT) block cleanout applications 18
sysstat free buffer requested 1130
sysstat immediate (CR) block cleanout applications 10959
sysstat immediate (CURRENT) block cleanout applications 10
sysstat no work - consistent read gets 77392
sysstat physical reads 67827
sysstat physical reads direct 66710
sysstat physical writes 2
sysstat physical writes from cache 2
sysstat physical writes non checkpoint 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98223
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 587
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67289
sesstat physical reads direct 66710


15) Oracle再起動
諸々情報を綺麗にするので再起動!!

$ sudo service oracle restart


16) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


17) CDBのSYSで統計取得(再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


18) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)3回目

キターーーーーーーーーーーーーーっ!。 予想的中!!。(競馬ならいいのにw) 

大量のREDOログが生成され、物理読み込みも初生しています。direct path readで全表走査させた時とは明らかに違う!!!(以前、見た光景!!w

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91170 consistent gets
66720 physical reads
964436 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


19) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)3回目

physical reads が想定ブロック数以上あるため、物理読み込みされ全ブロックが読み込まれていると読み取れます。また、physical reads direct は変化していません。(変化のない統計は記載していません)
つまり direct path read ではなく scattered read で全表走査が行われたことを示しています。

immediate (CR) block cleanout applications                     10959

という統計から、遅延ブロッククリーンアウトが発生し、ほぼ想定していたブロック数であることも確認できます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 98846
sysstat db block changes 10992
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67078
sysstat immediate (CR) block cleanout applications 10959
sysstat no work - consistent read gets 72660
sysstat physical reads 67069

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98234
sesstat db block changes 10988
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67071
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67063


20) Oracle再起動
以前の検証で、scattered readでブロッククリーンアウトされた場合のSELECT文であっても結果は永続化されるので、再度読み込ませた場合はクリーンアウト済みなので再度遅延クリーンアウトが発生しないことは確認確認済みですが、今一度確認しておきましょうw

$ sudo service oracle restart


21) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


22) CDBのSYSで統計取得(再再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


23) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)4回目

想定通り、遅延ブロッククリーンアウトは発生せず、REDOログも生成されていません! めでたしめでたしw

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.32

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
4539159 bytes sent via SQL*Net to clientyoutub
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

24) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)4回目
physical reads はありますが、 physical reads direct は変化していません。これは scattered read で全データを読み込んだと見て良いでしょうね。読み込んだブロックサイズも該当表の想定データブロック数程度です。
また、遅延ブロッククリーンアウトが発生したことを示す統計は変化していないことから、遅延ブロッククリーンアウトは発生していないことも読み取れます。(^^)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 87659
sysstat db block changes 34
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67058
sysstat no work - consistent read gets 83510
sysstat physical reads 67048

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 87049
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67051
sesstat no work - consistent read gets 83175
sesstat physical reads 67042



まとめ

SELECT文であっても、遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログが生成される。ただし、direct path read で読み込まれた場合を除く。
ということのようですね。

ふむふむという興味深い動きですよね。これ。:)

では、次回は direct path に関わる別の動きも確認してみましょうか。。。
このシリーズ、まだまだ引っ張れそうw

ということで次回へつづく。


台風の影響を心配したけどなんとか良い天気の遅い夏休みで。暑くも寒くもないく散歩の気持ちいい秋空 :)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8



| | | コメント (0)

2021年9月14日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8

Previously on Mac De Oracle
前回からのつづき(ちょいと寄り道中)
です。

では、とっとと試してみましょうw

バッファキャッシュから溢れ出る程度のデータ量だったら、どうなるのかなーーーー、という検証です。やりたいことは図の通りです。
Photo_20210911234101

検証方法を考えていたのですが、自動共有メモリー管理になっているのと、sga_max_size/sga_targetを小さくしすぎるとOracle Databaseが起動しないなど諸々引きそうなので、shared_pool_sizeを大きく設定して、バッファキャッシュに回せるメモリーを減らすことで、バッファキャッシュを小さく、バッファから溢れる程度のデータ量も少なくて済むようにして試してみることにします。

準備段階から書いてます。再現させる環境をどうセットアップしたかっていうことも重要だと思うのですよね。少々長くなっちゃいますが。



検証準備

CDBに接続して初期化パラメータを調整!!

SGAコンポーネントの状況
Database Buffersが、3G以上になってます。検証データ量も多くなってしまうので、これを1GB程度まで下げたいですね。検証時間も節約できますし、最小の手数で検証できるほうが良いですから:)

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 3472883712 / 1024 / 1024 AS "MB" from dual;

MB
----------
3312

Elapsed: 00:00:00.00


sga_max_size,sga_min_sizeが4GBですが、ここはそのままにします。あまり小さくしすぎると起動しなくなったり。(^^;;;;

SYS@orclcdb> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 4G
sga_min_size big integer 0
sga_target big integer 4G
unified_audit_sga_queue_size integer 1048576


自動SGA管理なので、Shared Pool Sizeに大きめの値を設定。
自動SGA管理下で自動管理対象メモリーコンポーネントパラメータに値を設定した場合、その値が下限値となり最低でもその値は確保されるという仕組みを利用します!

SYS@orclcdb> show parameter shared_pool

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
shared_pool_reserved_size big integer 39426457
shared_pool_size big integer 0


起動しなくなっても戻しやすいようにspfileをpfileに書き出して退避後に、shared_pool_sizeを3GBへ増やします。これで4Gのうちの3G程度がshared poolに割り当てられ、バッファキャッシュは1GBぐらいになるはず。(VirtualBoxなのでスナップショット取得しておいて戻すのもありですけどw)

SYS@orclcdb> create pfile='pfile20210912.ora' from spfile;

File created.

Elapsed: 00:00:00.00
SYS@orclcdb> alter system set shared_pool_size = 3g scope=both;

System altered.

Elapsed: 00:00:00.77


Database Buffersがいい感じにシュリンクしました。1GBぐらいになりました。これで進めますよー。

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 3238002688 bytes
Database Buffers 1040187392 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 1040187392 / 1024 / 1024 AS "MB" from dual;

MB
----------
992

Elapsed: 00:00:00.01


ざっくりとブロック数を計算

SYS@orclcdb> select 1040187392 / 8192 AS "blocks" from dual;

blocks
---------------
126976

Elapsed: 00:00:00.01


前回のHOGE表に200,000 rowsで、66,667 blocks のデータを生成したので、126,976 blocks を満たすデータ量にしようとすると 400,000 rowsほど必要になりそうですね。。。。少々多めで、バッファキャッシュから溢れる程度の量で、 500,000 rowsのデータを登録することにしましょう!!!!

SYS@orclcdb> select ceil(126976 / 66667) * 200000 AS "rows" from dual;

rows
----------
400000

Elapsed: 00:00:00.00


これまでの検証から 10%-15%程度がCOMMITクリーンアウトされ、残りが遅延されるのは確認できたので、126976 blocks のバッファキャッシュだと、 17,777 blocks ぐらいがコミットクリーンアウトされそうですね。(今回のテストケースではコミットクリーンアウトされないけど。。された場合は最大でこの程度。。。というメモです。はい)

SYS@orclcdb> select ceil(126976 * 0.14) AS "blocks for commit cleanout" from dual;

blocks for commit cleanout
--------------------------
17777

Elapsed: 00:00:00.00

前回作成したデータは、200,000rowsで、66,667 blocksだったので、500,000 rows だと、ざっくり 166,668 blocks ほど。

SCOTT@orcl> select ceil(66667 / 2 * 5) AS "blocks" from dual;

blocks
----------
166668

Elapsed: 00:00:00.00


なので、遅延ブロッククリーンアウトされると想定される(コミットクリーンアウト分を覗くと)ブロック数は、148,891 blocks 程度にはなりそう。

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 17777) AS "blocks" from dual;
blocks
----------
148891

Elapsed: 00:00:00.01


それに加えて、バッファキャッシュに収まらず、コミットする前にバッファキャッシュから落とされ、ストレージへかきだされてしまうブロック数は、これまた、ざっくり計算すると 39,692 blocks ほどですかね。バッファキャッシュのサイズから全てのブロックは乗り切らないので、最初に読み込まれていたブロックから落とされていくことにはなりますね。。
とはいえ、この検証ではキャッシュ落とされるブロック数は特に気にしてなくて、バッファキャッシュ以上のブロック数が生成されていればいいので、落とされそうなのが確認できればOK.

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 126976) AS "blocks" from dual;

blocks
----------
39692

Elapsed: 00:00:00.00


とりあえず、生成するデータ量(行数)の算出とバッファキャッシュサイズの調整はおわり。


次に、実際にデータを生成してブロック数とセグメントサイズを確認して、実行用スクリプトの調整を行なっておきます。



PDBでテストデータの実サイズの確認
SCOTT@orcl> @droppurge_create_hoge.sql
1* drop table hoge purge

Table dropped.

Elapsed: 00:00:00.25
1* create table hoge (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.04
1* select segment_name,blocks from user_segments where segment_name like '%HOGE%'

no rows selected

Elapsed: 00:00:00.11


500,000行登録!!

SCOTT@orcl> @insert_each_rows_5.sql
1* begin for i in 1..500000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:02:37.68


データが登録されているブロック数は、166,667 blocks で、事前に計算していた 166,668 blocks にほぼおなじ。(狙い通り)
セグメントサイズは、約 1344 MB ですね。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
166667

Elapsed: 00:00:05.03
SCOTT@orcl> select segment_name,blocks,bytes/1024/1024 AS "MB" from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS MB
------------------------------ ---------- ----------
HOGE 172032 1344

Elapsed: 00:00:00.17


Scattered read でTable Full Scanできるように少々隠しパラメータを調整しておきますね。念の為。(セッションレベルで調整してます)
セグメントサイズが、1344 MBなので、_very_large_object_threshold は、2048 MBぐらい設定しておけば、Scattered readのまま行けそうですね。

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 2040
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off


準備完了!!!!!





準備長かったけどw やっと本題です!!!w 実行している内容はいままでと同じなのでかなり端折ってポイントだけ記載。

バッファキャッシュから溢れるほどのデータ量で。コミットクリーンアウトはどうなるのだろうか。。。。想定では、ほぼコミットクリーンアウトできないはずではあるのだが。。。。

CDBのSYSで統計取得(コミット後)

commit cleanouts successfully completedはどれぐらいだったのか。。。。。ありません。commit cleanouts successfully completedに差分がなかったので、コミットクリーンアウトしようして失敗、commit cleanout failures: block lostと同数なので、1ブロックもコミットクリーンアウトできない!  commit cleanout failures: block lostがバッファキャッシュに対象ブロックがなかったことを示しています。

つまり、バッファキャッシュに乗り切らなため、最初にINSERTされたブロックはそのままストレージへ物理書き込みされて追い出された結果。。ということになりますね。

登録したブロック全てが遅延クリーンアウト対象になってしまった、ということになります。コミット時にクリーンアウトできてないわけだから!!!!!!!! (イメージ図でざっくり書いたとおりの感じに。。。

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanout failures: block lost 3028
sysstat commit cleanouts 3028
sysstat consistent gets 274
sysstat db block changes 1
sysstat no work - consistent read gets 149
sysstat physical reads 60

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanout failures: block lost 3028
sesstat commit cleanouts 3028
sesstat db block changes 1


次に、Scattered Readが実行される全表走査を行わせ、遅延ブロッククリーンアウトどれだけ発生するか結果確認!

PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)
想定通り、物理読み込み(この時点ではscattered readなのか、direct path readなのかわかりませんが)になっています。また、大量のREDOログが生成されているので遅延ブロッククリーンアウトが発生しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:01:03.08

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
367852 consistent gets
160269 physical reads
14670268 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off


物理読み込みを伴う全表走査でどの程度の遅延ブロッククリーンアウトが発生したか統計を確認!!!!

CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

想定どおり、INSERTした全ブロックが、immediate (CR) block cleanout applications = 166667 で遅延ブロッククリーンアウトされたことがわかります。(冒頭に記載していますが、データが格納されているブロック数は、 166667 blocks でしたよね)
physical readsは意図通り発生していますが、physical reads directが変化していないので、狙い通りScattered Readになったようですね

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 241
sysstat DBWR thread checkpoint buffers written 241
sysstat DBWR undo block writes 1109
sysstat cleanouts only - consistent read gets 166667
sysstat commit cleanouts 55
sysstat commit cleanouts successfully completed 55
sysstat consistent gets 377331
sysstat db block changes 166904
sysstat deferred (CURRENT) block cleanout applications 29
sysstat immediate (CR) block cleanout applications 166667
sysstat immediate (CURRENT) block cleanout applications 15
sysstat no work - consistent read gets 38449
sysstat physical reads 160805
sysstat physical writes 137551
sysstat physical writes from cache 137551
sysstat physical writes non checkpoint 137466

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 166667
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 374538
sesstat db block changes 166710
sesstat deferred (CURRENT) block cleanout applications 3
sesstat immediate (CR) block cleanout applications 166667
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 36779
sesstat physical reads 160513


念の為、今一度、物理読み込みを伴う全表走査を行なって、クリーンアウトされたのか確認してみましょうw(疑い深いw)
もう一度、PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

redo size0 なのでクリーンアウトは発生してない。想定通り

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:00:33.17

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
200399 consistent gets
148198 physical reads
0 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off

同様に、統計でも確認してみます!
CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

クリーンアウトを示す統計値は上昇していません!!!(うんうんw)

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 732
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 200522
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 200403
sysstat physical reads 148200
sysstat physical writes 68570
sysstat physical writes from cache 68570
sysstat physical writes non checkpoint 68331

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 200414
sesstat db block changes 9
sesstat no work - consistent read gets 200366
sesstat physical reads 148198



OK. Done. ということで、まとめ!

バッファキャッシュには収まりきれないデータ量の場合、コミットクリーンアウトしようとしていたブロックも追い出されてしまうので、結果的に、全ブロックが遅延ブロッククリーンアウトになった。というイメージしていた結果の通りでした。
(今回のケースもシンプルケースなので比較的予想しやすい結果ですが、クリーンアウトに関わる統計は以外に多く、複雑な動きになるものもあります。再現するののめんどくさいのでしませんがw)
Photo_20210911234101

寄り道はここまで、次回は、こんどこそ、direct path readと遅延ブロッククリーンアウトの関係をみていきたいと思います。


来週天気いいかなー。遅い夏休みなのに。微妙な気がしてきた。。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7


| | | コメント (0)

2021年9月12日 (日)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7

Previously on Mac De Oracle
前回は、コミットクリーンアウトと遅延クリーンアウト、そして、そこにTable Full ScanでScattered Read (待機イベントだと db file scattered read) を絡めてストレージへ永続化されたクリーンアウトが遅延されてしまったブロックを物理読み込みませつつ遅延ブロッククリーンアウトを再現させてみました。
また、次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。なんてことを言っていましたが、またまた、ちょいと寄り道ですしますw

バッファキャッシュから溢れるぐらいのデータをぐるぐる系INSERTで、しかも1回のコミットにしたら、コミット前にあふれたデータはストレージへ書き出され、かつ、クリーンアウトも遅延されるよなー。という予想を元に、ちょいと遊んでから次に進みたいw と思います。

これまでの流れから、基本的なクリーンアウトおよび遅延ブロッククリーンアウトとしては以下ようなパターンを確認してきました。

バッファキャッシュの上でコミットクリーンアウトおよび、遅延ブロッククリーンアウト(単純なタイプ)が行われているケース
Photo_20210911234001

ここからが想像というか、私が理解している範囲から想像した動き。バッファキャッシュから溢れはしないけど、いっぱいいっぱいな場合は、クリーンアウトされるブロックがキャッシュ上に多くあるだろうな。と.
とは言っても、バッファキャッシュ上ではあるわけです。
Photo_20210911234002


そこで、ちょいと意地悪をして、バッファキャッシュから溢れ出る程度のデータ量だったどうなるのかなーーーーと。冒頭ですでコメントしているわけですけどもw 多分、以下のような動きだよねー、と。
そういえば、以前、DBTSで行なったセッションの「バッファキャッシュ欠乏症」の部分で、似たようなバッファキャッシュから溢れ出したブロックの挙動をなんとかするみたいな資料も今回の動きを想像するにはよいかもしれないですね。
Photo_20210911234101


と、思い、頭の中のイメージを Pagesでざざっと作ったところで、本日はここまで。次回へつづく。


Beat SaberとWalkingの合わせ技で、効果的な減量継続中w



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6



| | | コメント (0)

2021年9月10日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6

Previously on Mac De Oracle
前々回と前回はバッファキャッシュの10%を超えるデータ量のINSERT文の実行とCOMMITの実行で、バッファキャッシュの10%-15%程度は、COMMIT時にクリーンアウトされ、残ったブロックのクリーンアウトは先送りされる。という検証を2つのパターンで確認してみました。

どのような流れで発生するかを各ステップ毎にシステム統計(CDB)とクエリーを実行するセッションのセッション統計(PDB)を取得し、どのように統計値が変化すれば、コミットクリーンアウトや遅延ブロッククリーンアウトが起きているのかを見ながらすすめました。以下2つのエントリーで確認した動きの違いはイメージできたのか少々不安ではありますがw (そこそこ長いエントリーなのでw)

こちら前々回は、クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
で、

前回は、クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

なんです :) 


まだ、イメージつかめない方もいるかもしれないので

超ざっくりした絵が頭の中に浮かばない方向けに、上記検証を行う前に、私の頭の中にうかんだ、ラフイメージをほぼそのまま

(こまけーとこは気にしないでくださいね。ラフイメージですから、こうだろうなーというのを想像している状態そのままのイメージですのでw)

クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
No4


クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
No5

再現させてるケースはシンプルなものなので処理時間云々を比較してはいないですが、複雑なケースになると、本来スマートスキャンさせたいのにシングルブロックリードが多くなったりするケースなど、以前紹介したURLを見ていただければ参考になるかもしれないですね。
クエリーやDMLの処理時間が伸びてビビるぐらいに仕事量が増えてたり、先送りされたことで、もろもろ後処理が複雑化する場合もあるわけで)

ということで、こんな図をイメージしながら、ネタ作ってます。はいw


次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5



| | | コメント (0)

2021年9月 9日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

前回はバッファキャッシュの10%を超えるデータブロックへのINSERT文の実行とCOMMITの実行で、バッファキャッシュの13%-15%程度はCOMMIT時にクリーンアウトされ、残りは遅延ブロッククリーンアウト(先送り)される。
direct path readではないSELECT文による(前回のケースでは scattered read))遅延ブロッククリーンアウトは、1度のみ発生するという状況を確認しました。

ところで、
前回のエントリで、2度、全表走査(前回の検証ではscattered read)を実行しているのですが、物理読み込みは発生させていません。(INSERT→COMMIT→SELECT→SELECTという流れで、十分なバッファキャッシュがあるので、当然ではあるのですがw)
バッファキャッシュに乗ったままのブロックが遅延クリーンアウトされていたわけです。

前回のエントリ:古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

前述の1.2.それぞれの実行で物理読み込みだったなにか変化はあるのだろうか。。。予想では、上記に加えてscattered readに伴う物理読み込みが増えるだけのはずです。その動きを見てみることにします。(こういう動きを見ていると楽しいですよねw)
手順は前回と同じですが、各全表走査の前にインスタンスを再起動してバッファキャッシュを空にしておきます。
(buffer cacheをflushすればいいじゃん。という声も聞こえてきそうですが、今回は再起動でクリアしました。はいw)


前回から多少変更したスクリプトも載せておきます(本文中にもありますが)、細かい解説は後述

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 1056
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




0) 対象表をdrop/create
オブジェクト作り直し

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにインスタンス再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別するため

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(統計差分取得のためのベースラインを取得しているだけ)

SYS$orclcdb> @show_stat scott


4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

未コミットの状態なので特に、気にせず、ふーーーん。ぐらいの感じで眺めていただければいいですね。前回同様に、commit cleanouts, commit cleanouts successfully completed, deferred (CURRENT) block cleanout applications, immediate (CURRENT) block cleanout applicationsといったクリーンアウト関連統計が極わずかありますが、この時点で発生しているのは本題ではないので気にしなくてOK

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 23589
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 22643
sysstat DBWR transaction table writes 50
sysstat DBWR undo block writes 848
sysstat cleanouts and rollbacks - consistent read gets 5
sysstat commit cleanout failures: callback failure 20
sysstat commit cleanouts 1320
sysstat commit cleanouts successfully completed 1300
sysstat consistent gets 124041
sysstat db block changes 757165
sysstat deferred (CURRENT) block cleanout applications 715
sysstat immediate (CR) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 196
sysstat no work - consistent read gets 46398
sysstat physical reads 4063
sysstat physical writes 23589
sysstat physical writes from cache 23589
sysstat physical writes non checkpoint 23494

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49596
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 15


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
前回同様ノイズもなく、綺麗にコミット時のクリーンアウトが発生しています。バッファキャッシュの約14-5%程度なのは前回と変わらずですね。

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) Oracle Databaseを再起動してバッファキャッシュをクリア

ここが前回と違う手順で、クリーンアウトされないブロックはずーーーーーっと残るよね。ということの確認でもあります。(alter system flush buffer_cacheでも同じことができるわけですが、ここでは再起動しています)

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


9) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


10) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したので統計値の差分取得用ベースライン統計の取得)

SYS$orclcdb> @show_stat scott


11) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の1回目

ここでは待機イベントまでは確認できませんが、Table full scanと実行統計よりRedoログがたっぷり生成されていることは確認できます。SELECT文ですが。。。つまり、遅延ブロッククリーンアウトが発生しているということですね。確認は後述の統計で。
狙い通り、物理読み込みも発生しています!

Note)
"_very_large_object_threshold" = 1056 としているのは、direct path readとなる上限セグメントサイズをhoge表が超えているためdirect path readを抑止するためにこの隠しパラメータで上限値を引き上げ、scattered readになるように強制しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.10

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


12) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の1回目

遅延ブロッククリーンアウト関連統計値が上昇しているので、遅延ブロッククリーンアウトの発生が確認できます。ここまでは前回と同じ。(同じじゃないと困りますがw)

違う点は、事前にインスタンスを再起動しているため、physical reads が上昇しています。これは hoge表を scattered readで全表走査しているからです。(phsical read directは発生していない)バッファキャッシュを経由するのでconsistent gets,no work - consistent read gets も上昇しています。狙い通りです。

そして、重量な遅延ブロッククリーンアウトですが、バッファキャッシュでヒットしていた時と同数のブロックで発生しています。(ニッコリ

インスタンスの停止や起動があったとしても、クリーンアウトが先送りされたブロックはアクセスされない限りクリーンアウトされず残っているということを示しています!!!!(ここ試験にでますよー。嘘)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 7
sysstat commit cleanouts successfully completed 7
sysstat consistent gets 98967
sysstat db block changes 11016
sysstat deferred (CURRENT) block cleanout applications 3
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 72543
sysstat physical reads 67403

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98357
sesstat db block changes 11012
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72208
sesstat physical reads 67397


13) Oracle Databaseを再起動してバッファキャッシュをクリア

再度、インスタンスを再起動して、バッファキャッシュをクリアします。後続の全表走査では、遅延ブロッククリーンアウトは発生せず、物理読み込み(この検証では scattered readさせています)を伴うTable full scanが行われるだけのはずです。

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


14) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


15) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したのでベースラインとなる統計を取得)

SYS$orclcdb> @show_stat scott


16) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の2回目

実行統計から、physical readsが、発生しています。Redoは生成されていないことも読み取れるので、遅延ブロッククリーンアウトは発生していないことも確認できます。:) 想定通りですね。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.44

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


17) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の2回目

physical reads は発生していますが、physical reads directではないことが確認できます。Table full scanをscattered readで読み込んでいるという想定通りの結果。 
遅延ブロッククリーンアウトを示deferred (CURRENT) block cleanout applications 、immediate (CURRENT) block cleanout applicationsや、コミットクリーンアウトを示すcommit cleanouts 、commit cleanouts successfully completed という統計が極わずかに変動していますが、今回の検証ではノイズなので気にしたくてOK。

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 11
sysstat commit cleanouts 15
sysstat commit cleanouts successfully completed 15
sysstat consistent gets 87375
sysstat db block changes 79
sysstat deferred (CURRENT) block cleanout applications 12
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 83322
sysstat physical reads 67028
sysstat physical writes 11
sysstat physical writes from cache 11
sysstat physical writes non checkpoint 11

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 86936
sesstat db block changes 29
sesstat deferred (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 83098
sesstat physical reads 67023




まとめ

遅延ブロッククリーンアウトは、インスタンスを停止して残ったままということが確認できました。(クリアされるまで残るのですよねー)
クリーンアウトが遅延されているブロックが物理読み込みされた(アクセスされた)タイミングで遅延ブロッククリーンアウトが発生することも確認できました。(物理読み込みの有無には関係しない)
バッファキャッシュ上の遅延ブロッククリーンアウト同様、クリーンアウトされたブロックでは、再度、クリーンアウト対象になるような更新が発生しなければ、遅延ブロッククリーンアウトは発生しない

久しぶりにシステム統計やセッション統計を見ててワクワクしてきましたよーーーーっw

ところで、
冒頭でも記載しましたが、バッファキャッシュ上の遅延ブロッククリーンアウト(再掲)に加え、物理読み込みを伴う遅延ブロッククリーンアウトのauto traceの結果をまとめて記載しておきます。SELECT文ですが、REDOログが生成されている場合は遅延ブロッククリーンアウトが発生しているということになります。

実行計画

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed


3. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータを物理読み込みし、バッファキャッシュに載せている動きが見えます。

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

4. 3.の直後に再度全表走査した場合。事前にキャッシュをクリアしているため、物理読み込みがありますが、遅延ブロッククリーンアウトは発生していません。(Redoが生成されていないことで確認できます)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

ではでは。
次回は、scattered readのtable full scanではなく、direct path readだったらどうなるのか調べてみましょうか。。(いろいろな再現方法があるわけですが、手間のかからないお手軽な再現方法で確認してみようと思いますw)


洗濯機の修理が終わって一安心w ということで、次回へつづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4


| | | コメント (0)

2021年9月 3日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

Previously on Mac De Oracle
前回はバッファキャッシュの10%未満のデータブロックへのINSERT文の実行とCOMMITの実行で、遅延ブロッククリーンアウトは発生せず、COMMIT時にすべての対象ブロックがクリーンアウトされるということを確認しました。

今回は、そのデータ量を倍にして、バッファキャッシュの10%程度を超えるデータブロックが遅延ブロッククリーンアウトされるのかを見ていくことにします。手順は前回と同じですが、遅延ブロッククリーンアウトさせた後で、もう一度全表走査してクリーンアウトが繰り返されないことも確認しておきます(次回以降に予定している確認への伏線なのですがw)


0) 対象表をdrop/create
オブジェクトを作り直して前提条件を合わせておきます

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat

4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

INSERTしただけです。未コミットなので特に気になる情報は現れていません。この値からコミット後にどのように変化するのか? という部分に注目する必要があるんですよー。
deferred (CURRENT) block cleanout applications と immediate (CURRENT) block cleanout applications が僅かにありますが、この時点では気にする部分ではないです

(値の変化が1以上ある統計のみ表示)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 22756
sysstat DBWR checkpoints 33
sysstat DBWR thread checkpoint buffers written 22756
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 606
sysstat consistent gets 49761
sysstat db block changes 744980
sysstat deferred (CURRENT) block cleanout applications 4
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 192
sysstat physical reads 18
sysstat physical writes 22756
sysstat physical writes from cache 22756
sysstat physical writes non checkpoint 22756

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 49501
sesstat db block changes 744980
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 65
sesstat physical reads 17


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

この結果、ノイズも少なく、綺麗に取れてます!!! w

前々回の事前確認の通り、2倍のデータブロック数は、 66667ブロック で、バッファキャッシュの10%は、ざっくり計算で、42394ブロック。つまり、想定では 42394ブロック ほどが、commit時のブロッククリーンアウトの対象と想定していました。

覚えてますか? みなさん!

実際にcommit時にクリーンアウトされたのはどれぐらいでしょう? 
結果は、55700ブロックとなりました。想定より多いですねw ほぼ合ってはいますが。
実際にはバッファキャッシュの13%〜15%程度が閾値になっているように見えます。とはいえ、commit時にcleanoutされたブロック数は 55700ブロック ですから、残る 10967ブロック のcleanoutは遅延されたということは確実です。commit対象のデータブロック全てをcleanoutするわけではない、ということは確認できたのではないでしょうか?

(差分が1以上ある統計のみ記載)
(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

SCOTT@orcl> sset autot trace exp stat
SCOTT@orcl> salter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

200000 rows selected.

Elapsed: 00:00:05.14

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

遅延ブロッククリーンアウトは、事前の計算通り、 10967ブロック 発生しています。SELECT文では、immediate (CR) block cleanout applications として現れることも確認できます。
また、cleanouts only - consistent read gets として も同数計上されているところが見てます。綺麗に現れています。

commit cleanouts, ommit cleanouts successfully completed がでていますが、ここでは気にしなくてよいですね、極わずかで。SELECT文なので。
immediate (CURRENT) block cleanout applications、deferred (CURRENT) block cleanout applications もでていますが、同じく極わずかで、対象表のものではないと考えられるためここでは気にしなくて良いですね。

しかし、計算通りに発生してくれると確認が楽w (想定外の動きじゃなくてよかったw)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 272
sysstat DBWR thread checkpoint buffers written 272
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 117000
sysstat db block changes 11207
sysstat deferred (CURRENT) block cleanout applications 10
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 83351
sysstat physical reads 939
sysstat physical writes 272
sysstat physical writes from cache 272
sysstat physical writes non checkpoint 260

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98162
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72113
sesstat physical reads 122


10) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

そして、ここからがおまけの確認ステップです

もう一度、同じ表を全表走査してみます。どうなると思いますか? 遅延されていたブロッククリーンアウトも行われたのですから、当然、該当オブジェクトで遅延ブロッククリーンアウトは発生しない。はず! ですよね。

確認してみましょう。(発生してたらどうしようw、もうしそうなったらバグレポートでも上げようかなw)

.......

Redoは生成されてない! (よかった! 想定どおりだ!w)

SCOTT@orcl> @table_full_scan
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:05.43

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

SCOTT@orcl>

11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)2回目

これもノイズが少なく綺麗に取れました。該当セッションでは物理読み込みも発生していないので、キャッシュから全データを読み込んだようです。

そして、想定どおり、該当セッションでは遅延ブロッククリーンアウトは発生していません!

commit cleanouts、commit cleanouts successfully completed、deferred (CURRENT) block cleanout applicationsが1ブロックありますがCDB側の管理情報関連でしょうね。気にする部分ではないですね。

(差分のあった統計のみ記載)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 80150
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 80075

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 80076
sesstat db block changes 9
sesstat no work - consistent read gets 80043




まとめ

おおよそ、バッファキャッシュの10%程度が commit時にcleanout されるという点については、約15%程度と見ておいたほうが良さそうですが、まあ大きな違いはないので、その辺りに閾値があると考えて問題はなさそうです。
また、それを超えるブロックについては、cleanoutが先送りされ、最初に該当ブロックにアクセスしたSQLがその影響を受ける。

この検証ではSELECT文では、immediate (CR) block cleanout applications という形で統計に現れました。UPDATE文やDELETE文の場合は他の統計として現れそうですね。(CURRENT)関連のcleanoutの統計は今回動いていないのでSQL文を変えて同じような検証をしてみると面白い結果をえられそうです。

そして、SELECT文で、遅延ブロッククリーンアウトされてしまえば、その間に更新が発生しなければ、クリーンアウトは発生しない(おまけで検証した部分ですが、別検証では興味深い動きを紹介する予定です。その伏線でもあります)

次回へつづく


5年目を迎えた、パナソニックのドラム洗濯機がH故障した。慌てて近所のコインランドリーを検索w 近所にあってよかったw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3



| | | コメント (0)

2021年9月 2日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3

Previously on Mac De Oracle
前回は準備を終えたところまででした。

今日は、簡単なところから確認していきましょう。

もしも、「遅延ブロッククリーンアウトが起きない程度のブロック更新量だったなら。。。」。結果は遅延ブロッククリーンアウトは起きないはず。 ですよね。

ざっと手順を紹介しておきましょう。下図の 1)〜9)の順で行います
20210901-221016

前述の手順で、各操作後の統計の差分(変化量)を見る。マニュアルの統計の説明ってざっくり過ぎてよくわからないのが多いわけですがw、操作と値の変化を合わせて観察すると、それなりには理解できる程度に値が変化していることに気づきますw :)

1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.


3) CDBのSYSで統計取得(初回)
初回なのですべてリストしていますが、CON_ID=0のCDBのシステム統計([g]v$sysstat)とCON_ID=3のPDBの2)のCLIENT_INFOを設定されたセッションのセッション統計([g]v$sesstat)を取得します。
今回のケースではcleanoutが含まれている統計の差異だけに着目すれば良いのですが。準備運動程度のテストケースなので一応すべて載せておきます :)
(マルチテナントだと、DBRWの動きを見るにはCDBのDBWR関連の統計を見る必要があるため、CDBのシステム統計とPDBの当該セッションのセッション統計を対象にしています)

SYS$orclcdb> @show_stat
SOURCE NAME VALUE CON_ID
------- ------------------------------------------------------------ -------------------- ----------
sysstat DBWR checkpoint buffers written 0 0
sysstat DBWR checkpoints 0 0
sysstat DBWR fusion writes 0 0
sysstat DBWR lru scans 0 0
sysstat DBWR object drop buffers written 0 0
sysstat DBWR parallel query checkpoint buffers written 0 0
sysstat DBWR revisited being-written buffer 0 0
sysstat DBWR tablespace checkpoint buffers written 0 0
sysstat DBWR thread checkpoint buffers written 0 0
sysstat DBWR transaction table writes 0 0
sysstat DBWR undo block writes 0 0
sysstat cleanouts and rollbacks - consistent read gets 6 0
sysstat cleanouts only - consistent read gets 0 0
sysstat commit cleanout failures: block lost 0 0
sysstat commit cleanout failures: buffer being written 0 0
sysstat commit cleanout failures: callback failure 0 0
sysstat commit cleanout failures: cannot pin 0 0
sysstat commit cleanout failures: hot backup in progress 0 0
sysstat commit cleanout failures: write disabled 0 0
sysstat commit cleanouts 570 0
sysstat commit cleanouts successfully completed 570 0
sysstat consistent gets 158,083 0
sysstat db block changes 3,247 0
sysstat deferred (CURRENT) block cleanout applications 316 0
sysstat immediate (CR) block cleanout applications 6 0
sysstat immediate (CURRENT) block cleanout applications 15 0
sysstat no work - consistent read gets 108,516 0
sysstat physical reads 12,551 0
sysstat physical reads direct 0 0
sysstat physical writes 0 0
sysstat physical writes direct 0 0
sysstat physical writes from cache 0 0
sysstat physical writes non checkpoint 0 0
sysstat transaction tables consistent read rollbacks 0 0
sysstat transaction tables consistent reads - undo records applied 0 0
sesstat DBWR checkpoint buffers written 0 3
sesstat DBWR checkpoints 0 3
sesstat DBWR fusion writes 0 3
sesstat DBWR lru scans 0 3
sesstat DBWR object drop buffers written 0 3
sesstat DBWR parallel query checkpoint buffers written 0 3
sesstat DBWR revisited being-written buffer 0 3
sesstat DBWR tablespace checkpoint buffers written 0 3
sesstat DBWR thread checkpoint buffers written 0 3
sesstat DBWR transaction table writes 0 3
sesstat DBWR undo block writes 0 3
sesstat cleanouts and rollbacks - consistent read gets 0 3
sesstat cleanouts only - consistent read gets 0 3
sesstat commit cleanout failures: block lost 0 3
sesstat commit cleanout failures: buffer being written 0 3
sesstat commit cleanout failures: callback failure 0 3
sesstat commit cleanout failures: cannot pin 0 3
sesstat commit cleanout failures: hot backup in progress 0 3
sesstat commit cleanout failures: write disabled 0 3
sesstat commit cleanouts 1 3
sesstat commit cleanouts successfully completed 1 3
sesstat consistent gets 374 3
sesstat db block changes 4 3
sesstat deferred (CURRENT) block cleanout applications 1 3
sesstat immediate (CR) block cleanout applications 0 3
sesstat immediate (CURRENT) block cleanout applications 0 3
sesstat no work - consistent read gets 220 3
sesstat physical reads 28 3
sesstat physical reads direct 0 3
sesstat physical writes 0 3
sesstat physical writes direct 0 3
sesstat physical writes from cache 0 3
sesstat physical writes non checkpoint 0 3
sesstat transaction tables consistent read rollbacks 0 3
sesstat transaction tables consistent reads - undo records applied 0 3


4) PDBのSCOTTユーザーでデータINSERT(コミットなし)
データを1行単位でインサートしています。バルクインサートも使ってないです。綺麗なぐるぐる系ですねw。コミット時の効果を確認しやすいようにコミットは後で実行します!
このインサートで、前回の事前準備の時に確認しておいた、33334ブロックが更新されることになります

SCOTT@orcl> @insert_each_rows
1* begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.


5) CDBのSYSで統計取得(INSERT後、未コミット)

注) 3)の統計との差分のみ記載

未コミットであるこの時点で、commitクリーンアウトが発生(該当セッション統計でも同数発生。commit cleanoutsとcommit cleanouts successfully completed)してますが、これは気にしなくてもよいですね。
実行したトランザクションは、未コミットなので、この実行による直接的な影響ではないので。
また、deferred (CURRENT) block cleanout applicationsやimmediate (CURRENT) block cleanout applications の遅延ブロッククリーンアウトを示す統計も微量ですがこれも同様とみて良いでしょう。

この時点で統計取得理由は、操作毎に変化する統計を追うためなので、ふーーん。ぐらいでの雰囲気でOKです :)
コミット後とその後に該当オブジェクトをアクセスさせた時の遅延ブロッククリーンアウトの有無部分の部分が主役ですので。

参考)
INSERTした行の含まれる全ブロックはバッファキャッシュに載り切るブロック数なので、バッファキャッシュから書き出されているような動きも観測されていないのは確認できると思います。
(physical writes from cache,physical writes non checkpoint,physical writesの統計に変化がないので未記載ですが、それらの統計が動いていないということがその理由です)


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 22884
sysstat db block changes 373785
sysstat deferred (CURRENT) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 59
sysstat physical reads 12


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 22816
sesstat db block changes 373785
sesstat deferred (CURRENT) block cleanout applications 5
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 12


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

注)5)と7)で取得した統計の差分のみ記載

ここが主役ですよー。このケースではバッファキャッシュの10%に満たないブロック数になるようにしたINSERT文(繰り返し実行)で 33334ブロックになるようにしました。これらを1度でcommitした場合、すべてのブロックがcommit時にblock cleanoutされるはずです。

では見てみましょう。

SCOTTのセッション統計より、commit cleanouts および commit cleanouts successfully completed から想定どおり全ブロックがcommit時にcleanoutされていることがわかります!
システム統計はインスタンス全体なのでPDBのそれら統計より大きめにでているのも確認できます。CDB側ではほんの少し deferred (CURRENT) block cleanout applications がありますが、管理情報系の遅延ブロッククリーンアウトでしょうね。 
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1で紹介したとおり 10% 未満では commit 時点で該当ブロックすべてcleanoutされることが確認できました。


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 139
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 55
sysstat commit cleanouts 33343
sysstat commit cleanouts successfully completed 33343
sysstat consistent gets 18111
sysstat db block changes 138
sysstat deferred (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 10853
sysstat physical reads 827
sysstat physical writes 139
sysstat physical writes from cache 139
sysstat physical writes non checkpoint 139


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 33334
sesstat commit cleanouts successfully completed 33334
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

_serial_direct_read = never はdirect path readさせないためのおまじないです。direct path readで読み込まれたケースの動きは別エントリーで見ていく予定なので、direct path readを発生させずtable full scan (ようするにバッファキャッシュに載せる動き)で読み込むよう強制しています。
また、行数が多いのでその時間の短縮のためにautot trace exp statを有効にしてSELECT文を実行させつつ、termout offと同じ効果とauto explainの機能でSELECT文の実行統計からredoの生成有無を確認しています。(redoがあるということはなんらかの更新が行われているわけで、SELECT文の場合は比較的容易に遅延ブロッククリーンアウトの発生を推測できる統計にもなります)
以下のケースでは多少redoが生成されていますが、おそらく recursive callによるもので、HOGE表のオブジェクトそのものに対するもではなさそうです。(このケースではhoge表のオブジェクトに対する遅延ブロッククリーンアウトや
コミット時のブロッククリーンアウトの観察が主題なので、周りのノイズはあまり気にしなくてもOK(追いかけたい場合は別ですがw)


SCOTT@orcl> set autot trace exp stat
SCOTT@orcl> alter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

100000 rows selected.

Elapsed: 00:00:02.59

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 57526 | 55M| 9098 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 57526 | 55M| 9098 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
21 recursive calls
13 db block gets
40465 consistent gets
3 physical reads
2212 redo size
203382760 bytes sent via SQL*Net to client
73706 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orcl> set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

注)7)のコミット時点からとの差異のみ記載。

多少ですが、SELECT文でredoが生成されてはいます。これは、commit cleanouts/commit cleanouts successfully completed (commit時に実施されるblock cleanout)と遅延ブロッククリーンアウトの統計の一つ、deferred (CURRENT) block cleanout applications が現れている影響ですね。コミット時点でHOGE表の全ブロックはcleanout済みなので recursive callによる内部管理情報関連で定常的に現れるものと考えられ、この検証では気にするところではないのでスルーしてください。

また、physical reads は、ほぼないため、この全表走査では、物理読み込み(direct path read含む)は、発生していないことも確認できます。
HOGE表はバッファキャッシュに載ったままという意図通りの状態にはなっているようです。
これ、今後のテストケースでも利用するので、キャッシュからエージアウトされないという点が確認できてると、以降の検証やりやすいんです:)


(CDB) システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 34849
sysstat DBWR transaction table writes 32
sysstat DBWR undo block writes 897
sysstat commit cleanouts 558
sysstat commit cleanouts successfully completed 558
sysstat consistent gets 69973
sysstat db block changes 2862
sysstat deferred (CURRENT) block cleanout applications 311
sysstat immediate (CURRENT) block cleanout applications 28
sysstat no work - consistent read gets 57908
sysstat physical reads 812
sysstat physical writes 34849
sysstat physical writes from cache 34849
sysstat physical writes non checkpoint 34738


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 5
sesstat consistent gets 47037
sesstat db block changes 38
sesstat deferred (CURRENT) block cleanout applications 4
sesstat no work - consistent read gets 43103
sesstat physical reads 117


まとめ

DMLにより更新されたブロック数が、バッファキャッシュの10%未満のブロック数である場合、commit時にすべてcleanoutされ、対象表では遅延ブロッククリーンアウトは発生しない。
(想定通りなのですが、ちょいと安心w 19cでどう動くか確認してなかってしので少々ドキドキしてたw のはナイショ)

というわけで、今回はここまで。

いきなり涼しくなって、なんだこりゃ。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2



| | | コメント (0)

2021年8月31日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2

Previously on Mac De Oracle

前回は、遅延ブロッククリーンアウトに限らず、大きく変わったわけでも、最近実装された機能でもないのに意外に知られてないのか、良いところ悪いところ含め、現場でロストしてしまっているような知識って意外と多いのかもねー。なんて感じたので遅延ブロッククリーンアウトネタのURLリンクをまとめてみた。

続編書くにしても、同じようなことやっても面白くないので、ブロックダンプのような方法は使わず、[g]v$sysstatや[g]v$sesstatなどの統計から、ちょいと血糖値や尿酸値高めだよね的な角度からどのような変化が起きるか見ていくことにした :)

 

環境はVirtualBox上の19cでこれからの主流になるマルチテナントで試してみます。(非マルチテナントでの変化見ててもこれからはあまり役に立たないので)

今日は準備編

VirtualBox
https://www.virtualbox.org/

Pre-Built Developer VMs (for Oracle VM VirtualBox)のDatabase App Development VMとか
https://www.oracle.com/downloads/developer-vm/community-downloads.html

 

 

事前に準備しておくスクリプトは以下のとおり。繰り返し実行するので作っておくと便利ですよ。:)

まず最初に、遅延ブロッククリーンアウトはバッファキャッシュの10%ほどのブロックをコミット時にクリーンアウトして、残りを先送りするという基本的なお約束があるので、上記環境のOracle 19cがどの程度のバッファキャッシュなのかとブロックサイズを確認。これ大切ですよ。スクリプト準備する上でも :)

メモリサイズは大きめですが。。。w うちのは。(^^;;;


$ VBoxManage -v
6.1.26r145957
$
$ VBoxManage showvminfo 'Oracle DB Developer VM 19.3' | grep -E 'Memory|CPUs'
Memory size: 16384MB
Number of CPUs: 4
$

コミット時にブロッククリーンアウトされそうなブロック数をざっくり算出すると 42394 ブロックぐらいになりそう。


SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb>
SYS@orclcdb> show parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192
SYS@orclcdb>
SYS@orclcdb> select ceil( 3472883712 / 8192 * 0.1 ) from dual;

CEIL(3472883712/8192*0.1)
-------------------------
42394

Elapsed: 00:00:00.00

42394ブロックを超える程度のサイズのデータを生成するINSERT文と、その範囲に収まるデータ量生成INSERT文スクリプトを作れば良さそうですね。

想定されるブロックに収まる程度の量。1ブロック 8KB でデフォルトのPCTFREEは10%なのでざっくり6000bytes/rec超えるぐらい。
で、1ブロックに3行ぐらい入るようにすれば面白いかな。。

ということで、

表はなんどもdrop/createするので以下のDDLで。初回は索引を作らず、表のみので影響をみることにする。


$ cat droppurge_create_hoge.sql

drop table hoge purge;
create table hoge (id number, data varchar2(2000));

データ作成(バッファキャッシュの10%未満のデータ登録)
なお、確実に遅延ブロッククリーンアウトの影響を見たいので1行ごとのINSERTを繰り返し、コミット前後の状態の変化も見たいのでcommitも含めていない。(commitは別途実行する)


$ cat insert_rows.sql

begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
33334

データ作成(バッファキャッシュの10%を超えるのデータ登録)
単純にループ回数を倍にして増量。これで事前に算出したバッファキャッシュの10%以上のブロック数は更新される。。。はず。


$ cat insert_rows_2.sql

begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認(20,000ブロックぐらい?は遅延される想定)


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ここまでが遅延ブロッククリーンアウトを意図的に起こすためのデータ作成SQLスクリプト

以降は、遅延ブロッククリーンアウトの発生等を見るための[g]v$sysstatと[g]v$sestatを取得するスクリプトと、[g]v$sesstatから特定のセッションを取得するためのクライアント情報をセットするスクリプト。


$ cat set_client_info.sql
BEGIN
DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
END;
.
l
/

システム統計とセッション統計を取得して差分を見ていく必要があるので各統計のスナップショット取得用スクリプトが必要なわけですが、今回はマルチテナント環境。なので、システム統計はCDB全体から、セッション統計は該当するPDBかつ、前述のスクリプトでClient Infoが設定されているセッションに限定するスクリプトを作る必要があるんですよね。DBWRの動きも含めてみたいときって。少し多めに統計名を取得していますが、実際に重要なのはcleanout系の統計ですね。いくつかのテストケースを実施する上で合わせてみておきたい統計も事前に入れてあります:)


$ cat show_stat.sql
set linesize 400
set tab off
set pagesize 1000
col name for a60
col value for 999,999,999,999,999
SELECT
'sysstat' AS "SOURCE"
, name
, value
, con_id
FROM
gv$sysstat
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
UNION ALL
SELECT
'sesstat' AS "SOURCE"
, name
, value
, vsesstat.con_id
FROM
gv$sesstat vsesstat
inner join gv$statname vstatnam
on
vsesstat.statistic# = vstatnam.statistic#
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
and sid = (
select
sid
from
gv$session
where
username = upper('&1')
and client_info = 'TargetSession'
)
order by
4, 1, 2
;

undefine 1

ちなみに、統計を使って状況を確認する方法って意外に利用されているんですよね。日本だとあまり活用されてないようにも感じることは多いのですが、日々の統計を追っかけてると、どのメトリックが高く跳ね上がるのか把握できるので知ってて損することはないと思います:)

そういえば、Tanel PoderのSnapperもその手のツールではありますね。
Session Snapper
http://tech.e2sn.com/oracle-scripts-and-tools/session-snapper

私の過去のセッションでもElappsed Timeを見せないでチューニング効果を見てもらうネタとしてシステム統計を使ってたりします。
db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニング
https://www.slideshare.net/discus_hamburg/db-tech-showcase-tokyo-2013-a35-sql

少々脱線しますが、
最近、VirtualBox、なつかしー。なんて言う方もいますが、古いバージョンのOracleを残しておけるので、リリース毎の動きの差などを見たい場合は便利なのですよーw(クラウドだと強制アップグレードされちゃうので旧バージョンとの動作比較をネタにしたいときなどには向いてないw)

次回へつづく


東京では救急車のサイレンがまだまだ通常より多く聞こえます。。。。

Stay home, Stay Safe and Stay Hydrated.


古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1


 

| | | コメント (0)

2021年8月23日 (月)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1

昔からOracle Database触ってると結構当たり前だったことではあるけど、最近、主にExadata以降にOraclerになったエンジニアへ伝承というかノウハウの一つとして継承されていないのかなー。
と感じることが何度かあり、属人化とは少々違う、失われた知識みたいなのもあるのかではないかと。。。そんなアトモスフィアを感じつつ、遅延ブロッククリーンアウトについて軽く書こうかなと。

と、今思ってるだけで、めんどくさくなって続かないかもしれませんw 

 

昔からある有名な遅延ブロッククリーンアウトの情報はMOSにもありますが、読んだことある方はどれぐらいいるのだろう。かなーーーり昔は日本独自?のMetalinkとかKROWNで細かく書かれていた(ような)記憶はあるが、最近はそこまで書かれてないような。。。まあいいかw

遅延ロギング・ブロック・クリーンアウトについて(KROWN:48106) (Doc ID 1716869.1)

こまけーことは置いといて、MOSとか、他のブログを読むと動きはざっくり理解できると思うのですが、

ポイントは、Buffer Cache サイズのおおよそ10%ぐらいまでがcommit時にクリーンアウトされる最大サイズ、それ以外は先送りされるというところ。
commit時にクリーンアウトされないので、後続のSQL文で該当するブロックがアクセスされたタイミングでタイミングでクリーンアウトされる。通常は更新を伴わないSELECT文でも発生する。そこで気づく方は多いわけです。はい。

SELECT文なのになんでREDOが生成されているんだろう???? と。

例えば、Buffer Cacheが 10ブロック分あったとして、UPDATE文で5ブロック更新してcommitした場合、10ブロックの10%の1ブロックだけがcommit時にブロッククリーンアウトされ、残りの 4ブロックのクリーンアウトは遅延される。ざっくり言えばそんな感じ。
DMLでINSERT/DELETE/UPDATEでどのどの程度のブロックが更新されるかで、遅延されるブロック数はざっくり判断できちゃいます。
ちなみに、どのタイミングでどの程度、クリーンアウトされたかという統計情報はあるが、トランザクションがcommitされたタイミングでどれぐらいクリーンアウトが遅延されたブロックが存在するかを示す統計はないので、どの程度の更新ブロックがあるかと、commit時にクリーンアウトできるブロック数の差分からざっくり判断するのがリーズナブル(他の流派の方もいるかもしれないがw)

OLTPのようなショートトランザクションでは、あまりクリーンアウトが先送りされることはないわけですが、ロングトランザクションでコミット1回というタイプだと、commit時にはブロックをクリーンアウトしきれない程度のブロックが更新され、クリーンアウトが遅延されるという状況は仕様どおりなわけです。。。。ロングトランザクションで発生しやすいということにはなりますね。(だったら、ぐるぐる系のバッチ更新処理って最高じゃね?、。。。それはちょっと。。。w 向かう方向が違うかなw)

話は少し代わりますが、前述のようにcommtのタイミングではBuffer Cacheの10%程度までなので、Buffer Cache を大きくすれば、commit時にクリーンアウトされる最大サイズも大きくなる。。。というのは簡単にイメージできると思います。。。

そして、遅延クリーンアウトブロックが発生しないケースがあるのご存知ですか?(知ってて損はないですよ。。)

 



以下のサイトで有益な情報を提供しているので一度は見ておくと良いと思います。

最近のOraclerは知らないかもしれないけど、AskTomでもTomが、遅延グロッククリーンアウトについて答えてます。 


このQAでのポイントは回答にある以下の部分、ここでも10 percent of our buffer cacheと言及されていますよね。これ2005のQAです。この頃まだ小学生だった方達が今Oraclerとしてデビューしてたりするわけですよねw 

”If we were to re-run the above example with the buffer cache set to hold at least 5,000 blocks, we'll find that we generate little to no redo on any of the SELECTs - we will not have to clean dirty blocks during either of our SELECT statements. This is because the 500 blocks we modified fit comfortably into 10 percent of our buffer cache, and we are the only user. There is no one else is mucking around with the data, and no one else is causing our data to be flushed to disk or accessing those blocks. In a live system, it will be normal for at least some of the blocks to not be cleaned out sometimes.”

Does select generate undo ? / AskTom / 2005

これは比較的新しいQAですね
delayed block cleanout / AskTom / 2017

レジェンド、ジョナサンルイスのブログでも
Clean it up / Jonathan Lewis / 2009

MaxGuage for Oracleで有名なエクセムさんのブログ

この記事で引用されているMetalinkは、現在のMOSには存在しないのでMOSのアカウントがあるかたが頑張って検索してもヒットしないのでご注意を。(日付が記載されていないので何年の記事かわからないですが、記事自体かなり古いはずなのでネタ元も古いと思ってください)
遅延ブロッククリーンアウト / エクセムさんのブログ

このブログでも10%ルールの話は出てきています。
Delayed Block Cleanout in Oracle / databasejournal By David Fitzjarrell / 2015


比喩がわかりにくいかもしれないけど、有名な Shit the Oracle さんのブログ (このエントリーも日付がないけど、適当なタイミングでUpdateされているような、気がしないでもない)
遅延ブロッククリーンアウト / SHIFT-the-Oracle


以下、システムサポートさんのブログ、わかりやすいですよね。冒頭で少し紹介した遅延クリーンアウトブロックが発生しないケースにも軽くふれてますね。direct path writeしているケースに注目ですよ!
フルスキャンで何故シングルブロックリードが発生するのか?(2/2)


NTTDのOracle ACE 高橋さんもExadataでの遅延ブロッククリーンアウトの洗礼を受けた模様、比較的最近なのですね。 :)
exadataと遅延ブロッククリーンアウトとシングルブロックリード / オラクルデータベースの技術メモ

ブロックダンプしながら確認するなんて、マニアックですよね。w このかた。前述のシステムサポートさんの記事で紹介されているdirect path writeのケースも検証されています。素敵です。
遅延ブロッククリーンアウトを観測する① / SQL*Plusの使いにくさは異常


ということで、私が書かなくても、これだけの情報があるので、書こうか書かないか、迷うーーーーっw。
紹介したエントリーの内容を確認するための方法こんな方法でも確認できるよー的な内容にしましょうかね。。。

では、Stay Home and Stay Safe.

次回へつづく。

| | | コメント (0)

2021年2月18日 (木)

MicrosoftのEdgeがChromium版Edgeでたので久々にブラウザベンチマーク

MicrosoftのEdgeがChromium版Edgeになって、なんとMac版もでたので久々に、古い Mac Pro mid2012 Mojaveでベンチマークとってみた

20210218-210959

対処対象ブラウザは、Safari / Firefox / Google Chrome / Microsoft Edge (Chromium版Edge) / Vivaldi / Opera を JetStream2の総合値で比較

昔のBrowser Benchmarkのエントリー(古い順)
IEって遅いんだよー。ってことをあまり気にしてない方へ。 / 2010年

まだ、レガシーブラウザと戦うのですか... / 2013年

あれから1年.....あの恐怖が再びw / 2014年

Edgeも出たし、JetStreamも出たので久々にブラウザのベンチマーク / 2015年

JetStream2の結果(2021版は以下のとおり) Safariが少々良い結果を出していますが、Firefoxを除き、あまり差はなくなってますね。Firefox頑張れ?(WindowsでIE使いたいって方も少数派だと思うので今回はWindowsでの比較は略。気が向いたらやるかもしれませんがWindows BootCampで起動するMacBookしかないんだよなーw)

Safari Safari_20210218205801
Safariresults

 

Firefox
20210218-133230
20210218-133224

 

Google Chrome

Chrome


Chromeresults

 

Microsoft Edge (Chromium版Edge)

Chromemsedge


Chromemsedgeresults

 

Vivaldi

Vivaldi


Vivaldiresults

 

Opera

Operaresults


Opera

 

気軽に食べ歩きにいける世の中になってほしい。。。

| | | コメント (0)

2019年12月 2日 (月)

実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2

実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 Day 2のエントリーです.

まずは、Day 1の答え



以下の実行計画 (SQL*Plusのauto trace機能を利用) は、TABLE ACCESS FULL ではありますが、Execution PlanセクションとStatisticsセクションしかありません。
また、SQL文にWHERE句がある場合、実行計画の補足情報として、Predicate Information (identified by operation id) セクションがリストされます。
このセクションでは述語、つまり、WHERE句に関わる情報がリストされます。
Predicate Information (identified by operation id) セクションが一切リストされていないこのケースでは、WHERE句自体がSQL文中に存在しないことも読み取ることができます。

ということで、以下の3パターンの可能性はなくなりました。理由はすでにお分かりですよね?

2) select * from tab3 where id + 1 = 10;
3) select * from tab3 where id between 1 and 400000;
4) select /*+ FULL(tab3) */ * from tab3 where id between 1 and 10;

結果として、単純な全表走査を行うSQL文である 1) が正解ということになります。

治療の必要は基本的にありませんが、全表走査しているだけで性能要件を満たせない場合には、全表走査を早くするための治療が必要になる場合があります.
必要があって全表走査しているのであれば全表走査は悪ではありません。
(性能要件は事前に問診等で確認しておくことをおすすめします)

1) select * from tab3;

20191130-192926_20191201010101



では、本題であるDay 2のレントゲン写真は、以下!

これには、先ほど解説したばかりのPredicate Information (identified by operation id) がリストされ、2 - access("UNIQUE_ID"=1) という部分から unique_id列でアクセスしていることが読み取れます。

20191201-05116

Id=2のoperationでは、INDEX UNIQUE SCANが行われています。INDEX UNIQUE SCANしている対象オブジェクトは、TAB3_PK です。 INDEX という部分から TAB3_PK は索引であることも読み取れます。
かつ、UNIQUE SCAN ということなので、索引は一意索引または、主キー索引で、一意に値を特定できる索引であることも合わせて読み取れます。

この実行計画という名のレントゲン写真から、どのようなSQL文をイメージしますか? また、どのような特徴をもっていると思いますか?

つづきは、Day 3にて。:)



寒いのも、寒い場所も嫌いですw

| | | コメント (0)

2019年3月21日 (木)

Join Elimination(結合の排除)と 参照整合性制約 / FAQ

偶に聞かれることがあるので、再び、Join Elimination(結合の排除)について
まずは、以下のSQL文を。
order表とcustomers表をinner joinしている単純な文ですが、重要なのは、実行計画の方!


order表とcustomers表をinner joinしているのに、order表だけ(この場合、order表の主キー索引だけのIndex Only Scanになっていますが)で、customes表を結合していせん。

理由は単純で、以下のSQL文では、customsers表の結合が不要なだけなんです。なぜかわかりますか?
以前、浅瀬でジャブジャブしていたセッション資料にヒントがあります。
order表に定義されている参照整合性制約によりcustomer_idがcustomsers表に存在していることを確認するための結合は不要と、オプティマイザーが判断した結果なんですよね。これ。
上記以外のケースでも無駄な結合を排除しようとする最適化を行うことがあります。内部的にはSQL文を書き換えてくれているわけですね。無駄に結合を行わないために。。。10053トレースをとって、 Join Elimination で grep をかけてみるとオプティマイザの気持ちが見えてきます:)
ORCL@OE> explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 , customers c
8 where
9 o.customer_id = c.customer_id
10 and order_id < 2400;

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ORDER_ID"<2400)
上記、SQL文は、参照整合性制約により、orders表に存在するcustomer_idがcustomers表に存在することが保証されているため、結合により存在確認が不要となり、Optimizerは内部的にSQL文を以下のように書き換えたということになります。賢いですよね。
ORCL@OE> r
1 explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 where
8* order_id < 2400

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ORDER_ID"<2400)

13 rows selected.


order表の参照整合性制約を確認しておきます。

ORCL@OE> r
1 select
2 table_name
3 ,owner
4 ,constraint_name
5 ,constraint_type
6 ,r_owner
7 ,r_constraint_name
8 ,status
9 ,rely
10 from
11 user_constraints
12 where
13* constraint_type='R'

TABLE_NAME OWNER CONSTRAINT_NAME C R_OWNER R_CONSTRAINT_NAME STATUS RELY
------------------------------ ------------------------------ ------------------------------ - ------------------------------ ------------------------------ -------- ----
ORDERS OE ORDERS_CUSTOMER_ID_FK R OE CUSTOMERS_PK ENABLED
INVENTORIES OE INVENTORIES_WAREHOUSES_FK R OE WAREHOUSES_PK ENABLED
INVENTORIES OE INVENTORIES_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_ORDER_ID_FK R OE ORDER_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
PRODUCT_DESCRIPTIONS OE PD_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED

ORCL@OE> r
1 select
2 table_name
3 ,column_name
4 ,constraint_name
5 from
6 user_cons_columns
7 where
8 table_name in ('ORDERS','CUSTOMERS')
9 order by
10* table_name

TABLE_NAME COLUMN_NAME CONSTRAINT_NAME
------------------------------ ------------------------------ ------------------------------
CUSTOMERS CUSTOMER_ID CUSTOMERS_PK
CUSTOMERS CUST_FIRST_NAME CUST_FNAME_NN
CUSTOMERS CUSTOMER_ID CUSTOMER_ID_MIN
CUSTOMERS CREDIT_LIMIT CUSTOMER_CREDIT_LIMIT_MAX
CUSTOMERS CUST_LAST_NAME CUST_LNAME_NN
ORDERS ORDER_ID ORDER_PK
ORDERS ORDER_TOTAL ORDER_TOTAL_MIN
ORDERS ORDER_MODE ORDER_MODE_LOV
ORDERS CUSTOMER_ID ORDER_CUSTOMER_ID_NN
ORDERS CUSTOMER_ID ORDERS_CUSTOMER_ID_FK
ORDERS ORDER_DATE ORDER_DATE_NN




Oracle SQL DeveloperでリバースエンジニアリングしたERDは以下のとおり

20190321-144842

では、最後に、参照整合性制約を無効化した場合、実行計画はどうなるか見ておきましょう。
ORCL@OE> alter table orders disable constraint orders_customer_id_fk;

Table altered.

ORCL@OE> r
1 select
2 table_name
3 ,owner
4 ,constraint_name
5 ,constraint_type
6 ,r_owner
7 ,r_constraint_name
8 ,status
9 ,rely
10 from
11 user_constraints
12 where
13* constraint_type='R'

TABLE_NAME OWNER CONSTRAINT_NAME C R_OWNER R_CONSTRAINT_NAME STATUS RELY
------------------------------ ------------------------------ ------------------------------ - ------------------------------ ------------------------------ -------- ----
ORDERS OE ORDERS_CUSTOMER_ID_FK R OE CUSTOMERS_PK DISABLED
INVENTORIES OE INVENTORIES_WAREHOUSES_FK R OE WAREHOUSES_PK ENABLED
INVENTORIES OE INVENTORIES_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_ORDER_ID_FK R OE ORDER_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
PRODUCT_DESCRIPTIONS OE PD_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED

あらまあ、不思議w わざとらしいw
customers表げ結合されちゃってネステッドループ結合に!
ORCL@OE> r
1 explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 , customers c
8 where
9 o.customer_id = c.customer_id
10* and order_id < 2400

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------
Plan hash value: 2552081916

----------------------------------------------------------------------------------------------
| 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 | ORDER_PK | 46 | | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | CUSTOMERS_PK | 319 | 1276 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - filter("O"."CUSTOMER_ID">0)
4 - access("ORDER_ID"<2400)
5 - access("O"."CUSTOMER_ID"="C"."CUSTOMER_ID")

目黒方面の密林で、美登利の寿司弁当を食べるのが最近のマイブームw
ではまた。

| | | コメント (0)

2018年11月 4日 (日)

RDS Oracle 雑多なメモ#16 - 再び:) / FAQ

再び忘れがちなので、備忘録。

RDS Oracleでマスターユーザー以外で、SQL*PLusの Auto trace そして、DBMS_XPLAN.DISPLAY や DBMS_XPLAN.DISPLAY_CURSOR を使おうとすると以下のようなエラーに遭遇! 
なにも準備してないと。(explain plan for文だけは準備していなくても可能なのでが)


TEST> set autot trace exp stat
SP2-0618: Cannot find the Session Identifier. Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

とか

...略...
TEST> select * from table(dbms_xplan.display_cursor(format=>'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
User has no SELECT privilege on V$SESSION

なんてことに、

RDS Oracle、マスターユーザーでは可能なのですが、PLUSTRACEロールも作成されていない、かつ、 plustrce.sql がない.

AWSUSER> select role from dba_roles where role = 'PLUSTRACE';

no rows selected


ということで、いちいち調べるのも面倒なFAQとなっているので、備忘録として書いておきました。

マスターユーザー以外のユーザーに alter session システム権限を付与しておきます。
該当システム権限が付与されていないと、セッションレベルで statistics_level パラメータを変更できません。
このパラメータは、dbms_xplan.display_cursor でactual planを取得する際に必要になるのですが、最悪付与されていない場合には、SQL文に以下のヒントを追加することで代替可能ではありますが、いちいちSQL文に以下のヒントを追加しなければならないので面倒。
とはいえ、alter sessionは付与したくないということもなくはなく、そんな時は以下のヒントで頑張っください。

ex.

SELECT
/*+ gather_plan_statistics */
*
FROM
hoge
WHERE
id = 1;


次に必要なのは、 v$sessionなどを含むいくつかのパフォーマンスビューへのSELECTオブジェクト権限、グローバルな一時表として定義されているplan_tableへの全オブジェクト権限です。
管理面を考えてロールを作成し関連権限をロールに付与、作成したロールを対象ユーザーに付与するようにすると便利です。


alter sessionsシステム権限を含む最低限必要なオブジェクト権限とそれを付与するロール作成スクリプトの例は以下の通り。

foobar$ cat create_dev_role.sql

-- create developer role
create role dev_role;

-- for show parameter
grant alter session to dev_role;
-- for dbms_xplan.display_cursor and auto trace
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SESSION', 'DEV_ROLE', 'SELECT');

-- for dbms_xplan.display_cursor
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL_PLAN_STATISTICS_ALL', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$PARAMETER', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL_PLAN', 'DEV_ROLE', 'SELECT');

-- for auto trace
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$STATNAME', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$MYSTAT', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SESSTAT', 'DEV_ROLE', 'SELECT');

-- for auto trace (plan_table - temporary table)
exec rdsadmin.rdsadmin_util.grant_sys_object('PLAN_TABLE$', 'DEV_ROLE', 'ALL');

参考
Oracle DB インスタンスの一般的な DBA タスク


ということで、動作確認を兼ねたサンプルは以下のとおり。

まずは、RDS Oracleのマスターユーザーで.
マスターユーザー以外のユーザーの作成と権限とロールの付与(チューニングが必要とされる開発者等)を想定

foobar$ sqlplus awsuser/xxxxxxx@xxxx.xxxxxxxx.rds.amazonaws.com:1521/HOGE

SQL*Plus: Release 12.1.0.2.0 Production on Fri Nov 2 22:05:14 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

Last Successful login time: Fri Nov 02 2018 22:01:24 +09:00

...中略...

AWSUSER> l
1 create user test identified by xxxxxx
2 default tablespace users
3 temporary tablespace temp
4* quota unlimited on users
AWSUSER> /

User created.

AWSUSER> grant connect, resource to test;

Grant succeeded.

AWSUSER> @create_dev_role

Role created.

Grant succeeded.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

AWSUSER> grant dev_role to test;

Grant succeeded.

AWSUSER> exit


作成したユーザーで接続して、各方法で実行計画を取得できるか確認!
クエリーを実行するため適当な表を作成してデータを登録しておく。

foobar$ sqlplus test/xxxxxxx@xxxx.xxxxxxxx.rds.amazonaws.com:1521/HOGE

SQL*Plus: Release 12.1.0.2.0 Production on Fri Nov 2 22:15:23 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

...中略...
TEST>
TEST> l
1 create table hoge (
2 id number not null primary key
3 ,foobar varchar2(20)
4* ) nologging
TEST> /

Table created.

TEST>
TEST> l
1 begin
2 for i in 1..10000 loop
3 insert into
4 hoge (
5 id
6 ,foobar
7 )
8 values (
9 i
10 ,to_char(i)
11 );
12 end loop;
13 commit;
14* end;
TEST> /

PL/SQL procedure successfully completed.


SQL*Plusのauto traceが行えるか確認!

TEST> set autot trace exp stat
TEST> select * from hoge where id = 1;

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 2757398040

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 25 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
342 bytes sent via SQL*Net to client
488 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

参考
文のトレースについて


explain plan for文は準備なしで問題ないのですが、念のための確認! DBMS_XPLAN.DISPLAYプロシージャを利用して実行計画を取得

TEST> 
TEST> explain plan for
2 select * from hoge where id = 1;

explained.

TEST> @show_explain

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
Plan hash value: 2757398040

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 25 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

14 rows selected.

Elapsed: 00:00:00.05

スクリプト例は以下の通り。$ORACLE_HOME/rdbms/admin/utlxpls.sql や、utlxplp.sqlが利用できれば楽なんでが、それら中身は、DBMS_XPLAN.DISPLAYなので大差ない内容。

$ cat show_explain.sql

set linesize 200
set long 100000
set longchunk 200
set tab off

SELECT
*
FROM
TABLE(
DBMS_XPLAN.DISPLAY(
format => 'ALL -PROJECTION -ALIAS'
)
)
;


Actual planをDBMS_XPLAN.DISPLAY_CURSORプロシージャで取得〜

TEST> 
TEST> @show_realplan

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
statistics_level string TYPICAL

Session altered.

*** SQL that you want to get an actual plan ***
1* select * from hoge where id = 1
***********************************************

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID 6f67zkz43kr76, child number 1
-------------------------------------
select * from hoge where id = 1

Plan hash value: 2757398040

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

19 rows selected.

Elapsed: 00:00:00.11

Session altered.


NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
statistics_level string TYPICAL

Actual planを取得するスクリプト例
SQL*Plusのコマンドを駆使してはいますが、ポイントはset statistics_level = allにすることと、DBMS_XPLAN.DISPLAY_CURSORをコールする際のformatパラメータです
set termout off/onでSQL文の結果を表示しないようにしています。この設定はSQLファイルからSQL文を実行した場合にだけ有効です。(ちょっとしたTips :)

$ cat show_realplan.sql

show parameter statistics_level
alter session set statistics_level = all;

set linesize 200
set long 100000
set longchunk 200
set tab off

PROMPT *** SQL that you want to get an actual plan ***

select * from hoge where id = 1
.
l

PROMPT ***********************************************

set termout off
r
set termout on


-- get the actual plan
set timi on
SELECT
*
FROM
TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(
format => 'ALLSTATS LAST'
)
)
;
set timi off
alter session set statistics_level = typical;
show parameter statistics_level

参考
DBMS_XPLANサブプログラムの要約


Apple Store Ginzaで、息子が ”iPadでムービーを作ろう” に参加するので、その合間に、パタパタブログを書き、その足で英会話に向かう日曜日の午後w



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ

| | | コメント (0) | トラックバック (0)

2018年5月 2日 (水)

Oracle Database Connect 2018 エキスパートはどう考えるか? 体感!パフォーマンスチューニング Ⅱ (番外編=没ネタ)

Oracle Database Connect 2018 エキスパートはどう考えるか?体感!パフォーマンスチューニング Ⅱ
~Autonomous Databaseの到来において必要となるチューニングとは~

これ、パフォーマンスチューニングネタをまとめ上げるまで、みなさんスケジュール調整し、
オンライン/オフラインミーティングを繰り返してネタを詰めていくという、かなーり面倒なことをやっています。
私なんて、ネタの候補検討だけで16時間ぐらい使ってますからねw(自分の余暇を使って、半分楽しみながら、締め切りがあるので半分苦しみながらw)



余談はこれくらいにして、今日の本題です。:)

Oracle Database Connect 2018 エキスパートはどう考えるか? 体感!パフォーマンスチューニング Ⅱ で没にしたネタがあるのですが、
そのまま捨てるのも勿体ないので、多少取得情報を追加した状態で公開しちゃおうと思います。


お時間のありますときに、頭の体操、AWRレポートの解析方法(最近はADDM、ASHレポートまで含まれています)のトレーニングにどうぞ。

遅延原因の想定と、想定原因の特定情報は、このエントリでは公開しません。おそらくGW開けの12日あたりか、その一週間後の19日ごろには原因を公開しようかと:)の後半に追記してあります:)

なお、本ページに公開した情報以外に、追加で見たい情報がある場合は、コメント欄やtwのmentionで具体的にリクエストいただければ、取得されている情報の範囲内で追加公開します。取得されてない情報はその旨を追記していきます。:)
まずは、エスパー力全開で、原因を想定してみてください。>
(かなり難易度高めだとは思いますが、過去この状況に遭遇した経験をお持ちのかたは、ここに公開した程度の情報から勘で言い当てたりしちゃうんですよね。実は、昨年末に、何年か振りでこの症状を目にしたのでネタにしよう思いました。)

以下取得された情報は以下のとおり。以下の情報を元に遅延原因を想定してみてください。



なーんでだ?!


状況説明
試験時には問題のなかった処理(30分以内に終了する状態が正常な状態です。)をリリースしました。
ところが、30分以上経過しても終了しません。遅延原因を想定、特定してください。
なお、本番環境のメンテナンス時間中に試験を行ったため、データベースインスタンス内で試験を行いリリースしています。
ちなみに、試験時の処理時間は、約21分でしたが、リリース時は約56分と大幅に遅延。


他のトランザクションが走行している状況をミックスするとより
本物っぽくなり、特定に苦労するので面白いのですが、
遅延している処理だけが走行している状況にしてあります。


資料1 試験時(正常時)AWRレポート
資料2 リリース時(遅延時)AWRレポート
資料3 試験時(正常時)とリリース時(遅延時)のAWR DIFFレポート

資料4 試験時(正常時)CPU利用率グラフ(調査による多少のノイズあり)

1

資料5 リリース時(遅延時)CPU利用率グラグ(スパイク部分調査による多少のノイズあり)

2

GWも後半スタート、天気は少々荒れそうですが、みなさん、よいGWを!





Twitterでのやり取り....

20180506_82405
20180506_82353




ここまでくるとなにかが見えてきたかな。。。原因を特定するまであと一歩な感じ。

追加資料1(遅延時)
top

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
13058 oracle 20 0 10.434g 365704 359548 R 100.0 1.4 1:05.70 oracle_13058_or
5973 oracle 20 0 1955632 253648 73812 S 4.7 1.0 1:42.62 gnome-shell
4657 oracle -2 0 10.417g 58792 55660 S 2.0 0.2 0:12.15 ora_vktm_orcl12
1285 root 20 0 483544 76000 21372 S 1.7 0.3 0:55.11 Xorg
4862 oracle 20 0 10.417g 68960 65868 S 1.3 0.3 0:01.23 ora_lg00_orcl12
223 root 20 0 0 0 0 S 0.7 0.0 0:00.49 kworker/u24:3
13059 oracle 20 0 157976 4564 3544 R 0.7 0.0 0:00.29 top
...略...

追加資料2(遅延時)
sar -P ALL

   ...略...
02:47:29 AM CPU %user %nice %system %iowait %steal %idle
02:47:34 AM all 4.78 0.00 2.36 0.07 0.00 92.80
02:47:34 AM 0 1.06 0.00 1.06 0.85 0.00 97.03
02:47:34 AM 1 0.20 0.00 0.40 0.00 0.00 99.40
02:47:34 AM 2 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 3 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 4 0.00 0.00 0.20 0.00 0.00 99.80
02:47:34 AM 5 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 6 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 7 68.08 0.00 31.92 0.00 0.00 0.00
02:47:34 AM 8 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 9 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 10 0.00 0.00 0.00 0.00 0.00 100.00
02:47:34 AM 11 0.00 0.00 0.20 0.20 0.00 99.60
...略...

追加資料3(遅延時)
perf top -C 7

Samples: 107K of event 'cpu-clock', Event count (approx.): 29550214790                                                              
Overhead Shared Object Symbol
4.36% libc-2.17.so [.] vfprintf
2.77% oracle [.] lxoCpStr
2.00% [kernel] [k] __radix_tree_lookup
1.94% oracle [.] dbgfcsIlcsGetNextDef
1.58% oracle [.] skgovprint
1.48% [kernel] [k] __do_softirq
1.44% [kernel] [k] selinux_file_permission
1.43% oracle [.] dbgaFmtAttrCb_int
1.36% [kernel] [k] system_call_after_swapgs
1.33% oracle [.] dbgaAttrFmtProcArg
1.26% oracle [.] dbgtfmWriteMetadata
1.13% oracle [.] dbgtfdFileWrite
1.02% libc-2.17.so [.] _IO_default_xsputn
...略...





その後のやりとり (Twitter)

20180513_80620

そして核心に迫る追加資料が...あったんです。(しばちょうさん風にはしづらかったw)





正常時(試験時)の追加情報はないようです。リリース後は常に遅いらしい。

ふむふむ。

以下、遅延時のstrace -c -pの情報を取得してもらいました。
straceで眺めるとsystem call write()がダントツで上位にきています。その次が lseek()

追加資料4
strace -c -p

[oracle@localhost ˜]$ sudo strace -c -p 13058
Process 13058 attached
^CProcess 13058 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
77.24 47.883538 1 35085592 write
19.22 11.913638 1 17542850 lseek
3.53 2.191099 1 3414889 getrusage
0.01 0.006377 12 524 semop
0.00 0.000403 7 62 close
0.00 0.000210 4 54 chown
0.00 0.000126 2 63 open
0.00 0.000099 2 54 lstat
0.00 0.000050 1 54 chmod
0.00 0.000029 1 54 stat
0.00 0.000028 3 10 semtimedop
0.00 0.000015 0 54 fcntl
0.00 0.000010 1 8 read
0.00 0.000007 1 8 select
0.00 0.000000 0 3 mmap
0.00 0.000000 0 6 rt_sigprocmask
0.00 0.000000 0 1 rt_sigreturn
0.00 0.000000 0 1 readlink
------ ----------- ----------- --------- --------- ----------------
100.00 61.995629 56044287 total

そろそろ突き止めた感が。

なにかを、どこかに、書き出してますよね。

lsofで書き込みに絞って取得してもった結果は以下のとおり。

[root@localhost ˜]# lsof -p 13058 | grep -E '[0-9]+w'
oracle_13 13058 oracle 1w CHR 1,3 0t0 1038 /dev/null
oracle_13 13058 oracle 2w CHR 1,3 0t0 1038 /dev/null
oracle_13 13058 oracle 7w REG 8,17 2351882843 71176413 /u01/app/oracle/diag/rdbms/orcl12c/orcl12c/trace/orcl12c_ora_13058.trc
oracle_13 13058 oracle 8w REG 8,17 358606101 71176414 /u01/app/oracle/diag/rdbms/orcl12c/orcl12c/trace/orcl12c_ora_13058.trm

ほう。 .trcファイルへ大量の書き込みがあります。

このようになるときは、 なにが起っていると思いますか?

.trcファイルに大量の書き込みのあるケースとして、SQLトレースが有効になっている場合です。(意図したSQLトレースなら問題はないですが...意図していない場合はちょいと問題ですよね。)

ということで、確定診断に移りましょう。

SQL*Plus: Release 12.2.0.1.0 Production on Wed Apr 25 01:23:13 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

Connected.
ORCL@SYSTEM>
ORCL@SYSTEM> select username,status,event,sql_trace from v$session where paddr = (select addr from v$process where spid = 13058)

USERNAME STATUS EVENT SQL_TRAC
------------------------------ -------- ---------------------------------------------------------------- --------
SCOTT ACTIVE log file switch completion ENABLED

見ての通り、
SQL_TRACE列がdisabled(デフォルト)からenabledに変わっていることから、.trcファイルへの大量の書き出しは、セッションレベルでSQLトレースが有効化されたため、と言えますよね。


今回の新規リリースバッチ処理遅延の原因は、

SQLトレースを有効化したままリリースしたしまったことにより、オーバーヘッドが発生し、グルグル系バッチ処理のスループットが低下したため! 

ということでした。SQLトレースを利用したあとの無効化をお忘れなく! :)


SQLトレースは便利な機能の一つですが、インスタンスレベルやグルグル系バッチ処理での利用時は大量のトレース情報の書き出し等によるオーバーヘッドが発生します。
便利なツールではありますが、スループットの低下等による影響を考慮して利用するタイプのツールでもあります。
また、利用した後は、必ず、無効化することもお忘れなく...

ミイラ取りがミイラになってしまってはいみないですから.....ね :)

| | | コメント (0) | トラックバック (0)

2018年3月29日 (木)

Oracle JavaScript Extention Toolkit(JET) de ブラウザの性能比較

なんだか余裕ないので、ちょいと、息抜きw

Oracle JavaScript Extention Toolkit(JET)
Oracle JET

さあ、あなたのIE11とそれ以外のモダンブラウザで 10,000 nodesを描画してPerformanceを比較してみましょう!。
Oracle JET > Visualizations > Diagram > Performance

以下、Safari 11.3 (macOS High Sierra/2 x 2.4 GHz 6-Core Intel Xeon/Mac Pro Mid(2012))
20180329_225621


こちらも、どうぞ。
Edgeも出たし、JetStreamも出たので久々にブラウザのベンチマーク

| | | コメント (0) | トラックバック (0)

2018年3月18日 (日)

Temp落ち #9 - 自動PGA管理で_pga_max_sizeと戯れたPGAサイズって本当に使えるのか? 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理下で、_pga_max_size隠しパラメータとpga_aggregate_targetを最大値に設定するした場合、global memory boundは
最大で、約839GBまで増加することがわかりました。

ただ、これは、内部的なパラメータ上の話。


今日は、実際にソートやハッシュ結合を行なわせ、PGAのSQL Work Areaサイズがどこまで利用できるものなのか確認してみることにします。

さて、どういう結末になりますやら(w

随分前から同じようなことを試している方はいますし....:)

なぜ今そこをdiggingしちゃってるのか? って?

メモリーたっぷりあるのに、何故Temp落ち? が話題になったからに決まってるじゃないですかw

 

まず、デフォルト設定では最大サイズだった 1GB を超えられるか? の確認

注意) 事前にpga_aggregate_target および _pga_max_size をそれぞれ 4TB - 1に設定し、パラメータの上では、global memory boundが約839GBにしてあります。

なお、Temp落ちの確認データやスクリプトはTemp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認のエントリーを参照のこと。

ソートやハッシュ結合は2GB程度になるように調整しています。
2GBのソート

ORCL@SCOTT> @auto_sortwk2gb_optimal.sql

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C750000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
========================================================================================================================================================
| 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 | | | | 34 | +2 | 1 | 1M | | | . | 5.26 | Cpu (1) |
| 1 | SORT ORDER BY | | 1M | 553K | 34 | +2 | 1 | 1M | | | 2GB | 26.32 | Cpu (5) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90829 | 17 | +1 | 1 | 1M | 2637 | 3GB | . | 68.42 | Cpu (3) |
| | | | | | | | | | | | | | direct path read (10) |
========================================================================================================================================================

2GBのハッシュ結合

ORCL@SCOTT> @auto_hashwk2gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C084000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
===========================================================================================================================================================
| 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 | | | | 37 | +2 | 1 | 840K | | | . | 4.35 | Cpu (1) |
| 1 | HASH JOIN | | 843K | 351K | 37 | +2 | 1 | 840K | | | 2GB | 26.09 | Cpu (5) |
| | | | | | | | | | | | | | PGA memory operation (1) |
| 2 | TABLE ACCESS FULL | M1 | 843K | 90828 | 15 | +1 | 1 | 840K | 2633 | 3GB | . | 52.17 | Cpu (1) |
| | | | | | | | | | | | | | direct path read (11) |
| 3 | TABLE ACCESS FULL | M2 | 846K | 90581 | 23 | +16 | 1 | 840K | 2619 | 3GB | . | 17.39 | Cpu (2) |
| | | | | | | | | | | | | | direct path read (2) |
===========================================================================================================================================================

ソートやハッシュ結合は4GB程度になるように調整しています。
見ての通り、ソートは4GBのメモリー内ソートですが、ハッシュ結合は、2GBまで使ったところで Temp落ち! (こんなもんなんですよ。実は!)
4GBのソート

ORCL@SCOTT> @auto_sortwk4gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C085000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C085000'
16 ORDER BY
17* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-----------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=2686238998)
============================================================================================================================================================
| 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 | | | | 40 | +2 | 1 | 2M | | | . | 27.78 | Cpu (5) |
| 1 | SORT ORDER BY | | 2M | 182K | 41 | +1 | 1 | 2M | | | 4GB | 38.89 | Cpu (6) |
| | | | | | | | | | | | | | PGA memory operation (1) |
| 2 | UNION-ALL | | | | 12 | +2 | 1 | 2M | | | . | | |
| 3 | TABLE ACCESS FULL | M1 | 855K | 90828 | 6 | +2 | 1 | 850K | 2633 | 3GB | . | 22.22 | Cpu (2) |
| | | | | | | | | | | | | | direct path read (2) |
| 4 | TABLE ACCESS FULL | M1 | 855K | 90828 | 6 | +8 | 1 | 850K | 2633 | 3GB | . | 11.11 | Cpu (2) |
============================================================================================================================================================

4GBのハッシュ結合 最大3GBのPGAが消費されていますが、Temp落ちしないサイズ、つまり、optimalで処理する場合には、最大2GBが最大サイズとなっています。以下を3GBのハッシュ結合にすると、2GBまでPGAを消費し、一時表領域が3GB利用されます。

ORCL@SCOTT> @auto_hashwk4gb_optimal.sql
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=3532417599)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 216 | +2 | 1 | 3M | | | | | . | . | 4.49 | Cpu (7) |
| 1 | HASH JOIN | | 2M | 670K | 217 | +1 | 1 | 3M | 18091 | 4GB | 18091 | 4GB | 3GB | 4GB | 79.49 | Cpu (87) |
| | | | | | | | | | | | | | | | | direct path read temp (19) |
| | | | | | | | | | | | | | | | | direct path write temp (18) |
| 2 | VIEW | | 2M | 182K | 9 | +2 | 1 | 2M | | | | | . | . | | |
| 3 | UNION-ALL | | | | 9 | +2 | 1 | 2M | | | | | . | . | | |
| 4 | TABLE ACCESS FULL | M1 | 752K | 90828 | 5 | +2 | 1 | 750K | 2633 | 3GB | | | . | . | 1.92 | Cpu (3) |
| 5 | TABLE ACCESS FULL | M1 | 752K | 90828 | 4 | +7 | 1 | 750K | 2633 | 3GB | | | . | . | 1.92 | Cpu (3) |
| 6 | VIEW | | 2M | 181K | 78 | +99 | 1 | 2M | | | | | . | . | 0.64 | Cpu (1) |
| 7 | UNION-ALL | | | | 78 | +99 | 1 | 2M | | | | | . | . | | |
| 8 | TABLE ACCESS FULL | M2 | 759K | 90581 | 50 | +92 | 1 | 750K | 2618 | 3GB | | | . | . | 9.62 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (13) |
| 9 | TABLE ACCESS FULL | M2 | 759K | 90581 | 36 | +141 | 1 | 750K | 2618 | 3GB | | | . | . | 1.92 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=========================================================================================================================================================================================

では、最後に
ソートやハッシュ結合のサイズが8GB程度ならどうでしょうか?
結果は、ソートは4GBを使い切ったのち、Temp落ち、ハッシュ結合は、やはり、3GBまで利用したのちTemp落ちでした。なんと!(ちょっとわざとらしいリアクションしてすみませんw)

8GBのソート

ORCL@SCOTT> @auto_sortwk8gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C090000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C090000'
16 UNION ALL
17 SELECT *
18 FROM
19 m1 m13
20 WHERE
21 id <= 'C090000'
22 UNION ALL
23 SELECT *
24 FROM
25 m1 m14
26 WHERE
27 id <= 'C090000'
28 ORDER BY
29* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=946172832)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 279 | +2 | 1 | 4M | | | | | . | . | 5.79 | Cpu (6) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| | | | | | | | | | | | | | | | | local write wait (7) |
| 1 | SORT ORDER BY | | 4M | 363K | 279 | +2 | 1 | 4M | 38603 | 8GB | 32072 | 8GB | 4GB | 8GB | 84.30 | Cpu (49) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| | | | | | | | | | | | | | | | | direct path read temp (28) |
| | | | | | | | | | | | | | | | | direct path write temp (126) |
| 2 | UNION-ALL | | | | 176 | +2 | 1 | 4M | | | | | . | . | | |
| 3 | TABLE ACCESS FULL | M1 | 907K | 90828 | 16 | +1 | 1 | 900K | 2633 | 3GB | | | . | . | 4.55 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (9) |
| 4 | TABLE ACCESS FULL | M1 | 907K | 90828 | 8 | +17 | 1 | 900K | 2633 | 3GB | | | . | . | 2.48 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 5 | TABLE ACCESS FULL | M1 | 907K | 90828 | 79 | +26 | 1 | 900K | 2633 | 3GB | | | . | . | 2.48 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 6 | TABLE ACCESS FULL | M1 | 907K | 90828 | 72 | +106 | 1 | 900K | 2633 | 3GB | | | . | . | 0.41 | Cpu (1) |
=========================================================================================================================================================================================

8GBのハッシュ結合

ORCL@SCOTT> @auto_hashwk8gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 (
10 SELECT * FROM m1 m11
11 UNION ALL
12 SELECT * FROM m1 m12
13 UNION ALL
14 SELECT * FROM m1 m13
15 ) m1
16 INNER JOIN
17 (
18 SELECT * FROM m2 m21
19 UNION ALL
20 SELECT * FROM m2 m22
21 UNION ALL
22 SELECT * FROM m2 m23
23 ) m2
24 ON
25 m1.id = m2.id
26 AND m1.rev# = m2.rev#
27 WHERE
28* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=2506347387)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 350 | +2 | 1 | 7M | | | | | . | . | 9.18 | Cpu (18) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| 1 | HASH JOIN | | 5M | 1M | 350 | +2 | 1 | 7M | 31032 | 7GB | 31032 | 7GB | 3GB | 8GB | 67.15 | Cpu (81) |
| | | | | | | | | | | | | | | | | direct path read temp (36) |
| | | | | | | | | | | | | | | | | direct path write temp (22) |
| 2 | VIEW | | 2M | 272K | 27 | +2 | 1 | 2M | | | | | . | . | | |
| 3 | UNION-ALL | | | | 27 | +2 | 1 | 2M | | | | | . | . | 0.97 | Cpu (2) |
| 4 | TABLE ACCESS FULL | M1 | 752K | 90828 | 16 | +1 | 1 | 750K | 2633 | 3GB | | | . | . | 7.25 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (12) |
| 5 | TABLE ACCESS FULL | M1 | 752K | 90828 | 5 | +18 | 1 | 750K | 2633 | 3GB | | | . | . | 1.45 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 6 | TABLE ACCESS FULL | M1 | 752K | 90828 | 7 | +23 | 1 | 750K | 2633 | 3GB | | | . | . | 1.93 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (2) |
| 7 | VIEW | | 2M | 272K | 130 | +97 | 1 | 2M | | | | | . | . | | |
| 8 | UNION-ALL | | | | 130 | +97 | 1 | 2M | | | | | . | . | 0.48 | Cpu (1) |
| 9 | TABLE ACCESS FULL | M2 | 759K | 90581 | 47 | +88 | 1 | 750K | 2618 | 3GB | | | . | . | 4.83 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (7) |
| 10 | TABLE ACCESS FULL | M2 | 759K | 90581 | 41 | +134 | 1 | 750K | 2618 | 3GB | | | . | . | 4.35 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 11 | TABLE ACCESS FULL | M2 | 759K | 90581 | 53 | +174 | 1 | 750K | 2618 | 3GB | | | . | . | 2.42 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (4) |
=========================================================================================================================================================================================

 

 

これ、Linuxのカーネルパラメータのvm.max_map_count

/proc/sys/vm/max_map_count

65530

が絡んでるよねという話と、隠しパラメータの realfree_heap関連のパラメータでも調整できそうだよね。という話はあるんですが...それはPL/SQLのはなし...で


_realfree_heap_pagesize 65536 TRUE
_use_realfree_heap TRUE TRUE

とりあえず、
vm/max_map_countを196608

にして
_realfree_heap_pagesize=65536 や
_realfree_heap_pagesize=256K

などとして戯れてみましたが

ソートやハッシュ結合で利用可能なサイズは、それらで制御できるものでもありません。いまのところ。

ORCL@SCOTT> @auto_sortwk8gb_optimal.sql

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

/proc/sys/vm/max_map_count
196608

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C090000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C090000'
16 UNION ALL
17 SELECT *
18 FROM
19 m1 m13
20 WHERE
21 id <= 'C090000'
22 UNION ALL
23 SELECT *
24 FROM
25 m1 m14
26 WHERE
27 id <= 'C090000'
28 ORDER BY
29* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=946172832)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 295 | +2 | 1 | 4M | | | | | . | . | 3.38 | Cpu (8) |
| 1 | SORT ORDER BY | | 4M | 363K | 297 | +1 | 1 | 4M | 38603 | 8GB | 38600 | 8GB | 4GB | 8GB | 91.56 | Cpu (43) |
| | | | | | | | | | | | | | | | | direct path read temp (12) |
| | | | | | | | | | | | | | | | | direct path write temp (162) |
| 2 | UNION-ALL | | | | 205 | +2 | 1 | 4M | | | | | . | . | 0.84 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M1 | 907K | 90828 | 6 | +2 | 1 | 900K | 2633 | 3GB | | | . | . | 2.11 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 4 | TABLE ACCESS FULL | M1 | 907K | 90828 | 7 | +8 | 1 | 900K | 2633 | 3GB | | | . | . | 0.42 | Cpu (1) |
| 5 | TABLE ACCESS FULL | M1 | 907K | 90828 | 100 | +14 | 1 | 900K | 2633 | 3GB | | | . | . | 0.84 | Cpu (2) |
| 6 | TABLE ACCESS FULL | M1 | 907K | 90828 | 94 | +113 | 1 | 900K | 2633 | 3GB | | | . | . | 0.84 | Cpu (2) |
=========================================================================================================================================================================================

ちなみに、PL/SQLで巨大なPGAメモリーを利用する場合は、しっかり9GBとか...やばい状態になったら、pga_aggregate_limitで抑えてくれると思いますが.....

[oracle@localhost ˜]$ ulimit -v -m
virtual memory (kbytes, -v) unlimited
max memory size (kbytes, -m) unlimited

 

orcl12c@SYS> @show_param

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
_pga_max_size 4398046511103 FALSE
_realfree_heap_pagesize 65536 TRUE
_use_realfree_heap TRUE TRUE
pga_aggregate_limit 0 FALSE
pga_aggregate_target 4398046511103 FALSE


/proc/sys/vm/max_map_count
65530
PGAを大量に消費するスクリプトは以下のブログを参考に
Max PGA Research: Check that a process can allocate a large volume of memory / Yury's Blog

ORCL@SCOTT> @plsql_pga8gb 8192
old 2: c_count number := 1024*&1;
new 2: c_count number := 1024*8192;

PL/SQL procedure successfully completed.
実際に確保されたPGAサイズなど。。。
ORCL@SCOTT> r
1 select
2 vss.value/1024/1024/1024 "GB"
3 ,vsn.name
4 ,vss.sid
5 from
6 v$sesstat vss
7 inner join v$statname vsn
8 on
9 vss.statistic# = vsn.statistic#
10 and vss.con_id = vsn.con_id
11 and (vsn.name like '%pga%' or vsn.name like '%uga%')
12 where
13 sid IN (select sid from v$session where username='SCOTT')
14 order by
15* sid,name

GB NAME SID
---------- ---------------------------------------------------------------- ----------
9.49698084 session pga memory 321
9.49698084 session pga memory max 321
1.48221001 session uga memory 321
3.99991362 session uga memory max 321

[oracle@localhost ˜]$ free -h
total used free shared buff/cache available
Mem: 22G 1.0G 9.6G 11G 11G 10G
Swap: 4.0G 8.7M 4.0G

・・・中略・・・

[oracle@localhost ˜]$ free -h
total used free shared buff/cache available
Mem: 22G 10G 128M 11G 11G 705M
Swap: 4.0G 8.8M 4.0G

ここまでくればおわかりだと思いますが、global memory bound (約839GB)からは巨大なPGAサイズになりそうに思えますが、Temp落ちせずにソート可能なサイズは、隠しパラメータを調整した場合でも最大4GB、ハッシュ結合は、2GBが上限、となっています。いまのところ。。。。

ということは、2014年の以下のプレゼンでも話題になっていましたよね。:)
Overcome Oracle PGA Memory Limits Mar.5.2014 Alex Fatkulin / Enkatic

 

だったら、どのようにして、Temp落ち に立ち向かえばいいんだろう.....次回へ続く :)

 


10gR2(64bit)のころのままなので、以下のシリーズも合わせて読んでおくといいですよ:)


Mac De Oracle なんですが、Windows(32bit)でのOracleな話
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #2
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #8
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #9
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #10
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #11
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #12
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #13
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #14
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #15
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #16
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #17
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #18
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #19
_pga_max_sizeってOracle11gではどうなったっけ? という確認。
_pga_max_sizeってOracle11gではどうなったっけ? という確認。シーズン2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8

 


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版
Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版
Temp落ち #8 - 自動PGA管理でパラメータ上設定可能な最大サイズは?(実際に利用可能なサイズとは限りませんが。意味深) 12.2.0.1版

| | | コメント (0) | トラックバック (0)

2018年3月12日 (月)

Temp落ち #8 - 自動PGA管理でパラメータ上設定可能な最大サイズは?(実際に利用可能なサイズとは限りませんが。意味深) 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理下では、SQL Work Areaサイズは、pga_aggregate_targetのサイズに応じて変化し、最大1GBまで調整され、それのサイズ以上のソートやハッシュ結合は、もれなく、"Temp落ち" する。
手動PGA管理下では、最大2GBまで指定できました。。自動だと1GBまでなのか。。。と残念がる声が、昔は海だった方面から聞こえた気がしましたが、たぶん、気のせいw

というところまででした。


今日は、自動PGA管理下ではそれが最大なのか? のか? 自己責任で試してみることにしますw

まずはこれまでの復習。

自動PGA管理で、SQL Work Areaサイズの算出に関わるパラメータは以下の通り

pga_aggregate_target = 10MB 〜 4TB - 1
_pga_max_size = 200MB 〜 2GB
Oracleが動的に値を調整している_pga_max_sizeパラメータへユーザーが値を設定してしまうとOracleの自動調整が無効化され、設定した値で固定されてしまうので注意が必要です。

私の観測範囲だとおおよそ以下のように変化します。最近は大量のメモリーを搭載したサーバーが多いので、pga_aggregate_targetが10GB以上という状況も普通になってきたので、自動PGA管理下のSQL Work Areaサイズは最大1GBとざっくり丸暗記しても困ることは思いますw(ちょっと乱暴かw)

pga_aggregate_target = 10MB〜10GB - 1 :
_pga_max_size = 200MB 〜 2047MB
GREATEST(pga_aggregate_target*0.2 ,200MB)

pga_aggregate_target = 10GB以上〜4TB-1 :
_pga_max_size = 2GB
LEAST(pga_aggregate_target*0.2 ,2GB)


_pga_max_sizeはpga_aggregate_targetの値に応じて動的に変化し、それらを元に _smm_max_size が算出される
LEAST(pga_aggregate_target * 0.2, _pga_max_size * 0.5)

pga_aggregate_target = 10MB〜10GB-1
_pga_max_size = 200MB〜2047MB
_smm_max_size = 2MB〜1023MB


pga_aggregate_target = 10GB〜4TB-1
_pga_max_size = 2048MB
_smm_max_size = 1024MB
_smm_max_sizeは、v$pgastatのglobal memory boundだろうということは確からしいということまでは確認しました。

20180307_143308
20180307_143330
いままでの結果をまとめると
PGAのSQL Work Areaサイズの最大サイズを示す global memory boundは、pga_aggregate_targetが10MB〜10GB-1までは2MB〜1023MBで調整され、pga_aggregate_targetが10GB〜4TB-1では、1024MB (1GB) で固定されている、というのが、Oracle 10GR2〜12cR2まで動きであることは間違いなさそう:)

自動PGA管理下でSQL Work Areaを1GBを超えるサイズにするにはどのパラメータをどのような値に設定すればよいか。。。
その鍵を握るパラメータは

_pga_max_size



(いまいちピンとこないという方は、過去のエントリーを参照していただけるとスッキリすると思います。

ただ、冒頭にも書きましたが(昔それでハマったw)、(Oracleが動的に値を調整している_pga_max_sizeパラメータへユーザーが値を設定してしまうとOracleの自動調整が無効化され、設定した値で固定されてしまうので注意が必要です。
という点はお忘れなく。また、今後も同仕様のままである保証はなく、世界中のOracle使いの方達が調べ上げた結果、現状はこんな感じ。
という状況だと認識しておいたほうが無難だと思っています。

とはいえ、実際どこまで設定可能なのか?  1GBを以上使えるのか? 知りたいですよね...

長い前置きはこれぐらいにして、
実際にSQL Work Areaサイズに対応したメモリーが実際にどの程度割り当てられるのかということの確認は後回しですが、 パラメータの上では、どこまで設定できるのかというところを確認しました。


なお、
以下のエラーメッセージから、_pga_max_sizeに設定可能な値は、pga_aggregate_targetと同様(Big Integer)、10MB〜4TB-1までであると思われます。

orcl12c@SYS> show parameter _pga_max_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 2G

orcl12c@SYS> alter system set "_pga_max_size" = 4T scope=memory;
alter system set "_pga_max_size" = 4T scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。 ORA-00093:
_pga_max_sizeは、10Mから4096G-1の間に設定する必要があります。

ということで、
_pga_max_sizeを1GB/10GB/100GB/1TB/4T-1のそれぞれに設定したうえで、Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 で利用したスクリプトを利用し、global memory boundやその他パラメータの変化を確認しました。

ログの例

orcl12c@SYS> alter system set "_pga_max_size" = 10m scope=both;

システムが変更されました。

orcl12c@SYS> show parameter _pga_max_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 10m


orcl12c@SYS> @dotest_auto_workarea
<< 10m >>
接続されました。
1* alter system set pga_aggregate_target=&1 scope=spfile
旧 1: alter system set pga_aggregate_target=&1 scope=spfile
新 1: alter system set pga_aggregate_target=10m scope=spfile

システムが変更されました。

データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ORACLEインスタンスが起動しました。

Total System Global Area 2147483648 bytes
Fixed Size 8794848 bytes
Variable Size 603983136 bytes
Database Buffers 1526726656 bytes
Redo Buffers 7979008 bytes
データベースがマウントされました。
データベースがオープンされました。
+++ initial parameters +++

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
__pga_aggregate_target 16777216 FALSE

・・・中略・・・

pga_aggregate_limit 0 FALSE
pga_aggregate_target 10485760 FALSE

50行が選択されました。

/proc/sys/vm/max_map_count

65530

+++ v$pgastat +++

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
aggregate PGA target parameter 10 MB 0
aggregate PGA auto target 4 MB 0
global memory bound 2 MB 0

・・・中略・・・


結果は以下のグラフと表にまとめたとおり。
_pga_max_size=4TB-1、pga_aggregate_target=4TB-1の時のglobal memory bound (v$pgastat) = _smm_max_size が最大となり、約839GBとなることがわかりました。
パラメータ上は839GB程度まで増加しますが、ほんとうにPGAがそんなサイズまで利用可能なのでしょうか?....(怪しいです。答え、知ってるんですけどねw。。

1g
10g
100g
1t
4t1
Globalmemorybound

ということで、

次回は実際に、そんなに使えんのかよ! という実験をしてみたいと思います :)

つづく。




10gR2(64bit)のころのままなので、以下のシリーズも合わせて読んでおくといいですよ:)
Mac De Oracle なんですが、Windows(32bit)でのOracleな話
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #2
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #8
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #9
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #10
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #11
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #12
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #13
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #14
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #15
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #16
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #17
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #18
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #19
_pga_max_sizeってOracle11gではどうなったっけ? という確認。
_pga_max_sizeってOracle11gではどうなったっけ? という確認。シーズン2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版
Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版


| | | コメント (0) | トラックバック (0)

2018年3月 8日 (木)

Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理で利用可能なSQL Work Areaサイズはいくつなのか?の確認でした。
これまで同様、隠しパラメータ等の変更をしないデフォルトの設定では最大1GBまで到達することを確認しました。
また、手動PGA管理で利用可能な最大サイズより小さいことも再確認しました。


今日は自動PGA管理下ではオンメモリーで処理可能なサイズは1GBまでなのか、それを超えた場合はもれなく "Temp落ち" なのか確認することにします。

 

確認用データおよび方法は前々回のエントリーを参照ください。

自動PGA管理でGlobal Memory Bound = 1GB(自動PGA管理での最大 Sort作業領域サイズ)

メモリーソートで1GBぐらいまで利用していることが確認できます。(ソートされるデータ量は1GB以下程度に制限しています。)
ORCL@SCOTT> @auto_sortwk1gb_optimal

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C045000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 4.78 | 3.85 | 0.34 | 0.59 | 30001 | 334K | 2633 | 3GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
==================================================================================================================================================
| 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 | | | | 10 | +2 | 1 | 450K | | | . | 33.33 | Cpu (2) |
| 1 | SORT ORDER BY | | 456K | 302K | 11 | +1 | 1 | 450K | | | 1GB | 33.33 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 456K | 90827 | 3 | +2 | 1 | 450K | 2633 | 3GB | . | 33.33 | Cpu (2) |
==================================================================================================================================================

 

Temp落ちする程度のデータ量でソートさせた場合も、PGAの作業領域は最大1GBまで利用されていたことが確認できます。
ORCL@SCOTT> @auto_sortwk1gb_mpass    

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C050000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 16 | 6.91 | 7.91 | 0.71 | 33335 | 334K | 7996 | 4GB | 4431 | 1GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 22 | +2 | 1 | 500K | | | | | . | . | 6.25 | Cpu (1) |
| 1 | SORT ORDER BY | | 507K | 326K | 22 | +2 | 1 | 500K | 5363 | 1GB | 4431 | 1GB | 1GB | 1GB | 68.75 | Cpu (4) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (6) |
| 2 | TABLE ACCESS FULL | M1 | 507K | 90828 | 13 | +1 | 1 | 500K | 2633 | 3GB | | | . | . | 25.00 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=====================================================================================================================================================================================

自動PGA管理でGlobal Memory Bound = 1GB(自動PGA管理での最大 Hash作業領域サイズ)

Hash Joinの場合も、自動PGA管理の最大サイズ程度まで利用されていることが確認できます。(Temp落ちしない程度のデータ量にしています。)
ORCL@SCOTT> @auto_hashwk1gb_optimal

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C042000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 7.29 | 5.05 | 0.68 | 1.55 | 28001 | 695K | 5251 | 5GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
==================================================================================================================================================
| 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 | | | | 14 | +2 | 1 | 420K | | | . | 22.22 | Cpu (2) |
| 1 | HASH JOIN | | 423K | 268K | 15 | +1 | 1 | 420K | | | 1GB | 22.22 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 432K | 90827 | 2 | +2 | 1 | 420K | 2633 | 3GB | . | 11.11 | Cpu (1) |
| 3 | TABLE ACCESS FULL | M2 | 423K | 90580 | 13 | +3 | 1 | 420K | 2618 | 3GB | . | 44.44 | Cpu (4) |
==================================================================================================================================================
Hash Joinの場合もSort時と同じように、一時表領域も利用が必要となるデータ量になると、一旦、自動PGA管理の最大サイズ程度まで利用したうえでTemp落ちしていることが確認できます。
ORCL@SCOTT> @auto_hashwk1gb_mpass

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C055000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
============================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
============================================================================================
| 24 | 7.18 | 15 | 1.99 | 36668 | 687K | 10829 | 6GB | 5578 | 1GB |
============================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
======================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
======================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 33 | +2 | 1 | 550K | | | | | . | . | 14.29 | Cpu (4) |
| 1 | HASH JOIN | | 551K | 293K | 33 | +2 | 1 | 550K | 5578 | 1GB | 5578 | 1GB | 1GB | 1GB | 67.86 | Cpu (8) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (10) |
| 2 | TABLE ACCESS FULL | M1 | 551K | 90828 | 16 | +1 | 1 | 550K | 2633 | 3GB | | | . | . | 7.14 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 3 | TABLE ACCESS FULL | M2 | 557K | 90580 | 16 | +16 | 1 | 550K | 2618 | 3GB | | | . | . | 10.71 | Cpu (3) |
======================================================================================================================================================================================

PGAの最大サイズは異なりますが、自動PGA管理、手動PGA管理いずれでの場合でも制限値を超えた場合は、もれなく "Temp落ち" するということはご理解いただけたのではないかと思います。

では、次回は恒例?w の隠しパラメータと戯れた場合、自動PGA管理下ではどこまで増加させることができるのか?。。。。確認してみたいと思います。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版

 

| | | コメント (0) | トラックバック (0)

2018年3月 7日 (水)

Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版

Previously on Mac De Oracle
pga_aggregate_target scope=memory or bothでそれまでとはことなる動きとエラーは発生するところの話でした。


きょうは、自動PGA管理下ではどうなるか確認しておきます。
以前、簡単に確認した範囲結果から、これまでの仕様と大きな違いはないとみています。(変わっていないと思っていたのでブログでも書いていなかったのですが、"Temp落ち"ネタの準備運動も兼ね現状を確認しながら進めてみたいと思います)


まず環境情報から、
確認に利用するインスタンスのPGA以外のパラメータは以下の通りです。
(隠しパラメータは必要がある場合には適宜変更します。また、いくつかのパラメータは確認の都合上物理メモリーサイズ以上に設定することがあります。それらの変更が必要な場合には事前に解説する予定です。)

OS等の情報は以前のエントリーを参照のこと。

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
sga_max_size big integer 12G
sga_min_size big integer 0
sga_target big integer 12G

今回の主役である自動PGA管理のパラメータの初期設定は以下の通り(隠しパラメータを除く)
pga_aggregate_limit = 0 に設定し、pga_aggregate_targetの上限値制限を仕様上のサイズ4096GB - 1まで利用できるようにしておきます。(以前と同様の手順で確認するために、pga_aggregate_limitによる制限を無効化しています。なお、pga_aggregate_limitを0以外に設定して行う場合には、pga_aggregate_targetを4TB - 1まで設定することを考慮すると、pga_aggregate_limitは、8TB以上に設定する必要があります。)

orcl12c@SYS> show parameter pga_aggregate

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 0
pga_aggregate_target big integer 10M

上記設定からスタートして、pga_aggregate_targetを10MB/50MB/100MB/500MB/1GB/5GB/10GB/50GB/100GB/500GB/1T/4TB - 1と増加させながら、pgaサイズおよび、関連する隠しパラメータ(_pga_max_sizeや_smm_*など)がどのように変化するか、pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8の手順で確認します。

昔のように実行ログをペタペタ貼っていると長くなるので、確認結果を表とグラフにしました。
想定通り以前と変わっていませんでした:)

デフォルト設定のままであれば、pga_aggregate_targetを限界値まで大きく増やしたとしても、10GB以降、個々のソート操作やハッシュ結合操作で利用できるPGAのSQL work areaサイズはv$pgastatのglobal memory boundの示す通り、1GB(シリアル実行時)が最大であることが確認できます。 えーーー。手動PGA管理より最大サイズ小さいじゃんと、今更驚かないようにしましょうねw (以前からそうなのですからw)
つまり、自動PGA管理の場合、特定の隠しパラメータを変更しない限り、1GBを超えるソート処理やハッシュ結合操作は全て、"Temp落ち" する宿命にあるわけです。12cR2であっても。
え"〜〜〜〜〜っ! と一応、驚いておきましょうw。 メモリーを沢山積んでるマシンは最近多いわけですが、このあたりはなぜか変わってません。
20180307_143101

ちなみに、v$pgastatの示すglobal memory boundは、どこから? という確認がしたくて、 _smm_max_sizeの変化と比較しているグラグも作成しておきました。
_smm_max_sizeパラメータはpga_aggregate_targetのサイズ変化に伴い変化する隠しパラメータですが、このパラメータがglobal memory boundの元になっているのは確からしいですね。
20180307_143308

最後のグラフは、pga_aggregate_targetの値とv$pgastatのglobal memory boundの変化をまとめたグラフです。
global memory boundで示されるPGAのSQL Work Areaサイズの最大サイズには上限があることがわかります:)
20180307_143330

次回は、自動PGA管理では、1GBを超えるソートやハッシュ結合はもれなく"Temp落ち"するのか確認してみることにします。


参考:このエントリーで利用したスクリプト
(今後の確認も兼ねてw このエントリーでネタにしている以外で関連しそうなパラメータも取得して変化を確認できるようにしてあります。)

orcl12c@SYS> !cat show_param.sql
set linesize 200
col ksppinm for a46
col ksppstvl for a30
col ksppstdf for a30
select
a.ksppinm
,b.ksppstvl
,b.ksppstdf
from
x$ksppi a join x$ksppcv b
on a.indx = b.indx
where
a.ksppinm in (
'pga_aggregate_target'
,'pga_aggregate_limit'
,'_use_realfree_heap'
,'_realfree_heap_pagesize'
)
or a.ksppinm like '%\_smm%' escape '\'
or a.ksppinm like '%\_pga%' escape '\'
order by
a.ksppinm
/

!echo /proc/sys/vm/max_map_count
!more /proc/sys/vm/max_map_count

orcl12c@SYS> !cat show_pgstat.sql
select
name
,case
when unit='bytes' then
value/1024/1024
else value
end "VALUE"
,case
when unit='bytes' then
'MB'
else unit
end "UNIT"
,con_id
from
v$pgastat
where
name in (
'aggregate PGA target parameter'
,'aggregate PGA auto target'
,'global memory bound'
,'total PGA used for auto workareas'
,'maximum PGA used for auto workareas'
,'total PGA used for manual workareas'
,'maximum PGA used for manual workareas'
)
;
orcl12c@SYS> !cat auto_workarea.sql
conn sys/oracle@orcl12c as sysdba
alter system set pga_aggregate_target=&1 scope=spfile
.
r
shutdown immediate
startup

prompt +++ initial parameters +++
@show_param

prompt +++ v$pgastat +++
@show_pgstatå
orcl12c@SYS> !cat doTest_auto_workarea.sql
prompt << 10m >>
@auto_workarea 10m

prompt << 50m >>
@auto_workarea 50m

prompt << 100m >>
@auto_workarea 100m

prompt << 500m >>
@auto_workarea 500m

prompt << 1g >>
@auto_workarea 1g

prompt << 5g >>
@auto_workarea 5g

prompt << 10g >>
@auto_workarea 10g

prompt << 50g >>
@auto_workarea 50g

prompt << 100g >>
@auto_workarea 100g

prompt << 500g >>
@auto_workarea 500g

prompt << 1t >>
@auto_workarea 1t

prompt << 4t - 1 >>
@auto_workarea 4398046511103


実行例

orcl12c@SYS> @doTest_auto_workarea.sql
<< 10m >>
Connected.
1* alter system set pga_aggregate_target=&1 scope=spfile
old 1: alter system set pga_aggregate_target=&1 scope=spfile
new 1: alter system set pga_aggregate_target=10m scope=spfile

System altered.

Database closed.
Database dismounted.
ORACLE instance shut down.
ORACLE instance started.

Total System Global Area 1.2885E+10 bytes
Fixed Size 8807168 bytes
Variable Size 1375735040 bytes
Database Buffers 5033164800 bytes
Redo Buffers 24743936 bytes
In-Memory Area 6442450944 bytes
Database mounted.
Database opened.
+++ initial parameters +++

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
__pga_aggregate_target 33554432 FALSE

・・・略・・・

pga_aggregate_limit 0 FALSE
pga_aggregate_target 10485760 FALSE

50 rows selected.

/proc/sys/vm/max_map_count

65530

+++ v$pgastat +++

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
aggregate PGA target parameter 10 MB 0
aggregate PGA auto target 4 MB 0
global memory bound 2 MB 0
total PGA used for auto workareas .106445313 MB 0
maximum PGA used for auto workareas .106445313 MB 0
total PGA used for manual workareas 0 MB 0
maximum PGA used for manual workareas 0 MB 0

7 rows selected.

・・・略・・・


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)

| | | コメント (0) | トラックバック (0)

2018年2月24日 (土)

Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)



Previously on Mac De Oracle

手動PGA管理下で利用される4つのパラメータ(HASH_AREA_SIZE / SORT_AREA_SIZE / BITMAP_MERGE_AREA_SIZE / CREATE_BITMAP_AREA_SIZE)は、Integer型であり2,147,483,647バイト(つまり2GB - 1)までは指定可能、かつ、個々の作業領域は同程度確保されること。そのサイズを超えるデータ量の場合は一時表領域が利用される。つまり"Temp落ち"が発生するというところまでした。

SQL> show parameter _area_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
bitmap_merge_area_size integer 2147483647
create_bitmap_area_size integer 2147483647
hash_area_size integer 2147483647
sort_area_size integer 2147483647

64bit環境でInteger型なので、そんなもんでしょうね。 仕様ですからね、こればっかりはどうにもならない。


で、自動PGA管理で確認しようとパタパタ準備していたところ、12.1.0.2まではなかった12.2.0.1での変更点に気付いたので、そちらを先に。
(少し脱線)

pga_aggregate_targetをscope=memoryで設定する際、物理メモリーの空きサイズ以上に設定しようとするとORA-00855エラーなり設定できないよう動作が変更されたようです。

また、spfileにのみ設定する場合には動作が異なり、設定時には、ORA-00855は発生しません。
アラートログファイルには、起動時のワーニングとして記録され、アラートログにもなにも記録されれず、正常起動しpga_aggregate_targetには指定したサイズで設定されるという、
少々分かりづらい仕様に変わったようです。 (今頃気づくとは、遅い!w

(18cではどうなるんだろう。。なんとなく、Autonomousを意識してるようなアトモスフィアを感じます..が、どうなんでしょうw?)

以下、動作確認の記録。
Guest OSのメモリーサイズは以下の通り。空いているメモリーサイズは500MB〜600MB程度です。

Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
orcl12c@SYS> !cat /proc/meminfo | grep Mem
MemTotal: 3781732 kB
MemFree: 43300 kB
MemAvailable: 559076 kB


orcl12c@SYS> show parameter sga

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 2G
sga_min_size big integer 0
sga_target big integer 2G
unified_audit_sga_queue_size integer 1048576

orcl12c@SYS> show parameter pga_agg

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 2G
pga_aggregate_target big integer 200M


この状態で、pga_aggregate_limitの50%のサイズでpga_aggregate_limitの制限内、pga_aggregate_target=1g としてメモリー上でのみ変更してみると

orcl12c@SYS> alter system set pga_aggregate_target=1g scope=memory;
alter system set pga_aggregate_target=1g scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


空きメモリーサイズ以下ならどうなるか試して見ます。

orcl12c@SYS> alter system set pga_aggregate_target=500m scope=memory;

システムが変更されました。


空きメモリーサイズを多少上回るサイズでは。。。(やはり、ORA-00855となります)

orcl12c@SYS> alter system set pga_aggregate_target=700m scope=memory;
alter system set pga_aggregate_target=700m scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


scope=bothとなる状態で、空きメモリー以上のサイズを設定しようとしても同様のエラーになりますが.....

orcl12c@SYS> alter system set pga_aggregate_target=1g;
alter system set pga_aggregate_target=1g
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


scope=spfileとした場合には、エラーにならず変更可能です。このあたりの動きは、他のパラメータと同様、起動時にエラーで起動しないという動きになるのかな??? と思いきや.....
なんと、正常起動し、pga_aggregate_targetにはしっかり、1GBが設定されています。!!!!! (動きが違う!!!)

orcl12c@SYS> alter system set pga_aggregate_target=1g scope=spfile;

システムが変更されました。

orcl12c@SYS> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
orcl12c@SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 2147483648 bytes
Fixed Size 8794848 bytes
Variable Size 603983136 bytes
Database Buffers 1526726656 bytes
Redo Buffers 7979008 bytes
データベースがマウントされました。
データベースがオープンされました。
orcl12c@SYS>
orcl12c@SYS> show parameter pga_agg

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 2G
pga_aggregate_target big integer 1G

なぜ、動作が違うんだろう........


2018/2/25訂正
以下のメッセージよくよく見たら、pga_aggregate_limitパラメータへの警告でpga_aggregate_targetのORA-00855とは無関係なワーニングでした。orz.

pga_aggregate_limitの下限値は2G、sea_max_size=2Gなのに、Guest OSへのメモリー割り当て4Gなのでそりゃでるわね。というこでした。

とはいえ、 pga_aggreagte_target scope=memoryとspfileではORA-00855エラーの有無の違いはあるようで、spfileに設定した場合にはORA-00855は発生せず、アラートログファイルには何も記録されていない(謎


なお、起動時のアラートログファイルには以下の"WARNING"メッセージが記録されます。しかも、メッセージを見る限り、pga_aggregate_limit へのワーニングと読めるが....

2018-02-24T22:16:38.907017+09:00
WARNING: pga_aggregate_limit value is too high for the
amount of physical memory on the system
PGA_AGGREGATE_LIMIT is 2048 MB
PGA_AGGREGATE_TARGET is 1024 MB.
physical memory size is 3693 MB
limit based on physical memory and SGA usage is 1275 MB
SGA_TARGET is 2048 MB

おまけ


12.1.0.2までは該当するメッセージはありません。
[oracle@vbgeneric ˜]$ sqlplus -version

SQL*Plus: Release 12.1.0.2.0 Production

[oracle@vbgeneric ˜]$ oerr ORA 855
[oracle@vbgeneric ˜]$



12.2.0.1で実装されたメッセージと機能だということがわかります。
[oracle@localhost ˜]$ sqlplus -version

SQL*Plus: Release 12.2.0.1.0 Production

[oracle@localhost ˜]$ oerr ORA 855
00855, 00000, "PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory."
// *Cause: PGA_AGGREGATE_TARGET value was too high for the current system global area (SGA) size and amount of physical memory available.
// *Action: Reduce the SGA size or increase the physical memory size.

ということで、次回こそw 自動PGA管理下での確認へ

続く。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認

| | | コメント (0) | トラックバック (0)

2018年2月22日 (木)

Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認


Previously on Mac De Oracle
手動PGA管理で作業領域として指定可能な最大サイズは、2GB - 1までということの確認でした。
本当に、その程度のサイズまでPGAの作業領域が利用されるのでしょうか?。 念のために確認しておきましょう。 実は、それより少ないサイズで頭打ちなんてことは、ないかな〜 と わざとらしく言ってみたりして(意味深w

その前に、指定した作業領域を使い切れるぐらい(つまり、Temp落ちさせられる程度)のデータ量の表を準備しておきます。
今回は、Nested Loop Join(NLJ)やソート回避などのための索引は作成しません。Temp落ちのネタなので。

M1とM2の2表を作成し、それぞれ、2.5GB程度のセグメントサイズにしておきます。 なお、以下の無名PL/SQLブロックでは、FORALLを利用して1000行単位でバルク処理しています。配列を利用するのでメモリー使用量にはそれなりに配慮が必要ですが。:)
単純なぐるぐる系INSERTにしてしまうとデータ量が多い場合、性能的に辛くなってしまうので、ここ重要!

ORCL@SCOTT> l
1 CREATE TABLE m1
2 (
3 id CHAR(7) NOT NULL
4 ,rev# NUMBER NOT NULL
5 ,value NUMBER NOT NULL
6 ,description VARCHAR2(4000)
7 ,additional_info CHAR(200)
8* ) NOLOGGING
ORCL@SCOTT> /

Table created.

ORCL@SCOTT> l
1 DECLARE
2 TYPE IDS_t IS TABLE OF m1.id%TYPE INDEX BY PLS_INTEGER;
3 TYPE REV#S_t IS TABLE OF m1.rev#%TYPE INDEX BY PLS_INTEGER;
4 TYPE VALS_t IS TABLE OF m1.value%TYPE INDEX BY PLS_INTEGER;
5 IDs IDS_t;
6 REV#s REV#S_t;
7 VALs VALS_t;
8 k PLS_INTEGER := 1;
9 BEGIN
10 FOR i IN 1..100000 LOOP
11 FOR j IN 1..10 LOOP
12 IDs(k) := 'C' || TO_CHAR(i, 'FM000000');
13 REV#s(k) := j;
14 VALs(k) := i + j;
15 k := k + 1;
16 END LOOP;
17 IF MOD(i, 100) = 0 THEN
18 FORALL l in 1..1000 EXECUTE IMMEDIATE
19 'INSERT /*+ APPEND_VALUE NO_GATHER_OPTIMIZER_STATISTICS */ INTO m1 '
20 || 'VALUES(:1, :2, :3, LPAD(''X'',2000, ''X''), LPAD(''9'',200,''9''))'
21 USING IDs(l), REV#s(l), VALs(l);
22 COMMIT;
23 k := 1;
24 END IF;
25 END LOOP;
26* END;
ORCL@SCOTT> /

PL/SQL procedure successfully completed.

Elapsed: 00:01:22.45
ORCL@SCOTT> select count(1) from m1;

COUNT(1)
----------
1000000

ORCL@SCOTT> select segment_name,sum(bytes)/1024/1024/1024 "GB" from user_segments where segment_name='M1' group by segment_name;

SEGMENT_NAME GB
------------------------------ ----------
M1 2.5625

ORCL@SCOTT> l
1 CREATE TABLE m2
2 (
3 id CHAR(7) NOT NULL
4 ,rev# NUMBER NOT NULL
5 ,value NUMBER NOT NULL
6 ,description VARCHAR2(4000)
7* ) NOLOGGING
ORCL@SCOTT> /

Table created.

ORCL@SCOTT> l
1 INSERT /*+ APPEND NO_GATHER_OPTIMIZER_STATISTICS */ INTO m2
2* SELECT id,rev#,value,description FROM m1
ORCL@SCOTT> /

1000000 rows created.

Elapsed: 00:00:40.22
ORCL@SCOTT> commit;

Commit complete.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'M1',no_invalidate=>false,method_opt=>'FOR ALL COLUMNS SIZE SKEWONLY');

PL/SQL procedure successfully completed.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'M2',no_invalidate=>false,method_opt=>'FOR ALL COLUMNS SIZE SKEWONLY');

PL/SQL procedure successfully completed.

これで、準備完了。
ちなみに、NO_GATHER_OPTIMIZER_STATISTICSヒントを利用していますが、データ登録後に、ヒストグラムも含めて取得したかったため、バルクロード時のオンラインオプティマイザ統計収集を行わないようにするためのヒントです。
データ登録後オプティマイザ統計を取得するため、データ登録時のオンラインオプティマイザ統計のオーバーヘッドは無駄となるためです。利用できるなら利用したほうがよいとは思いますが、制限もあるのでご一読を(バルク・ロードのためのオンライン統計収集


 

手動PGA管理で2GB - 1(手動PGA管理での最大 Sort作業領域サイズ)

メモリーソートで2GBぐらいまで利用していることが確認できます。(ソートされるデータ量が2GB以下程度に制限しています。)
ORCL@SCOTT> @manual_sortwk2gb_optimal    
1* alter session set workarea_size_policy=manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C075000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 5.42 | 4.13 | 1.29 | 50001 | 334K |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
===================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
===================================================================================================================================
| 0 | SELECT STATEMENT | | | | 15 | +2 | 1 | 750K | . | 20.00 | Cpu (1) |
| 1 | SORT ORDER BY | | 752K | 439K | 16 | +1 | 1 | 750K | 2GB | 60.00 | Cpu (3) |
| 2 | TABLE ACCESS FULL | M1 | 752K | 90828 | 3 | +2 | 1 | 750K | . | 20.00 | Cpu (1) |
===================================================================================================================================

 

Temp落ちする程度のデータ量でソートさせた場合も、PGAの作業領域は一旦、2GBまで利用されていることが確認できます。
ORCL@SCOTT> @manual_sortwk2gb
1* alter session set workarea_size_policy=manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 ORDER BY
9 id
10* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 46 | 11 | 15 | 20 | 66668 | 334K | 2148 | 2GB | 2146 | 2GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 60 | +2 | 1 | 1M | | | | | . | . | 6.38 | Cpu (2) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| 1 | SORT ORDER BY | | 1M | 553K | 61 | +1 | 1 | 1M | 2148 | 2GB | 2146 | 2GB | 2GB | 2GB | 91.49 | Cpu (32) |
| | | | | | | | | | | | | | | | | direct path read temp (10) |
| | | | | | | | | | | | | | | | | direct path write temp (1) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90822 | 27 | +2 | 1 | 1M | | | | | . | . | 2.13 | Cpu (1) |
=====================================================================================================================================================================================

 

手動PGA管理で2GB - 1(手動PGA管理での最大 Hash作業領域サイズ) Hash Joinの場合も、手動PGA管理で設定可能な最大サイズ程度まで利用されていることが確認できます。(Temp落ちしない程度のデータ量にしています。)

ORCL@SCOTT> @manual_hashwk2gb_optimal
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set hash_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 8.05 | 4.66 | 0.16 | 3.23 | 50001 | 717K | 2618 | 3GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
==================================================================================================================================================
| 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 | | | | 21 | +2 | 1 | 750K | | | . | 12.50 | Cpu (1) |
| 1 | HASH JOIN | | 752K | 181K | 22 | +1 | 1 | 750K | | | 2GB | 25.00 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 752K | 90828 | 2 | +2 | 1 | 750K | | | . | 25.00 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M2 | 759K | 90581 | 20 | +3 | 1 | 750K | 2618 | 3GB | . | 37.50 | Cpu (3) |
==================================================================================================================================================

 

Hash Joinの場合もSort時と同じように、一時表領域も利用させる程度のデータ量になると、一旦、手動PGA管理で設定可能な最大サイズ程度まで利用したうえでTemp落ちしていることが確認できます。

ORCL@SCOTT> @manual_hashwk2gb
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set hash_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13* AND m1.rev# = m2.rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 12 | 5.68 | 2.17 | 4.59 | 66668 | 706K | 3133 | 3GB | 515 | 511MB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 30 | +2 | 1 | 1M | | | | | . | . | 25.00 | Cpu (4) |
| 1 | HASH JOIN | | 1M | 189K | 31 | +1 | 1 | 1M | 515 | 511MB | 515 | 511MB | 2GB | 515MB | 43.75 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (1) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90822 | 6 | +0 | 1 | 1M | | | | | . | . | 12.50 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M2 | 1M | 90574 | 24 | +5 | 1 | 1M | 2618 | 3GB | | | . | . | 18.75 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=====================================================================================================================================================================================

おまけ 1つのSQLの実行で利用されるPGAの作業領域は1つだけではないということも確認しておきましょうかね。(知ってる方はスルーしてよいですよ:)
以下の例では、MERGE JOINさせていますが、表M1と表M2それぞれのSort作業領域は、Merge Join終了時まで保持されることになるため、最大3GBのSort作業領域が利用されています。(SQLモニターの結果ではわかりにくいのですが。。。)
なお、手動PGA管理、自動PGA管理に関係なく、実行される操作により複数の作業領域が同時に確保されることがあります。
(ちなみに、自動PGA管理で利用されるPGA_AGGREGATE_LIMITがPGA_AGGREGATE_TARGETの2倍とされるのも、このような動きが考慮された結果だと知っていると、納得感はあるかもしれません。)

 

前回使った図で朱色で示したSQL Work Areaが複数ありますが、Hash/Sort/Bitmap系など複数のタイプおよび同一タイプの作業領域が同時に確保されることも意図した図になっているのは、これが理由なんです。

Structure_of_pga
ORCL@SCOTT> @manual_sortwk2gb2_optimal
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 USE_MERGE(m1 m2)
5 */
6 *
7 FROM
8 m1
9 INNER JOIN m2
10 ON
11 m1.id = m2.id
12 AND m1.rev# = m2.rev#
13 WHERE
14* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 17 | 10 | 4.75 | 2.19 | 50001 | 667K | 5251 | 5GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1391069689)
========================================================================================================================================================
| 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 | | | | 23 | +10 | 1 | 750K | | | . | 14.29 | Cpu (2) |
| 1 | MERGE JOIN | | 752K | 849K | 23 | +10 | 1 | 750K | | | . | | |
| 2 | SORT JOIN | | 752K | 439K | 32 | +1 | 1 | 750K | | | 2GB | 21.43 | Cpu (3) |
| 3 | TABLE ACCESS FULL | M1 | 752K | 90828 | 8 | +2 | 1 | 750K | 2633 | 3GB | . | 35.71 | Cpu (3) |
| | | | | | | | | | | | | | direct path read (2) |
| 4 | SORT JOIN | | 759K | 410K | 23 | +10 | 750K | 750K | | | 1GB | 14.29 | Cpu (2) |
| 5 | TABLE ACCESS FULL | M2 | 759K | 90581 | 5 | +10 | 1 | 750K | 2618 | 3GB | . | 14.29 | Cpu (2) |
========================================================================================================================================================

SQLモニターの結果ではわかりにくいわけですが、v$sesstatをから眺めれば状況がよくわかります!
2つのソート作業領域が同時に確保されたことで、2GB + 1GB = 3GB程度のサイズにまで達していることが確認できます。

ORCL@SYSTEM> r
1 select
2 vss.value/1024/1024/1024 "GB"
3 ,vsn.name
4 ,vss.sid
5 from
6 v$sesstat vss
7 inner join v$statname vsn
8 on
9 vss.statistic# = vsn.statistic#
10 and vss.con_id = vsn.con_id
11 and vsn.name like '%pga%'
12 where
13 sid IN (select sid from v$session where username='SCOTT')
14 order by
15* sid,name

GB NAME SID
---------- ---------------------------------------------------------------- ----------
3.04611414 session pga memory 203
3.04611414 session pga memory max 203

では、次回はやっとw、真打、自動PGA管理下での確認。(引っ張り過ぎかもしれないw)
つづく。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ

 

| | | コメント (0) | トラックバック (0)

2018年2月20日 (火)

Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ

自動PGA管理は12cでどうなんだっけ?という確認の前に、
いままで何度か質問されたことがあり、FAQだと思っているので

手動PGA管理で利用する以下パラメータの最大サイズはいくつ? 

HASH_AREA_SIZE
SORT_AREA_SIZE
BITMAP_MERGE_AREA_SIZE
CREATE_BITMAP_AREA_SIZE

ということを書いておきたいと思います。

これからしばらく続く Temp落ち ネタで利用する環境で固定部分は以下のとおり
(初期化パラメータ等は必要に応じて載せるつもりです。)


環境は以下のとおり。
host osとguest osのバージョンやメモリーサイズなど

discus:˜ oracle$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.13.3
BuildVersion: 17D47

discus:˜ oracle$ system_profiler SPHardwareDataType | grep -E 'Processor Name|Cores|Memory'
Processor Name: 6-Core Intel Xeon
Total Number of Cores: 12
Memory: 32 GB

discus:˜ oracle$ VBoxManage -v
5.2.6r120293

discus:˜ oracle$ VBoxManage showvminfo e3d4f948-b2e6-4db3-a89d-df637d87a372 | grep -E 'Memory size|OS type|Number of CPUs'
Memory size: 23569MB
Number of CPUs: 12
OS type: Linux26_64


orcl12c@SYS> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production 0
PL/SQL Release 12.2.0.1.0 - Production 0
CORE 12.2.0.1.0 Production 0
TNS for Linux: Version 12.2.0.1.0 - Production 0
NLSRTL Version 12.2.0.1.0 - Production 0


orcl12c@SYS> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 ORCL READ WRITE NO

さて、今日の本題

手動PGA管理で各SQL Work Area Sizeを決定する以下の初期化パラメータの最大サイズは? いくつでしょう? 
すでにご存知のかたはスキップしていいですよ:)

マニュアルを読んでみるときづくのですが、手動PGA管理で各SQL Work Area Sizeを決定する4パラメータとも、「0以上、上限はOS依存」のような記述になっています。

そう、マニュアルを読んだだけではまったく参考にならないわけです。(え〜〜っ!)

そこで、みなさんサポートを頼るなり、ご自分でMOSを検索するなり、そこそこの苦労をして入手することになります。
私みたいな性格だと、どのマニュアルでもいいからまとめて載せてよーめんどくさいから。と、めんどくさい病の発作がでたりしますw

なので、環境があるのなら、you 試しちゃいなよー。が手っ取り早いかなーと(最終的にMOSとかサポートを頼るにしてもw)

上限はOS依存とだけしか記載されていませんが、私の観測範囲では、2GB - 1 が上限 となっています。
以下、Linux/Solaris/Windowsでの検証ログ。

Oracle® Databaseリファレンス 12cリリース2 (12.2) E72905-03より

HASH_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/HASH_AREA_SIZE.htm

SORT_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/SORT_AREA_SIZE.htm

BITMAP_MERGE_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/BITMAP_MERGE_AREA_SIZE.htm

CREATE_BITMAP_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/CREATE_BITMAP_AREA_SIZE.htm


Linux

orcl12c@SYS> !uname -r; cat /etc/redhat-release /etc/oracle-release
4.1.12-94.3.6.el7uek.x86_64
Red Hat Enterprise Linux Server release 7.3 (Maipo)
Oracle Linux Server release 7.3

orcl12c@SYS> create pfile from spfile;

File created.

orcl12c@SYS> --2GB
orcl12c@SYS> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

orcl12c@SYS> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> --2GB - 1
orcl12c@SYS> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

orcl12c@SYS> alter system set hash_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set sort_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
orcl12c@SYS> create spfile from pfile;

File created.

orcl12c@SYS>


Solaris 11 (x86)

SQL> !uname -r; cat /etc/release
5.11
Oracle Solaris 11.3 X86
Copyright (c) 1983, 2015, Oracle and/or its affiliates. All rights reserved.
Assembled 06 October 2015

SQL>
SQL> create pfile from spfile;

ファイルが作成されました。

SQL> --2GB
SQL> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

SQL> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> --2GB - 1
SQL> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

SQL> alter system set hash_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set sort_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ERROR:
ORA-12514: TNS:
リスナーは接続記述子でリクエストされたサービスを現在認識していません


警告: Oracleにはもう接続されていません。
SQL> conn sys/oracle@orcl122 as sysdba
ERROR:
ORA-12514: TNS:
リスナーは接続記述子でリクエストされたサービスを現在認識していません


SQL> exit
bash-4.1$ export ORACLE_SID=orcl122
bash-4.1$ sqlplus / as sysdba

SQL*Plus: Release 12.2.0.1.0 Production on 月 2月 19 22:44:47 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SQL> startup
ORACLEインスタンスが起動しました。

Total System Global Area 838860800 bytes
Fixed Size 8790120 bytes
Variable Size 356519832 bytes
Database Buffers 465567744 bytes
Redo Buffers 7983104 bytes
データベースがマウントされました。
データベースがオープンされました。
SQL> show parameter _area_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
bitmap_merge_area_size integer 2147483647
create_bitmap_area_size integer 2147483647
hash_area_size integer 2147483647
sort_area_size integer 2147483647
workarea_size_policy string AUTO
SQL>
SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SQL>
SQL> create spfile from pfile;

File created.


Microsoft Windows

Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
SQL>
SQL> $ver

Microsoft Windows [Version 10.0.16299.125]

SQL> create pfile from spfile;

ファイルが作成されました。

SQL> --2GB
SQL> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

SQL> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> --2GB - 1
SQL> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

SQL> alter system set hash_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set sort_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ERROR:ORA-12514: TNSリスナーは接続記述子でリクエストされたサービスを現在認識していません
SQL> 警告: Oracleにはもう接続されていません。
SQL> exit
Disconnected
C:\Users\discus>set ORACLE_SID=orcl122SQL>
C:\Users\discus>sqlplus / as sysdba
SQL*Plus: Release 12.2.0.1.0 Production on 月 2月 19 22:28:45 2018
Copyright (c) 1982, 2016, Oracle. All rights reserved.
SQL>
アイドル・インスタンスに接続しました。
SQL> create spfile from pfile;
SQL>
File created.
SQL> exit
Disconnected

C:\Users\discus>


ということで、

手動PGA管理でSQL Work Area Sizeを制御する初期化パラメータで指定可能な最大サイズは

2GB -1

ということになります。(HP-UXやAIXは未確認なので情報いただけたら、ここに追記しまーす。:)

次回へつづく。




Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)

| | | コメント (0) | トラックバック (0)

2016年9月22日 (木)

メモ:SIMDあれこれ

SIMDあれこれ自分メモ

primitive: blog / introdunction to SIMD programming
http://i-saint.hatenablog.com/entry/2015/05/26/212441

wikipedia / Streaming SIMD Extensions
https://ja.wikipedia.org/wiki/Streaming_SIMD_Extensions

IA Software User Society / コンパイラー最適化入門: 第1回 SIMD 命令とプロセッサーの関係
http://www.isus.jp/products/c-compilers/compiler_part1/

IA Software User Society / コンパイラー最適化入門: 第2回 SIMD 命令と伝統的な IA 命令
http://www.isus.jp/products/c-compilers/compiler_part2/

IA Software User Society / コンパイラー最適化入門: 第3回 インテル® コンパイラーのベクトル化レポートを活用する
http://www.isus.jp/products/c-compilers/compiler_part3/

IA Software User Society / コンパイラー最適化入門: 第4回 自動ベクトル化はどんな時に行われるか
http://www.isus.jp/products/c-compilers/compiler_part4/

IA Software User Society / コンパイラー最適化入門: 第5回 明示的にベクトル化されたコードを記述する
http://www.isus.jp/products/c-compilers/compiler_part5/

IA Software User Society / コンパイラー最適化入門: 第6回 ベクトル化の裏技集
http://www.isus.jp/products/c-compilers/compiler_part6/

Oracle In-Memory Column Store Internals – Part 1 – Which SIMD extensions are getting used?
http://blog.tanelpoder.com/2014/10/05/oracle-in-memory-column-store-internals-part-1-which-simd-extensions-are-getting-used/

SIMD-Scan: Ultra Fast in-Memory Table Scan using on- Chip Vector Processing Units
http://www.vldb.org/pvldb/2/vldb09-327.pdf

Rethinking SIMD Vectorization for In-Memory Databases
http://www.cs.columbia.edu/~orestis/sigmod15.pdf

Oracle … as usual / SIMD Extensions in and out Oracle 12.1.0.2
https://laurent-leturgez.com/2015/04/22/simd-extensions-in-and-out-oracle-12-1-0-2/

Laurent Leturgez, / Ukoug15 SIMD outside and inside Oracle 12c (12.1.0.2)
http://www.slideshare.net/lolo115/ukoug15-simd-outside-and-inside-oracle-12c-12102

Cellプログラミングチュートリアル Version 0.2c
http://cell.fixstars.com/ps3linux/tutorial/index.html

| | | コメント (0) | トラックバック (0)

2016年7月19日 (火)

DB Tech Showcase 2016 Tokyo - E35 - SQLチューニング総合診療所的予防医学のセッション資料

DB Tech Showcase 2016 Tokyo - E35 - SQLチューニング総合診療所的予防医学のセッション資料を公開しました。

ぼくとつと、性能試験データの質などのことを言い続ける感じになっていたでしょうか?w

ところで、次回のJPOUG主催のイベントの開催が決まりました。セッション内容は現在準備中ですが、確定次第随時更新します。
お申し込みは以下のイベントベージよりお願いします。

JPOUG in 15 minutes #1

| | | コメント (0) | トラックバック (0)

2015年5月17日 (日)

Meetup! JPOUG @ Oracle CloudWorld 2015

意外に心?w 時間? に余裕がないのですが、途切れ途切れでも更新していこうかなと50%いや、60%
ぐらい思ってます。

久々の投稿ですが、一ヶ月前のネタです m(_ _)m

Meetup! JPOUG 開催報告

ショートセッション

スペシャル企画 「な〜んでだ?」


ショートセッションのSQLチューニング総合診療Oracle CloudWorld出張所は、db tech showcase 2014 Tokyoの続編のようなw (ような、じゃなくて、続編ですね、キッパリ

いろいろな性能病は無駄な物理I/Oや論理I/Oを削減していくことが、治療の第一歩!。

な〜んでだ? #3は、時間が押してしまい残り5分程度で駆け足で終わらせてしまったので、「な〜んでだ?」は?な雰囲気にできなかったのが心残りでした (笑

AWRを眺めていると、あるような、ないような時間が見えたりしてモヤモヤすることもあると思うんですよね。
そんなときの参考にならないかな? と思ったネタでした。



Oracle Databaseが雲の向こうに行っちゃっても、人工知能型オプティマイザでも登場してこないかぎり、まだ、まだ、人間のサポートが必要だと思うんだよねw
いつまで必要なのかは、わからないけど。:)

賢い人工知能型オプティマイザがうまれたとして、各インスタンスごとに性格の違うオプティマイザが成長しちゃったら面白い世界になるかもしれない。
と妄想していると、眠れなくなるので、今日はこのへんで。

| | | コメント (0) | トラックバック (0)

2015年1月 2日 (金)

机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (実解答編)

JPOUG Advent Calendar 2014 17日目のエントリーで公開したクイズを実解答編です。
実際にOracle Database 12c 12.1.0.2.0を使ってどの表を駆動表(外部表)にするか確認してみます。


予想はあたるか、外れるか...どのような結果が待っているのか..ワクワクします。 :-)
Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) B71277-02 - 7 結合


前回の予想も参考に....机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

問題1
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引
create table a1 (
id number primary key
, data varchar2(1000)
) nologging
/
create table a2 (
id number primary key
, data varchar2(1000)
) nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
A1 SYS_C0010377 10000 10000 295
A2 SYS_C0010378 2000 2000 59

SELECT
/* SQL01 */
/*+
MONITOR
USE_NL(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
WHERE
a1.id BETWEEN 1 AND 100
/


私の答え(予想)
Nested Loop結合かつ、INNER JOINですから駆動表(外部表)はデータセットの小さい方。
(Nested Loop結合でOUTER JOINだと結合順は固定されるので駆動表は見つけやすいですが...INNER JOINの場合はそうはいかないですよね。ちなみに、最近のOracleだと、OUTER JOINでHash結合だと外部表を入れれかえて最適化するケースもあります。いずれどこかでそのネタをやると思います。)

ただ、結合条件キーはどちらも主キーですし、WHERE a1.id BETWEEN 1 AND 100は、a2表にも適用されることになるでしょうから、a1、a2どちらも最大で100行ヒットする可能性はあります。
どちらでも正解になる可能性はありますが...w (最初から引っ掛けかよw

細かい条件は提示していないので、提示した情報だけで机上で駆動表(外部表)を決めるとすれば、全体のサイズが小さい、a2でいいんじゃないでしょうか?(私ならそうします。机上ですから)
(当初、もう少し詳細な情報を提示しようとしていたのですが忘れてました...なのでa1だと思った方も可能性は五分五分ですね。前提条件不足でした。...ごめんなさい。ごめんなさい。)

Oracle Database 12c 12.1.0.2.0の動き
以下、SQLモニタリングレポートをみると、駆動表は、 a2表でした。 :)
ちなみに、a1を駆動表にした場合は、若干Buffer Getsが増加していました。誤差の範囲ですが...

SQL Monitoring Report

Global Stats
=======================================
| Elapsed | Other | Fetch | Buffer |
| Time(s) | Waits(s) | Calls | Gets |
=======================================
| 0.00 | 0.00 | 3 | 37 |
=======================================

SQL Plan Monitoring Details (Plan Hash Value=3695212685)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 20 | | |
| 1 | NESTED LOOPS | | 20 | 23 | 1 | +0 | 1 | 20 | | |
| 2 | NESTED LOOPS | | 20 | 23 | 1 | +0 | 1 | 20 | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED | A2 | 20 | 3 | 1 | +0 | 1 | 20 | | |
| 4 | INDEX RANGE SCAN | SYS_C0010378 | 20 | 2 | 1 | +0 | 1 | 20 | | |
| 5 | INDEX UNIQUE SCAN | SYS_C0010377 | 1 | | 1 | +0 | 20 | 20 | | |
| 6 | TABLE ACCESS BY INDEX ROWID | A1 | 1 | 1 | 1 | +0 | 20 | 20 | | |
=====================================================================================================================================================

問題2
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題1と同じです。)

SELECT
/* SQL02 */
/*+
MONITOR
USE_HASH(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
/

私の答え(予想)
Hash結合でINNER JOINかつ、WHERE句はないのでこの問題は簡単ですよね! (これは迷わないはず!!!)

Hash結合も外部表はデータセットの小さい方ですから...この場合だと、a2が外部表になるはず。

Oracle Database 12c 12.1.0.2.0の動き
以下、SQLモニタリングレポートから....外部表は、予想通りのa2

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.01 | 0.01 | 0.00 | 135 | 497 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1713954154)
==================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
==================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 2000 | | | |
| 1 | HASH JOIN | | 2000 | 121 | 1 | +0 | 1 | 2000 | 1M | | |
| 2 | TABLE ACCESS FULL | A2 | 2000 | 19 | 1 | +0 | 1 | 2000 | | | |
| 3 | TABLE ACCESS FULL | A1 | 10000 | 102 | 1 | +0 | 1 | 10000 | | | |
==================================================================================================================================


問題3
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
なお、D1とD2の多重度は、D1:D2 = 1:100
(個人的にUMLの多重度表記のほうが好きなので、UML表記の多重度で記述します。

表と索引
create table d1 (
id number
, data varchar2(1000)
) nologging
/
alter table d1 add constraint pk_d1 primary key (id) using index nologging
/
create table d2 (
id number not null
, seq# number not null
, data varchar2(1000)
) nologging
/
alter table d2 add constraint pk_d2 primary key (id, seq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
D1 PK_D1 200 200 9
D2 PK_D2 20000 20000 870

SELECT
/* SQL03 */
/*+
MONITOR
*/
*
FROM
d2
WHERE
EXISTS (
SELECT
1
FROM
d1
WHERE
d1.id = d2.id
AND d1.id IN (1,5)
)
/


私の答え(予想)
これはちょっと意地悪な問題ですが、わかる人ならわかるはず。だと思って(信じて)作った問題です。 :)

最近のオプティマイザは、相関副問合せを可能であれば結合に書き換える(unnest)傾向が強いのをご存知でしょうか? 
となれば、方向はだいだい見えてきます。

d1.id IN (1,5)から最大で2件ヒットすると予想できますよね。
さらに、Nested Loop結合に書き換え、d2を内部表として結合できれば無駄がない。
つまり、駆動表は d1が理想的なはず。

d2を外部表にしてしまうと、WHERE条件がないので結合に置き換えてしまうとHash結合または、d2を全表走査して相関副問合せを都度実行のいづれかになってしまうでしょうね。(unnestの傾向が強いのでHash結合になる可能性が高そう)
ヒットするデータ件数から考えると、どちらも無駄なデータアクセスが多くなる可能性は高いのではないでしょうか。


Oracle Database 12c 12.1.0.2.0の動き
おおお〜。予想通り、相関副問合せは、unnestされて結合に書き換えられています。 さらに 12cの新機能であるAdaptive Joinになっています。興味深い。

で本題である、外部表はどうなっているかというと....pk_d1をindex only scanしてNested Loop結合になっています。つまり、駆動表は、予想通り、d1になっています。Yahoo!
id=1でHash結合がありますが、試行はしたようですが、実際にはNested Loop結合だったようです。

SQL Monitoring Report

Global Stats
=======================================
| Elapsed | Other | Fetch | Buffer |
| Time(s) | Waits(s) | Calls | Gets |
=======================================
| 0.01 | 0.01 | 15 | 44 |
=======================================

SQL Plan Monitoring Details (Plan Hash Value=804853015)
==============================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
==============================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 0 | | |
| 1 | HASH JOIN | | 200 | 5 | 1 | +0 | 1 | 0 | | |
| 2 | NESTED LOOPS | | 200 | 5 | 1 | +0 | 1 | 200 | | |
| 3 | NESTED LOOPS | | 200 | 5 | 1 | +0 | 1 | 200 | | |
| 4 | STATISTICS COLLECTOR | | | | 1 | +0 | 1 | 1 | | |
| 5 | INLIST ITERATOR | | | | 1 | +0 | 1 | 2 | | |
| 6 | INDEX UNIQUE SCAN | PK_D1 | 2 | 1 | 1 | +0 | 2 | 2 | | |
| 7 | INDEX RANGE SCAN | PK_D2 | 1 | 1 | 1 | +0 | 2 | 200 | | |
| 8 | TABLE ACCESS BY INDEX ROWID | D2 | 100 | 2 | 1 | +0 | 200 | 200 | | |
| 9 | INLIST ITERATOR | | | | | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED | D2 | 100 | 2 | | | | | | |
| 11 | INDEX RANGE SCAN | PK_D2 | 1 | 1 | | | | | | |
==============================================================================================================================================


問題4
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題3と同じです。)

SELECT
/* SQL04 */
/*+
MONITOR
*/
*
FROM
d1
INNER JOIN d2
ON
d1.id > d2.id
AND d2.id BETWEEN 1 AND 5
AND d2.seq# BETWEEN 2 AND 4
/

私の答え(予想)
結合条件が ">" であることに気付きましたか? ;)


これは、Nested Loop結合にも、Hash結合にもなりません。 Merge(sort/merge)結合が選択されます。

内部表は索引が利用できないのでソートが発生します。(ソートは避けられない)
外部表では索引を利用してソート処理が回避可能であれば、回避する。

つまりソート処理の影響を最小にしようとするはず、なので...

d1:d2=1:100という比率からd2のソート処理を重いと判断し回避するために外部表にするだろうなぁ〜〜。
私は、d2が外部表になるだろうと予想しています。
(直積じゃないMerge結合なんて最近お目にかかったことないのですが...)

余談)
Hash結合のない時代のOracleで、Merge結合の実行計画を見せられて、なんでソートしてるんですかね〜と、某ベンダーの方に質問されて一瞬固まったことを思い出したw


Oracle Database 12c 12.1.0.2.0の動き
良かったw。 予想どおりソートを回避したようで、 外部表は、 d2ですね。

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.02 | 0.01 | 0.01 | 198 | 48 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=705618498)
=============================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=============================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 2955 | | | |
| 1 | MERGE JOIN | | 3966 | 10 | 1 | +0 | 1 | 2955 | | | |
| 2 | TABLE ACCESS BY INDEX ROWID | D2 | 20 | 4 | 1 | +0 | 1 | 15 | | | |
| 3 | INDEX RANGE SCAN | PK_D2 | 20 | 3 | 1 | +0 | 1 | 15 | | | |
| 4 | SORT JOIN | | 199 | 6 | 1 | +0 | 15 | 2955 | 145K | | |
| 5 | TABLE ACCESS FULL | D1 | 199 | 5 | 1 | +0 | 1 | 199 | | | |
=============================================================================================================================================


問題5
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引定義
create table b1 (
id number
, data varchar2(1000)
) nologging
/
alter table b1 add constraint pk_b1 primary key(id) using index nologging
/
create table b3 (
id number
, seq# number
, data varchar2(1000)
) nologging
/
alter table b3 add constraint pk_b3 primary key (id, seq#) using index nologging
/
create table b2 (
id number
, seq# number
, subseq# number
, data varchar2(1000)
) nologging
/
alter table b2 add constraint pk_b2 primary key (id ,seq#, subseq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
B1 PK_B1 20000 20000 870
B2 PK_B2 5000 5000 228
B3 PK_B3 500 500 23

ERDと多重度
b1 : b3 = 1 : 0..2
b3 : b2 = 1 : 0..10
(UML表記の多重度で記述しています。
20141221_120733

SELECT
/* SQL05 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
INNER JOIN b3
ON
b1.id = b3.id
INNER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
INNER JOINでHash結合かつ、WHERE句もないので簡単な問題ですよね。

外部表は、b3

ついでに、続けると、 b3とb2を結合すると最大で5000件、b3とb1を結合した場合、最大で500件なので、 結合順として理想的なのは、 b3, b1, b2 ですよね。


Oracle Database 12c 12.1.0.2.0の動き
結果は、予想通りでした〜。これは簡単だから!。

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.06 | 0.04 | 0.02 | 335 | 1476 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=3711653655)
===================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
===================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 5000 | | | |
| 1 | HASH JOIN | | 5000 | 315 | 1 | +0 | 1 | 5000 | 1M | | |
| 2 | HASH JOIN | | 500 | 247 | 1 | +0 | 1 | 500 | 1M | | |
| 3 | TABLE ACCESS FULL | B3 | 500 | 9 | 1 | +0 | 1 | 500 | | | |
| 4 | TABLE ACCESS FULL | B1 | 20000 | 238 | 1 | +0 | 1 | 20000 | | | |
| 5 | TABLE ACCESS FULL | B2 | 5000 | 68 | 1 | +0 | 1 | 5000 | | | |
===================================================================================================================================


問題6
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題5と同じです。)

SELECT
/* SQL06 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
LEFT OUTER JOIN b3
ON
b1.id = b3.id
LEFT OUTER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
OUTER JOINなのですが、Hash結合なので、多分、予想通りにはならないだろうな〜〜と。

最近のオプティマイザは外部結合かつHash結合の場合、外部表と内部表をデータセットサイズに応じて入れ替える傾向が強いんですよね。PGAの使用サイズが小さくなるように....

とは言っても、机上の話なので、入れ替えないとしたらどうなるだろう..と、外部結合なので、内部表は、そのまま内部表として結合したほうがわかりやすいといえばわかりやすい。
(統計情報に乖離がある場合には入れ替えないほうが良い結果だったりすることもあります。)

注)()内を先に結合するものとします。
入れ替えなかった場合は、記述したままなので、(外部表、内部表)=外部表、内部表ということで、 (b1, b3), b2
データセットの小さい順に従えば、外部表と、内部表をいれかえて、b3, b1この結果セットの最大件数は元の外部表がb1ですから20,250件、b2が5,000件なので、また外部表と内部表を入れ替えて、結果として、b2, (b3, b1)ってことに...
(内部的に結合順が入れ替えられ、Right Joinに置き換えられることが多いんですよね〜いつ頃からだろう...10gあたりか。)

で、机上ならどちらを取るかということですが、机上では、ひとまず、SQL文の通りとしておくケースが多いです。あくまで私の場合ですよ。(難しいんですよ、これ。外れる可能性大w)
オプティマイザが内部的に外部表と内部表を入れ替えて問題がなければよし、問題があれば、内部的な入れ替えをヒントで止めるというのが私の基本方針なので、この辺りは、チューナーさんのさじ加減次第じゃないかなぁ、と思っています。

あはは、むずかし過ぎたか... 机上だと orz.

Oracle Database 12c 12.1.0.2.0の動き
結果を見てみると〜〜〜〜〜っ。恐れていたw 外部表と内部表の置き換え操作(LEFT OUTERがRIGHT OUTER)になってますね〜っ。あはは、見事に、外れました....orz.

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.34 | 0.21 | 0.13 | 1651 | 2648 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1453650298)
======================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
======================================================================================================================================
| 0 | SELECT STATEMENT | | | | 4 | +0 | 1 | 24750 | | | |
| 1 | HASH JOIN RIGHT OUTER | | 203K | 316 | 4 | +0 | 1 | 24750 | 3M | | |
| 2 | TABLE ACCESS FULL | B2 | 5000 | 68 | 1 | +0 | 1 | 5000 | | | |
| 3 | HASH JOIN RIGHT OUTER | | 20250 | 247 | 4 | +0 | 1 | 20250 | 1M | | |
| 4 | TABLE ACCESS FULL | B3 | 500 | 9 | 1 | +0 | 1 | 500 | | | |
| 5 | TABLE ACCESS FULL | B1 | 20000 | 238 | 4 | +0 | 1 | 20000 | | | |
======================================================================================================================================

問題ごとの解説をおまけで何回かに分けて、書いた方が良さそうだなw

ということで、おまけエントリーを書くかも...し、れ、な、い。



机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!!
机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

| | | コメント (1) | トラックバック (0)

2015年1月 1日 (木)

机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

JPOUG Advent Calendar 2014 17日目のエントリーで公開したクイズの予想解答編です。
机上ですし、限られた情報しか提示していないので、実際にやってみたら違う...ということも十分ありえます。 (^^;;;;


外部表はどれがいいか予想できると、いろいろな制限のあるチューニング現場では役立つこともあるんです。LEADINGヒント等で結合順を考える場合にも役立ちますyo!
机上の予想なので間違うこともあります。オプティマイザも統計情報から予想しているわけで(Adaptiveな機能を除く)ハズすことも多いですから... (^^

Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) B71277-02 - 7 結合


問題1
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引
create table a1 (
id number primary key
, data varchar2(1000)
) nologging
/
create table a2 (
id number primary key
, data varchar2(1000)
) nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
A1 SYS_C0010377 10000 10000 295
A2 SYS_C0010378 2000 2000 59

SELECT
/* SQL01 */
/*+
MONITOR
USE_NL(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
WHERE
a1.id BETWEEN 1 AND 100
/

私の答え(予想)
Nested Loop結合かつ、INNER JOINですから駆動表(外部表)はデータセットの小さい方。
(Nested Loop結合でOUTER JOINだと結合順は固定されるので駆動表は見つけやすいですが...INNER JOINの場合はそうはいかないですよね)

ただ、結合条件キーはどちらも主キーですし、WHERE a1.id BETWEEN 1 AND 100は、a2表にも適用されることになるでしょうから、a1、a2どちらも最大で100行ヒットする可能性はあります。
どちらでも正解になる可能性はありますが...w (最初から引っ掛けかよw

細かい条件は提示していないので、提示した情報だけで机上で駆動表(外部表)を決めるとすれば、全体のサイズが小さい、a2でいいんじゃないでしょうか?(私ならそうします。机上ですから)
(当初、もう少し詳細な情報を提示しようとしていたのですが忘れてました...なのでa1だと思った方も可能性は五分五分ですね。前提条件不足でした。...ごめんなさい。ごめんなさい。)


問題2
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題1と同じです。)

SELECT
/* SQL02 */
/*+
MONITOR
USE_HASH(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
/

私の答え(予想)
Hash結合でINNER JOINかつ、WHERE句はないのでこの問題は簡単ですよね! (これは迷わないはず!!!)

Hash結合も外部表はデータセットの小さい方ですから...この場合だと、a2が外部表になるはず。

問題3
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
なお、D1とD2の多重度は、D1:D2 = 1:100
(個人的にUMLの多重度表記のほうが好きなので、UML表記の多重度で記述します。

表と索引
create table d1 (
id number
, data varchar2(1000)
) nologging
/
alter table d1 add constraint pk_d1 primary key (id) using index nologging
/
create table d2 (
id number not null
, seq# number not null
, data varchar2(1000)
) nologging
/
alter table d2 add constraint pk_d2 primary key (id, seq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
D1 PK_D1 200 200 9
D2 PK_D2 20000 20000 870

SELECT
/* SQL03 */
/*+
MONITOR
*/
*
FROM
d2
WHERE
EXISTS (
SELECT
1
FROM
d1
WHERE
d1.id = d2.id
AND d1.id IN (1,5)
)
/


私の答え(予想)
これはちょっと意地悪な問題ですが、わかる人ならわかるはず。だと思って(信じて)作った問題です。 :)

最近のオプティマイザは、相関副問合せを可能であれば結合に書き換える(unnest)傾向が強いのをご存知でしょうか? 
となれば、方向はだいだい見えてきます。

d1.id IN (1,5)から最大で2件ヒットすると予想できますよね。
さらに、Nested Loop結合に書き換え、d2を内部表として結合できれば無駄がない。
つまり、駆動表は d1が理想的なはず。

d2を外部表にしてしまうと、WHERE条件がないので結合に置き換えてしまうとHash結合または、d2を全表走査して相関副問合せを都度実行のいづれかになってしまうでしょうね。(unnestの傾向が強いのでHash結合になる可能性が高そう)
ヒットするデータ件数から考えると、どちらも無駄なデータアクセスが多くなる可能性は高いのではないでしょうか。

問題4
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題3と同じです。)

SELECT
/* SQL04 */
/*+
MONITOR
*/
*
FROM
d1
INNER JOIN d2
ON
d1.id > d2.id
AND d2.id BETWEEN 1 AND 5
AND d2.seq# BETWEEN 2 AND 4
/

私の答え(予想)
結合条件が ">" であることに気付きましたか? ;)


これは、Nested Loop結合にも、Hash結合にもなりません。 Merge(sort/merge)結合が選択されます。

内部表は索引が利用できないのでソートが発生します。(ソートは避けられない)
外部表では索引を利用してソート処理が回避可能であれば、回避する。

つまりソート処理の影響を最小にしようとするはず、なので...

d1:d2=1:100という比率からd2のソート処理を重いと判断し回避するために外部表にするだろうなぁ〜〜。
私は、d2が外部表になるだろうと予想しています。
(直積じゃないMerge結合なんて最近お目にかかったことないのですが...)

余談)
Hash結合のない時代のOracleで、Merge結合の実行計画を見せられて、なんでソートしてるんですかね〜と、某ベンダーの方に質問されて一瞬固まったことを思い出したw


問題5
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引定義
create table b1 (
id number
, data varchar2(1000)
) nologging
/
alter table b1 add constraint pk_b1 primary key(id) using index nologging
/
create table b3 (
id number
, seq# number
, data varchar2(1000)
) nologging
/
alter table b3 add constraint pk_b3 primary key (id, seq#) using index nologging
/
create table b2 (
id number
, seq# number
, subseq# number
, data varchar2(1000)
) nologging
/
alter table b2 add constraint pk_b2 primary key (id ,seq#, subseq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
B1 PK_B1 20000 20000 870
B2 PK_B2 5000 5000 228
B3 PK_B3 500 500 23

ERDと多重度
b1 : b3 = 1 : 0..2
b3 : b2 = 1 : 0..10
(UML表記の多重度で記述しています。
20141221_120733

SELECT
/* SQL05 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
INNER JOIN b3
ON
b1.id = b3.id
INNER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
INNER JOINでHash結合かつ、WHERE句もないので簡単な問題ですよね。

外部表は、b3

ついでに、続けると、 b3とb2を結合すると最大で5000件、b3とb1を結合した場合、最大で500件なので、 結合順として理想的なのは、 b3, b1, b2 ですよね。


問題6
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題5と同じです。)

SELECT
/* SQL06 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
LEFT OUTER JOIN b3
ON
b1.id = b3.id
LEFT OUTER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
OUTER JOINなのですが、Hash結合なので、多分、予想通りにはならないだろうな〜〜と。

最近のオプティマイザは外部結合かつHash結合の場合、外部表と内部表をデータセットサイズに応じて入れ替える傾向が強いんですよね。PGAの使用サイズが小さくなるように....

とは言っても、机上の話なので、入れ替えないとしたらどうなるだろう..と、外部結合なので、内部表は、そのまま内部表として結合したほうがわかりやすいといえばわかりやすい。
(統計情報に乖離がある場合には入れ替えないほうが良い結果だったりすることもあります。)


注)()内を先に結合するものとします。
入れ替えなかった場合は、記述したままなので、(外部表、内部表)=外部表、内部表ということで、 (b1, b3), b2
データセットの小さい順に従えば、外部表と、内部表をいれかえて、b3, b1。
この結果セットの最大件数は元の外部表がb1ですから20,250件、b2が5,000件なので、また外部表と内部表を入れ替えて、結果として、b2, (b3, b1)ってことに...
(内部的に結合順が入れ替えられ、Right Joinに置き換えられることが多いんですよね〜いつ頃からだろう...10gあたりか。)

で、机上ならどちらを取るかということですが、机上では、ひとまず、SQL文の通りとしておくケースが多いです。あくまで私の場合ですよ。(難しいんですよ、これ。外れる可能性大w)
オプティマイザが内部的に外部表と内部表を入れ替えて問題がなければよし、問題があれば、内部的な入れ替えをヒントで止めるというのが私の基本方針なので、この辺りは、チューナーさんのさじ加減次第じゃないかなぁ、と思っています。

あはは、むずかし過ぎたか... 机上だと orz.

結合順がほぼ想定できる場合、状況次第で変わりそうな結合順、いろいろありますよね。
オプティマイザの気持ちになって考えてみると、いろいろ気づくことも多いんじゃないかなぁ。と思います。

オプティマイザってソートが嫌いなんだ〜、とか、PGA少ない方が好きなんだ〜とか....


役に立ったか、どうなのか不明なオチになりましたが.....

本年もよろしくお願いいたします。 m(_ _)m


あ、そうそう、一つ忘れてました。

次回は、Oracle Database 12c 12.1.0.2.0を使って試したオプティマイザが選択した駆動表(外部表)がどれだったかのか公開する予定です。



机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!!

| | | コメント (0) | トラックバック (0)

2014年8月23日 (土)

IO Resource Manager 関係のメモ

黒い画面の好きなyskwkzhrさんも気に入るよね、きっと :)


PART 1 of 4 "Test interdatabase consolidation with Oracle Exadata IORM"

PART 2 of 4 "Test interdatabase consolidation with Oracle Exadata IORM"

PART 3 of 4 "Test intradatabase consolidation with Oracle Exadata IORM"

PART 4 of 4 "Test intradatabase consolidation with Oracle Exadata IORM"


ここで使ってるモニターリングツールはSwingbenchでも有名なDominic Gilesさんのサイトね。ステキです:)
Database Time Monitor
CPU Monitor
Monitor DB

| | | コメント (0) | トラックバック (0)

2014年8月19日 (火)

pivotとSQL*PlusとSETコマンドと #2

昨日の続きですw

v$sys_time_modelの列データを行データへpivotで変換し、かつシェルで定期的に取得してみたものの、出力形式は今ひとつ。 iostatやvmstatのように出力したい。

前回のエントリーの出力結果は以下の通りでした。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:10:47 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66172667 7714783 127809578 27114874

経過: 00:00:00.02
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66179178 7720782 128085853 27160868

経過: 00:00:00.01
21:10:57 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66180864 7723782 128249102 27197862

経過: 00:00:00.00

じゃまな出力は、以下の通り

  • SQL*Plusのプロンプト
  • ヘッダー行
  • 経過時間
  • 出力の状態から見て、余分な改行

そして、不足している出力はメトリックのログ取得時のタイムスタンプ


以下のSQL*Plusシステム変数を調整追加すればなんとかなりそうな感じ。。。。

  • SQL*Plusのプロンプトは、 set sqlp "" で抑止。
  • ヘッダー行は、 set head off で抑止
  • 経過時間は、 set timi off で抑止
  • 余分な改行は、 たぶん、 set newp none で抑止
  • 直接関係ないけど、Excelにコピペするときにじゃまになるので set tab offでタブの混入抑止


不足しているログ取得時のタイムスタンプは、シェルのdateコマンドで取得した日時をSQL文に埋め込むことでなんとかなりそうな気がします。

と頭に浮かんだら忘れないうちに試してみますよ〜

★横に長くてごめんなさい。時間取れたらSyntaxHighlighterとか入れます詐欺 m(_ _)m

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off tab off sqlp \"\" head off newp none"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
echo "SELECT db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
sleep 10
done
) | sqlplus /nolog


ん〜〜〜〜〜、なんか、惜しい!!!!  いい感じにななったのに。... しばし考える。。。。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:45:43 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

21:45:43 > 接続されました。
21:45:44 SCOTT> 67126005 8476651 182158980 37408311

67129138 8479651 182310474 37445305


きた〜〜〜、神が降りてきたのでちょっと書き換えた

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off sqlp \"\""
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
t=`date +'%DT%T'`
echo "SELECT SUBSTR('${t}',1,INSTR('${t}','T')-1) as logged_date,SUBSTR('${t}',INSTR('${t}','T')+1) as logged_time,db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
echo "set head off newp none"
sleep 10
done
) | sqlplus /nolog


ん〜〜〜、まだ余計な改行というか空行がある。。。なんだこれ。。。。再び、考え中........ あ、あれだ! 出力行数を返すやつ!

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:56:21 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

21:56:21 > 接続されました。
21:56:22 SCOTT>
LOGGED_D LOGGED_T DB_TIME DB_CPU BG_TIME BG_CPU
-------- -------- ------------------------------- ------------------------------- ------------------------------- -------------------------------
08/17/14 21:56:21 67688748 8937570 198751230 40650823

08/17/14 21:56:31 67695983 8945569 199012673 40699815


で、できたのがこれ。

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off sqlp \"\" feed off"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
t=`date +'%DT%T'`
echo "SELECT SUBSTR('${t}',1,INSTR('${t}','T')-1) as logged_date,SUBSTR('${t}',INSTR('${t}','T')+1) as logged_time,db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
echo "set head off newp none"
sleep 10
done

) | sqlplus /nolog

出力結果は以下のようになり、 iostatやvmstat風にヘッダー行は一度だけ、その後、定期的に取得されるメトリックが出力されていくイメージに! :)

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 22:02:59 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

22:02:59 > 接続されました。
22:02:59 SCOTT>
LOGGED_D LOGGED_T DB_TIME DB_CPU BG_TIME BG_CPU
-------- -------- ------------------------------- ------------------------------- ------------------------------- -------------------------------
08/17/14 22:02:59 68026469 9252517 211057107 42641516
08/17/14 22:03:09 68034074 9261515 211217278 42686507
08/17/14 22:03:19 68039874 9267514 211414489 42730501
08/17/14 22:03:29 68045221 9272514 211576132 42769498
08/17/14 22:03:39 68051396 9276513 211844118 42862480
^C
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing optionsとの接続が切断されました。


Enjoy!



pivotとSQL*PlusとSETコマンドと

| | | コメント (0) | トラックバック (0)

2014年8月18日 (月)

pivotとSQL*PlusとSETコマンドと

Oracle Databaseの性能試験で以下のようなメトリックを定期取得して、分析やビジュアライズに利用している方も多いと思います。(思ってます。)
でも、ですねぇ。
以下のv$sys_time_model動的パフォーマンスビューも典型例なのですが、列持ちのメトリックが多いので集計にはかなり苦労します。というか、してます。

v$sys_time_model動的パフォーマンスビューの出力例)

SCOTT> SELECT stat_name, value from v$sys_time_model;

STAT_NAME VALUE
---------------------------------------------------------------- ----------
DB time 64073868
DB CPU 6549986
background elapsed time 42988476
background cpu time 11080311

・・・以下略・・・

列持ちなんですよね、 列持ち!(しつこいw

行持ちにしたいですよね。 どう料理しましょう。 まさか、手作業ではやってないですよね。

SQL文でやってますよね! 私もそうです。
ちなみに、UNION連打はしてませんからね!(キリっ!

昔はほかに手がなかったのですが、Oracle11gから便利で比較的読みやすい構文がサポートされています。

列持ちを行持ちにするといえば....そうです、あれです。 pivot

ということで、

pivotを使って、v$sys_time_modelを例にオレオレv$sys_time_modelを作り出してみます。
(これができれば、数あるオラクルの動的パフォーマンスビューをもっと好きになれるんじゃないかなぁ。と思います。)

では、早速

v$sys_time_modelの stat_name列の列値が、'DB time'、'DB CPU'、 'background elapsed time'、'background cpu time'の4つのメトリックを列持ちから行持ちに変え、オレオレv$sys_time_model作り出すSQL文です。
ビューは作りませんけど (^^;;;

SELECT 
db_time
,db_cpu
,bg_time
,bg_cpu
FROM
(
SELECT
stat_name
,value
FROM
v$sys_time_model
)
PIVOT
(
MAX(value)
FOR stat_name IN
(
'DB time' AS db_time
,'DB CPU' AS db_cpu
,'background elapsed time' AS bg_time
,'background cpu time' AS bg_cpu
)
)
;


これを実行すると以下のような結果になります。本来4行なのですが、1行にできるんです! 便利ですね。 pivot (pivotの逆の操作をする unpivotもあります)

   DB_TIME     DB_CPU    BG_TIME     BG_CPU
---------- ---------- ---------- ----------
63895360 6364016 32965031 8718671

いい感じになってきました。


しかし、まだ物足りないですよね。 そう!
取得時のタイムスタンプとか、例えば iostat や vmstatのように定期的に取得したくなってきます!!!!

「門外不出のOracle現場ワザ」 第5章 DBアクセスの空白地帯 コネクションプーリングを極めるの定期的にSQLを発行するシェルを作成するには? でも解説されているのでこの方法で取得されている方も多いと思います。:)

ただ、そのまんまだと以下のような出力になってしまいます。 iostatやvmstatの出力をイメージしちゃうと余分な表示が多いわけです。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:10:47 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66172667 7714783 127809578 27114874

経過: 00:00:00.02
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66179178 7720782 128085853 27160868

経過: 00:00:00.01
21:10:57 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66180864 7723782 128249102 27197862

経過: 00:00:00.00

前述の出力は以下のコードで取得したのですが、実はそんなに手を加えなくても vmstatやiostatのような出力形式で、みなさんの大好きなExcelで集計しやすくすることができるんですよ。
どこを変更すればよいか分かった人、手を挙げて〜〜〜〜っ!

注)scottユーザにselect any dictionaryシステム権限付けてます。

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi on time on"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
echo "SELECT db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
sleep 10
done
) | sqlplus /nolog


つづきは、次のエントリーで :)

| | | コメント (0) | トラックバック (0)

2014年7月26日 (土)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #3

つづきです。
バッファキャッシュにヒットした場合はどうかというと、物理I/Oは発生しないので、そのまんまの結果ですよね。(^^;;;

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 195 0.11 0.18 0 6287 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 0.11 0.18 0 6287 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6287 pr=0 pw=0 time=206651 us)
2907 2907 2907 NESTED LOOPS (cr=3380 pr=0 pw=0 time=116870 us cost=5362 size=1744812 card=2851)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID BATCHED HIGH_CLUSTERING_FACTOR (cr=3108 pr=0 pw=0 time=42395 us cost=2861 size=872406 card=2851)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=0 pw=0 time=12292 us cost=8 size=0 card=2851)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=272 pr=0 pw=0 time=36902 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=0 pw=0 time=37280 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.00
SQL*Net message from client 195 0.00 0.15
SQL*Net more data to client 193 0.00 0.01
********************************************************************************

12cで、optimizer_features_enable='11.2.0.1'にしてバッファキャッシュをクリアすれば、11gと同じ実行計画(TABLE ACCESS BY INDEX ROWID)になって、db file parallel readになるよね!

という確認もしておいた!

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.42 1.59 0 0 0 0
Execute 1 0.00 0.01 0 0 0 0
Fetch 195 18.34 61.02 2450 6287 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 18.77 62.63 2450 6287 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6287 pr=2450 pw=0 time=92881090 us)
2907 2907 2907 NESTED LOOPS (cr=3380 pr=2232 pw=0 time=59488238 us cost=5362 size=1744812 card=2851)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=3108 pr=2221 pw=0 time=31142410 us cost=2861 size=872406 card=2851)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=7 pw=0 time=3493710 us cost=8 size=0 card=2851)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=272 pr=11 pw=0 time=10377721 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=218 pw=0 time=13380036 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.21
Disk file operations I/O 1 0.00 0.00
db file sequential read 452 0.21 1.54
SQL*Net message from client 195 15.35 16.71
SQL*Net more data to client 193 0.00 0.47
db file parallel read 190 0.05 5.91
********************************************************************************


整形前SQLトレースより抜粋 (db file parallel readが索引で現れている

WAIT #140513423347200: nam='db file parallel read' ela= 16615 files=1 blocks=13 requests=13 obj#=93726 tim=6258017207

straceより抜粋...io_submit()、だよね〜。:)

io_submit(140513465466880, 13, ...中略... ) = 13


最後に、11g R2 11.2.0.1で同じことを確認(確認するまでもないんだけどね)

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 169 0.58 6.88 2148 5439 0 2509
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 171 0.58 6.88 2148 5439 0 2509

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84 (SCOTT)

Rows Row Source Operation
------- ---------------------------------------------------
2509 NESTED LOOPS (cr=5439 pr=2148 pw=0 time=19292592 us)
2509 NESTED LOOPS (cr=2930 pr=1931 pw=0 time=24524478 us cost=5012 size=1530612 card=2501)
2509 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2683 pr=1922 pw=0 time=24392808 us cost=2510 size=765612 card=2502)
2509 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=174 pr=5 pw=0 time=11616 us cost=8 size=0 card=2502)(object id 113222)
2509 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=247 pr=9 pw=0 time=0 us cost=0 size=0 card=1)(object id 113220)
2509 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2509 pr=217 pw=0 time=0 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2509 NESTED LOOPS
2509 NESTED LOOPS
2509 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2509 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2509 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2509 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 169 0.00 0.00
SQL*Net message from client 169 24.57 25.54
Disk file operations I/O 1 0.00 0.00
db file parallel read 167 0.09 5.66
db file sequential read 357 0.01 0.92
SQL*Net more data to client 167 0.00 0.01
********************************************************************************

11gまでは、実行計画上には現れず実行時に内部的に行っていたI/O最適化の動きが、12cからは実行計画上でも確認できるようになったというのは分かり易くていい。
時間あったら、v$sesstatも載せるかも。 :)

さて、12c 12.1.0.2.0のダウンロードも終わったので、別の楽しみが増えましたよね、みなさん!

ダウンロード! しましたか〜〜〜〜〜っ! :)

みなさんの、12c ネタ祭りを楽しみにしております。 (^^



TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2

| | | コメント (0) | トラックバック (0)

2014年7月25日 (金)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2

Oracle Database 12c R1 12.1.0.2.0 EEが公開されましたが、12.1.0.1.0で試してあります。

注)バッファキャッシュをクリア後にSQL文を実行してあります。

SELECT
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
FROM
low_clustering_factor t1
INNER JOIN high_clustering_factor t2
ON
t1.id = t2.id
WHERE
t2.id BETWEEN 30001 AND 35000

上記のSQL文でTABLE ACCESS BY INDEX ROWID BATCHED操作を行わせてみた。

以下、SQLトレースの結果です。予想通り db file parallel readが発生しています。
高いクラスタリングファクタを持つ索引をアクセス、不連続なROWDをかき集めかき集めたROWIDをある程度まとめて1度のI/Oリクエストでバッファキャッシュへ読み込む動作ですよね!

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.07 6 6 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 195 0.48 4.92 2288 6338 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 0.50 4.99 2294 6344 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6338 pr=2288 pw=0 time=2150320 us)
2907 2907 2907 NESTED LOOPS (cr=3431 pr=2087 pw=0 time=6036546 us cost=3368 size=1159620 card=3514)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID BATCHED HIGH_CLUSTERING_FACTOR (cr=3107 pr=2082 pw=0 time=5948676 us cost=1012 size=579810 card=3514)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=0 pw=0 time=15435 us cost=11 size=0 card=3514)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=324 pr=5 pw=0 time=56981 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=201 pw=0 time=312848 us cost=1 size=165 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.00
db file sequential read 209 0.03 0.32
SQL*Net message from client 195 19.95 21.21
db file parallel read 194 0.09 4.33
SQL*Net more data to client 193 0.00 0.01
********************************************************************************

SQLトレース(整形前)のトレースログより : db file parallel read (この時は、最小 3blocks〜15blocksの範囲で行われていた。

WAIT #139657829206792: nam='db file parallel read' ela= 64710 files=1 blocks=12 requests=12 obj#=93726 tim=4037078687

ちなみにstraceでみるとio_submitが呼び出されていて11gの頃と変わりはなかった :)

つまり、実行計画上、db file parallel readやるからね!! 物理I/Oを行うときは! という意思が明確に表示されるようになった。 
分かり易くなっていい!!!!
これだと物理I/Oがあれば、db file parallel readやってるな〜と実行計画を見ただけで想像できますな:)




TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1

| | | コメント (0) | トラックバック (0)

2014年7月23日 (水)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1

Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) - ROWIDによる表アクセス: 例にも記載され、昨年試していたときにも現れていた、table access by index rowid batched という操作って、実際のところdb file parallel readをやるよ! と実行計画に表すようになったということだよね! それだけだよね。

ということを確認してみた。(TODOと書いてから1年経過してしまった orz )

詳細はあした以降に書きまする。 :)

20140723_220538

| | | コメント (0) | トラックバック (0)

2014年6月11日 (水)

html5j パフォーマンス部 @ DeNA へ参加してきた

html5j パフォーマンス部 第二回勉強会へ参加してきた。

勉強会2連チャンでした :)

html5jとか勉強会(そんな名称になる前から参加してました :))には何度か参加していますが、パフォーマンスという単語に脊髄反応して当日のセッション内容を確認もぜず参加してしまったw のは内緒。


データベースが遅い! と呼ばれ、調査すると本当に遅いのはブラウザの上、なんてことも意外に多いんですよね。

10301204_712086918857590_3502558762

講義を受けてるみたいで脳に汗かいた。 

1.品質管理と統計学「IT業界へどうつなぐ?」 - 成蹊大学 / 中西寛子
2.ソフトウェア工学の基礎を学ぶ - 大槻肇


ところで、
今回で2回目のようですが、16回先までテーマが決まっているようでおどろきました。
他の勉強会を運営している方がってどうしてるんだろ?。  

そして、DeNAさんのセミナー会場いいですよね〜〜〜。
都合がつけば次回も参加すると思います。 :)

| | | コメント (0) | トラックバック (0)