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

帰ってきた! 標準はあるにはあるが癖の多いSQL #19 - c_alias の癖(おまけ)

書き漏らしていたことに気づいたので、
今日のテーマは、

帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出る

の癖のおまけw

Oracle Database、PostgreSQL、MySQLの表エイリアスと列エイリアスの文法の癖の違いを思い出してもらった上で、今日の列エイリアスの癖も合わせて覚えておくと良いかもしれません。

とは言え、サブクエリで該当構文を使うことはあまり無さそうな気はします。
どちらかというと、表値コンストラクタの記述で効果的な構文だと思うので。。。(表値コンストラクタネタは、別エントリーにて)

 

まず、帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出るの列エイリアスの癖の復習から。

 

インラインビュー(サブクエリ)の列エイリアスを以下のような構文で書けるDBと書けないDBを見てみましょう。

SELECT
*
FROM
(
SELECT
empno
, 'a'
FROM
emp
) t01 (empno, dummy_col)
;

 

Oracle Database 23ai ver. 23.8 Oracle Databaseではこの構文は許されていませんよね! 表値コンストラクタがサポートされた流れで通常のサブクエリでも使えるようになるかもしれませんが、、、、。

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> l
1 SELECT
2 *
3 FROM
4 (
5 SELECT
6 empno
7 , 'a'
8 FROM
9 emp
10 ) t01 (empno, dummy_col)
11*
COTT@localhost:1521/freepdb1> /
) t01 (empno, dummy_col)
*
行10でエラーが発生しました。:
ORA-03048: SQL予約語'('は、'..., 'a'
FROM
emp
) t01 'の後では構文的に有効ではありません ヘルプ:
https://docs.oracle.com/error-help/db/ora-03048/

 

 

 

MySQL 8.4 MySQLでは可能でしたよね。

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.4.6 |
+-----------+

mysql> SELECT
-> *
-> FROM
-> (
-> SELECT
-> empno
-> , 'a'
-> FROM
-> emp
-> ) t01 (empno, dummy_col)
-> ;
+-------+-----------+
| empno | dummy_col |
+-------+-----------+
| 7782 | a |
| 7839 | a |

...略...

| 7844 | a |
| 7900 | a |
+-------+-----------+
14 rows in set (0.01 sec)

 

 

PostgreSQL 17.5 PostgreSQLでも可能ですよね。
ここまでは、帰ってきた! 標準はあるにはあるが癖の多いSQL #18 - t_alias と c_alias にも癖が出るでも書いてました。

perftestdb=> SELECT
perftestdb-> *
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> , 'a'
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) t01 (empno, dummy_col)
perftestdb-> ;
empno | dummy_col
-------+-----------
7369 | a
7499 | a

...略...

7902 | a
7934 | a
(14 rows)

 

で、書き忘れていたのは以降の癖。

以下のように、複数ある列の一方だけのつもりで、列エイリアスを書いたら。。。。どうなるか。

どうなると思います?

SELECT
*
FROM
(
SELECT
empno
, 'a'
FROM
emp
) t01 (dummy_col)
;

 

 

Oracle Database 23ai 23.8 Oracle Databaseではそもそもサブクエリに対するこの構文は許されていないのでエラーです。

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 *
3 FROM
4 (
5 SELECT
6 empno
7 , 'a'
8 FROM
9 emp
10 ) t01 (dummy_col)
11*
10:30:52 SCOTT@localhost:1521/freepdb1> /
) t01 (dummy_col)
*
行10でエラーが発生しました。:
ORA-03048: SQL予約語'('は、'..., 'a'
FROM
emp
) t01 'の後では構文的に有効ではありません ヘルプ:
https://docs.oracle.com/error-help/db/ora-03048/

 

MySQL 8.4.5 おお! 変化しましたね。 MySQLでは全列定義しないとエラーになるようです。全列のエイリアスを指定するか、しないかのどちらかということですね! わかりやすい気がしますね。これ。

mysql> SELECT
-> *
-> FROM
-> (
-> SELECT
-> empno
-> , 'a'
-> FROM
-> emp
-> ) t01 (dummy_col)
-> ;
ERROR 1353 (HY000): In definition of view, derived table or common table expression, SELECT list and column names list have different column counts

 

PostgreSQL 17.5 では、真打w PostgreSQL。PostreSQLでは一部でも構文エラーにはならない!!!! まじか、じゃ、指定した列エイリアスはどの列を対象にするの?!!!!!!

サブクエリの列数と同数の列エイリアスを指定しない場合、SELECTリストの列順に対応させているだけのようですね!。この例では、empno列と、無名の列の2列がありますが、列エイリアスで置き換えられたのは、最初のempno列!です

エラーにならないだけに、ちょっと注意が必要な癖ですよね!
エラーにはならないことが、正しいというわけでもないので。。。このケースでは意図しない列名を列エイリアスで置換してしまっているわけで。。MySQLのようにエラーにしてくれた方が嬉しいのではないだろうか。

perftestdb=> SELECT
perftestdb-> *
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> , 'a'
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) t01 (dummy_col)
perftestdb-> ;
dummy_col | ?column?
-----------+----------
7369 | a
7499 | a

...略...

7902 | a
7934 | a
(14 rows)

 

最後に、サブクエリで、各列毎に列エイリアスを指定した方が可読性は良いと考えているので、その比較用w
(前述した列エイリアス構文が、その威力を発揮するのは表値コンストラクタを利用する場合ぐらいになるだろうと思っています)

 

こんな感じでも、

SELECT
*
FROM
(
SELECT
empno
, 'a' AS dummy_col
FROM
emp
) t01
;

 

下記のような場合でも読みやすいとおもいます:)

SELECT
*
FROM
(
SELECT
empno AS empno
, 'a' AS dummy_col
FROM
emp
) t01
;

 

 

Oracle

SCOTT@localhost:1521/freepdb1> l  1  SELECT
2 *
3 FROM
4 (
5 SELECT
6 empno
7 , 'a' AS dummy_col
8 FROM
9 emp
10 ) t01
11*
SCOTT@localhost:1521/freepdb1> /

EMPNO DUMMY_COL
---------- ---------
7369 a
7499 a

...略...

7902 a
7934 a

14行が選択されました。

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 *
3 FROM
4 (
5 SELECT
6 empno AS empno
7 , 'a' AS dummy_col
8 FROM
9 emp
10 ) t01
11*
SCOTT@localhost:1521/freepdb1> /

EMPNO DUMMY_COL
---------- ---------
7369 a
7499 a

...略...

7902 a
7934 a

14行が選択されました。

 

 

MySQL

mysql> SELECT
-> *
-> FROM
-> (
-> SELECT
-> empno
-> , 'a' AS dummy_col
-> FROM
-> emp
-> ) t01
-> ;
+-------+-----------+
| empno | dummy_col |
+-------+-----------+
| 7782 | a |
| 7839 | a |

...略...

| 7844 | a |
| 7900 | a |
+-------+-----------+
14 rows in set (0.00 sec)

mysql> SELECT
-> *
-> FROM
-> (
-> SELECT
-> empno AS empno
-> , 'a' AS dummy_col
-> FROM
-> emp
-> ) t01
-> ;
+-------+-----------+
| empno | dummy_col |
+-------+-----------+
| 7782 | a |
| 7839 | a |

...略...

| 7844 | a |
| 7900 | a |
+-------+-----------+
14 rows in set (0.00 sec)

 

 

PostgreSQL

perftestdb=> SELECT
perftestdb-> *
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> , 'a' AS dummy_col
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) t01
perftestdb-> ;
empno | dummy_col
-------+-----------
7369 | a
7499 | a

...略...

7902 | a
7934 | a
(14 rows)

perftestdb=> SELECT
perftestdb-> *
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno AS empno
perftestdb(> , 'a' AS dummy_col
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) t01
perftestdb-> ;
empno | dummy_col
-------+-----------
7369 | a
7499 | a

...略...

7902 | a
7934 | a
(14 rows)

 

癖にも色々あります。
PostgreSQLのケースではエラーにならないからといって、だいじょーふだ!!、とは言えないタイプも癖もあます。

別エントリーで予定している表値コンストラクタではこの列エイリアスの構文が重要だったりするので、混乱しないよう、
効果的な場所で使っていく必要はありそうだなぁと思っているところ:)
Oracleのように、そもそも表値エイリアスでしか使えない場合は悩む必要もないわけですが。(いまのところ)

 

やっと東京も東北方面の気温に近づきつつある。。気もするw

では、また。

 

Enjoy SQL and 癖 !

 



関連エントリー

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