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月26日 (金)

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

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

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


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

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

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

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

...略...

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


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

oracle@Mac ~ % ./print_env.sh 

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

*** macOS ver. ***
ProductName: macOS
ProductVersion: 26.2
BuildVersion: 25C56

*** VirtualBox ver. ***
7.2.4r170995

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

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

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

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

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

Time: 28.617 ms


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

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

...略...

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

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


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

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

...略...

SCOTT@localhost:1521/freepdb1> select banner_full from v$version;

BANNER_FULL
--------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04


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

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

経過: 00:50:43.88

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

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

経過: 00:00:17.99


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

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

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

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

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

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

SCOTT@localhost:1521/freepdb1> @sql_oracle_65534

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

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

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

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

...略...

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

17行が選択されました。

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

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

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

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

18行が選択されました。


統計値差を確認!

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

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

SCOTT@localhost:1521/freepdb1> @list_diff 2

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


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

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

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

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

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

...略...

'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1')

...略...

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


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

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

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

18行が選択されました。

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


経過: 00:05:51.08


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

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

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

...略...

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

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

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

...略...

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

...略...

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

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

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

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

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

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


Tvc_65534rows_postgresql

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

にして再起動!

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

...略...

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

...略...

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

...略...

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

...略...

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

...略...

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

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

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

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

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.4.7 MySQL Community Server - GPL

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

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

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

mysql> \. sql_mysql_65534.sql
ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes
No connection. Trying to reconnect...
Connection id: 9
Current database: perftestdb

...略...

Current database: perftestdb

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

...略...

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

...略...

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

mysql>


Tvc_65534rows_mysql

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

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

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

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

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

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

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


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

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






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

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


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






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


| | | コメント (0)

2025年12月12日 (金)

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


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

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


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


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


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




ということで本題。

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

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


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

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

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


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


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

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

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



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

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

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

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

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

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

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

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

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

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


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







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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Oracle Database (23.8)

SCOTT@localhost:1521/freepdb1> select banner_full from v$version;

BANNER_FULL
---------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04


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

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

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


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

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

PostgreSQL (17.6)

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

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

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

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

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

経過: 00:00:00.00


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

SCOTT@localhost:1521/freepdb1> @show_sqlmonitor

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

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

...略...

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

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

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

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

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

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

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


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

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

PostgreSQL (17.6)

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

MySQL (8.4.7)

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

Oracle Database (23.8)

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


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

PostgreSQL (17.6)

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

MySQL (8.4.7)

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

Oracle Database (23.8)

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


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

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

PostgreSQL (17.6)

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

MySQL (8.4.7)

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

Oracle Database (23.8)

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

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

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

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

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

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

PostgreSQL (17.6)

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

MySQL (8.4.7)

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

Oracle Database (23.8)

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

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

PostgreSQL (17.6)

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

MySQL (8.4.7)

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

Oracle Database (23.8)

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

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

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

PostgreSQL (17.6)

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

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

Oracle Database (23.8)

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

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

SCOTT@localhost:1521/freepdb1> @show_sqlmonitor

...略...

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

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


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

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

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

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

...略...

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

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

MySQL (8.4.7)

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

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

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

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

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


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


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

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


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

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

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

経過: 00:00:00.05


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

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

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

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

...略...

65532
65533
65534

65534行が選択されました。

経過: 00:53:31.31

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

Tvc_for_adventcalendar


PostgreSQL (17.6)

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

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

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

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

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

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


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


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


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

Enjoy SQLs and SQLの癖!





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

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

SQL> @make_tvc_sql0 65534 oracle
SQL> @sql_oracle_65534

make_tvc_sql0.sql

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

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


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






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

| | | コメント (0)

2025年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月29日 (金)

x86 GuestOS on VirtualBox for Apple Silicon / ARM. Rebooted. :)

一年ぐらい前、ARM版 7.1 Betaがリリースされ、ARM版7.0のテストビルドで起動していたx86 GuestOS起動ができなくなった。。。という話のその後です。

7.0.9 for Apple Silicon / ARMで Intel版VirtualBoxから移行して、起動できた〜〜ニヤニヤしていた。。。
VirtualBox TestBuild 7.0.97r163779 (2024-07-04T18:53:02Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録

のですが、

7.1にアップデートしたら。。。。起動しなくなっちゃったんですよ....
VirtualBox TestBuild 7.1.0_BETA1r164292 (2024-08-07T18:27:07Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録


ずーーーーっと起動しなかったので、もうしばらく無理なのだろうかと、おもい、ARM64ネイティブなGuestOSで遊び始めたあと、
Oracle Database 23ai 23.8 (aarch64) on Oracle Linux 8u10 (aarch64) on VirtualBox 7.1 for Apple Silicon 始動 w


なにげに、とあるスレッドをだらだら読んでいると。。。。。なに!!!〜。もしかして、デフォで起動しない設定に戻されてしまったのか。。。
ということに気づいたわけです! はいw
Discuss the VirtualBox 7.1.0_BETA1 release here
https://forums.virtualbox.org/viewtopic.php?p=549283#p549283

上記スレッドで対処方法がコメントされていたことに全く気づかず、7.1以降使えなくなったままだったのかと思い込んでいただけでした。気づくの遅いw

ということで、Apple Silicon な VittualBoxで x86アーキテクチャのGuestOSのOracle Database/MySQL/PostgreSQLで遊ぶ環境が復活しました。

VBoxManage setextradata global "VBoxInternal2/EnableX86OnArm" 1

を設定しないといけないらしい、これを設定すると、”Dev Preview”マークがGUIに現れると!

結果から先に言っておくと、以下のリリース、7.2でも、x86_64のOracle Databaseは、ORA-3113で起動せず。
MySQL/PostgreSQLは起動しました。ちょっと先祖帰りした感じではありますね。とはいえ、x86 GuestOSも起動できる環境が復活できたので、起動確認もrebootさせることにしましたw

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

*** mac info. ***
ProductName: macOS
ProductVersion: 14.7.7
BuildVersion: 23H723

*** maxOS ver. ***
Model Name: Mac Studio
Chip: Apple M1 Ultra
Total Number of Cores: 20 (16 performance and 4 efficiency)
Memory: 64 GB

*** VirtualBox ver. ***
7.2.0r170228

oracle@Mac-Studio ~ % VBoxManage getextradata global "VBoxInternal2/EnableX86OnArm"
No value set!
oracle@Mac-Studio ~ %VBoxManage setextradata global "VBoxInternal2/EnableX86OnArm" 1

Virtualbox719_on_x86_test

MySQL/PostgreSQLをインストールしてあるx86 Guestが起動するか見てみます。おおおお、起動しましたね!!!!

oracle@Mac-Studio ~ % VBoxManage list -platform-arch=x86 vms | grep 'mysql8 postgrsql13'
"Oracle Linux 8 mysql8 postgrsql13" {a61fa92b-7849-459d-9bf4-ec075f5b983e}
oracle@Mac-Studio ~ % VBoxManage list runningvms
"Oracle Linux 8 mysql8 postgrsql13" {a61fa92b-7849-459d-9bf4-ec075f5b983e}


MySQLは無事に起動しているでしょうか。。。おお、大丈夫だ!

[master@localhost ~]$ sudo service mysqld status
Redirecting to /bin/systemctl status mysqld.service
● mysqld.service - MySQL 8.0 database server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: active (running) since Thu 2025-08-28 06:42:56 EDT; 16min ago
Process: 1370 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)
Process: 1086 ExecStartPre=/usr/libexec/mysql-prepare-db-dir mysqld.service (code=exited, status=0/SUCCESS)
Process: 1040 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)
Main PID: 1123 (mysqld)
Status: "Server is operational"
Tasks: 37 (limit: 22947)
Memory: 450.0M
CGroup: /system.slice/mysqld.service
└─1123 /usr/libexec/mysqld --basedir=/usr

8月 28 06:42:37 localhost.localdomain systemd[1]: Starting MySQL 8.0 database server...
8月 28 06:42:56 localhost.localdomain systemd[1]: Started MySQL 8.0 database server.


続いて、PostgreSQLは?。。。。おお、こちらも起動してますね

[master@localhost ~]$ sudo service postgresql-16 status
Redirecting to /bin/systemctl status postgresql-16.service
● postgresql-16.service - PostgreSQL 16 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-16.service; enabled; vendor preset: disabled)
Active: active (running) since Thu 2025-08-28 06:43:01 EDT; 17min ago
Docs: https://www.postgresql.org/docs/16/static/
Process: 1411 ExecStartPre=/usr/pgsql-16/bin/postgresql-16-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
Main PID: 1425 (postgres)
Tasks: 7 (limit: 22947)
Memory: 31.8M
CGroup: /system.slice/postgresql-16.service
├─1425 /usr/pgsql-16/bin/postgres -D /var/lib/pgsql/16/data/
├─1454 postgres: logger
├─1471 postgres: checkpointer
├─1472 postgres: background writer
├─1480 postgres: walwriter
├─1481 postgres: autovacuum launcher
└─1482 postgres: logical replication launcher

8月 28 06:42:58 localhost.localdomain systemd[1]: Starting PostgreSQL 16 database server...
8月 28 06:43:00 localhost.localdomain postgres[1425]: 2025-08-28 06:43:00.762 EDT [1425] LOG: redirecting log output to logging collector process
8月 28 06:43:00 localhost.localdomain postgres[1425]: 2025-08-28 06:43:00.762 EDT [1425] HINT: Future log output will appear in directory "log".
8月 28 06:43:01 localhost.localdomain systemd[1]: Started PostgreSQL 16 database server.


MySQLのバージョンは、以下の通り

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

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

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

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

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

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

PostgreSQLのバージョンは以下のとおり。クエリーもとりあえず大丈夫そう。

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


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


では最後に、Oracle DatabaseをインストールしてあるVMで。。。。。。ん〜〜〜惜しい、起動できず。。。。

[oracle@localhost ~]$ uname -rm
5.4.17-2102.201.3.el8uek.x86_64 x86_64
[oracle@localhost ~]$
[oracle@localhost ~]$
[oracle@localhost ~]$ lsnrctl start

LSNRCTL for Linux: Version 21.0.0.0.0 - Production on 28-8月 -2025 20:08:06

Copyright (c) 1991, 2021, Oracle. All rights reserved.

/opt/oracle/product/21c/dbhome_1/bin/tnslsnrを起動しています。お待ちください...

TNSLSNR for Linux: Version 21.0.0.0.0 - Production
システム・パラメータ・ファイルは/opt/oracle/homes/OraDBHome21cEE/network/admin/listener.oraです。
ログ・メッセージを/opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xmlに書き込みました。
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 21.0.0.0.0 - Production
開始日 28-8月 -2025 20:08:08
稼働時間 0 日 0 時間 0 分 0 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/oracle/homes/OraDBHome21cEE/network/admin/listener.ora
ログ・ファイル /opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
リスナーはサービスをサポートしていません。
コマンドは正常に終了しました。
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 木 8月 28 20:08:18 2025
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SYS@ORCLCDB> startup
ORA-03113: 通信チャネルでend-of-fileが検出されました
SYS@ORCLCDB>


とういことで、また、楽しみが一つ戻ってきた:)


まだまだ、蒸し暑すぎる東京の地より。
では、また。



関連エントリー
Oracle Linux 8 and MySQL 8.0.32 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160167
MySQL 8.0.32 , PostgreSQL 13.4 and Oracle Database 21c on Oracle Linux 8 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r160702
ySQL 8.0.32 , PostgreSQL 13.6 and Oracle Database 21c on Oracle Linux 8.5 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r161342
MySQL 8.0.32 , PostgreSQL 13.6 and Oracle Database 21c on Oracle Linux 8.5 on VirtualBox for Apple Silicon Test Build 7.0.97_BETA5r161709
MySQL 8.0.36 , PostgreSQL 13.14, Oracle Database 21c, Oracle Database 23ai on VirtualBox for Apple Silicon Test Build 7.0.97_BETA r162957
VirtualBox TestBuild for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 / 7.0.97r162957(2024/4/26) / 7.0.97r163029(2024/5/3)
VirtualBox TestBuild 7.0.97r163376 (2024-05-28T15:08:56Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.0.97r163425 (2024-06-05T13:13:46Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.0.97r163606 (2024-06-21T11:55:16Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox TestBuild 7.0.97r163779 (2024-07-04T18:53:02Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録
VirtualBox-7.1.0_BETA2-164697 (2024-09-06T20:27:41Z) for macOS/ARM64における現時点でのOracle Database 21cの起動、停止時間の記録 (VM起動せず)
Oracle Database 23ai 23.8 (aarch64) on Oracle Linux 8u10 (aarch64) on VirtualBox 7.1 for Apple Silicon 始動 w

| | | コメント (0)

2025年8月24日 (日)

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

Previously on Mac De Oracle
前回は、ANY_VALUE()、癖ないじゃんという話でした。

 

今日は、

t_alias と c_alias 

なんて言うと、バイオハザードがちょっと浮かぶのは私だけか。。(t-virusっぽい響きがあるだけだがw)

ということで、唐突ですがw、

SCOTT@localhost:1521/freepdb1> select * from dept order by deptno;

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

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> select * from emp order by deptno,empno;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7934 MILLER CLERK 7782 82-01-23 1300 10
7369 SMITH CLERK 7902 80-12-17 800 20
7566 JONES MANAGER 7839 81-04-02 2975 20
7788 SCOTT ANALYST 7566 87-04-19 3000 20
7876 ADAMS CLERK 7788 87-05-23 1100 20
7902 FORD ANALYST 7566 81-12-03 3000 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30

14行が選択されました。

 

上記の表が、Oracle database/MySQL/PostgreSQLにあったとします。というか、あります。

 

この時、2表を結合(外部結合でも内部結合でも良いです)する際、DEPT表をインラインビュー化し、そのビューに表エイリアスと列エイリアスを記述する場合、どのような構文が浮かびますか?

t_alias = 表エイリアス
c_alias = 列エイリアス

と言うのが今日の癖のテーマです:)

 

よくあるのは以下のな感じ. 列、表 の直後に列エイリアス、表エイリアスを記述する。見落としもつけ忘れも発生しにくく、可読性も良いと思います。

SELECT
e.empno
,e.ename
,d.department_name
,d.department_loc
FROM
emp e
INNER JOIN
(
SELECT
deptno
,dname AS department_name
,loc AS department_loc
FROM
dept
) d
ON
e.deptno = d.deptno

;

 

ところが、以下のような構文も可能なrdbmsもあります。はい。これが今日の癖! www

FROM table_reference [AS] alias ( column1 [, column2 [, ...]] )

参考) PostgreSQL 17 / 7.2. テーブル式 / 7.2.1.2. テーブルと列の別名
https://www.postgresql.jp/document/17/html/queries-table-expressions.html#QUERIES-TABLE-ALIASES

SELECT
e.empno
,e.ename
,d.department_name
,d.department_loc
FROM
emp e
INNER JOIN
(
SELECT
deptno
,dname
,loc
FROM
dept
) d (
deptno
, department_name
, department_loc
)
ON
e.deptno = d.deptno
;

 

 

まず最初は、PostgreSQLから。
マニュアルに明記されているくらいですから当然、どちらも問題なく使えます。

perftestdb=> SELECT
perftestdb-> e.empno
perftestdb-> ,e.ename
perftestdb-> ,d.department_name
perftestdb-> ,d.department_loc
perftestdb-> FROM
perftestdb-> emp e
perftestdb-> INNER JOIN
perftestdb-> (
perftestdb(> SELECT
perftestdb(> deptno
perftestdb(> ,dname
perftestdb(> ,loc
perftestdb(> FROM
perftestdb(> dept
perftestdb(> ) d (
perftestdb(> deptno
perftestdb(> , department_name
perftestdb(> , department_loc
perftestdb(> )
perftestdb-> ON
perftestdb-> e.deptno = d.deptno
perftestdb-> ;
empno | ename | department_name | department_loc
-------+--------+-----------------+----------------
7369 | SMITH | RESEARCH | DALLAS
7499 | ALLEN | SALES | CHICAGO
7521 | WARD | SALES | CHICAGO
7566 | JONES | RESEARCH | DALLAS
7654 | MARTIN | SALES | CHICAGO
7698 | BLAKE | SALES | CHICAGO
7782 | CLARK | ACCOUNTING | NEW YORK
7788 | SCOTT | RESEARCH | DALLAS
7839 | KING | ACCOUNTING | NEW YORK
7844 | TURNER | SALES | CHICAGO
7876 | ADAMS | RESEARCH | DALLAS
7900 | JAMES | SALES | CHICAGO
7902 | FORD | RESEARCH | DALLAS
7934 | MILLER | ACCOUNTING | NEW YORK
(14 rows)

perftestdb=> SELECT
perftestdb-> e.empno
perftestdb-> ,e.ename
perftestdb-> ,d.department_name
perftestdb-> ,d.department_loc
perftestdb-> FROM
perftestdb-> emp e
perftestdb-> INNER JOIN
perftestdb-> (
perftestdb(> SELECT
perftestdb(> deptno
perftestdb(> ,dname AS department_name
perftestdb(> ,loc AS department_loc
perftestdb(> FROM
perftestdb(> dept
perftestdb(> ) d
perftestdb-> ON
perftestdb-> e.deptno = d.deptno
perftestdb-> ;
empno | ename | department_name | department_loc
-------+--------+-----------------+----------------
7369 | SMITH | RESEARCH | DALLAS
7499 | ALLEN | SALES | CHICAGO
7521 | WARD | SALES | CHICAGO
7566 | JONES | RESEARCH | DALLAS
7654 | MARTIN | SALES | CHICAGO
7698 | BLAKE | SALES | CHICAGO
7782 | CLARK | ACCOUNTING | NEW YORK
7788 | SCOTT | RESEARCH | DALLAS
7839 | KING | ACCOUNTING | NEW YORK
7844 | TURNER | SALES | CHICAGO
7876 | ADAMS | RESEARCH | DALLAS
7900 | JAMES | SALES | CHICAGO
7902 | FORD | RESEARCH | DALLAS
7934 | MILLER | ACCOUNTING | NEW YORK
(14 rows)

 

次に、MySQL。
明確に記載されているドキュメントは見つけられなかった(知ってたら教えてw)のですが、以下のようにどちらも実行できます。

(ありがとうございました。篠田さんに教えていただきました)

MySQL 8.0 リファレンスマニュアル / SQL ステートメント / データ操作ステートメント / サブクエリー / 導出テーブル

mysql> SELECT
-> e.empno
-> ,e.ename
-> ,d.department_name
-> ,d.department_loc
-> FROM
-> emp e
-> INNER JOIN
-> (
-> SELECT
-> deptno
-> ,dname
-> ,loc
-> FROM
-> dept
-> ) d (
-> deptno
-> , department_name
-> , department_loc
-> )
-> ON
-> e.deptno = d.deptno
-> ;
+-------+--------+-----------------+----------------+
| empno | ename | department_name | department_loc |
+-------+--------+-----------------+----------------+
| 7782 | CLARK | ACCOUNTING | NEW YORK |
| 7839 | KING | ACCOUNTING | NEW YORK |
| 7934 | MILLER | ACCOUNTING | NEW YORK |
| 7369 | SMITH | RESEARCH | DALLAS |
| 7566 | JONES | RESEARCH | DALLAS |
| 7788 | SCOTT | RESEARCH | DALLAS |
| 7876 | ADAMS | RESEARCH | DALLAS |
| 7902 | FORD | RESEARCH | DALLAS |
| 7499 | ALLEN | SALES | CHICAGO |
| 7521 | WARD | SALES | CHICAGO |
| 7654 | MARTIN | SALES | CHICAGO |
| 7698 | BLAKE | SALES | CHICAGO |
| 7844 | TURNER | SALES | CHICAGO |
| 7900 | JAMES | SALES | CHICAGO |
+-------+--------+-----------------+----------------+
14 rows in set (0.00 sec)

mysql> SELECT
-> e.empno
-> ,e.ename
-> ,d.department_name
-> ,d.department_loc
-> FROM
-> emp e
-> INNER JOIN
-> (
-> SELECT
-> deptno
-> ,dname AS department_name
-> ,loc AS department_loc
-> FROM
-> dept
-> ) d
-> ON
-> e.deptno = d.deptno
-> ;
+-------+--------+-----------------+----------------+
| empno | ename | department_name | department_loc |
+-------+--------+-----------------+----------------+
| 7782 | CLARK | ACCOUNTING | NEW YORK |
| 7839 | KING | ACCOUNTING | NEW YORK |
| 7934 | MILLER | ACCOUNTING | NEW YORK |
| 7369 | SMITH | RESEARCH | DALLAS |
| 7566 | JONES | RESEARCH | DALLAS |
| 7788 | SCOTT | RESEARCH | DALLAS |
| 7876 | ADAMS | RESEARCH | DALLAS |
| 7902 | FORD | RESEARCH | DALLAS |
| 7499 | ALLEN | SALES | CHICAGO |
| 7521 | WARD | SALES | CHICAGO |
| 7654 | MARTIN | SALES | CHICAGO |
| 7698 | BLAKE | SALES | CHICAGO |
| 7844 | TURNER | SALES | CHICAGO |
| 7900 | JAMES | SALES | CHICAGO |
+-------+--------+-----------------+----------------+
14 rows in set (0.01 sec)

 

最後の、Oracle Database.
最後に出てきたと言うことは、はい、本日のオチですねw

マニュアル上でも記載はないので、その通りの結果です。

参考) Oracle Database 23 / SQL言語リファレンス / SELECT / table_reference::=
https://docs.oracle.com/cd/G11854_01/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2126863

SCOTT@localhost:1521/freepdb1> @t_alias_c_alias_test  1  SELECT
2 e.empno
3 ,e.ename
4 ,d.department_name
5 ,d.department_loc
6 FROM
7 emp e
8 INNER JOIN
9 (
10 SELECT
11 deptno
12 ,dname
13 ,loc
14 FROM
15 dept
16 ) d (
17 deptno
18 , department_name
19 , department_loc
20 )
21 ON
22* e.deptno = d.deptno
) d (
*
行16でエラーが発生しました。:
ORA-02000: ON or USINGキーワードがありません。 ヘルプ:
https://docs.oracle.com/error-help/db/ora-02000/

経過: 00:00:00.01

1 SELECT
2 e.empno
3 ,e.ename
4 ,d.department_name
5 ,d.department_loc
6 FROM
7 emp e
8 INNER JOIN
9 (
10 SELECT
11 deptno
12 ,dname AS department_name
13 ,loc AS department_loc
14 FROM
15 dept
16 ) d
17 ON
18* e.deptno = d.deptno

EMPNO ENAME DEPARTMENT_NAME DEPARTMENT_LOC
---------- ---------- ---------------- ---------------
7369 SMITH RESEARCH DALLAS
7499 ALLEN SALES CHICAGO
7521 WARD SALES CHICAGO
7566 JONES RESEARCH DALLAS
7654 MARTIN SALES CHICAGO
7698 BLAKE SALES CHICAGO
7782 CLARK ACCOUNTING NEW YORK
7788 SCOTT RESEARCH DALLAS
7839 KING ACCOUNTING NEW YORK
7844 TURNER SALES CHICAGO
7876 ADAMS RESEARCH DALLAS
7900 JAMES SALES CHICAGO
7902 FORD RESEARCH DALLAS
7934 MILLER ACCOUNTING NEW YORK

14行が選択されました。

 

と言うことで、

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

ここまで。

普段使わないような構文に出会うと、ちょっと固まったりしますよねw

あ、そうそう、固まるといえば、
最近、クマとの接近遭遇が市街地でも多くなり、
散歩に行くにも、"クルマ"に気をつけてー、のついでに、”クマ”にも気をつけて〜っ 
と言った方がいいかもねと言う程度に頻度が高くて、マジで散歩しにくいので、
なんとかして欲しいと思う、
万が一であったら、固まってしまいそう。。。。。

涼しくなりつつある、北の地より。

ではまた。

Enjoy SQL and SQLの癖w

 


関連エントリー

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

 

| | | コメント (0)

2025年7月 4日 (金)

実行計画は, SQL文のレントゲン写真だ! No.67 / AI Vector Search - VECTOR INDEX HNSW SCAN のバリエーション

Previously on Mac De Oracle
前回は、VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(完結編)でした。
今日は、再びレントゲン写真に戻り、VECTOR INDEX HNSW SCAN のバリエーションをいくつか確認しておこうと思います。
(なかなか興味深いので、一度診ておけば、いざというときに慌てなくて済むと思います)

 

いきなりってのもあれなので、先に以下のマニュアルを一読しておくと良いと思います。マニュアルでもポイントが解説されているネタなので:) 解説しないとちょっと分かりずらい点が多いからだと思いますがw

Oracle Database 23ai / Oracle AI Vector Search ユーザーズ・ガイド / HNSW ベクトルインデックスのオプティマイザプランのバリエーションを解説している章があります。なかなか興味深い。
おそらく、表には積極的に登場してこない補助表が、突然実行計画に現れることへの戸惑いと実行計画の読み方にちょっとした癖がある点の緩和と実行計画のバリエーションごとのメリデメを理解してもらうためにも解説が必要だったのだろうなぁ。と想像。
Oracle Database / Release 23 / Oracle AI Vector Search User's Guide / Optimizer Plans for HNSW Vector Indexes

INMEMORYな索引なのに、OperationにINMEMORYというキーワードが無くて、おや? と違和感があったり、ちょっとめんどくさい癖があるなぁと。。。w

まずは、
vector_index_neighbor_graph_reloadパラメータはCDBレベルで restart に設定した ( Oracle Database 23ai 23.6以降はデフォルトが restart になっています ) ので再起動しても vector index (HNSW) はポピュレートされメモリー上に復活しているはず。。。という確認から。

[oracle@localhost ~]$ sudo service oracle stop
[sudo] oracle のパスワード:
Stopping oracle (via systemctl):
[ OK ]
[oracle@localhost ~]$
[oracle@localhost ~]$ sudo service oracle start
Starting oracle (via systemctl):
[ OK ]
[oracle@localhost ~]$
[oracle@localhost ~]$ sqlplus scott@localhost:1521/freepdb1

...略...

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

POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS
-------------------------- ----------- ---------- --------------------------
1MB POOL 369098752 236978176 DONE
64KB POOL 150994944 2686976 DONE
IM POOL METADATA 16777216 16777216 DONE

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> @show_vector_segments

OBJ MEMBYTES
---------- ----------
0 131072
80126 239534080

経過: 00:00:00.02

 

それぞれの実行計画のpros/consはマニュアルで読んでもらうとして、
VECTOR INDEX HNSW SCAN PRE-FILTER WITH JOIN BACKから診てみましょう。

 

JOIN BACKするかしないかの違いは最後にもう一度ベース表をアクセスするかどうか。(次の実行計画で言うと、Id=5でSEARCH_DATAをtable access by index rowidでアクセスしている箇所がJOIN BACK)
これらのバリエーションはデータ量とフィルタリング量との兼ね合いになるのでベクトル索引で近傍検索の実行計画をヒントで固定化するのは比較的難易度が高そう(どちらの傾向に固定した方が良いかの判断は難しい)だろうな、と思っているところ。固定できるか、したほうが良いかの見極めというか、割り切りなのかもしれないが決め打ちするだけの情報は揃えた上で決める必要はりそう。最初は経過観察なのが良いだろうと思っているところだが。。。とはいえ、覚えていて損ないかなぁ。

また、これらのバリエーションでは、VECTOR INDEX (HNSW)以外に、補助表の主役であるMAP表が登場します(Id=9の部分)。
VECTOR INDEX (HNSW)本体だけでなく補助表の存在も把握しておくことが大切な理由はここにもあります。(ベース表に比べるとサイズは小さいわけですが)

後半で別途まとめますが、この実行では内部ビューが新たに作られています。
Id=7の VW_HPJ_91CF1FF7 がそれです。内部的に作成されるビューにはそれぞれのトランスフォームに関連する短縮名が付与されるのが、これまでのOracle Databaseのオプティマイザのお約束ですね。
VW_HPJ_、 Hnsw scan Pre-filter with Join back -> HPJ になりそうですよね。 VW_HPJ_という内部ビューをみたらVECTOR_INDEX_TRANSFORM VECTOR INDEX HNSW SCAN PRE_FILTER WITH JOIN BACKが行われていると考えてよいでしょうね。

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_with_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


Plan hash value: 3994424349

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | | 6649 (100)| | 20 |00:00:00.93 | 412 | 410 | | | |
| 1 | VIEW | | 1 | 1 | 157 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | | 20 |00:00:00.93 | 412 | 410 | | | |
| 3 | VIEW | | 1 | 1 | 157 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 1 | 1665 | | 6649 (1)| 00:00:01 | 20 |00:00:00.93 | 412 | 410 | 4096 | 4096 | 4096 (0)|
|* 5 | TABLE ACCESS BY INDEX ROWID | SEARCH_DATA | 1 | 1 | 1665 | | 6648 (1)| 00:00:01 | 20 |00:00:00.94 | 412 | 410 | | | |
| 6 | VECTOR INDEX HNSW SCAN PRE-FILTER| SEARCH_DATA_HNSW_IX | 1 | 1 | 1665 | | 6648 (1)| 00:00:01 | 20 |00:00:00.93 | 392 | 390 | | | |
| 7 | VIEW | VW_HPJ_91CF1FF7 | 1 | 6235 | 152K| | 6647 (1)| 00:00:01 | 6235 |00:00:00.06 | 392 | 390 | | | |
|* 8 | HASH JOIN RIGHT OUTER | | 1 | 6235 | 9M| 3296K| 6647 (1)| 00:00:01 | 6235 |00:00:00.06 | 392 | 390 | 8506K| 2096K| 9004K (0)|
| 9 | TABLE ACCESS FULL | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 1 | 125K| 1831K| | 102 (0)| 00:00:01 | 125K|00:00:00.01 | 373 | 371 | | | |
|* 10 | INDEX RANGE SCAN | SEARCH_DATA_COMMNITY_IX | 1 | 6235 | | | 23 (0)| 00:00:01 | 6235 |00:00:00.01 | 19 | 19 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$7185E227 / "from$_subquery$_003"@"SEL$3"
4 - SEL$7185E227
5 - SEL$7185E227 / "SEARCH_DATA"@"SEL$2"
6 - SEL$7185E227 / "SEARCH_DATA"@"SEL$2"
7 - SEL$BF33016E / "VW_HPJ_91CF1FF7"@"SEL$2"
8 - SEL$BF33016E
9 - SEL$BF33016E / "VTIX_RIDVID"@"SEL$2"
10 - SEL$BF33016E / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
5 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')
8 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")
10 - access("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

10 - SEL$BF33016E / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_with_join_back)

 

次に、VECTOR INDEX HNSW SCAN PRE-FILTER WITHOUT JOIN BACKを診てみましょう。
違いは、JOIN BACKがないところですよね、ヒントの通りw。
実行計画のId=5でVECTOR INDEX HNSW SCAN PRE_FILTERが行われていますが、その後でベース表を再度アクセスすることはありません。
なお、今回の検索パターンだと join back したほうが多少軽めですよね。んーーー難しい。普段はオプティマイザにお任せのほうが良いかもなと思うわけです。よほどのことがない限り。はい。

ここでも、新たな内部ビュー、 VW_HPF_475999B9 が作成されています。 VW_HPF_、 Hnsw scan Pre-Filter without join back -> HPF でしょうか。ちょっとムズイw VW_HPF_内部ビューを見つけたら、VECTOR INDEX HNSW SCAN PRE-FILTER WITHOUT JOIN BACKが行われてると理解して良さそうです。

このケースでも補助表であるMAP表が登場します。なにこれ? 俺は作ってないぞ! と驚かないようにしてくださいねw。(当ブログを読んだ方は驚くことはないはずですがw)
また、マニュアルにも記載されていますが、Hash join だけでなく Nested Loops Joinになることもあります。データ量と索引有無次第ではありますが、覚えておくと良いでしょう。(この例では Apaptive Planが選択されているため、どちらの結合方式になるかは、Join Cardinarity次第です)

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_without_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 5998 (100)| | 20 |00:00:02.77 | 5929 | 5904 | | | |
| 1 | VIEW | | 1 | 20 | 3140 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | | | |
| * 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:02.77 | 5929 | 5904 | | | |
| 3 | VIEW | | 1 | 20 | 3140 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | | | |
| * 4 | SORT ORDER BY STOPKEY | | 1 | 20 | 35220 | 5998 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | 4096 | 4096 | 4096 (0)|
| 5 | VECTOR INDEX HNSW SCAN PRE-FILTER | SEARCH_DATA_HNSW_IX | 1 | 20 | 35220 | 5997 (1)| 00:00:01 | 20 |00:00:02.77 | 5929 | 5904 | 1278K| 1076K| 1175K (0)|
| 6 | VIEW | VW_HPF_475999B9 | 1 | 6235 | 1071K| 5996 (1)| 00:00:01 | 6235 |00:00:02.13 | 5914 | 5904 | | | |
| * 7 | HASH JOIN OUTER | | 1 | 6235 | 499K| 5996 (1)| 00:00:01 | 6235 |00:00:02.13 | 5914 | 5904 | 1448K| 1287K| 1856K (0)|
|- 8 | NESTED LOOPS OUTER | | 1 | 6235 | 499K| 5996 (1)| 00:00:01 | 6235 |00:00:10.37 | 5540 | 5533 | | | |
|- 9 | STATISTICS COLLECTOR | | 1 | | | | | 6235 |00:00:10.36 | 5540 | 5533 | | | |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED| SEARCH_DATA | 1 | 6235 | 407K| 5893 (1)| 00:00:01 | 6235 |00:00:10.36 | 5540 | 5533 | | | |
| * 11 | INDEX RANGE SCAN | SEARCH_DATA_COMMNITY_IX | 1 | 6235 | | 23 (0)| 00:00:01 | 6235 |00:00:00.01 | 19 | 19 | | | |
|- 12 | TABLE ACCESS BY INDEX ROWID | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 0 | 1 | 15 | 102 (0)| 00:00:01 | 0 |00:00:00.01 | 0 | 0 | | | |
|- * 13 | INDEX UNIQUE SCAN | SYS_C0013920 | 0 | | | | | 0 |00:00:00.01 | 0 | 0 | | | |
| 14 | TABLE ACCESS FULL | VECTOR$SEARCH_DATA_HNSW_IX$78074_80224_0$HNSW_ROWID_VID_MAP | 1 | 125K| 1831K| 102 (0)| 00:00:01 | 125K|00:00:00.03 | 374 | 371 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$2D1A9934 / "from$_subquery$_003"@"SEL$3"
4 - SEL$2D1A9934
5 - SEL$2D1A9934 / "SEARCH_DATA"@"SEL$2"
6 - SEL$6D23FDEA / "VW_HPF_475999B9"@"SEL$475999B9"
7 - SEL$6D23FDEA
8 - SEL$6D23FDEA
10 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
11 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
12 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"
13 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"
14 - SEL$6D23FDEA / "VTIX_RIDVID"@"SEL$EBB9871C"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
7 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")
11 - access("SEARCH_DATA"."COMMUNITY"='AUSTIN')
13 - access("SEARCH_DATA".ROWID="VTIX_RIDVID"."BASE_TABLE_ROWID")

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

10 - SEL$6D23FDEA / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix pre_filter_without_join_back)

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)

 

次は、VECTOR INDEX TRANSFORM IN-FILTER WITH JOIN BACK これまでの PRE-FILTERではなく、IN-FILTER + JOIN BACKをヒントで強制しています

なお、マニュアルにも記載がありますが、In-filterの実行計画の読み方は少々癖があります!

Id=8の元表のアクセスを見てください!。いきなり、TABLE ACCESS BY USER ROWID で SEARCH_DATA 表をアクセスしています!!!
この実行計画の開始ポイントは、Id=8ではなく、Id=6の VECTOR INDEX HNSW SCAN IN-FILTER で、VECTOR INDEX (HNSW)である SEARCH_DATA_HNSW_IX 索引をトラバースしている部分です:)
Id=6で識別されたベクトルごとに、Id=8の元表に対応するrowidのフィルタが適用され、関連する列が抽出されます!!!!!!!

VECTOR INDEX TRANSFORMの時の実行計画の読み方は正しく覚えないと軽くハマりそうですねw 少々癖があるので覚えるしかないですよ!w(ここも試験にでるよ!!w しらんけど)

最後に、Id=5でJOIN BACKして、COUNT STOPKEYの操作へ遷移していきます!!!!

また、ここでも内部生成の新たなビューが登場しています。
VW_HIJ_ ですね。 HIJ -> Hnsw scan In-filter with Join back ということでしょうね。想像するに。 

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_with_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 3 (100)| | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 1 | VIEW | | 1 | 1 | 157 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 3 | VIEW | | 1 | 1 | 157 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 1 | 1652 | 3 (34)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | 4096 | 4096 | 4096 (0)|
|* 5 | TABLE ACCESS BY INDEX ROWID | SEARCH_DATA | 1 | 1 | 1652 | 2 (0)| 00:00:01 | 20 |00:00:02.28 | 4725 | 4431 | | | |
| 6 | VECTOR INDEX HNSW SCAN IN-FILTER| SEARCH_DATA_HNSW_IX | 1 | 1 | 1652 | 2 (0)| 00:00:01 | 20 |00:00:02.28 | 4705 | 4423 | | | |
| 7 | VIEW | VW_HIJ_475999B9 | 4773 | 1 | | 1 (0)| 00:00:01 | 265 |00:00:01.59 | 4705 | 4423 | | | |
|* 8 | TABLE ACCESS BY USER ROWID | SEARCH_DATA | 4773 | 1 | 1652 | 1 (0)| 00:00:01 | 265 |00:00:01.59 | 4705 | 4423 | | | |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$79710E8E / "from$_subquery$_003"@"SEL$3"
4 - SEL$79710E8E
5 - SEL$79710E8E / "SEARCH_DATA"@"SEL$2"
6 - SEL$79710E8E / "SEARCH_DATA"@"SEL$2"
7 - SEL$860F096D / "VW_HIJ_475999B9"@"SEL$2"
8 - SEL$860F096D / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
5 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')
8 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

8 - SEL$860F096D / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_with_join_back)

 

最後に、VECTOR INDEX TRANSFORM IN-FILTER WITHOUT JOIN BACK In-filtewrでJoin Backなしというタイプです。

このタイプがもっとも無駄がなさそうですね。今回の例で使っている検索パターンだと。。。:)

これもマニュアルの記述されているとおり、実行計画の開始位置にクセがあります。
Id=5の VECTOR INDEX HNSW SCAN IN-FILTER で、VECTOR INDEX (HNSW) をトラバースするところがスタートです。
次に、Id=7のベース表をVECTOR INDEXから取得したrowidでアクセス。
その後、COUNT STOPKEYの操作へ入ります。

そして、ここでも新顔の内部ビュー、 VW_HIF_ -> Hnsw scan In-Filter with join back ということで、 HIFになっていると思われます:)

SELECT
/*+
GATHER_PLAN_STATISTICS
*/
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_without_join_back)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
WHERE
community = 'AUSTIN'
ORDER BY
v_distance
FETCH APPROX FIRST 20 ROWS ONLY
)
/


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 3 (100)| | 20 |00:00:00.52 | 4705 | 733 | | | |
| 1 | VIEW | | 1 | 20 | 3140 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | | | |
|* 2 | COUNT STOPKEY | | 1 | | | | | 20 |00:00:00.52 | 4705 | 733 | | | |
| 3 | VIEW | | 1 | 20 | 3140 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | | | |
|* 4 | SORT ORDER BY STOPKEY | | 1 | 20 | 34960 | 3 (34)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | 4096 | 4096 | 4096 (0)|
| 5 | VECTOR INDEX HNSW SCAN IN-FILTER| SEARCH_DATA_HNSW_IX | 1 | 20 | 34960 | 2 (0)| 00:00:01 | 20 |00:00:00.52 | 4705 | 733 | 835K| 835K| 543K (0)|
| 6 | VIEW | VW_HIF_475999B9 | 4773 | 1 | 151 | 1 (0)| 00:00:01 | 265 |00:00:00.02 | 4705 | 733 | | | |
|* 7 | TABLE ACCESS BY USER ROWID | SEARCH_DATA | 4773 | 1 | 67 | 1 (0)| 00:00:01 | 265 |00:00:00.02 | 4705 | 733 | | | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$3 / "from$_subquery$_001"@"SEL$1"
2 - SEL$3
3 - SEL$81BAFB36 / "from$_subquery$_003"@"SEL$3"
4 - SEL$81BAFB36
5 - SEL$81BAFB36 / "SEARCH_DATA"@"SEL$2"
6 - SEL$066A4CD4 / "VW_HIF_475999B9"@"SEL$475999B9"
7 - SEL$066A4CD4 / "SEARCH_DATA"@"SEL$2"

...略...

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

2 - filter(ROWNUM<=20)
4 - filter(ROWNUM<=20)
7 - filter("SEARCH_DATA"."COMMUNITY"='AUSTIN')

...略...

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

7 - SEL$066A4CD4 / "SEARCH_DATA"@"SEL$2"
- VECTOR_INDEX_TRANSFORM(search_data search_data_hnsw_ix in_filter_without_join_back)

 

まとめ VECTOR INDEX TRANSFORM の特徴

・VECTOR INDEX (HNSW)以外に、補助表であるMAP表が使われる(場合によってはその索引も)
・In-filter時の実行計画開始の開始ポイントに癖があるので要注意
・内部で生成されるビューがバリエーション分増加した

 

最後に、今回新たに登場した内部ビューと、これまでに把握されている内部ビューのまとめ。

VECTOR INDEX TRANSFORM

VW_HPJ_ / Hnsw scan Pre-filter with Join back
VW_HPF_ / Hnsw scan Pre-Filter with join back
VW_HIJ_ / Hnsw scan In-filter with Join back
VW_HIF_ / Hnsw scan In-Filter with join back

 

上記に加え以前からいくつかメジャーな内部生成ビューがまとめられています。覚えておくとなにが行われているか分かり易いと思いますよ。
Internal Views / Oracle Scratchpad / Jonathan Lewis

 

では、また!

 

Enjoin Execution Plans, SQLs, and AI Vector Search!

 

 


Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ!

実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)
実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

 

| | | コメント (0)

2025年6月25日 (水)

VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(完結編)

Previously on Mac De Oracle
前回はVECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(後編) でした。
謎は、ほぼ解決したかな?と、思ったのですが、モヤモヤは晴れずwww
ということで、完結編的な何か。という位置付けのまとめを書いておくことにしました。(なお、23.5以降VECTOR INDEX関連も機能追加があったり。。。まあ、追いかけるの大変です)

注)ちょい古いリリースなので最新リリースとは挙動など異なる点がある可能性がある点、ご了承ください

SCOTT@localhost:1521/freepdb1> select banner_full from v$version;

BANNER_FULL
--------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05


インメモリー近傍グラフ・ベクトル索引の特徴まとめ (的なもの)

インメモリー近傍グラフ・ベクトル索引は、ディクショナリービューからは、OBJECTでもSEGMENTも無い扱いになっているよう見えてしまう。
しかし、vecsys.vector$indexにリストされるOBJECT_IDは、*_OBJECTSと共通で、vecsys.vector$indexにはあるが、*_OBJECTS上はIDが欠番のように見える(後述)。無いけどあるみたいな不思議な扱いになっている。(今のところ)
なぜそうしたのだろう?....


  • Oraclerならお馴染みの *_OBJECTS や *_SEGMENTS には現れないが、in-memory onlyのオブジェクトとして専用のプール上にポピューレートされた場合に存在している in-memory indexである。
    (ここテストにでますよ。嘘w)

    また、in-memory vector indexの補助表及び関連する索引はリストされる(セグメントサイズは、deferred segment creationであるものも含むため状況次第)
    メモリー上とは言っても他と同じ扱いで、属性としてin-memory onlyのような列を追加すればよかったのでは?と、個人的には思っていたりする。。。


参考)
(上記の時事検証 23ai 23.4で行ったため、最新のリリースとは差異がある可能性があり、気づいた差分について随時補足追記してあります)
作成したin-memory vector index ( SEARCH_DATA_HNSW_IX ) の IDX_OBJN(列名の短縮系も*_OBJECTSで利用されているOBJECT_IDとな異なり、IDX_OBJN ( N は Numberの略と思われる) という、いわゆるシノニムになってしまっている(この点も状況をわかりにくくしている原因でしょうね。Oraclerじゃない方が作ったような違和感を感じる)

SCOTT@localhost:1521/freepdb1> select idx_objn, idx_name from vecsys.vector$index;

IDX_OBJN IDX_NAME
---------- -------------------------------
80113 SEARCH_DATA_HNSW_IX

で、この IDX_OBJN = OBJECT_ID ( *_OBJECTS で使われている )、いきなりシノニムになってる! と仮説をたて UESR_OBJECTSを検索してみると、存在しない! (何ぃ〜〜〜っ!)
ちがうのか?(*_OBJECTSにはリストされないだけで、採番元は同じなので、はやり仮説は正しかったw。後述)

SCOTT@localhost:1521/freepdb1> select object_name,object_type from user_objects where object_id = 80113;

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


念の為、data_object_idでも検索してみる(こちらにもないですよねーw)

SCOTT@localhost:1521/freepdb1> select object_name,object_type from user_objects where data_object_id = 80113;

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


USER_SEGMENTSをVECTOR INDEX NAMEで検索してみるがやはり無い。ストレージ上のデータをそのままインメモリー化しているわけではないいうことだろうと想像するが。ではメモリー上の情報は。。。

SCOTT@localhost:1521/freepdb1> select segment_name from user_segments where segment_name = 'SEARCH_DATA_HNSW_IX';

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


VECTOR INDEXとしては、 *_INDEXES から確認は可能であるが”!。。。

SCOTT@localhost:1521/freepdb1> select index_name,index_type,table_name from user_indexes where table_name='SEARCH_DATA';

INDEX_NAME INDEX_TYPE TABLE_NAME
------------------------------ --------------------------- ------------------------------
SYS_IL0000078074C00009$$ LOB SEARCH_DATA
SEARCH_DATA_HNSW_IX VECTOR SEARCH_DATA


in-memory vector indexはSGA上の専用メモリープール上に造られる。
では、v$vector_memory_poolからポピュレートされていることを確認。

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 pool
3 , alloc_bytes
4 , used_bytes
5 , populate_status
6 FROM
7* v$vector_memory_pool
SCOTT@localhost:1521/freepdb1> /

POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS
-------------------------- ----------- ---------- --------------------------
1MB POOL 369098752 236978176 DONE
64KB POOL 150994944 2686976 DONE
IM POOL METADATA 16777216 16777216 DONE


ちなみに、vector indexがポピュレートされる前の状態は以下。1MB/64KB Poolが消費されたことがわかります。つまり、236,978,176 + 2,686,976 = 239,665,152 = 228.6MB

POOL                       ALLOC_BYTES USED_BYTES POPULATE_STATUS
-------------------------- ----------- ---------- --------------------------
1MB POOL 369098752 0 DONE
64KB POOL 150994944 0 DONE
IM POOL METADATA 16777216 16777216 DONE


in-memory vector indexのメモリセグメントの詳細を確認するには v$vector_mem_segments_detail を参照する。vecsys.vector$indexのidx_objnにリストされていた 80113 が 239MB 程度使用されていることがわかる。
(このサイズ、覚えておいてくださいね。後で使います。 239,534,080 = 228.4MB)

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 obj
3 , membytes
4 FROM
5* v$vector_mem_segments_detail
SCOTT@localhost:1521/freepdb1> /

OBJ MEMBYTES
---------- ----------
0 131072
80113 239534080

in-memory vector index (HNSW)の補助表と関連索引の object_id を見てみましょう。
vecsys.vector$indexでは、idx_objn となっていた列の値と連続していること点が確認できます。つまり、object_id を採番しているシーケンスを利用している。。。in-memory vector index (HNSW) も *_OBJECTSに含まれている扱い。。。だが、*_objectsには含まれていません!!!!

CREATE VECTOR INDEXで作成された、in-memory vector index( SEARCH_DATA_HNSW_IX )の idx_objn = 80113 , その後に作成される補助表及び索引の *_objects.object_id は、80114 以降が採番されています。
かつ、80113 という object_id は、*_objects にはリストされず。
妙ですよね。内部的には *_objectsに含まれていてもおかしくないと思われる扱いにであるかのように見えますよね。うーーむ。

SCOTT@localhost:1521/freepdb1> l
1 WITH
2 vector_idx_auxiliary_tables
3 AS (
4 SELECT
5 idx_name AS vector_index_name
6 , REPLACE(aux_table_name,'"','') AS aux_table_name
7 FROM
8 (
9 SELECT
10 vvi.idx_name AS idx_name
11 ,vvi.idx_auxiliary_tables.rowid_vid_map_name
12 ,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
13 ,vvi.idx_auxiliary_tables.shared_journal_change_log_name
14 FROM
15 vecsys.vector$index vvi
16 ) objs
17 UNPIVOT (
18 aux_table_name FOR related_obj_name IN
19 (
20 rowid_vid_map_name
21 , shared_journal_transaction_commits_name
22 , shared_journal_change_log_name
23 )
24 )
25 )
26 SELECT
27 object_id
28 ,object_name
29 ,object_type
30 FROM
31 user_objects
32 WHERE
33 object_name in (
34 SELECT
35 aux_table_name AS segment_name
36 FROM
37 vector_idx_auxiliary_tables
38 WHERE
39 vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
40 UNION ALL
41 SELECT
42 index_name AS segment_name
43 FROM
44 user_indexes
45 WHERE
46 EXISTS
47 (
48 SELECT
49 1
50 FROM
51 vector_idx_auxiliary_tables
52 WHERE
53 vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
54 AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
55 )
56* )
SCOTT@localhost:1521/freepdb1> /

OBJECT_ID OBJECT_NAME OBJECT_TYPE
---------- -------------------------------------------------------------------------------------------------------------------------------- -----------------------
80114 VECTOR$SEARCH_DATA_HNSW_IX$78074_80113_0$HNSW_ROWID_VID_MAP TABLE
80115 SYS_C0013840 INDEX
80117 VECTOR$SEARCH_DATA_HNSW_IX$78074_80113_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS TABLE PARTITION
80116 VECTOR$SEARCH_DATA_HNSW_IX$78074_80113_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS TABLE
80118 PK_XID_80113 INDEX
80120 VECTOR$SEARCH_DATA_HNSW_IX$78074_80113_0$HNSW_SHARED_JOURNAL_CHANGE_LOG TABLE PARTITION
80119 VECTOR$SEARCH_DATA_HNSW_IX$78074_80113_0$HNSW_SHARED_JOURNAL_CHANGE_LOG TABLE
80124 SYS_IL0000080119C00007$$ INDEX PARTITION
80123 SYS_IL0000080119C00007$$ INDEX

9行が選択されました。

SCOTT@localhost:1521/freepdb1> l
1 select object_id, data_object_id from user_objects where object_name = 'SEARCH_DATA'
SCOTT@localhost:1521/freepdb1> /

OBJECT_ID DATA_OBJECT_ID
---------- --------------
78074 78796

ここまでで、in-memory vector indexはどこ?
って話の場所的なところは見切れたかなと思いますが、
もう一つ、explain plan for create vector index及び、dbms_space.create_index_costプロシージャでの見積もりサイズは、信じて良いのか?、よくないのか?

エラーにはなってないが、信用してよいのだろうか。。。
ちなみに、23.6以降だが、INDEX_VECTOR_MEMORY_ADVISORプシージャによりインメモリーサイズを見積もることができるようになった。とか。。。すぐには用意できないのでw 23.4で一旦確認してみる


  • CREATE VECTOR INDEXで作成されるINMEMORYの索引オブジェクトに加え、補助表とよばれる表が複数存在する。未検証だが変更をトラックするタイプの補助表は更新量によってはセグメントサイズが一時的に増加する可能性あり

変更をトラッキングするのための2つのパーティション表と関連索引に関しては変更が無い限りセグメントは作成されない。はず。(deferred segment creationになっているようなので)
つまり新規作成時にはオブジェクトとしては存在するものの、セグメントは存在しない。
ということは、残る一つの補助表(仮でMAP表と呼ぶことにする)とその索引はある程度のセグメントを保持することになるだろうと予想。
また、インメモリー索引自体はベース表のVECTOR列のサイズに依存するのではないだろうか。。。と。

とりあえず、補助表のセグメントサイズを改めて確認してみる(in-memory vector index を何度かdrop/createしていためセグメント名に含まれるobject_idが異なりますが気になさらず。。)
今回のケース 125,000行のデータでは合計で 9MB 程度です。誤差の範囲程度のサイズではあります。ではメモリ上のサイズを占めるデータのベースとなる表のセグメントサイズも今一度確認しておきましょう。

SEGMENT_NAME                                                                     SEGMENT_TYPE               MB
-------------------------------------------------------------------------------- ------------------ ----------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP TABLE 3
SYS_C0013800 INDEX 6


VECTORデータ型の列以外もありますが、ベース表のセグメントサイズは、 249MB あります。。。おやおや、サイズ、近似してますよね。。

SCOTT@localhost:1521/freepdb1> r
1* select segment_name,segment_type,bytes / 1024 /1024 "MB" from user_segments where segment_name = 'SEARCH_DATA'

SEGMENT_NAME SEGMENT_TYPE MB
------------------------------ ------------------ ----------
SEARCH_DATA TABLE 248


もう少しベース表のセグメントサイズを調べてみましょう。

SCOTT@localhost:1521/freepdb1> l
1 create table search_data_without_vector_desc
2 as
3 select
4 id
5 ,primary_description
6 ,description
7 ,location_desc
8 ,district
9 ,ward
10 ,community
11 ,c_year
12* from search_data
SCOTT@localhost:1521/freepdb1> /

表が作成されました。

SCOTT@localhost:1521/freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>upper('search_data_without_vector_desc'),cascade=>true,no_invalidate=>false);

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

おお、10MBなので、VECTORデータ型の列を含むSEARCH_DATA表のVECTORデータ型部分のサイズは、238MB 程度みたいですね。むむむ。。。

SCOTT@localhost:1521/freepdb1> r
1 select
2 segment_name
3 ,segment_type
4 ,bytes / 1024 /1024 "MB"
5 from
6 user_segments
7 where
8* segment_name = upper('search_data_without_vector_desc')

SEGMENT_NAME SEGMENT_TYPE MB
------------------------------- ------------------ ----------
SEARCH_DATA_WITHOUT_VECTOR_DESC TABLE 10


in-memory vector index (HNSW)のメモリー消費サイズは、 228.4MB
(これに、補助表のMAP表と索引のセグメントサイズ 9MB を加算すると、237.4MB おおおおおおおおーっ)

ベース表のVECTORデータ型だけのセグメントサイズは、238MB 

で、ですねぇ〜、EXPLAIN PLAN FOR CREATE VECTOR INDEX ...の見積もりサイズと、DBMS_SPACE.CREATE_INDEX_COSTプロシージャによる見積もり。。。VECTOR INDEXに対応できているという情報は未確認のままですが。。。

EXPLAIN PLAN FOR CREATE VECTOR INDEX ...
- estimated index size: 293M bytes


DBMS_SPACE.CREATE_INDEX_COSTプロシージャによるセグメントサイズ見積もり...
Segment Size (MB) :280

という結果になりました。偶然近い値だったの??。。ということでもなさそうな気はします。。

ただし、ストレージのセグメントサイズではなくて、VECTOR MEMORY AREAで消費される索引サイズの見積もりとして....

みなさんは、どう思います????


おまけの疑問というか違和感(これまでも少しだけ触れたが、妙な違和感がある)


  • これまでのIN-MEMORYとはことなり、VECTOR INDEX (HNSW)の場合、実行計画のオペレーション ( VECTOR INDEX HNSW SCAN ) には、INMEMORYというワードが含まれず勘違いしやすい気がするのに、
    なぜ? INMEMORYを含めなかったのだろう。また、*_OBJECTS/*_SEGMENTSを使わず、OBJECT_IDをIDX_OBJNというシノニムまで作り、vecsys.vector$index で別管理かつ、補助表をJSONに書き込んでいる。
    SQL文面倒なんすけど。。というのとこれまでの*_OBJECTSの存在と、vecsys.vector$indexのidx_objnの分離(*_objectsのobject_idの採番と同じなのに。。)といい、これまでと違う空気感が強い。

少し調べたのですが、
以下メッセージからも読み取れるように INMEMORY というワードは、Oracle Database上、In-Memory Column Store Architectureをイメージさせるものではないことは確かようだ、
では、なぜ、INMEMORYというキーワードを実行計画のオペレーション名に含めなかった理由は少々理解しにくい。INMEMORY VECTOR INDEX HNSW SCANのほうがイメージしやすいと思うのは私だけだろうか。。。
このフワフワしてるところが違和感の原因でもあるな。。。

ORA-51815
INMEMORY NEIGHBOR GRAPH HNSW vector index snapshot is too old.
Oracle Database 23ai / Error Messages Oracle Database / Database Error Messages / ORA-51815

最後に、vector_index_neighbor_graph_reload を restartにすると再ロードされるよ(scope=bothでspfileにも同時に反映できるよ!)


  • 23.4までは初期化パラメータ vector_index_neighbor_graph_reload のデフォルトが OFF であった。
    インスタンス再起動で VECTOR INDEX (HNSW) がインメモリー上に再作成されない!!!w(なんだとーーー!)
    23.6以降では、デフォルトが変更になり、 restart がデフォルトになっています。ここ試験にでるよ(知らんけどw

おまけに、ADB-Sだと、V$VECTOR_INDEXなんてビューが提供されていたり。。。なぜ全てで提供しないのだろう。。。23.4だからって話でもなさそうで。。


  • 関連するディクショナリービューなどが整備されてないように見える(今後整備されるような気はする)。対応しきれていない部分はJSON化して回避しているようにも見える(やめて〜〜w)

ということで、最終的な私の理解のビジュアル化w 2025/6/25時点、かつ、Oracle Database 23ai 23.4 を元にした理解は以下の通り(まだ理解不足な箇所はあるかもしれない)

Create_vector_index_and_aux_tables_etc

なお、
DBMS_VECTOR.GET_INDEX_STATUSプロシージャが返すステータスをみると興味深い内容が載っている
Oracle Database 23 / Oracle AI Vector Search User's Guide / Vector Index Status, Checkpoint, and Advisor Procedures / GET_INDEX_STATUS

CREATE VECTOR INDEX (HNSW)に関する状態を取得するプロシージャが返すステータスには以下のように記載されている。In-memory vector index (HNSW)のDDL発行からvector memory poolにロードされ、multi-layered HNSW graphができあがるまでのステータスがわかります!!!


そしてここでも、なんでプロシージャ必要だったのだろう。。。。と、言う素朴な疑問が!!!

Oracle Databaseには昔から、V$SESSION_LONGOPSってビューがあって。。。。
長時間操作のステータスがわかるようになっているのだが.....

ますます、なぜ、これまでの機能やビューを有効に再利用していないのだろう。。。という点が気になる...。が、現状は見切れたかな、と。
(もう一つのVector Indexのタイプと更新トラッキングと反映、いくつかの実行計画パターンの確認を除く)


  • HNSW Index Initialization
    Initialization phase for the HNSW vector index creation

  • HNSW Index Auxiliary Tables Creation
    Creation of the internal auxiliary tables for the HNSW Neighbor Graph vector index

  • HNSW Index Graph Allocation
    Allocation of memory from the vector memory pool for the HNSW graph

  • HNSW Index Loading Vectors
    Loading of the base table vectors into the vector pool memory

  • HNSW Index Graph Construction
    Creation of the multi-layered HNSW graph with the previously loaded vectors

  • HNSW Index Creation Completed
    HNSW vector index creation finished


では、また。

Enjoy SQL! and AI vector search!



Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN
VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編)
VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(後編)


| | | コメント (0)

2025年6月18日 (水)

VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(後編)

Previously on Mac De Oracle
前回は、VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編) でした。
今日はその後編です。

10046トレースよりVECTOR INDEX(HNSW)作成では前回の通り、補助表3つ(うち2つは変更をトラックするためなので作成直後は空)、それらの表に加え、各1つの索引が作成されるということがハッキリしました。
ただ、VECTOR INDEXの補助表はvecsys.vector$indexという表にJSONとして保持されている点と、USER_INDEXESから確認できるVECTOR INDEX自体は実態を持たない、さらに、Oraclerにはお馴染みの*_INDEXESなどから単純に取得できないことも見えてきました(今後もっと便利になることを期待したいですね)

(2025/6/18追記)
これ、HNSWってインメモリーと言っても、元ネタは永続化されているわけで、それをメモリープールにポピュレートしてるって理解(間違ってないとは思うけど、違ってたらコメントもらえるとありがたいです)なので、後編ではそのあたりを見ておこうかと。。。
インメモリー近傍グラフ・ベクトル索引

(作成される補助表の名称にVECTOR INDEXのベース表の名称が入っているようなので中間一致で検索していますが、これだとノイズも多くなるので検索キツイですよね。索引名はシステム生成名称なのでベース表の名称で中間一致検索はできません!。このケースに限ればなんとか拾えてるけどという感じではありますね。。。ということで後半ではJSONから引っこ抜いてなんとかするパズルもwやってますw)

SCOTT@localhost:1521/freepdb1> select index_name , table_name, index_type from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME INDEX_TYPE
------------------------------ -------------------------------------------------------------------------------- ---------------------------
SYS_IL0000079525C00007$$ VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG LOB
SYS_IL0000078074C00009$$ SEARCH_DATA LOB
SEARCH_DATA_HNSW_IX SEARCH_DATA VECTOR
SYS_C0013800 VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP NORMAL
PK_XID_79519 VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS NORMAL

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 idx_name
3 ,(
4 SELECT
5 table_name
6 FROM
7 user_tables
8 WHERE
9 user_tables.table_name = (
10 SELECT
11 object_name
12 FROM
13 user_objects
14 WHERE
15 user_objects.object_id = vecsys.vector$index.idx_base_table_objn
16 )
17 ) AS tab_name
18 ,JSON_SERIALIZE(
19 idx_auxiliary_tables
20 RETURNING VARCHAR2 PRETTY
21 ) AS idx_auxiliary_tables
22 FROM
23 vecsys.vector$index
24 WHERE
25* idx_name = 'SEARCH_DATA_HNSW_IX'

IDX_NAME TAB_NAME IDX_AUXILIARY_TABLES
------------------------------ ------------------------------ ----------------------------------------------------------------------------------------------------------------------------------
SEARCH_DATA_HNSW_IX SEARCH_DATA {
"rowid_vid_map_objn" : 79520,
"shared_journal_transaction_commits_objn" : 79522,
"shared_journal_change_log_objn" : 79525,
"rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP",
"shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
}

 

これまでの調査の結果、VECTOR INEDEX (HNSW) であるSEARCH_DATA_HNSW_IXはオブジェクトでもなく、セグメントもない(当然、シノニムにもなれない。仮にも索引ですから)
VECTOR INDEX (HNSW)の索引セグメントのサイズは、作成直後のIDX_AUXILIARY_TABLESと、索引があればそれら索引のセグメントサイズの合計ということになりますよね!!!
(やっと見えてきたw、なんなんだこれ)

 

こんな感じでJSONからIDX_AUXILIARY_TABLESにある関連表のオブジェクトID、または、テーブル名を取り出しUSER_INDEXESから索引も合わせて取得したうえで、それぞれのセグメントサイズの合計を取得すれば物理的なサイズは見えますよね。。。。(めんどくさいw)

SCOTT@localhost:1521/freepdb1> l
1 WITH
2 vector_idx_auxiliary_tables
3 AS (
4 SELECT
5 idx_name AS vector_index_name
6 , REPLACE(aux_table_name,'"','') AS aux_table_name
7 FROM
8 (
9 SELECT
10 vvi.idx_name AS idx_name
11 ,vvi.idx_auxiliary_tables.rowid_vid_map_name
12 ,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
13 ,vvi.idx_auxiliary_tables.shared_journal_change_log_name
14 FROM
15 vecsys.vector$index vvi
16 ) objs
17 UNPIVOT (
18 aux_table_name FOR related_obj_name IN
19 (
20 rowid_vid_map_name
21 , shared_journal_transaction_commits_name
22 , shared_journal_change_log_name
23 )
24 )
25 )
26 SELECT
27 aux_table_name AS segment_name
28 FROM
29 vector_idx_auxiliary_tables
30 WHERE
31 vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
32 UNION ALL
33 SELECT
34 index_name AS segment_name
35 FROM
36 user_indexes
37 WHERE
38 EXISTS
39 (
40 SELECT
41 1
42 FROM
43 vector_idx_auxiliary_tables
44 WHERE
45 vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
46 AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
47 )
48*
SCOTT@localhost:1521/freepdb1> /

SEGMENT_NAME
---------------------------------------------------------------------------------------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_SHARED_JOURNAL_CHANGE_LOG
SYS_C0013800
PK_XID_79519
SYS_IL0000079525C00007$$

6行が選択されました。

経過: 00:00:00.16

 

では、あらためて、EXPLAIN PLAN FOR CREATE VECTOR INDEX...の見積もりサイズと、実際のサイズを比較してみましょう!

SCOTT@localhost:1521/freepdb1> l
1 explain plan for
2 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
3 ORGANIZATION
4 INMEMORY NEIGHBOR GRAPH
5 DISTANCE COSINE
6* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

解析されました。

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Plan hash value: 2727344110

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | CREATE INDEX STATEMENT | | 125K| 188M| 14689 (1)| 00:00:01 |
| 1 | VECTOR INDEX BUILD | SEARCH_DATA_HNSW_IX | | | | |
----------------------------------------------------------------------------------------------

Note
-----
- estimated index size: 293M bytes



なんどかコメントしてますが、SEARCH_DATA_HNSW_IXという索引セグメントは無く!、複数の補助表と索引群から構成されている。それらの作成直後のサイズは....

SCOTT@localhost:1521/freepdb1> r
1 WITH
2 vector_idx_auxiliary_tables
3 AS (
4 SELECT
5 idx_name AS vector_index_name
6 , REPLACE(aux_table_name,'"','') AS aux_table_name
7 FROM
8 (
9 SELECT
10 vvi.idx_name AS idx_name
11 ,vvi.idx_auxiliary_tables.rowid_vid_map_name
12 ,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
13 ,vvi.idx_auxiliary_tables.shared_journal_change_log_name
14 FROM
15 vecsys.vector$index vvi
16 ) objs
17 UNPIVOT (
18 aux_table_name FOR related_obj_name IN
19 (
20 rowid_vid_map_name
21 , shared_journal_transaction_commits_name
22 , shared_journal_change_log_name
23 )
24 )
25 )
26 SELECT
27 user_segments.segment_name
28 , user_segments.segment_type
29 , user_segments.bytes / 1024 / 1024 AS "MB"
30 FROM
31 user_segments
32 INNER JOIN
33 (
34 SELECT
35 aux_table_name AS segment_name
36 FROM
37 vector_idx_auxiliary_tables
38 WHERE
39 vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
40 UNION ALL
41 SELECT
42 index_name AS segment_name
43 FROM
44 user_indexes
45 WHERE
46 EXISTS
47 (
48 SELECT
49 1
50 FROM
51 vector_idx_auxiliary_tables
52 WHERE
53 vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
54 AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
55 )
56 ) vector_idx_aux_objects
57 ON
58 vector_idx_aux_objects.segment_name = user_segments.segment_name
59*

SEGMENT_NAME SEGMENT_TYPE MB
-------------------------------------------------------------------------------- ------------------ ----------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP TABLE 3
SYS_C0013800 INDEX 6

 

誤差が大きいですよね。正しく見積もれてない可能性が高いように思いますが、少なく出るよりマシ程度かもしれません。今後のチューニングに期待。。。。。というところでしょうか。。。

SCOTT@localhost:1521/freepdb1> l
1 explain plan for
2 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
3 ORGANIZATION
4 INMEMORY NEIGHBOR GRAPH
5 DISTANCE COSINE
6* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

 

だと、

- estimated index size: 293M bytes
ですが、
実際には以下の通り、 9M Bytes程度でした。
(もしかして、VECTOR INDEX (HNSW)ってINMEMORYだからメモリーサイズ? んなことないか、いや、あったりしてw と思ったりし始めているw  23ai 23.6では、DBMS_VECTOR.INDEX_VECTOR_MEMORY_ADVISORプロシージャによりメモリーサイズを見積もれるようになっていたりする。。怪しい。2025/6/19追記)

SEGMENT_NAME                                                                     SEGMENT_TYPE               MB
-------------------------------------------------------------------------------- ------------------ ----------
VECTOR$SEARCH_DATA_HNSW_IX$78074_79519_0$HNSW_ROWID_VID_MAP TABLE 3
SYS_C0013800 INDEX 6

 

Bツリー索引の見積り(19cで確かめたときは、これほど大きな差にはなっていませんでした)に比べると精度は低そうですよね。と言うより、VECTOR INDEXには対応できてないのかもしれませんね....

おまけで、
DBMS_SPACEパッケージの索引サイズ見積もりも試しておきましょう。。。。。やはり、explain plan for文同様にVECTOR INDEXのセグメントサイズ見積もりには対応してなさそう。(小さくでるわけではないのですがw)

SCOTT@localhost:1521/freepdb1> set serveroutput on
SCOTT@localhost:1521/freepdb1> l
1 DECLARE
2 used_bytes NUMBER;
3 segment_bytes NUMBER;
4 BEGIN
5 DBMS_SPACE.CREATE_INDEX_COST (
6 ddl=> 'CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )'
7 || ' ORGANIZATION'
8 || ' INMEMORY NEIGHBOR GRAPH'
9 || ' DISTANCE COSINE'
10 || ' WITH TARGET ACCURACY 90'
11 , used_bytes => used_bytes
12 , alloc_bytes => segment_bytes
13 );
14 DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
15 DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
16* END;
SCOTT@localhost:1521/freepdb1> /
Segment Size (MB) :280
Index data Size (MB) :188.94672393798828125

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

 

Enjoy SQL, PL/SQL and VECTOR SEARCH!

VECTOR INDEXの謎はもう少し探る必要があるようだ...w (後編のおまけにつづくw)

めちゃ快適な気温の場所なので、
東京は猛暑と聞いて恐怖している

では、また。

 


参考)
今回利用したコードの一部

WITH
vector_idx_auxiliary_tables
AS (
SELECT
idx_name AS vector_index_name
, REPLACE(aux_table_name,'"','') AS aux_table_name
FROM
(
SELECT
vvi.idx_name AS idx_name
,vvi.idx_auxiliary_tables.rowid_vid_map_name
,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
,vvi.idx_auxiliary_tables.shared_journal_change_log_name
FROM
vecsys.vector$index vvi
) objs
UNPIVOT (
aux_table_name FOR related_obj_name IN
(
rowid_vid_map_name
, shared_journal_transaction_commits_name
, shared_journal_change_log_name
)
)
)
SELECT
aux_table_name AS segment_name
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
UNION ALL
SELECT
index_name AS segment_name
FROM
user_indexes
WHERE
EXISTS
(
SELECT
1
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
)
;
WITH
vector_idx_auxiliary_tables
AS (
SELECT
idx_name AS vector_index_name
, REPLACE(aux_table_name,'"','') AS aux_table_name
FROM
(
SELECT
vvi.idx_name AS idx_name
,vvi.idx_auxiliary_tables.rowid_vid_map_name
,vvi.idx_auxiliary_tables.shared_journal_transaction_commits_name
,vvi.idx_auxiliary_tables.shared_journal_change_log_name
FROM
vecsys.vector$index vvi
) objs
UNPIVOT (
aux_table_name FOR related_obj_name IN
(
rowid_vid_map_name
, shared_journal_transaction_commits_name
, shared_journal_change_log_name
)
)
)
SELECT
user_segments.segment_name
, user_segments.segment_type
, user_segments.bytes / 1024 / 1024 AS "MB"
FROM
user_segments
INNER JOIN
(
SELECT
aux_table_name AS segment_name
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
UNION ALL
SELECT
index_name AS segment_name
FROM
user_indexes
WHERE
EXISTS
(
SELECT
1
FROM
vector_idx_auxiliary_tables
WHERE
vector_idx_auxiliary_tables.aux_table_name = user_indexes.table_name
AND vector_idx_auxiliary_tables.vector_index_name = 'SEARCH_DATA_HNSW_IX'
)
) vector_idx_aux_objects
ON
vector_idx_aux_objects.segment_name = user_segments.segment_name
;
set serveroutput on
DECLARE
used_bytes NUMBER;
segment_bytes NUMBER;
BEGIN
DBMS_SPACE.CREATE_INDEX_COST (
ddl=> 'CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )'
|| ' ORGANIZATION'
|| ' INMEMORY NEIGHBOR GRAPH'
|| ' DISTANCE COSINE'
|| ' WITH TARGET ACCURACY 90'
, used_bytes => used_bytes
, alloc_bytes => segment_bytes
);
DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
END;
/

 


Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN
VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編)

 

 

| | | コメント (0)

2025年6月17日 (火)

VECTOR INDEX はどこ?、見積もりサイズだとそれなりのサイズだったのに... の謎を探るべく、我々は洞窟の奥へ向かった!(前編)

Previously on Mac De Oracle
前回は、実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCANでした。
今日はそれに絡んで気づいたVECTOR INDEXの謎を追いかけてみることにします。

前回の最後の宿題というか謎の一つ目、覚えてますか?  そう、VECTOR INDEXを作成後、まあよくある、USER_INDEXESを検索して作成されたVECTOR INDEXの名称を確認して、EXPLAIN PLAN FORで見積もった索引サイズとどの程度乖離しているのだろうと、手癖でUSER_SEGMENTSをアクセスしてみると、なな、なーーーーーんと、ない、無い!、なーーーい!。 (INMEMORYとはいっても元ネタがあってINMEMORY化されるわけで仕組み的に理解しにくいのと、これまでのディクショナリービューの使い方と異なっていてものすごく追いにくい)
なんで? という謎。

SCOTT@localhost:1521/freepdb1> select index_name,index_type,table_name from user_indexes where table_name='SEARCH_DATA';

INDEX_NAME INDEX_TYPE TABLE_NAME
------------------------------ --------------------------- ------------------------------
SYS_IL0000078074C00009$$ LOB SEARCH_DATA
SEARCH_DATA_HNSW_IX VECTOR SEARCH_DATA

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> select segment_name from user_segments where segment_name = 'SEARCH_DATA_HNSW_IX';

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

 

その後、アッ! 
いつもと違うビューがあったことを思い出し、覗いてみると、
作成したVECTOR INDEX (HNSW)の名称とともに、IDX_AUXILIARY_TABLES列(JSONデータ型)に、なにやらそれらしい値があるじゃありませんか!!!
そーなんだーーーーっ。VECTOR INDEXの名称は、VECTOR INDEXの補助表の集まりをまとめる為だけの存在?!!!
というところまででした。

SCOTT@localhost:1521/freepdb1> r
1* SELECT * FROM vecsys.vector$index

IDX_OBJN IDX_OBJD IDX_OWNER# IDX_NAME IDX_BASE_TABLE_OBJN IDX_BASE_TABLE_OWNER# IDX_PARAMS IDX_AUXILIARY_TABLES
---------- ---------- ---------- ------------------------------ ------------------- --------------------- ------------------------------ ------------------------------
78797 153 SEARCH_DATA_HNSW_IX 78074 153 {"type":"HNSW","num_neighbors" {"rowid_vid_map_objn":78798,"s
:32,"efConstruction":200,"dist hared_journal_transaction_comm
ance":"COSINE","accuracy":90," its_objn":78800,"shared_journa
vector_type":"FLOAT32","vector l_change_log_objn":78803,"rowi
_dimension":384,"degree_of_par d_vid_map_name":"VECTOR$SEARCH
allelism":1,"pdb_id":3,"indexe _DATA_HNSW_IX$78074_78797_0$HN
d_col":"VECTOR_DESC"} SW_ROWID_VID_MAP","shared_jour
nal_transaction_commits_name":
"VECTOR$SEARCH_DATA_HNSW_IX$78
074_78797_0$HNSW_SHARED_JOURNA
L_TRANSACTION_COMMITS","shared
_journal_change_log_name":"VEC
TOR$SEARCH_DATA_HNSW_IX$78074_
78797_0$HNSW_SHARED_JOURNAL_CH
ANGE_LOG"}

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 JSON_SERIALIZE(idx_params RETURNING VARCHAR2 PRETTY) AS "INDEX PARAM"
3 ,JSON_SERIALIZE(idx_auxiliary_tables RETURNING VARCHAR2 PRETTY) AS "INDEX AUX"
4* FROM vecsys.vector$index

INDEX PARAM INDEX AUX
-------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------
{ {
"type" : "HNSW", rowid_vid_map_objn" : 78798,
"num_neighbors" : 32, "shared_journal_transaction_commits_objn" : 78800,
"efConstruction" : 200, "shared_journal_change_log_objn" : 78803,
"distance" : "COSINE", "rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_ROWID_VID_MAP",
"accuracy" : 90, "shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"vector_type" : "FLOAT32", "shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
"vector_dimension" : 384, }
"degree_of_parallelism" : 1,
"pdb_id" : 3,
"indexed_col" : "VECTOR_DESC"
}

 

一旦ここまでの情報を整理すると

VECTOR INDEX(この例ではHNSW)の実態は、複数の補助表から構成されており、それらをまとめているのが 作成時に指定したVECTOR INDEXの名称。
オブジェクトとしては存在しているが、それ自体は、セグメントを持たない!!!(IN MEMORY展開される索引だからとはいっても。。なにかしらあるのでは。。?)

現時点の23aiには、使い勝手の良いディクショナリービューは提供されていない。。。。まじか!

その代わりに、vecsys.vector$indexディクショナリー表(ビューではない)から詳細を追うことができる。(vecsysスキーマ)

さらに、補助表を見つけるためには、該当ディクショナリー表のIDX_AUXILIARY_TABLES列に格納されているJSONの*_objnや、補助表のオブジェクトID、*_nameから探れ!、と。
ようするに、このJSONに対象となるVECTOR INDEXとその実態の依存関係が入っている!!!!!
(ちなみに、表しかないが、おそらく、関連索引もありそうだ、というかあるだろうな。補助表の索引は補助表をキーにしてUSER_INDEXESから探すしかないだろうけども。)

ここまで見てきたところで、
めんどくせーーーーーーーーーーーーーっ! なぜ、依存関係をJSONに突っ込んだの?。。。。という顔をしているところwwwww

そしてもう一つの謎、EXPLAIN PLAN FOR CREATE VECTOR INDEX ...foo bar ..として見積もられたVECTOR INDEXの見積もりサイズって、こいつら補助表含めた合計値? 
なのか?  。。。だよなw 精度的に微妙な気がしなくも無い

全部かき集めて、合計して、見積もりサイズとの差分を見てみよう!
B*Treeの索引見積もりって、結構いい感じのサイズを弾き出してくれるわけだが。。。果たして。。。こいつは、どうなんだ? SEARCH_DATA_HNSW_IXって実態持ってないし。。。

20250613-142549

 

では、もっと探ってみなければw

 

 

そのまえに、CREATE VECTOR INDEXの謎を探るべくw
10046トレースで洞窟の奥に潜入することにします! ww (わからなくなったら、これしかない!)

SCOTT@localhost:1521/freepdb1> desc search_data
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NUMBER
PRIMARY_DESCRIPTION VARCHAR2(40)
DESCRIPTION VARCHAR2(100)
LOCATION_DESC VARCHAR2(100)
DISTRICT VARCHAR2(30)
WARD NUMBER
COMMUNITY VARCHAR2(30)
C_YEAR NUMBER
VECTOR_DESC VECTOR(*, *)

SCOTT@localhost:1521/freepdb1> select count(*) from search_data

COUNT(*)
----------
125000

 

作成済みVECTOR INDEX (HNSW)を削除しておきます。

SCOTT@localhost:1521/freepdb1> drop index search_data_hnsw_ix;

索引が削除されました。

経過: 00:00:01.77

SCOTT@localhost:1521/freepdb1> select index_name , table_name, index_type from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME INDEX_TYPE
------------------------------ ------------------------------ ---------------------------
SYS_IL0000078074C00009$$ SEARCH_DATA LOB

SCOTT@localhost:1521/freepdb1> select table_name,column_name,segment_name,index_name from user_lobs where table_name like '%SEARCH_DATA%';

TABLE_NAME COLUMN_NAME SEGMENT_NAME INDEX_NAME
------------------------------ ------------------------------ ------------------------------ ------------------------------
SEARCH_DATA VECTOR_DESC SYS_LOB0000078074C00009$$ SYS_IL0000078074C00009$$

経過: 00:00:00.07

 

10046トレースの準備ができたので、トレースを設定しCREATE VECTOR INDEX文を実行して洞窟の奥へw

SCOTT@localhost:1521/freepdb1> show parameter timed_statistics

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
timed_statistics boolean TRUE

SCOTT@localhost:1521/freepdb1> show parameter max_dump_file_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
max_dump_file_size string 32M
SCOTT@localhost:1521/freepdb1> alter session set max_dump_file_size = unlimited;

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

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> show parameter tracefile_identifier

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
tracefile_identifier string
SCOTT@localhost:1521/freepdb1> alter session set tracefile_identifier = 'create_vector_index';

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

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context forever, level 12';

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

経過: 00:00:00.02
SCOTT@localhost:1521/freepdb1> l
1 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
2 ORGANIZATION
3 INMEMORY NEIGHBOR GRAPH
4 DISTANCE COSINE
5* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

索引が作成されました。

経過: 00:00:39.24
SCOTT@localhost:1521/freepdb1> alter session set events '10046 trace name context off';

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

経過: 00:00:00.01

...略...

[oracle@localhost ~]$ echo $ORACLE_BASE
/opt/oracle
[oracle@localhost ~]$ cd $ORACLE_BASE/diag/rdbms/free/FREE/trace
[oracle@localhost trace]$ pwd
/opt/oracle/diag/rdbms/free/FREE/trace
[oracle@localhost trace]$
[oracle@localhost trace]$ ll *create_vector_index*
-rw-r-----. 1 oracle oinstall 2836064 6月 12 00:24 FREE_ora_4133_create_vector_index.trc
-rw-r-----. 1 oracle oinstall 602896 6月 12 00:24 FREE_ora_4133_create_vector_index.trm
[oracle@localhost trace]$
[oracle@localhost trace]$ tkprof FREE_ora_4133_create_vector_index.trc FREE_ora_4133_create_vector_index.txt waits=yes explain=scott@localhost:1521/freepdb1 sys=yes

...略...

[oracle@localhost trace]$ ll *create_vector_index*
-rw-r-----. 1 oracle oinstall 2836064 6月 12 00:24 FREE_ora_4133_create_vector_index.trc
-rw-r-----. 1 oracle oinstall 602896 6月 12 00:24 FREE_ora_4133_create_vector_index.trm
-rw-rw-r--. 1 oracle oracle 500986 6月 12 00:30 FREE_ora_4133_create_vector_index.txt
[oracle@localhost trace]$
[oracle@localhost trace]$ view FREE_ora_4133_create_vector_index.txt

 

ということで、整形したトレースファイルからCREATE文を抜き出してみた。
これは私が実行したDDL文なので参考程度に...

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
ORGANIZATION
INMEMORY NEIGHBOR GRAPH
DISTANCE COSINE
WITH TARGET ACCURACY 90

 

以降、トレースから抜き出した補助表及関連する索引を作成するDDL
VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP表、および、主キー索引の作成(基本的にこの補助表が主役)

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP 
(base_table_rowid ROWID PRIMARY KEY, vertex_id NUMBER)

CREATE UNIQUE INDEX "SCOTT"."SYS_C0013760" on
"SCOTT"."VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP"("BASE_TABLE_ROWID")
NOPARALLEL



VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS表、および、主キー索引の作成

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS 
(usn NUMBER NOT NULL, slot NUMBER NOT NULL, seq
NUMBER NOT NULL, commit_scn NUMBER NOT NULL,
CONSTRAINT pk_xid_79461 PRIMARY KEY(usn, slot, seq))
PARTITION BY RANGE(commit_scn)
INTERVAL(100)
(PARTITION pdefault VALUES LESS THAN (0))

CREATE UNIQUE INDEX "SCOTT"."PK_XID_79461" on
"SCOTT"."VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS"("USN","SLOT","SEQ")
NOPARALLEL


VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG表の作成(HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS表の参照パーティションになっている)

CREATE TABLE SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG 
(usn NUMBER NOT NULL, slot NUMBER NOT NULL, seq NUMBER NOT
NULL, xcn NUMBER, base_table_rowid ROWID, dml_op VARCHAR2(10),
data_vector vector(384, FLOAT32),
CONSTRAINT fk_xid_79461 FOREIGN KEY(usn, slot, seq)
REFERENCES
SCOTT.VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS(usn, slot, seq))
PARTITION BY REFERENCE(fk_xid_79461)

各表をdescribeしてみると...

VECTOR列を持つ表のROWIDとVERTEX_IDをマップしていますね、表を見てなんとなく想像できる列名で素敵w。RDBMSのモデリングで列からなにやってるか想像もできねー設計しているのを見たりしてると心が清くなった気がしますね。
これがVECTOR INDEXの主役といってもよいでしょうね。

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_ROWID_VID_MAP
名前 NULL? 型
----------------------------------------- -------- ----------------------------
BASE_TABLE_ROWID NOT NULL ROWID
VERTEX_ID NUMBER

 

次の2つの表は、元の表のVECTORに影響のある更新がトラックされているみたい。詳しい資料読んではいないのですが、変更をトラックしていく表っぽうので、変更のない状況では空なんじゃないだろうか。。。あとで確認します。

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
名前 NULL? 型
----------------------------------------- -------- ----------------------------
USN NOT NULL NUMBER
SLOT NOT NULL NUMBER
SEQ NOT NULL NUMBER
COMMIT_SCN NOT NULL NUMBER

SCOTT@localhost:1521/freepdb1> desc VECTOR$SEARCH_DATA_HNSW_IX$78074_79461_0$HNSW_SHARED_JOURNAL_CHANGE_LOG
名前 NULL? 型
----------------------------------------- -------- ----------------------------
USN NOT NULL NUMBER
SLOT NOT NULL NUMBER
SEQ NOT NULL NUMBER
XCN NUMBER
BASE_TABLE_ROWID ROWID
DML_OP VARCHAR2(10)
DATA_VECTOR VECTOR(384, FLOAT32)

 

一応user_indexesビューから全体を見てみます。USER_INDEXESビューを通して見ると新たに7つのオブジェクトが作成されたことがわかります!!!!(赤字部分)
vecsys.vector$index表からは、補助表の存在しか見えませんでしたが、索引も作成されてます。実際にはこの表と索引のセグメントサイズの合計が、VECTOR INDEX作成時のセグメントサイズってことですよね?!(INMEMORYだからメモリーサイズって可能性もなくはないけど、どっちかわからんw) とはいえ、CREATE VECTOR INDEX ほげほげ索引で、指定したほげほげ索引自体はセグメントは持たない!(INMEMORYテーブルとは異なる持ち方をしているだけなのかもしれないが、わかりにくいw)


SCOTT@localhost:1521/freepdb1> select index_name , table_name from user_indexes where table_name like '%SEARCH_DATA%';

INDEX_NAME TABLE_NAME
------------------------------ ----------------------------------------------------------------------------------------------------
SYS_IL0000078074C00009$$ SEARCH_DATA
SEARCH_DATA_HNSW_IX SEARCH_DATA
SYS_C0013720 VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_ROWID_VID_MAP
PK_XID_79421 VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS
SYS_IL0000079427C00007$$ VECTOR$SEARCH_DATA_HNSW_IX$78074_79421_0$HNSW_SHARED_JOURNAL_CHANGE_LOG

経過: 00:00:00.29

表名を中間一致検索すると関連する索引と表をリストすることができますが、これだとなんかイケてない感じが強いのでvecsys.vector$index表より扱いやすいビューを提供してもらいたいですね! まじで (苦笑

長くなってしまったので、一旦、謎の1つをまとめておきたいと思います。
VECTOR INDEX (HNSW)のセグメントサイズとEXPLAIN PLAN FOR CREATE VECTOR INDEX...の見積もりサイズの差分確認は次回のおたのしみということで。

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
ORGANIZATION
INMEMORY NEIGHBOR GRAPH
DISTANCE COSINE
WITH TARGET ACCURACY 90

 

作成されるベクター索引(今回の例では、search_data_hnsw_ix)は *_INDEXESにはVECTOR索引としてリストされるが実体は持たない(INMEMORYとはいっても何がどうINMEMORY化されるかディクショナリーから追いやすかったテーブルのINMEMORY化とは傾向が違いそう)。
補助表とその索引を含めたオブジェクト群をアクセスするためのキーのような存在。
ベクター索引(今回のHNSW索引の例では、マップ表とベクターの更新をトラックするパーティション表と参照パーティション表からなる3表と付随する3つの索引から構成されている。 ベクター索引の主役は、ベクター列をもつベース表のROWIDをVERTEX_IDをマップするマップ表とその索引。変更がない状態であれば、おそらくこの2つのオブジェクトがセベクター索引のセグメントサイズの総量になりそう。

また、
これらオブジェクト(索引も含め)を簡単に一覧するビューは無く、中間一致検索で無理やり検索するか、vecsys.vector$index表のJSONデータを使って他のビューと結合して取得する必要がありそう(めんどくせぇw)

ということまでは見えた。(以下は、洞窟の奥へ潜入したときの手書きメモww)

もうひとつ、前述の通り、search_data_hnsw_ixというベクター索引の便宜上のオブジェクトは同一名のセグメントは持っていません。しかし、以下のようなSELECT文では、該当ベクター索引が利用されていることを示すオブジェクトとして現れてくるというのも特徴的ですね。
SQL文と実行計画によってはMAP表が現われてくることもあるようなので、その例についてもいずれ書こうと思っています:) (それはそれで謎いですよね。突然裏の主役が実行計画に顔を出してくるわけなのでw)

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )


10行が選択されました。

経過: 00:00:00.32

実行計画
----------------------------------------------------------
Plan hash value: 2333665681

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | 2 (50)| 00:00:01 |
| 1 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY | | 10 | 16400 | 2 (50)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| SEARCH_DATA | 10 | 16400 | 1 (0)| 00:00:01 |
| 6 | VECTOR INDEX HNSW SCAN | SEARCH_DATA_HNSW_IX | 10 | 16400 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

 

なお、今回利用している Oracle Database 23.4は最近の更新ペースからするとすでに古いリリースになっていますw 
VECTOR関連追加など含め結構な差分がりそうですね。例えば、DBMS_VECTOR.INDEX_VECTOR_MEMORY_ADVISOR()プロシージャが無いなど。(マニュアルには記載されているんですが、リリースいくつから有効というような表記は見当たらず)
また、VirtualBox Applienceだと、現時点では、23.7 が最新みたいなので入れ替えないとな。。。

 

23って、機能的にいつ頃落ち着くんだろうなぁ〜っ。

次回へ、つづく!

Enjoy SQL! and JSON?!

 

 


Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

 

| | | コメント (0)

2025年6月 5日 (木)

実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN

Previously on Mac De Oracle
前回は、Oracle Database 23ai freeで試すVector Search - データ準備編でした。
今回は、AI Vector SearchでVECTOR INDEX HNSW SCANとそれが利用できない場合の実行計画という名のSQL文のレントゲンを診ていきたいと思います。:)

その前に、VECTOR INDEX HNSW を利用する前に必要な準備がまだ残っていました。
vector_memory_sizeの設定です。デフォルトでは 0 になっています。

Oracle Dataabase 23ai / Oracle AI Vector Search User's Guide/ Oracle AI Vector Search Parameters

https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/oracle-ai-vector-search-parameters.html

SYS@FREE> show parameter memory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------

...略...

inmemory_xmem_size big integer 0
memory_max_size big integer 0
memory_max_target big integer 0
memory_size big integer 0
memory_target big integer 0
optimizer_inmemory_aware boolean TRUE
shard_apply_max_memory_size integer 0
shared_memory_address integer 0
vector_memory_size big integer 0

SYS@FREE> show parameter sga

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 1536M
sga_min_size big integer 0
sga_target big integer 1536M

 

Oracle Database 23ai / Oracle AI Vector Searchユーザーズ・ガイド / ベクトル・プールのサイズ設定

https://docs.oracle.com/cd/G11854_01/vecse/size-vector-pool.html

今回はギリギリですがw 512MBに設定して試します。

SYS@FREE> create pfile from spfile;

ファイルが作成されました。

経過: 00:00:00.01
SYS@FREE> ALTER SYSTEM SET vector_memory_size=512m SCOPE=spfile
SYS@FREE> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@FREE> startup
ORACLEインスタンスが起動しました。

Total System Global Area 1603726344 bytes
Fixed Size 5360648 bytes
Variable Size 654311424 bytes
Database Buffers 402653184 bytes
Redo Buffers 4530176 bytes
Vector Memory Area 536870912 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@FREE>
SYS@FREE> show parameter vector_memory_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
vector_memory_size big integer 512M

 

vector_memory_sizeサイズの準備ができたので、Vector Index (HNSW) を作成します。

まずは、HNSWタイプののVector Indexを作成します。

SCOTT@localhost:1521/freepdb1> l
1 CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
2 ORGANIZATION
3 INMEMORY NEIGHBOR GRAPH
4 IDISTANCE COSINE
5* WITH TARGET ACCURACY 90
SCOTT@localhost:1521/freepdb1> /

索引が作成されました。

経過: 00:00:36.13
SCOTT@localhost:1521/freepdb1> r
1 select index_name,table_name,index_type,index_subtype,uniqueness,ityp_name
2* from user_indexes where table_name = 'SEARCH_DATA'

INDEX_NAME TABLE_NAME INDEX_TYPE INDEX_SUBTYPE UNIQUENES ITYP_NAME
------------------------------ ------------------------------ --------------------------- ---------------------------- --------- --------------------
SYS_IL0000078074C00009$$ SEARCH_DATA LOB UNIQUE
SEARCH_DATA_HNSW_IX SEARCH_DATA VECTOR INMEMORY_NEIGHBOR_GRAPH_HNSW NONUNIQUE

経過: 00:00:00.00
SCOTT@localhost:1521/freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'SEARCH_DATA',no_invalidate=>false, cascade=>true);

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

 

ちなみに、vector_memory_sizeに指定したメモリサイズでは小さすぎる場合、以下のエラーが発生します。vector_memory_sizeを増加するか、データ量を削減するかどちらかなんですよね。Oracle Database 23ai Freeってメモリサイズの制限も厳しいので。

CREATE VECTOR INDEX search_data_hnsw_ix ON search_data ( vector_desc )
*
行1でエラーが発生しました。:
ORA-51961: ベクトル・メモリー領域が不足しています。 ヘルプ:
https://docs.oracle.com/error-help/db/ora-51961/

 

VECTOR INDEX HNSW SCAN + TABLE ACCESS BY INDEX ROWIDになるか確認してみましょう.

索引(Hierarchical Navigable Small World (HNSW)またはInverted File Flat (IVF)ベクトル索引)を利用させるためには、以下のガイドラインに従うことが重要なので一読しておくことをおすすめしておきます。(知らないと簡単にハマってしまうので)


Oracle Database 23ai / Oracle AI Vector Searchユーザーズ・ガイド / 索引使用のガイドライン

https://docs.oracle.com/cd/G11854_01/vecse/guidelines-using-vector-indexes.html

 

シカゴの犯罪データから、"Incident in which someone may have been murdered" という条件で殺人事件に絡んでそうな事件を10件検索してみます。APPROXというキーワードも重要なので忘れずに。

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH APPROX FIRST 10 ROWS ONLY
)
/

 

おおおおおお、索引使ってる:) ほっとした

ただ、これまでの INMEMORYとはことなり、INMEMORYにあるはずので索引アクセスのはずなのに、実行計画のOPERATIONには、INMEMORYというキーワードがないこと。ディクショナリービューの使い方もこれまでとは異なる傾向がり、なかなか分かりづらい部分も多い。今回気づいた違和感というか謎は要調査(2025/6/20追記)

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )
SCOTT@localhost:1521/freepdb1> /

ID DESCRIPTION COMMUNITY LOCATION_DESC DISTRICT V_DISTANCE
---------- ---------------------------------------- -------------------- ---------------------------------------- ---------- ----------
13405240 AGGRAVATED - OTHER DANGEROUS WEAPON AUSTIN APARTMENT 15TH .631286621
13405155 AGGRAVATED - OTHER DANGEROUS WEAPON AUSTIN HOSPITAL BUILDING / GROUNDS 15TH .631286621
13395521 AGGRAVATED - OTHER DANGEROUS WEAPON MORGAN PARK RESIDENCE 22ND .631286621
13404912 AGGRAVATED - OTHER DANGEROUS WEAPON ASHBURN SCHOOL - PUBLIC BUILDING 8TH .631286621
13405606 AGGRAVATED - OTHER DANGEROUS WEAPON AVALON PARK STREET 4TH .631286621
13509969 AGGRAVATED - OTHER DANGEROUS WEAPON AUBURN GRESHAM APARTMENT 6TH .631286621
13396440 AGGRAVATED - OTHER DANGEROUS WEAPON DUNNING STREET 16TH .631286621
13396497 AGGRAVATED - OTHER DANGEROUS WEAPON WOODLAWN APARTMENT 3RD .631286621
13405235 AGGRAVATED - OTHER DANGEROUS WEAPON NEAR WEST SIDE SIDEWALK 12TH .631286621
13396404 AGGRAVATED - OTHER DANGEROUS WEAPON NEAR NORTH SIDE CONVENIENCE STORE 18TH .631286621

10行が選択されました。

経過: 00:00:00.37
SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 id
3 , description
4 , community
5 , location_desc
6 , district
7 , TO_NUMBER( v_distance ) AS v_distance
8 FROM
9 (
10 SELECT
11 id
12 , description
13 , community
14 , location_desc
15 , district
16 , VECTOR_DISTANCE
17 (
18 vector_desc
19 , VECTOR_EMBEDDING
20 (
21 all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
22 )
23 , COSINE
24 ) v_distance
25 FROM
26 search_data
27 ORDER BY
28 v_distance
29 FETCH APPROX FIRST 10 ROWS ONLY
30* )


10行が選択されました。

経過: 00:00:00.32

実行計画
----------------------------------------------------------
Plan hash value: 2333665681

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | 2 (50)| 00:00:01 |
| 1 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 10 | 1570 | 2 (50)| 00:00:01 |
|* 4 | SORT ORDER BY STOPKEY | | 10 | 16400 | 2 (50)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| SEARCH_DATA | 10 | 16400 | 1 (0)| 00:00:01 |
| 6 | VECTOR INDEX HNSW SCAN | SEARCH_DATA_HNSW_IX | 10 | 16400 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)


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

 

ヒントで索引を利用できなくしてみます。新しいヒントが結構追加されていますが、それらについては、また、別の機会にでも。

Oracle Database 23ai / Oracle AI Vector Search User's Guide / Vector Index Hints

https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/vector-index-hints.html

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
/*+
NO_VECTOR_INDEX_SCAN(search_data)
*/
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH APPROX FIRST 10 ROWS ONLY
)
/


10行が選択されました。

経過: 00:00:03.84

実行計画
----------------------------------------------------------
Plan hash value: 2441119222

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | | 51357 (1)| 00:00:03 |
| 1 | VIEW | | 10 | 1570 | | 51357 (1)| 00:00:03 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 125K| 18M| | 51357 (1)| 00:00:03 |
|* 4 | SORT ORDER BY STOPKEY| | 125K| 195M| 244M| 51357 (1)| 00:00:03 |
| 5 | TABLE ACCESS FULL | SEARCH_DATA | 125K| 195M| | 8543 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)


統計
----------------------------------------------------------
5635 recursive calls
0 db block gets
48201 consistent gets
46497 physical reads
0 redo size
1589 bytes sent via SQL*Net to client
108 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
119 sorts (memory)
0 sorts (disk)
10 rows processed

 

APPROXキーワードをEXACTに変えました。これもVECTOR INDEXを利用できなくなる条件の一つです。診てみましょう

SELECT
id
, description
, community
, location_desc
, district
, TO_NUMBER( v_distance ) AS v_distance
FROM
(
SELECT
id
, description
, community
, location_desc
, district
, VECTOR_DISTANCE
(
vector_desc
, VECTOR_EMBEDDING
(
all_minilm_l6 USING 'Incident in which someone may have been murdered' AS data
)
, COSINE
) v_distance
FROM
search_data
ORDER BY
v_distance
FETCH EXACT FIRST 10 ROWS ONLY
)
/

 

実行計画とは関係ないですが、APPROXとEXACTの問い合わせ結果って結構違うのね。。。

SCOTT@localhost:1521/freepdb1> /

ID DESCRIPTION COMMUNITY LOCATION_DESC DISTRICT V_DISTANCE
---------- ---------------------------------------- -------------------- ---------------------------------------- ---------- ----------
13325717 OTHER CRIME AGAINST PERSON BRIDGEPORT APARTMENT 9TH .382904768
13331403 OTHER CRIME AGAINST PERSON HEGEWISCH APARTMENT 4TH .382904768
13329763 OTHER CRIME AGAINST PERSON NEAR NORTH SIDE SMALL RETAIL STORE 18TH .382904768
13330138 OTHER CRIME AGAINST PERSON ROSELAND STREET 5TH .382904768
13329559 OTHER CRIME AGAINST PERSON CHICAGO LAWN APARTMENT 8TH .382904768
13329389 OTHER CRIME AGAINST PERSON BELMONT CRAGIN ALLEY 25TH .382904768
13328588 OTHER CRIME AGAINST PERSON LOOP APARTMENT 1ST .382904768
13328610 OTHER CRIME AGAINST PERSON SOUTH CHICAGO APARTMENT 4TH .382904768
13325825 OTHER CRIME AGAINST PERSON WEST RIDGE PARKING LOT / GARAGE (NON RESIDENTIAL) 24TH .382904768
13326308 OTHER CRIME AGAINST PERSON PULLMAN STREET 5TH .382904768



実行計画
----------------------------------------------------------
Plan hash value: 2441119222

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 1570 | | 51357 (1)| 00:00:03 |
| 1 | VIEW | | 10 | 1570 | | 51357 (1)| 00:00:03 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 125K| 18M| | 51357 (1)| 00:00:03 |
|* 4 | SORT ORDER BY STOPKEY| | 125K| 195M| 244M| 51357 (1)| 00:00:03 |
| 5 | TABLE ACCESS FULL | SEARCH_DATA | 125K| 195M| | 8543 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------

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

2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)

 

ということで、
実行計画は, SQL文のレントゲン写真だ! No.66 / AI Vector Search - VECTOR INDEX HNSW SCAN
はここまで。

今回は一部だけ試してますが更新関連なども含めいろいろな仕組みがあるようで。。。ちょっと追っかけてみないと見えてこないところも多いです。マニュアルも多いですし。。。斜め読みしていて、まじか。。。。となっているところ。

ところで、前回explain plan 文でVector Indexサイズの見積もりを取得していたのですが、覚えてますでしょうか? 実はちょっとクセがあるみたいなんですよね。*_SEGMENTSビューからは索引名では見つからないので。。。


あれ、 SEARCH_DATA_HNSW_IX というVECTOR索引って、索引名で検索しても、*_SEGMENTSビューにでないのか。別の名前になってるのか。。。 セグメントサイズ見たかったのに。。。という謎だけ投げつけて、宿題としておきますw(わざとらしいw) うううーーーーむ、いろいろと知っておく必要なる事項は多そうですねぇ。。。。。

SCOTT@localhost:1521/freepdb1> select index_name,index_type,table_name from user_indexes where table_name='SEARCH_DATA';

INDEX_NAME INDEX_TYPE TABLE_NAME
------------------------------ --------------------------- ------------------------------
SYS_IL0000078074C00009$$ LOB SEARCH_DATA
SEARCH_DATA_HNSW_IX VECTOR SEARCH_DATA

経過: 00:00:00.05
SCOTT@localhost:1521/freepdb1> select segment_name from user_segments where segment_name = 'SEARCH_DATA_HNSW_IX';

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

SCOTT@localhost:1521/freepdb1> r
1* SELECT * FROM vecsys.vector$index

IDX_OBJN IDX_OBJD IDX_OWNER# IDX_NAME IDX_BASE_TABLE_OBJN IDX_BASE_TABLE_OWNER# IDX_PARAMS IDX_AUXILIARY_TABLES
---------- ---------- ---------- ------------------------------ ------------------- --------------------- ------------------------------ ------------------------------
78797 153 SEARCH_DATA_HNSW_IX 78074 153 {"type":"HNSW","num_neighbors" {"rowid_vid_map_objn":78798,"s
:32,"efConstruction":200,"dist hared_journal_transaction_comm
ance":"COSINE","accuracy":90," its_objn":78800,"shared_journa
vector_type":"FLOAT32","vector l_change_log_objn":78803,"rowi
_dimension":384,"degree_of_par d_vid_map_name":"VECTOR$SEARCH
allelism":1,"pdb_id":3,"indexe _DATA_HNSW_IX$78074_78797_0$HN
d_col":"VECTOR_DESC"} SW_ROWID_VID_MAP","shared_jour
nal_transaction_commits_name":
"VECTOR$SEARCH_DATA_HNSW_IX$78
074_78797_0$HNSW_SHARED_JOURNA
L_TRANSACTION_COMMITS","shared
_journal_change_log_name":"VEC
TOR$SEARCH_DATA_HNSW_IX$78074_
78797_0$HNSW_SHARED_JOURNAL_CH
ANGE_LOG"}

SCOTT@localhost:1521/freepdb1> r
1 SELECT
2 JSON_SERIALIZE(idx_params RETURNING VARCHAR2 PRETTY) AS "INDEX PARAM"
3 ,JSON_SERIALIZE(idx_auxiliary_tables RETURNING VARCHAR2 PRETTY) AS "INDEX AUX"
4* FROM vecsys.vector$index

INDEX PARAM INDEX AUX
-------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------
{ {
"type" : "HNSW", "rowid_vid_map_objn" : 78798,
"num_neighbors" : 32, "shared_journal_transaction_commits_objn" : 78800,
"efConstruction" : 200, "shared_journal_change_log_objn" : 78803,
"distance" : "COSINE", "rowid_vid_map_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_ROWID_VID_MAP",
"accuracy" : 90, "shared_journal_transaction_commits_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_TRANSACTION_COMMITS",
"vector_type" : "FLOAT32", "shared_journal_change_log_name" : "VECTOR$SEARCH_DATA_HNSW_IX$78074_78797_0$HNSW_SHARED_JOURNAL_CHANGE_LOG"
"vector_dimension" : 384, }
"degree_of_parallelism" : 1,
"pdb_id" : 3,
"indexed_col" : "VECTOR_DESC"
}

 

では、また!

Enjoin Execution Plans, SQLs, and AI Vector Search!

 


Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ!

実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)
実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID

 

AI Vector Search

Oracle Database 23ai freeで試すVector Search - ONNXモデル準備編
Oracle Database 23ai freeで試すVector Search - データ準備編

 

 

| | | コメント (0)

2025年5月26日 (月)

スマートロジックの亜種のお話 / PostgreSQL, MySQL, Oracle Databaseそれぞれの影響

本題に入る前に、

祝! Mac De Oracle 20周年記念!!!!! Happy 20th Anniversary Mac De Oracle !!!

20年前の第一回目の記事はどんな内容だったでしょう!!!!
Panther De Oracle10g その1

MacOS (Mac OS X 10.3 / Panther ) に直接イントールして遊べる Oracle Database があったころの話ですw
なつかしい!
また、当時は失敗作と名高いw (今のMac Studio並の筐体)PowerMac G4 Cube 450Mhz改 Sonnet 1.2Ghz 832GMB RAM を使っていました。これまた懐かしいSonnetのCPUカードで。

そして最後に、最も大切なブログの名前 "Mac De Oracle" は、どう閃いたのか!!!
当時、Zopeネタのブログ、 "Cube De Zope" からG4 Cube繋がりでインスパイアされて、 de だけ頂いて (^^) 、
(知ってるかたどれぐらいいるのでしょうね)

付けたタイトルが、Mac De Oracle でした。

あ、忘れちゃいけない、そもそもブログ書いたら〜。Oracleの!!
と、背中を推してくれたのは妻なんですけどねw 
彼女の一言がなかったら Mac De Oracleも Oracle ACEにもなってなかったでしょうね。
ありがとう!

 

ということで、

これからも雑多なネタ織り交ぜて、楽しく書いていくことになるw
Mac De Oracle (まだ残してあるIntel MacのVirtalBoxで動いてたりするネタも多いのですけどもw) を、よろしくお願いいたします。 m(_ _)m

 



では、本日のおはなし。(ちょいと長めです)

 

WHERE句でCOALESCE関数を利用したスマートロジックの亜種って、Poor performanceを起こすってネタ、世間ではかなり書かれている印象なのですが、今日は、そんな中でも癖の強そうな、COALESCE関数を使ったスマートロジックの亜種の話。。

スマートロジックの亜種と書いた理由は、動的SQLで素直に書けばなんの問題のないもない比較的単純なSQL文を、COALESCE関数とNULLを組み合わせ動的SQLを回避したいという意図で書かれたものだから。。です。

また、この亜種は利用するデータベース(オプティマイザーだが)で影響の出方も異なるというところが、興味深いところだったりします。
なお、影響が小さそうに見える症状でも、膨大な量のデータも扱っている方達には、塵も積もれば。。。。という点には、むむむむ。。。となるはず。。

Oracle Database 21c, MySQL 8.0.36, PostgreSQL 16.3 いずれも、ちょい前のリリースにしてあります。前からそうだからってのも診てもらおうと。。。
(なお、今回利用している環境は、MacBook Intel 上のVirtualBoxのVMを利用しています。このためだけにIntel Macが残っているww)

piraruku ~ % sw_vers
ProductName: macOS
ProductVersion: 12.7.6
BuildVersion: 21H1320
piraruku ~ % /usr/sbin/system_profiler SPHardwareDataType | grep -E '(Processor|Cores|Memory|Chip|Model Name)'
Model Name: MacBook
Processor Name: Dual-Core Intel Core m5
Processor Speed: 1.2 GHz
Number of Processors: 1
Total Number of Cores: 2
Memory: 8 GB
piraruku ~ % VBoxManage -V
7.0.10r158379

 

 

Oracle Databaseの表と索引、データ件数ですが、MySQL, PostgreSQLでも同一の表、索引、データを登録してあります。

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

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


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

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

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

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

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

 

 

 

まずは、問題のSQL文の例から診てもらいましょう。

SELECT
orders.order_id
, orders.order_date
, customers.first_name FROM orders INNER JOIN customers ON orders.customer_id = customers.customer_id WHERE customers.customer_id = COALESCE(:cust_id, customers.customer_id) AND orders.order_id = COALESCE(:order_id, orders.order_id);

 

みなさん, 上記SQL文のWHERE句で、なにをスマートにやりたい(と思っている)のか、わかりますか?

答え

バインド変数(cust_id, order_id)にどちも NULL がセットされた場合は、全顧客のオーダーをリスト.
特定のcust_idが指定されたら、該当顧客の前オーダーをリスト(このときorder_idはNULL).
特定のorder_idが指定された、該当オーダーをリスト(このときcust_idはNULL).
どちらの変数にも特定のオーダーと顧客が指定されたら該当するオーダーをリスト。

ようするに、全件取得と特定のデータの取得で個別のWHEREの条件に書きかける動的SQLにすればよいわけですけども、
動的SQLを回避したい、ただそこだけに着目してしまった"スマートロジックの亜種"になっています。

WHERE句のCOALESCE関数。SELECTリストならともかく、検索時の弊害が多くなる使い方ですよね。
スマートなように見えているのは机の上だけです。

実際にオプティマイザがどうするか次第ですが、大抵の場合、検索時のフィルタリングが諸悪の根源になる場合が多いいですよね。ご存知の方も多いと思います。
(データ量が多い場合は軽視できない部分です。それらについてもググると沢山ヒットするはずです)

で、
Oracle Database/MySQL/PostgreSQLのうち一つで、フィルタリング以外の影響を引き起こすことに気がついてしまったので、メモがわりに書いておきまっす”

(今回利用したスクリプトは後半に貼ってあります)


Oracle Database 21c から診てみましょう。

SCOTT@orclpdb1> select banner_legacy from v$version;

BANNER_LEGACY
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

 

 

1) :cust_id, :order_id どちらも NULL にした場合(なお、バインドピークは有効のままです)

事前の想定通り、全データ取得のケースであるにもかかわらず、Table full scannの操作で、COALESCE関数を伴うフィルタリングが現れています。明らかに電力の無駄遣いw データが少ないと影響は見えにくいですが。
(HASH JOINで利用されている赤字にしている作業用メモリサイズは覚えておいてくださいね。改善後の実行計画の部分で必要になります)

SCOTT@orclpdb1> @testrun1 null null

CUST_ID
----------
[null]

ORDER_ID
----------
[null]

1 SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12 orders.customer_id = customers.customer_id
13 WHERE
14 customers.customer_id = COALESCE(:cust_id, customers.customer_id)
15* AND orders.order_id = COALESCE(:order_id, orders.order_id)

...略...

Plan hash value: 23084738

-------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 (100)| 105 |00:00:00.01 | 13 | | | |
|* 1 | HASH JOIN | | 1 | 6 (0)| 105 |00:00:00.01 | 13 | 1376K| 1376K| 1558K (0)|
|* 2 | TABLE ACCESS FULL| ORDERS | 1 | 5 (0)| 105 |00:00:00.01 | 2 | | | |
|* 3 | TABLE ACCESS FULL| CUSTOMERS | 1 | 1 (0)| 319 |00:00:00.01 | 11 | | | |
-------------------------------------------------------------------------------------------------------------------------

...略...

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

1 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
2 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
3 - filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

なお、opt_param('_optim_peek_user_binds', 'false')ヒントでbind peekをオフにしても同じ結果でした。

2) :cust_id, :order_idともに値をNULL以外に設定した場合(バインドピーク有効のままですがハードパースさせています)

実行計画が変化しました。ただ、ORDERSではTable Full Scanのままです。(データ量が少ない影響かもしれないですね。とは言え気になるw)

SCOTT@orclpdb1> @testrun1 144 2435

CUST_ID
----------
144

ORDER_ID
----------
2435

...略...

Plan hash value: 4191309613

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 6 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 1 | 30 | 6 (0)| 1 |00:00:00.01 | 4 |
| 2 | NESTED LOOPS | | 1 | 1 | 30 | 6 (0)| 1 |00:00:00.01 | 3 |
|* 3 | TABLE ACCESS FULL | ORDERS | 1 | 1 | 19 | 5 (0)| 1 |00:00:00.01 | 2 |
|* 4 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 1 | | 0 (0)| 1 |00:00:00.01 | 1 |
| 5 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 1 | 1 | 11 | 1 (0)| 1 |00:00:00.01 | 1 |
----------------------------------------------------------------------------------------------------------------------------

...略...

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

3 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
4 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

 

ORDERSの主キー索引をユニークスキャンさせるようにindexヒントを追加すると素直に効いてくれますね。ですが、ここがポイントです。index full scanしています。

ここ試験にでますよ。これCOALESCE関数の影響です。主キー索引からindex unique scanすればよいはずですが、できななくなるんですよ。

index full scan + table access by index rowid batched (1行だけ) ですが. 結局、無駄なデータにアクセスして捨てていることになりますね。ほとんどを。これじゃダメですよね。
実行計画は、全くスマートじゃなくなっていますww

SCOTT@orclpdb1> @testrun1 144 2435

...略...

1 SELECT
2 /*+
3 gather_plan_statistics
4 index(orders pk_orders)
5 */
6 orders.order_id

...略...

Plan hash value: 109917740

--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 3 (0)| 1 |00:00:00.01 | 4 |
| 2 | NESTED LOOPS | | 1 | 3 (0)| 1 |00:00:00.01 | 3 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 1 | 2 (0)| 1 |00:00:00.01 | 2 |
|* 4 | INDEX FULL SCAN | PK_ORDERS | 1 | 1 (0)| 1 |00:00:00.01 | 1 |
|* 5 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
| 6 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS | 1 | 1 (0)| 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------------------------------

...略...

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

4 - filter("ORDERS"."ORDER_ID"=COALESCE(:ORDER_ID,"ORDERS"."ORDER_ID"))
5 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("CUSTOMERS"."CUSTOMER_ID"=COALESCE(:CUST_ID,"CUSTOMERS"."CUSTOMER_ID"))

 

 

3) 1)を動的SQLで回避した場合、全件取得はWHERE句のない文になりますよね!

最適な実行計画にするために動的SQL化し生成されたSQL文を実行して1)のケースがどう改善されるか診てみましょう

全行取得するのでWHERE句は不要。シンプルになりました。
余計なフィルタリングも消えました。

この例だとMERGE JOINになっていますが、データ量が少ない影響だと推測。
ソートに使うメモリを索引を使って回避している動きがあることから利用するメモリサイズが少なくなるためでしょうね。データ量が大きくなるとHash Joinに切り替わるかもしれないですね。
(理由は、1)の実行計画のHASH JOINが利用するメモリサイズとの比較で小さくなっていることがわかります)

  1  SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12* orders.customer_id = customers.customer_id

...略...

Plan hash value: 3079428679

---------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 9 (100)| 105 |00:00:00.01 | 20 | | | |
| 1 | MERGE JOIN | | 1 | 9 (12)| 105 |00:00:00.01 | 20 | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 2 (0)| 105 |00:00:00.01 | 16 | | | |
| 3 | INDEX FULL SCAN | FK_ORDERS_CUSTOMERS | 1 | 1 (0)| 105 |00:00:00.01 | 8 | | | |
|* 4 | SORT JOIN | | 105 | 7 (15)| 105 |00:00:00.01 | 4 | 20480 | 20480 |18432 (0)|
| 5 | TABLE ACCESS FULL | CUSTOMERS | 1 | 6 (0)| 319 |00:00:00.01 | 4 | | | |
---------------------------------------------------------------------------------------------------------------------------------------------

...略...

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

4 - access("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")
filter("ORDERS"."CUSTOMER_ID"="CUSTOMERS"."CUSTOMER_ID")

 

4) 2)の改善

動的SQL化によりCOALESCE関数は不要でわかりやすいSQL文ですよね。こちらのほうが自然ですw

見ての通り、WHERE句に記述されているCOALESCE関数を含むフィルタリングが消え、スッキリしたPredicate Informationの内容に変化しました。これで安心!

  1  SELECT
2 /*+
3 gather_plan_statistics
4 */
5 orders.order_id
6 , orders.order_date
7 , customers.first_name
8 FROM
9 orders
10 INNER JOIN customers
11 ON
12 orders.customer_id = customers.customer_id WHERE
13 customers.customer_id = :cust_id
14* AND orders.order_id = :order_id

...略...

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | Cost (%CPU)| A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (100)| 1 |00:00:00.01 | 4 |
| 1 | NESTED LOOPS | | 1 | 2 (0)| 1 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS | 1 | 1 (0)| 1 |00:00:00.01 | 2 |
|* 3 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
|* 4 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 1 (0)| 1 |00:00:00.01 | 2 |
|* 5 | INDEX UNIQUE SCAN | PK_ORDERS | 1 | 0 (0)| 1 |00:00:00.01 | 1 |
-----------------------------------------------------------------------------------------------------------

...略...

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

3 - access("CUSTOMERS"."CUSTOMER_ID"=:CUST_ID)
4 - filter("ORDERS"."CUSTOMER_ID"=:CUST_ID)
5 - access("ORDERS"."ORDER_ID"=:ORDER_ID)

 

次は、MySQL 8.0.36

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

1) :cust_id, :order_id どちらも NULL にした場合

Oracle Databaseでの症状同様に、無駄なフィルタリングが現れていますよね

mysql> set @cust_id = null;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+--------------------+
| @cust_id |
+--------------------+
| NULL |
+--------------------+v1 row in set (0.00 sec)

mysql> select @order_id;
+----------------------+
| @order_id |
+----------------------+
| NULL |
+----------------------+
1 row in set (0.01 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = COALESCE(@cust_id, customers.customer_id)
-> AND orders.order_id = COALESCE(@order_id, orders.order_id);
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=13.6 rows=10.5)
-> Filter: ((cast(orders.customer_id as double) = cast(coalesce(((@cust_id)),orders.customer_id) as double))
and (cast(orders.order_id as double) = cast(coalesce(((@order_id)),orders.order_id) as double))) (cost=2.05 rows=10.5)
-> Table scan on orders (cost=2.05 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1.01 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

2):cust_id, :order_idねともに値をNULL以外に設定した場合

このケースもOracle Databaseでの症状に類ていますよね。ふむふむ

mysql> set @cust_id = 144;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = 2435;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+----------+
| @cust_id |
+----------+
| 144 |
+----------+
1 row in set (0.00 sec)

mysql> select @order_id;
+-----------+
| @order_id |
+-----------+
| 2435 |
+-----------+
1 row in set (0.00 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = COALESCE(@cust_id, customers.customer_id)
-> AND orders.order_id = COALESCE(@order_id, orders.order_id);
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=13.6 rows=10.5)
-> Filter: ((orders.customer_id = coalesce(((@cust_id)),orders.customer_id))
and (orders.order_id = coalesce(((@order_id)),orders.order_id))) (cost=2.05 rows=10.5)
-> Table scan on orders (cost=2.05 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1.01 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

 

3) 1)を動的SQLにして適切なSQLになった対策後の結果

そもそも全行取得するのになんでフィルタリングさせちゃうようなWHERE句を書くのかと。。
フィルタリングはなくなり綺麗なものです。

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id;
+-----------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=127 rows=105)
-> Table scan on orders (cost=11.5 rows=105)
-> Single-row index lookup on customers using PRIMARY (customer_id=orders.customer_id) (cost=1 rows=1)
|
+-----------------------------------------------------------------------------------------------------------+

 

 

4) 3)同様に動的SQLにして、COALESCE関数を排除した結果。

無駄なフルタリングも一歳なし。こうでなくちゃw COAESCE関数を含むフィルタなんていらんのですよ。

mysql> set @cust_id = 144;
Query OK, 0 rows affected (0.00 sec)

mysql> set @order_id = 2435;
Query OK, 0 rows affected (0.00 sec)

mysql> select @cust_id;
+----------+
| @cust_id |
+----------+
| 144 |
+----------+
1 row in set (0.00 sec)

mysql> select @order_id;
+-----------+
| @order_id |
+-----------+
| 2435 |
+-----------+
1 row in set (0.00 sec)

mysql> explain format=tree
-> SELECT
-> orders.order_id
-> , orders.order_date
-> , customers.first_name
-> FROM
-> orders
-> INNER JOIN customers
-> ON
-> orders.customer_id = customers.customer_id
-> WHERE
-> customers.customer_id = @cust_id
-> AND orders.order_id = @order_id;
+-------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------+
| -> Rows fetched before execution (cost=0..0 rows=1)
|
+-------------------------------------------------------+

 

 

そして、最後は、PostgreSQL 16.3 

とりを務めているぐらいなのでw、
Oracle Database, MySQLとはちょっと違った影響がありそうなのは、気づいている方が多そう。。。。
それがどのような違いなのか診てみましょう

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

 

 

1) :cust_id, :order_id どちらも NULL にした場合

全行取得してINNER JOINするだけですが、Join Cardinarityが変です!!!!!

Rows Removed by Join Filter: 3129とあります。105rows*31rowsなので、なぜか直積しちゃってますね。わぉ! 細かいところだが一部のROWSなんとなく合わないようなきもするが、そこは見てないことにしておこう。。 フィルタリングが余計なのはOracle Database, MySQLと同じ。

症状としては重症の類ですよね。

データが多くなった場合の直積はフィルタリング以上にキツイですよーーーーっ!

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id NULL
perftestdb=> \set order_id NULL
perftestdb=> \echo :cust_id
NULL
perftestdb=> \echo :order_id
NULL
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = COALESCE(:cust_id, customers.customer_id)
perftestdb-> AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.33 rows=1 width=18) (actual time=0.025..1.416 rows=105 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
Inner Unique: true
Join Filter: (orders.customer_id = customers.customer_id)
Rows Removed by Join Filter: 3129
-> Seq Scan on public.orders (cost=0.00..2.31 rows=1 width=16) (actual time=0.016..0.041 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: (orders.order_id = COALESCE(orders.order_id))
-> Seq Scan on public.customers (cost=0.00..7.99 rows=2 width=10) (actual time=0.001..0.007 rows=31 loops=105)
Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
Filter: (customers.customer_id = COALESCE(customers.customer_id))
Planning Time: 0.456 ms
Execution Time: 1.453 ms

 

 

2):cust_id, :order_idともに値をNULL以外に設定した場合

主キーを利用するかと思いきや、COALESCE関数の影響で全表走査してフィルタリングしてほぼ捨ててますね なぜ(???)

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id 144
perftestdb=> \set order_id 2435
perftestdb=> \echo :cust_id
144
perftestdb=> \echo :order_id
2435
perftestdb=> explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.57 rows=1 width=18) (actual time=0.062..0.155 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Seq Scan on public.orders (cost=0.00..2.58 rows=1 width=16)
(actual time=0.038..0.064 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: ((orders.customer_id = 144) AND (orders.order_id = 2435))
Rows Removed by Filter: 104
-> Seq Scan on public.customers (cost=0.00..7.99 rows=1 width=10)
(actual time=0.020..0.085 rows=1 loops=1)

Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
Filter: (customers.customer_id = 144)
Rows Removed by Filter: 318
Planning Time: 0.253 ms
Execution Time: 0.225 ms

 

ちょいと気になるのでヒントでどうなるか追加で診ておきましょう。このケースでは改善。
pg_hint_planで索引スキャンでよりよくできますね。(データ量の影響だろうか。。)

perftestdb=> explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.29..16.34 rows=1 width=18) (actual time=0.039..0.043 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Index Scan using pk_orders on public.orders
(cost=0.14..8.16 rows=1 width=16) (actual time=0.027..0.028 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
, orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Index Cond: (orders.order_id = 2435)
Filter: (orders.customer_id = 144)
-> Index Scan using pk_customers on public.customers
(cost=0.15..8.17 rows=1 width=10) (actual time=0.008..0.010 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
, customers.address, customers.phone_number
Index Cond: (customers.customer_id = 144)
Planning Time: 0.290 ms
Execution Time: 0.083 ms

 

 

3) 1)の対策後。動的SQLにしてWHERE句を除外するだけです

無駄なWHERE句が消えて、スッキリ。こちらもOracle Databaseの改善後に類似したMerge Joinになってますね。直積が無くなってますねーーーー

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id NULL
perftestdb=> \set order_id NULL
perftestdb=> \echo :cust_id
NULL
perftestdb=> \echo :order_id
NULL
perftestdb=>
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Merge Join (cost=5.72..11.81 rows=105 width=18) (actual time=0.116..0.240 rows=105 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
Merge Cond: (customers.customer_id = orders.customer_id)
-> Index Scan using pk_customers on public.customers (cost=0.15..19.93 rows=319 width=10)
(actual time=0.012..0.040 rows=71 loops=1)
Output: customers.customer_id, customers.first_name
, customers.last_name, customers.address, customers.phone_number
-> Sort (cost=5.57..5.84 rows=105 width=16) (actual time=0.098..0.118 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.customer_id
Sort Key: orders.customer_id
Sort Method: quicksort Memory: 29kB
-> Seq Scan on public.orders (cost=0.00..2.05 rows=105 width=16)
(actual time=0.012..0.047 rows=105 loops=1)
Output: orders.order_id, orders.order_date, orders.customer_id
Planning Time: 0.324 ms
Execution Time: 0.289 ms

 

 

4) 3)と同様の対策。動的SQLにしてCOALESCE関数を利用しない。一般的な条件を付加するだけです。

データ少ないからか、Seq Scanのままですね。
まじかw

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \set cust_id 144
perftestdb=> \set order_id 2435
perftestdb=> \echo :cust_id
144
perftestdb=> \echo :order_id
2435
perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = :cust_id
perftestdb-> AND orders.order_id = :order_id;

QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..10.57 rows=1 width=18) (actual time=0.037..0.097 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Seq Scan on public.orders (cost=0.00..2.58 rows=1 width=16)
(actual time=0.022..0.039 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
     , orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Filter: ((orders.customer_id = 144) AND (orders.order_id = 2435))
Rows Removed by Filter: 104
-> Seq Scan on public.customers (cost=0.00..7.99 rows=1 width=10)
(actual time=0.011..0.053 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
     , customers.address, customers.phone_number
Filter: (customers.customer_id = 144)
Rows Removed by Filter: 318
Planning Time: 0.199 ms
Execution Time: 0.132 ms

 

pg_hint_planIndexScanを強制してみましょう。。すんなり改善。
プランナーの判断だと索引使ってくれなかったのですが、ヒントで改善できる状況は確認。状況に応じて人がアシストしてあげるのがいいですかね。

perftestdb=> explain (analyze,verbose)
perftestdb-> SELECT
perftestdb-> /*+
perftestdb*> IndexScan(orders pk_orders)
perftestdb*> IndexScan(customers pk_customers)
perftestdb*> */
perftestdb-> orders.order_id
perftestdb-> , orders.order_date
perftestdb-> , customers.first_name
perftestdb-> FROM
perftestdb-> orders
perftestdb-> INNER JOIN customers
perftestdb-> ON
perftestdb-> orders.customer_id = customers.customer_id
perftestdb-> WHERE
perftestdb-> customers.customer_id = :cust_id
perftestdb-> AND orders.order_id = :order_id;

QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.29..16.34 rows=1 width=18) (actual time=0.039..0.043 rows=1 loops=1)
Output: orders.order_id, orders.order_date, customers.first_name
-> Index Scan using pk_orders on public.orders (cost=0.14..8.16 rows=1 width=16)
(actual time=0.027..0.028 rows=1 loops=1)

Output: orders.order_id, orders.order_date, orders.order_mode, orders.customer_id
     , orders.order_status, orders.order_total, orders.sales_rep_id, orders.promotion_id
Index Cond: (orders.order_id = 2435)
Filter: (orders.customer_id = 144)
-> Index Scan using pk_customers on public.customers (cost=0.15..8.17 rows=1 width=10)
(actual time=0.008..0.010 rows=1 loops=1)

Output: customers.customer_id, customers.first_name, customers.last_name
     , customers.address, customers.phone_number
Index Cond: (customers.customer_id = 144)
Planning Time: 0.290 ms
Execution Time: 0.083 ms
(11 行)

 

 

まとめると、
当初の想定通りいいことは全くない!! スマートなやりかたに見えているだけでしたよね
COALESCE関数をWHERE句で利用したスマートロジックはかなりリスキー。

今回試した全てのケースで、余分なフィルタリングが常に行われたりするリスクに加え、
PostgreSQLでは直積まで発生しちゃいました。やばいです。

 

ということで、

Mac De Oracle ブログ開設 20周年記念のエントリー。おわり。

では、まだ。

Enjoy SQLs! and Optimizers!

 

 


==== サンプルコード ====
Oracle Database側で簡易的に動的SQL化して生成した全件取得、キー指定版のSQLをPostgreSQL/MySQLで再利用しました。

Oracle database

SCOTT@orclpdb1> !cat testrun1.sql
set termout on
variable cust_id number
variable order_id number
set autop on
set null [null]
exec :cust_id := &1
exec :order_id := &2

SELECT
/*+
gather_plan_statistics
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id)
.
l
set termout off
/
set termout on
@show_actualplan.sql

-- 簡易的にSQL*PLus内で動的SQLを組み立てて実行
set feed off
set timi off
set head off
set termout off
set veri off
spool temp_sql.sql
SELECT
'SELECT
/*+
gather_plan_statistics
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id '
||
CASE
WHEN
:cust_id IS NOT NULL
AND :order_id IS NOT NULL
THEN
'WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id'
END
FROM
dual;
select '.' from dual;
select 'l' from dual;
select 'set termout off' from dual;
select '/' from dual;
select 'set termout on' from dual;
select '@show_actualplan.sql' from dual;
spo off
set feed on
set timi on
set head on
set termout on
set veri on
@temp_sql.sql

undefine 1
undefine 2
undefine cust_id
undefine order_id

 

show_actualplan.sql

set long 20000
set longchunk 400
select * from table(dbms_xplan.display_cursor(format=>'ALL ALLSTATS LAST +OUTLINE'));

 

 

MySQL

set @cust_id = null;
set @order_id = null;
select @cust_id;
select @order_id;


--スマートロジック版(すまーとじゃないけど)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(@cust_id, customers.customer_id)
AND orders.order_id = COALESCE(@order_id, orders.order_id);


--動的SQL(生成済み全件取得のSQL)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id;


set @cust_id = 144;
set @order_id = 2435;
select @cust_id;
select @order_id;


--スマートロジック版(すまーとじゃないけど)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(@cust_id, customers.customer_id)
AND orders.order_id = COALESCE(@order_id, orders.order_id);


--動的SQL(生成済みキー指定のSQL)
explain format=tree
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = @cust_id
AND orders.order_id = @order_id;

 

 

PostgreSQL

\pset null [null]
\set cust_id NULL
\set order_id NULL
\echo :cust_id
\echo :order_id

--スマートロジック版(すまーとじゃないけど)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--動的SQL(生成済み全件取得のSQL)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
;


\pset null [null]
\set cust_id 144
\set order_id 2435
\echo :cust_id
\echo :order_id


--スマートロジック版(すまーとじゃないけど)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--スマートロジック版(すまーとじゃないけど、キー指定版、SQL hint付き)
explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = COALESCE(:cust_id, customers.customer_id)
AND orders.order_id = COALESCE(:order_id, orders.order_id);


--動的SQL(生成済みキー指定のSQL、SQL hint付き)
explain (analyze,verbose)
SELECT
/*+
IndexScan(orders pk_orders)
IndexScan(customers pk_customers)
*/
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id;


--動的SQL(生成済みキー指定のSQL)
explain (analyze,verbose)
SELECT
orders.order_id
, orders.order_date
, customers.first_name
FROM
orders
INNER JOIN customers
ON
orders.customer_id = customers.customer_id
WHERE
customers.customer_id = :cust_id
AND orders.order_id = :order_id;

| | | コメント (0)

2025年1月30日 (木)

実行計画は, SQL文のレントゲン写真だ! No.65 / 忘れ去られたオプティマイザーヒントとTABLE ACCESS BY USER ROWID

2025年最初のレントゲン写真ネタです。今日は、みなさん(私も忘れてましたw)、忘れちゃってると思う(あまり使うこともないので)ROWIDの話題で、2025年最初のSQLネタエントリーにしたいと思います。(他のネタを考えていたのですが、軽めのネタにしてみましたw)



ROWIDヒント、今現存公開されている最古のオンラインマニュアルでは、Oracle 7 Server 7.3.3 以降から9iのマニュアルに記載されています。(もっと前からあるよね〜。7.0のマニュアルとか流石に見つからないので確認できないですがw)
で、主役ではなくなりほぼ使われなくなったヒントとして、忘れ去られてしまうきっかけは、Oracle Database 10g以降のマニュアルに記載されなくなったことが大きいでしょうね。使うこともほぼ無いと言って良いヒントだからかなぁ
昔すぎて覚えてないですねw

他にも、使ってもらっては困る黒魔術的なヒントもマニュアルから隠されたりしたこともありました。v$sql_hintには残ってますが11g以降サポートされなくなりヒントを指定しても無視されるようになった有名なヒントですね。。。。w (まだ利用できたバージョンで、某氏が使おうとした黒魔術を必死で止めた思い出が蘇るw)
余談はこれぐらいで、本題へ。

現在確認できるオンラインマニュアルだと9iまで記載されていることが確認できます。
Oracle7 Server 7.3.3
Oracle7 Tuning, release 7.3.3 / Optimization Modes and Hints / Hints for Access Methods / ROWID

Oracle Database 8i 8.1.7
Oracle8i SQL リファレンス Vol.1 リリース 8.1 P.92 / ヒント / アクセス方法のヒント / ROWID

Oracle Database 9iR2
Oracle9i Database Performance Tuning Guide and Reference Release 2 (9.2) / ROWID


10g以降のマニュアルからは表記が消えています。ただし、v$sql_planには残っており、これまで通りに使えます(もそもそのアクセスパスを利用すること自体、かなりレアな状況ですよね :)
Oracle Database 10gR2
Oracle Database Online Documentation, 10g Release 2 (10.2) / Administration Database Performance Tuning Guide / 16 Using Optimizer Hints / 16.1.2.2 Hints for Access Paths

Oracle Database 11gR2
Oracle Database SQL言語リファレンス 11gリリース2 (11.2) / コメント / 表3-21 機能のカテゴリに分類したヒント


21cで確かめてみましょう。

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

SCOTT@orclpdb1> select * from v$sql_hint where name = 'ROWID';

NAME SQL_FEATUR CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OU CON_ID
---------- ---------- ---------- ---------- ------------ ---------- ---------- ---------- ----------
ROWID QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7 0

ROWID擬似列でそれぞれのROWIDを取り出してみますね。むかーし、ROWIDを使って、データブロック番号順にソートして性能問題を回避したことありましたっけw ( db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニング / バッファキャッシュ欠乏症 )

SCOTT@orclpdb1> select rowid,empno,ename from emp;

ROWID EMPNO ENAME
------------------ ---------- ------------------------------
AAASguAAMAAAACTAAA 7369 SMITH
AAASguAAMAAAACTAAB 7499 ALLEN
AAASguAAMAAAACTAAC 7521 WARD
AAASguAAMAAAACTAAD 7566 JONES
AAASguAAMAAAACTAAE 7654 MARTIN
AAASguAAMAAAACTAAF 7698 BLAKE
AAASguAAMAAAACTAAG 7782 CLARK
AAASguAAMAAAACTAAH 7839 KING
AAASguAAMAAAACTAAI 7844 TURNER
AAASguAAMAAAACTAAJ 7900 JAMES
AAASguAAMAAAACTAAK 7902 FORD
AAASguAAMAAAACTAAL 7934 MILLER

12行が選択されました。


まずROWIDヒントを使わず、単純に、ROWID疑義列を使って一本引きw

すごいですよね。ピンポイントでアクセスするので1blockだけしか読みこんんでないですよね。
ROWID関する注意などはマニュアルを見てくさだい。特にROWIDのリスキーな使い方などがわかると思うので。むかーーーーし、むかーーーし、ROWIDに仕様変更がありそういう使い方をしていたシステムの移行はかなり大変だったらしい。私は幸いなことにそういつ使い方をしていたシステムに出会わなかったのでラッキーではありましたが。。)

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.05

実行計画
----------------------------------------------------------
Plan hash value: 1116584662

-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 39 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------


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


さて、意地悪な実験ですw TABLE ACCESS BY USER ROWIDとなるところをですが、INDEXヒントやFULLヒントで抑止してみるととうなるか見ておきましょう。(想像通りだと思いますけども)

SCOTT@orclpdb1> select /*+ FULL(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 3956160932

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

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

1 - filter(ROWID='AAASguAAMAAAACTAAA')


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

SCOTT@orclpdb1> select /*+ INDEX(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

経過: 00:00:00.07

実行計画
----------------------------------------------------------
Plan hash value: 2898514743

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 39 | 2 (0)| 00:00:01 |
|* 2 | INDEX FULL SCAN | PK_EMP | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

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

2 - filter(ROWID='AAASguAAMAAAACTAAA')


統計
----------------------------------------------------------
144 recursive calls
2 db block gets
134 consistent gets
1 physical reads
0 redo size
1146 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
8 sorts (memory)
0 sorts (disk)
1 rows processed

最後に、ROWIDヒントが使えることをSQL Hint Reportも含めて確認してみましょう。

SCOTT@orclpdb1> set autot off
SCOTT@orclpdb1> select /*+ gather_plan_statistics rowid(emp) */ * from emp where rowid = 'AAASguAAMAAAACTAAA';

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20

経過: 00:00:00.00
SCOTT@orclpdb1> @show_actualplan

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
SQL_ID 69yuykdp7c1za, child number 0
-------------------------------------
select /*+ gather_plan_statistics rowid(emp) */ * from emp where rowid
= 'AAASguAAMAAAACTAAA'

Plan hash value: 1116584662

-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 1 (100)| | 1 |00:00:00.01 | 1 |
| 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 1 | 39 | 1 (0)| 00:00:01 | 1 |00:00:00.01 | 1 |
-----------------------------------------------------------------------------------------------------------------------------

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

1 - SEL$1 / "EMP"@"SEL$1"

Outline Data
-------------

/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('21.1.0')
DB_VERSION('21.1.0')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
ROWID(@"SEL$1" "EMP"@"SEL$1")
END_OUTLINE_DATA
*/

Column Projection Information (identified by operation id):
-----------------------------------------------------------

1 - "EMP"."EMPNO"[NUMBER,22], "EMP"."ENAME"[VARCHAR2,10], "EMP"."JOB"[VARCHAR2,9], "EMP"."MGR"[NUMBER,22],
"EMP"."HIREDATE"[DATE,7], "EMP"."SAL"[NUMBER,22], "EMP"."COMM"[NUMBER,22], "EMP"."DEPTNO"[NUMBER,22]

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1

---------------------------------------------------------------------------

1 - SEL$1 / "EMP"@"SEL$1"
- rowid(emp)

Enjoy SQL

では、また。



Related article on Mac De Oracle

実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編)

| | | コメント (0)

2024年12月27日 (金)

先生!、全行アクセスしてるのに Nested Loop Join しちゃうんです! (東京都 ITエンジニア 男性) - optimizer_mode は正しく設定しましょう!

ということで、(どういうことだw
(今日のねたはアドベントカレンダーのネタを考えていた時に思い出したネタです)

 

さて、本題。

Oracle DatabaseのSQLチューニングや実行計画の読み方を学び始めたとき、最初に学ぶ(多分)、コストベースオプティマイザが作り出す実行計画に大きく影響を与える初期化パラメータ。
そのパラメータは何か?、
みなさん、パッと思う浮かでしょうか?

_optim_peek_user_binds ってアンダースコアパラメータを思い浮かべた方は居ないはず!(と断言してしまったが、意外と結構居たりして..

それは絶対にないと信じwww、
今日は、 optimizer_mode 初期化パラメータのお話をしてみたいと思います。

 

冒頭で書いたようにに、 Oracle Database の実行計画やSQLチューニングを学び始めたときに、最初に習う、覚えるのは、このパラメータだったと。思う(私の記憶ではw 私の場合、その最初の頃が昔すぎて怪しい)

それが、
optimizer_modeパラメータです。

この類のパラメータがあるのは Oracle Database だけではないかと思います。
例えば、Hash Join/Merge Joinを無効にして、Nested Loop Joinだけにするなど複数のオプションを組み合わせて似たような挙動にすることはできるものは多いですが、Oracle Databaseのように単一パラメータで、緩めに制御できるのは他にはないと思います。

 

このパラメータ、むかーーーーーーーーーーーーーーーしからあって、現在の ALL_ROWS/FISRT_ROWS_N というオプションになる前はCHOOSEやRULEというオプションがありました。
これは、Oracle Database 10gリリース1(10.1) 10gR1でルールベースオプティマイザが非サポートとなったタイミングで廃止され、現在この初期化パラメータがサポートしているオプションは以下のようになっています。デフォルトは ALL_ROWS です。

Database / Oracle / Oracle Database / Release 19 / Database Reference / 1.248 OPTIMIZER_MODE https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/OPTIMIZER_MODE.html

 

OPTIMIZER_MODE = { FIRST_ROWS_[1 | 10 | 100 | 1000] | FIRST_ROWS | ALL_ROWS }

このパラメータのオプションの特徴をざっくり解説してしまうと、
optimizer_mode = ALL_ROWSだとスループットを最大限にする実行計画が選択されます。(一般的にバッチだったり、分析系のSQLの実行計画に向いているのがこのモードで、デフォルトはこれです)
optimizer_mode = FIRST_ROWS_Nに設定した場合、N行の結果を如何に速く返すか、つまり、レスポンスタイムの良さを実現するための実行計画が選択されます。(一般的にはOLTP向きとされる実行計画になります。結合方法として Nested Loop Joinが選ばれやすくなります。全行じゃなくても最初の1行を早くクライアントへ返してあげられるような実行計画になりやすいですのがこのモードです)

ちなみに FIRST_ROWS は下位互換として残されているだけなので最近では使うことはないです。

ALL_ROWSがデフォルトなので、多くの場合、デフォルトのままで、OLTP系のSQLでは、index scanや、nested loop joinになるようにWHERE句を記述したり、ヒント等で制御したりしているケースが多いのではないでしょうか?
一方、optimizer_mode = first_rows_n に設定しておき, OLTP向きの実行計画をデフォルトで選択しやすい状況にしているケースもそれなりに見かけます(かなり少ないと思いますが、ちゃんと考えて設定しているという意味では、自分たちのシステムのワークロードで重要なのはどれだ! 認識している証かもしれません。どちらにするかは方針次第ではあるのですが。)

と、ここまでが、ながーーーい前説ですw

 

今回のタイトル ”先生!、全行アクセスしてるのに、Nested Loop Joinしちゃうんです!”  

Sql_20250105102401

もうお分かりですねw 今日のネタ。

 

今日の患者さん、 optimizer_mode = first_rows_1 となっている環境で、where句もない結合を伴うクエリーが、Nested Loop Joinで、全行読み込んでしまったことに悩んでいました。
optimizer_mode = first_rows_n という設定になっていることにも気づいてなかったようですね。 all_rows の感覚のままでいると戸惑うのも当然です。

チューニング前に、オプティマイザに影響する初期化パラメータを確認しておくことをお勧めします!!!
チューニングをお願いされた場合、該当する初期化パラメータの設定も一緒に提供してもらう。
セッションレベルで変更されている場合もあるので、それらの情報も提供してもらうことが大切ですよ。忘れないでくださいね。
それらパラメータ情報も、ERに運び込まれた患者SQLを救うには大切な情報なのです!

このケースの場合、治療は非常に簡単で、初期化パラメータ optimizer_mode はそのままで、 ALL_ROWS ヒントを該当SQLに埋め込むのが手っ取り早いと思います。セッションレベルで optimizer_mode = all_rowsにするのもありです。

強力なヒントではないので軽視されがちな初期化パラメータですが、実は、ひょんなことで、その実力に気付いたりするものですwwwwwwww
ただ、ざっくりとした実行計画の傾向を支持するものなので、追加のヒントで矯正したりする必要もあることは忘れないでください。

 

では、早速、その効力を確認することにします。 21cを利用します。古くても新しくても挙動は同じです、
また、ネタ的に面白いので一時表での挙動も含めています :)
一時表は統計情報の持ち方等が永続表とは異なるので別の注意が必要です。(参考 津島博士のパフォーマンス講座 第35回 オプティマイザ統計の運用について(2)

統計情報に影響され難い例なので、知っておくと、どこかで役立つと思いますよ! 多分。:)



 

まず、環境と今回の主役となる初期化パラメータの確認から。

optimizer_dynamic_sampling
optimizer_mode
をセッションレベルで制御。ヒント制御しても同じ。
(なお、optimizer_adaptive_plansが発動すると分かりにくくなるので無効化しておきます)

 

SCOTT@orclpdb1> select banner_full from v$version;

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

SCOTT@orclpdb1> show parameter optimizer_mode

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_mode string ALL_ROWS

SCOTT@orclpdb1> show parameter optimizer_dynamic_sampling

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_dynamic_sampling integer 2

SCOTT@orclpdb1> show parameter optimizer_adaptive_plans

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
optimizer_adaptive_plans boolean TRUE

前提条件は以下の通り。

1. 表には大量のデータが登録されている。
2. 2表をINNNER JOINするが、WHERE句のないクエリー。
3. 統計の無い状態、無いが動的統計有効、それに統計のある状態、それぞれで検証

普通に考えれば、table full scan + Hash Joinが実行計画として選択されるケースですが、、、どうなりますか。ニヤニヤ。(想像できる結果なのでw

一時表での実行計画から確認してみます。

統計情報なし、動的統計取得なし、Adaptive plansも無効です。 データ量、SQL文ではWHERE句による絞り込み条件も無いため、全表走査+ハッシュ結合となって欲しいケースですが、 first_row_1 と all_rows の違いは如何に。。。

 

SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> create global temporary table hoge_tmp (id number not null primary key, memo varchar2(100)) on commit preserve rows;

表が作成されました。

SCOTT@orclpdb1> create global temporary table hoge_tmp2 (id number not null primary key, memo varchar2(100)) on commit preserve rows;

表が作成されました。

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

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

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

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

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

 

あらびっくり!(知ってたけどw
100,000行の結合がNested Loop Joinになっています。駆動表は Table full scan しちゃってます。って(WHERE句ないのでここは当然ですがw)
問題は、Nested Loop Joinになっている。INDEX UNIQUE SCANを 100,000回ぐるぐる繰り返しているということになります! まじですw

一時表で統計情報もなくて、動的統計取得も無効されている影響だな! そう思ったあなた。そういうケースもありますがw
WHERE句もないSQLでNested Loop Joinを選択してしまうのは危険ですよ。(昔は特殊な事情で、それでもこれで行くか〜というレアなこともなくはなかったですがw 最近はほぼないですからね)

(後半で、統計情報なんて関係ねぇってネタをご用意してありますので、長いですがお付き合いくださいw)

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4089392018

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | HOGE_TMP | 8168 | 518K| 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 65 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010735 | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
25 recursive calls
0 db block gets
119340 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

では、 all_rows にして再実行します。他の設定は同じです。
はい、見事に、 Table full scan + Hash Join の実行計画が選択されました!!!!

違いは、first_rows_1 であるか、 all_rows であるかだけです。それだけなんです。

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 1036K| 150 (3)| 00:00:01 |
|* 1 | HASH JOIN | | 8168 | 1036K| 150 (3)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 8168 | 518K| 74 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 8168 | 518K| 74 (2)| 00:00:01 |
--------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
22 recursive calls
0 db block gets
9334 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

さらに深掘りしてみましょう。
統計情報の有無が影響しないことを確認してみましょう。一時表なので永続表とは異なる統計情報の持ち方になっていることをお忘れなく。でも、大丈夫ですよ。持ってますからw

 

SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計取得
SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);

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

SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

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

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED
HOGE_TMP2 100000 SESSION
HOGE_TMP 100000 SESSION

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED
HOGE_TMP SYS_C0010733 100000 SESSION
HOGE_TMP2 SYS_C0010735 100000 SESSION

 

一時表のセッション固有統計により駆動表の見積もり行数が、100K 担っている点に注目。大量にデータがヒットすることが、見えていながら、 first_roww_1 という1行目のレスポンスタイムを最短にするため、Nested Loop Joinが行われているます!

統計情報なんて、関係ねぇっ、って感じなのが確認できたので、実は、ほっとしていたりw...

 

SCOTT@orclpdb1> -- dyamic sampling off
SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計あり
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4089392018

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 424 | 5 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | HOGE_TMP | 100K| 10M| 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 106 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010735 | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

Note
-----
- Global temporary table session private statistics used

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
115889 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

確認するまでもないですが、 all_rows で他の条件は同一のケースも見てみましょう。
こちらは安定の、table full scan + hash joinのままですね。(予想通りですw)

 

SCOTT@orclpdb1> -- dyamic sampling off
SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計あり
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 20M| | 6154 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 100K| 20M| 11M| 6154 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 100K| 10M| | 1110 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 100K| 10M| | 1110 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- Global temporary table session private statistics used

統計
----------------------------------------------------------
705 recursive calls
14 db block gets
10430 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
170 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

一時表でもう一つ確認しておきましょう。
一時表(Global Temporary Table)のセッション固有統計なしで、動的統計有効にした場合、 first_rows_1 / all_rows の実行計画はどうなるでしょうか。

一旦、セッションを終了して、一時表を空にします。

 

SCOTT@orclpdb1> exit
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0との接続が切断されました。
[oracle@localhost ~]$ sqlplus scott@orclpdb1

...略...

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

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

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

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

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010733
HOGE_TMP2 SYS_C0010735

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010733 SHARED
HOGE_TMP2 SYS_C0010735 SHARED

 

お〜〜〜〜。変化しました〜、動的統計取得で挙動が変わりますね。。。。とはいえ、 Merg Joinです!
実行計画、最悪ですよね。
重いソート処理を回避するために、主キーをindex full scan(主キー順に読み込む)した後に、 Table Access by index rowid ですよ。みなさん!
次に、table access fullの後に、SORT JOIN してます。。consistent getsもこれまでで最も多いですね。どうせなら table full scan + hash join を選んで欲しかったw
とはいえ、optimizer_mode = first_rows_1にするぐらいだから、動的統計って無効化していることも多いので、有効にするまでは気が回らなそうな気もしますね。
いずれにしてもあまり良い設定の相性ではないのは街がないですね。このケースでは。動的統計のレベルによっても変化してより良い実行計画に変化するとは思いますが。(今回の目的ではないのでその確認まではしません)

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 2412335391

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 569 (2)| 00:00:01 |
| 1 | MERGE JOIN | | 1 | 130 | 569 (2)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 97069 | 6161K| 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | SYS_C0010735 | 2 | | 2 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 95480 | 6060K| 566 (2)| 00:00:01 |
| 5 | TABLE ACCESS FULL | HOGE_TMP | 95480 | 6060K| 566 (2)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")
filter("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
23 recursive calls
0 db block gets
19853 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

同じ条件で、 all_rows に変えてみましょう。おそらくいい感じになるのではないでしょうか(これまで同様に)

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 一時表(Global Temporary Table)のセッション固有統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 95481 | 11M| | 4699 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 95481 | 11M| 7184K| 4699 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 95480 | 6060K| | 1109 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 97069 | 6161K| | 1110 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
13 recursive calls
0 db block gets
9426 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off
SCOTT@orclpdb1> exit

 

最後に、永続表での実行計画も確認しておきます。

 

SCOTT@orclpdb1> drop table hoge_tmp purge;

表が削除されました。

SCOTT@orclpdb1> drop table hoge_tmp2 purge;

表が削除されました。

SCOTT@orclpdb1> create table hoge_tmp (id number not null primary key, memo varchar2(100));

表が作成されました。

SCOTT@orclpdb1> create table hoge_tmp2 (id number not null primary key, memo varchar2(100));
2 /

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

SCOTT@orclpdb1> begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
2 /

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

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP
HOGE_TMP2

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010737
HOGE_TMP2 SYS_C0010739

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP SHARED
HOGE_TMP2 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010737 SHARED
HOGE_TMP2 SYS_C0010739 SHARED

 

永続表のケースで、統計なし、動的統計オフで、first_rows_1の場合は、一時表と同様に駆動表を全表走査した上で、Nested Loop Joinしています。first_rows_1の影響をそのまま受けています。

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4183149614

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 130 | 4 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | HOGE_TMP | 82 | 5330 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010739 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 65 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
84 recursive calls
23 db block gets
126963 consistent gets
231 physical reads
4336 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
13 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

 

他の条件は同じで、 all_rows に変更した場合同様に、全表走査+ハッシュ結合(いいですねぇ。バッチ処理ならこれが一番良いですね。

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 0;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1628381653

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 82 | 10660 | 6 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 82 | 10660 | 6 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP | 82 | 5330 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP2 | 82 | 5330 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
189 recursive calls
5 db block gets
9700 consistent gets
1 physical reads
184 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
28 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

永続表、統計情報なし、動的統計取得有効、first_rows_1。一時表同様の結果です。永続表と一時表による違いは無さそうですね。これはNLJより避けたいw

 

SCOTT@orclpdb1> -- first_rows_1
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 1178023564

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 130 | 795 (2)| 00:00:01 |
| 1 | MERGE JOIN | | 1 | 130 | 795 (2)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP | 124K| 7911K| 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | SYS_C0010737 | 2 | | 2 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 92574 | 5876K| 792 (2)| 00:00:01 |
| 5 | TABLE ACCESS FULL | HOGE_TMP2 | 92574 | 5876K| 792 (2)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")
filter("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
181 recursive calls
26 db block gets
16729 consistent gets
182 physical reads
140 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
24 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

永続表、統計情報なし、動的統計取得有効、all_rowsも、一時表のケースと同様。安定して、全表走査+ハッシュ結合が行われています。 all_rows にするべきSQLですからね。

 

SCOTT@orclpdb1> -- all_rows
SCOTT@orclpdb1> -- 統計なし
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = all_rows;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 3316548036

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 92574 | 11M| | 5148 (2)| 00:00:01 |
|* 1 | HASH JOIN | | 92574 | 11M| 6968K| 5148 (2)| 00:00:01 |
| 2 | TABLE ACCESS FULL| HOGE_TMP2 | 92574 | 5876K| | 1173 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| HOGE_TMP | 124K| 7911K| | 1177 (2)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - access("A"."ID"="B"."ID")

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

統計
----------------------------------------------------------
32 recursive calls
0 db block gets
9438 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73378 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

いよいよ最後、動的統計取得を無効化して、静的統計による挙動を確認します。

 

SCOTT@orclpdb1> -- 統計取得
SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);

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

SCOTT@orclpdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

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

SCOTT@orclpdb1> select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS
------------------------------ ----------
HOGE_TMP 100000
HOGE_TMP2 100000

SCOTT@orclpdb1> select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HOGE_TMP SYS_C0010737 100000
HOGE_TMP2 SYS_C0010739 100000

SCOTT@orclpdb1> select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME NUM_ROWS SCOPE
------------------------------ ---------- ---------------------
HOGE_TMP 100000 SHARED
HOGE_TMP2 100000 SHARED

SCOTT@orclpdb1> select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

TABLE_NAME INDEX_NAME NUM_ROWS SCOPE
------------------------------ ------------------------------ ---------- ---------------------
HOGE_TMP SYS_C0010737 100000 SHARED
HOGE_TMP2 SYS_C0010739 100000 SHARED

 

統計情報あり、動的統計有効ですが、動作しないはずですね。統計情報は最新ですし。 first_rows_1では期待した結果(良いという意味ではないw)が得られています。
駆動表を全表走査してNested Loop Joinが行われています。動的統計取得の副作用で、Merge Joinになることもなかったようですね。

 

SCOTT@orclpdb1> -- 統計あり
SCOTT@orclpdb1> -- 動的統計有効
SCOTT@orclpdb1> alter session set optimizer_adaptive_plans = false;

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

SCOTT@orclpdb1> alter session set optimizer_dynamic_sampling = 2;

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

SCOTT@orclpdb1> alter session set optimizer_mode = first_rows_1;

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

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;

100000行が選択されました。

実行計画
----------------------------------------------------------
Plan hash value: 4183149614

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 424 | 5 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 2 | 424 | 5 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | HOGE_TMP | 100K| 10M| 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | SYS_C0010739 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| HOGE_TMP2 | 1 | 106 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

4 - access("A"."ID"="B"."ID")

統計
----------------------------------------------------------
5 recursive calls
0 db block gets
126786 consistent gets
0 physical reads
0 redo size
2812231 bytes sent via SQL*Net to client
73599 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orclpdb1> set autot off

 

同一条件で、 all_rows の場合です。こちらも想定通り、全表走査+ハッシュ結合になっています。

最後に、 optimizer_mode をチューニングのゴールに合わせて、バッチ、分析系、そして、OLTPと、
all_rows または、first_rows_n のいずれか正しく設定することも想定外の実行計画を防ぐことに役立つか、お分かりいただけたのではないでしょうか?
効果の薄い機能ではなく、重要な役目をもつ、 optimizer_mode、お忘れなく。ヒントでも使えます。状況に合わせて使い分けることをお勧めします:)

 

おまけ 昔、OTHER_XML列からOUTLINEを取り出すなんてネタ書いてましたが、しっかりと、optimizer_modeに対応するヒントが含まれています。
OTHER_XMLの中身 / Mac De Oracle / 2015年12月 4日 (金) https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2015/12/other_xml-7f15.html

では、また。

良いお年をお迎えください。

Enjoy! SQL and Optimizer Features! :)

 



今回利用したSQLなど

 

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;


create global temporary table hoge_tmp (id number not null primary key, memo varchar2(100)) on commit preserve rows;
create global temporary table hoge_tmp2 (id number not null primary key, memo varchar2(100)) on commit preserve rows;


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- 一時表(Global Temporary Table)のセッション固有統計取得
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- dyamic sampling off
-- first_rows_1
-- 一時表(Global Temporary Table)のセッション固有統計あり
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- dyamic sampling off
-- all_rows
-- 一時表(Global Temporary Table)のセッション固有統計あり
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off



+++ 一時表(Global Temporary Table)のセッション固有統計なしで、動的統計有効 +++

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = first_rows_1;
alter session set optimizer_dynamic_sampling = 2;

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


-- first_rows_1
-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = first_rows_1;
alter session set optimizer_dynamic_sampling = 2;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 一時表(Global Temporary Table)のセッション固有統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_mode = all_rows;
alter session set optimizer_dynamic_sampling = 2;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off




--パーマネント表でも同じ

alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;


create table hoge_tmp (id number not null primary key, memo varchar2(100));
create table hoge_tmp2 (id number not null primary key, memo varchar2(100));


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

begin for i in 1..100000 loop insert into hoge_tmp values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/
begin for i in 1..100000 loop insert into hoge_tmp2 values(i,lpad('x',100,'x')); if mod(i,1000) = 0 then commit; end if; end loop; end;
/


select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');


-- first_rows_1
-- 統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

-- all_rows
-- 統計なし
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 0;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- first_rows_1
-- 統計なし
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 統計なし
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- 統計取得
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP',cascade=>true,no_invalidate=>false);
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE_TMP2',cascade=>true,no_invalidate=>false);

select table_name,num_rows from user_tables where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows from user_indexes where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,num_rows,scope from user_tab_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');
select table_name,index_name,num_rows,scope from user_ind_statistics where table_name in ('HOGE_TMP','HOGE_TMP2');

-- first_rows_1
-- 統計あり
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = first_rows_1;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off


-- all_rows
-- 統計あり
-- 動的統計有効
alter session set optimizer_adaptive_plans = false;
alter session set optimizer_dynamic_sampling = 2;
alter session set optimizer_mode = all_rows;
set autot trace exp stat
select * from hoge_tmp a inner join hoge_tmp2 b on a.id = b.id;
set autot off

 

 

| | | コメント (0)

2024年12月21日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #17 - 文字数取得関数も癖が強い

本エントリーは、
JPOUG Advent Calendar 2024
MySQL Advent Calendar 2024
PostgreSQL Advent Calendar 2024 の21目の記事です。

 

JPOUG Advent Calendar 2024の昨日のポストは、takashitel(おおの)さんの「RMANで表単位リカバリをする際の注意点(RMAN-06024)」
MySQL Advent Calendar 2024の昨日のポストは、yoku0825さんの「Blackholeストレージエンジンとバイナリログと 2024」
PostgreSQL Advent Calendar 2024の昨日のポストは、Noriyoshi Shinodaさんの「PostgreSQL と Oracle Database でシーケンスの動作を比較する」(シリーズ1)、および Shimooo_mutenkaさんの「【PostgreSQL】リストアの進捗状況を監視・表示するシェルスクリプト」(シリーズ2)
でした。

 


では、本題。

同じ名称の関数でも癖があるので移行するようなケースでは注意した方がいいよ!
という非互換はみなさんご存知の通りだと思います。

それでも検証漏れに気づいた時の雰囲気の悪さと言ったらなんとも言えませんよね。
その影響で、その他の非互換についても漏れているのでは?
と疑われてしまったり、玉突きでタスクが増えたり良いことはありません。

そんなことを一つでも減らしたいというネタです。:)

同じ結果を返す関数でも名称が違う程度ならまだ分かり易いですが、インプットする値による挙動の違いは把握するのが難しいですよね。
(マニュアルに書かれていたり書かれていなかったり、書かれていても気づきにくいところに書かれていたり、関数そのものの影響ではなかったり、様々な要因があります)

今日はそのような癖の一つで、文字数取得関数の癖。(すでに、前述した癖の一つに気づいちゃった方もいると思いますが、No Spoiler!でw)
今回のネタも有名なものの一つだと思いますが、あらためて、意識しておこう!
という意味も込めて取り上げてみました。(癖のあるシリーズでもまだ取り上げてなかったこともありますw)

 

長いのでw 最初に癖のまとめを。

文字数取得関数 ・Oracle Database

  • LENGTH()
  • NULLが渡された場合、 NULL が返される
  • 空文字が渡された場合、 NULL が返される(Oracle Databaseが空文字をNULLとして扱うため)
  • CHAR(n)全てが空白の文字列の場合、空白分の文字数が返る。CHAR(10 CHAR)の場合は、10.
  • CHAR型の後続空白は文字数にカウントされる。e.g. CHAR(10 CHAR) <= 'ABC'の場合、右側を空白埋めして 10 が返される

参考)
Oracle Database / Release 19 / SQL言語リファレンス / LENGTH

今回の主役であるLENGTH()関数については以下のような記載があります。
Oracle Database / Release 19 / SQL Language Reference / Table C-1 Oracle Support of Core SQL Features

"E021-04, CHARACTER_LENGTH function: use LENGTH function instead" 上記、ドキュメントに記載されている通り、Oracle DatabaseのLENGTH()関数は方言なんですよね!!
早速、癖者感が強くなったところでw、次へ進みましょうwwww

 

・MySQL

  • CHAR_LENGTH() / CHARACTER_LENGTH()
  • NULLが渡された場合、 NULL が返される
  • 空文字が渡された場合、0 が返される
  • 全て空白の文字列の場合、 0 が返されさる(空白がトリムされるため)
  • CHAR型の後続空白は文字数にカウントされない。(空白がトリムされるため)ただし、 CHAR_LENGTH(CAST('12345 ' AS CHAR(10)))、 CHAR_LENGTH(CAST('12345 ' AS CHAR(30)))のようなケースでは空白を含むリテラルの文字数が返る。(Release 8.0.36)

参考)
MySQL 8.0 リファレンスマニュアル / データ型 / 文字列データ型 / CHAR および VARCHAR 型
https://dev.mysql.com/doc/refman/8.0/ja/char.html

"CHARACTER_LENGTH() は CHAR_LENGTH() のシノニム。E021-04対応とのこと。標準対応ということですね。Oracle Databaseはやってないですけど" なお、 LENGTH( str ) は、Oracle Databaseだと LENGTHB( char ) 相当ですね。バイト数を返してくるので。まじか! この類の関数の癖の多さと強さは半端ないですね。 有名な違いなので見落とされることはなさそうな気もしますが、注意が必要な部分です。
Oracle Database 以外で要注意なのはCHAR型の右側のスペースの扱いですね。該当関数云々というよりも。

"CHAR 値は格納されると、指定された長さになるように右側がスペースで埋められます。 PAD_CHAR_TO_FULL_LENGTH SQL モードが有効になっていないかぎり、CHAR 値が取り出されるときに、末尾のスペースが削除されます。"

MySQL 8.0 リファレンスマニュアル / 関数と演算子 / 文字列関数および演算子 / LENGTH(str) MySQL 8.0 リファレンスマニュアル / 関数と演算子 / 文字列関数および演算子 / CHAR_LENGTH(str) ”文字で測定された文字列 str の長さを返します。 マルチバイト文字は、単一の文字としてカウントされます。 つまり、5 つの 2 バイト文字を含む文字列では、LENGTH() は 10 を返し、CHAR_LENGTH() は 5 を返します。”

MySQL 8.0 リファレンスマニュアル / 関数と演算子 / 文字列関数および演算子 / CHARACTER_LENGTH(str)

 

・PostgreSQL

  • CHAR_LENGTH() / CHARACTER_LENGTH() / LENGTH()
  • NULLが渡された場合、 NULL が返される
  • 空文字が渡された場合、0 が返される
  • 全て空白の文字列の場合、 0 が返されさる(空白がトリムされるため)
  • CHAR型の後続空白は文字数にカウントされない(空白がトリムされるため)

参考)
PostgreSQL 16.4文書 / 第9章 関数と演算子 / 9.4. 文字列関数と演算子
PostgreSQL 16.4文書 / D.1. サポートされている機能 / D.1. サポートされている機能 / CHARACTER_LENGTH関数

”021-04 コア CHARACTER_LENGTH関数 - 数える前にCHARACTER値の最後の空白を除去します

の冒頭でも以下のような注意点が記載されています。
”character型の値は関数あるいは演算子に適用される前にtextに変換され、character値の末尾の空白が削除されることになります。

 

とさらりとまとめちゃいましたが、なかなかの癖者であることがお分かりいただけたかと思います。

移行する際にこの対応が漏れてしまうと、バグに直結する部分なのでしっかりと対処したいですよね。これ。



この程度書いただけでお腹いっぱい感はありますがw、簡単な確認ログも載せておきます。

Oracle Database Oracle Databaseに慣れてると、まあ、ふむふむって結果ではあります。


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

1)
マニュアルに記載されているように、 NULL の場合は、NULLが返ります。

 

SCOTT@orclpdb1> set null [null]
SCOTT@orclpdb1> SELECT LENGTH(null) AS "NULL" FROM dual;

NULL
----------
[null]

 

2)
Oracle Database は空文字は NULL として扱われる別の癖があるため、空文字の場合は、NULL と同じ結果が返ります。


SCOTT@orclpdb1> SELECT LENGTH('') AS "Empty String" FROM dual;

Empty String
------------
[null]

 

3)


SCOTT@orclpdb1> SELECT LENGTH(' ') AS "SINGLE-BYTE SPACE" FROM dual;

SINGLE-BYTE SPACE
-----------------
1

 

4)


SCOTT@orclpdb1> SELECT LENGTH(' ') AS "MULTI-BYTE SPACE" FROM dual;

MULTI-BYTE SPACE
----------------
1

 

5)


SCOTT@orclpdb1> SELECT LENGTH('12345') AS "SINGLE-BYTE CHARs" FROM dual;

SINGLE-BYTE CHARs
-----------------
5

 

6)


SCOTT@orclpdb1> SELECT LENGTH('あいうえお') AS "MULTI-BYTE CHARs" FROM dual;

MULTI-BYTE CHARs
----------------
5

 

7)


SCOTT@orclpdb1> SELECT LENGTH('12345     ') AS "SINGLE-BYTE CHARs + SPACEs" FROM dual;

SINGLE-BYTE CHARs + SPACEs
--------------------------
10

 

8)


SCOTT@orclpdb1> SELECT LENGTH('あいうえお     ') AS "MULTI-BYTE CHARs + MLUTI-BYTE SPACEs" FROM dual;

MULTI-BYTE CHARs + MLUTI-BYTE SPACEs
------------------------------------
10

 

9)


SCOTT@orclpdb1> SELECT LENGTH(CAST('12345' AS CHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs" FROM dual;

SINGLE-BYTE CHARs + SPACEs
--------------------------
10

 

10)


SCOTT@orclpdb1> SELECT LENGTH(CAST('12345     ' AS CHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs" FROM dual;

SINGLE-BYTE CHARs + SPACEs
--------------------------
10

 

11)


SCOTT@orclpdb1> SELECT LENGTH(CAST('12345' AS VARCHAR2(10))) AS "SINGLE-BYTE CHARs + SPACEs" FROM dual;

SINGLE-BYTE CHARs + SPACEs
--------------------------
5

 

12)


SCOTT@orclpdb1> SELECT LENGTH(CAST('12345     ' AS VARCHAR2(10))) AS "SINGLE-BYTE CHARs + SPACEs" FROM dual;

SINGLE-BYTE CHARs + SPACEs
--------------------------
10

 

 

MySQL CHARの時の挙動に注目です。CASTしたケースの挙動はなかなかキツめな気がします。

Oracle DatabaseのLENGTH()に相当するのは、CHAR_LENGTH()ですが、空文字の扱いはOracle Databaseが特殊なので要注意なのは言うまでもありません。


+-----------+
| VERSION() |
+-----------+
| 8.0.36 |
+-----------+

 

1)


mysql> SELECT CHAR_LENGTH(null) AS "NULL";
+------+
| NULL |
+------+
| NULL |
+------+

 

2)
Oracle Databaseとは異なり。空文字は空文字としてカウントされています。こちらが標準の動き(Oracle Database側に癖があります)


mysql> SELECT CHAR_LENGTH('') AS "Empty String";
+--------------+
| Empty String |
+--------------+
| 0 |
+--------------+

 

3)


mysql> SELECT CHAR_LENGTH(' ') AS "SINGLE-BYTE SPACE";
+-------------------+
| SINGLE-BYTE SPACE |
+-------------------+
| 1 |
+-------------------+

 

4)


mysql> SELECT CHAR_LENGTH(' ') AS "MULTI-BYTE SPACE";
+------------------+
| MULTI-BYTE SPACE |
+------------------+
| 1 |
+------------------+

 

5)


mysql> SELECT CHAR_LENGTH('12345') AS "SINGLE-BYTE CHARs";
+-------------------+
| SINGLE-BYTE CHARs |
+-------------------+
| 5 |
+-------------------+

 

6)


mysql> SELECT CHAR_LENGTH('あいうえお') AS "MULTI-BYTE CHARs";
+------------------+
| MULTI-BYTE CHARs |
+------------------+
| 5 |
+------------------+

 

7)


mysql> SELECT CHAR_LENGTH('12345     ') AS "SINGLE-BYTE CHARs + SPACEs";
+----------------------------+
| SINGLE-BYTE CHARs + SPACEs |
+----------------------------+
| 10 |
+----------------------------+

 

8)


mysql> SELECT CHAR_LENGTH('あいうえお     ') AS "MULTI-BYTE CHARs + MLUTI-BYTE SPACEs";
+--------------------------------------+
| MULTI-BYTE CHARs + MLUTI-BYTE SPACEs |
+--------------------------------------+
| 10 |
+--------------------------------------+

 

9)
これはマニュアルに記載されている通りの結果ですね。Oracle Databaseとは異なり, CHAR(10)へキャストされた際に付加される空白は含まれていません。 なお、MySQL 8.0 リファレンスマニュアル / MySQL サーバーの管理 / MySQL Server / サーバー SQL モード / 5.1.11 サーバー SQL モード には、PAD_CHAR_TO_FULL_LENGTH の記述がありますが、最新版ではdeprecatedになっています。


mysql> SELECT CHAR_LENGTH(CAST('12345' AS CHAR(10))) AS "SINGLE-BYTE CHARs";
+-------------------+
| SINGLE-BYTE CHARs |
+-------------------+
| 5 |
+-------------------+

 

9-1)
前述の特性から言うと、ちょっと怪しい挙動ですよね。なんだろこの動き。(8.0.36)


mysql> SELECT CHAR_LENGTH(CAST('12345     ' AS CHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs";
+----------------------------+
| SINGLE-BYTE CHARs + SPACEs |
+----------------------------+
| 10 |
+----------------------------+

 

9-2)
面白い挙動ですね。もう一つついでに確認しておきましょう。リテラルで指定した文字列が、CASTしたCHAR(n)のn以下の場合、CHAR(n)のnが返る訳ではなく、リテラルの文字数(後続の空白を含む)そのものが返されます。なんとなく、怪しい挙動ですね。やはり。


mysql>  SELECT CHAR_LENGTH(CAST('12345     ' AS CHAR(30))) AS "SINGLE-BYTE CHARs + SPACEs";
+----------------------------+
| SINGLE-BYTE CHARs + SPACEs |
+----------------------------+
| 10 |
+----------------------------+

 

9-3)
CHAR(7)よりは長い文字列を渡しているので、超過した文字は切られて 7文字となっています。これは予想通り。


mysql>  SELECT CHAR_LENGTH(CAST('12345      ' AS CHAR(7))) AS "SINGLE-BYTE CHARs + SPACEs";
+----------------------------+
| SINGLE-BYTE CHARs + SPACEs |
+----------------------------+
| 7 |
+----------------------------+

 

MySQLのCAST()ではVARCHAR/TEXTへのキャストはできないようなので、CHAR(n)へのキャストのみ確認してみました。CHARだけ見れれば良いので十分ではありますが。CHARの時の文字列に続く空白の扱いの癖の影響なので。

PostgreSQL Oracle DatabaseのLENGTH()に相当するのは、CHAR_LENGTH()ですが、空文字の扱いはOracle Databasegが特殊なので要注意なのは言うまでもありません)
また、LENGTH()も同様に文字数を返してくるのがわかります。。。。PostgreSQLの場合、関数名も同じにできるし、NULL関連の癖に注意すれば良さそうに見えるわけですが。。。。。油断しちゃいけませんよねw


                                                 version                                                 
---------------------------------------------------------------------------------------------------------
PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-22), 64-bit

perftestdb=> \pset null [null]
Null表示は"[null]"です。

 

1)


perftestdb=> SELECT LENGTH(null) AS "NULL";
NULL
--------
[null]

 

2)
空文字の挙動はMySQLと同じですよね。


perftestdb=> SELECT LENGTH('') AS "Empty String";
Empty String
--------------
0

 

3)


perftestdb=> SELECT LENGTH(' ') AS "SINGLE-BYTE SPACE";
SINGLE-BYTE SPACE
-------------------
1

 

4)


perftestdb=> SELECT LENGTH(' ') AS "MULTI-BYTE SPACE";
MULTI-BYTE SPACE
------------------
1

 

5)


perftestdb=> SELECT LENGTH('12345') AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

 

6)


perftestdb=> SELECT LENGTH('あいうえお') AS "MULTI-BYTE CHARs";
MULTI-BYTE CHARs
------------------
5

 

7)


perftestdb=> SELECT LENGTH('12345     ') AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

 

8)


perftestdb=> SELECT LENGTH('あいうえお     ') AS "MULTI-BYTE CHARs + MLUTI-BYTE SPACEs";
MULTI-BYTE CHARs + MLUTI-BYTE SPACEs
--------------------------------------
10

 

9)
MySQLとPostgreSQLは同じ挙動ですね。


perftestdb=> SELECT LENGTH(CAST('12345' AS CHAR(10))) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

 

10)
空白が埋めされたわけではなく、文字列として空白を渡した場合も、トリムされて、5文字となります。(MySQLもこの動きを予想していたのになんだろあの動き)


perftestdb=> SELECT LENGTH(CAST('12345     ' AS CHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
5

 

11)


perftestdb=> SELECT LENGTH(CAST('12345' AS VARCHAR(10))) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

 

12)


perftestdb=> SELECT LENGTH(CAST('12345     ' AS VARCHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

 

13)


perftestdb=> SELECT LENGTH(CAST('12345' AS TEXT)) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

 

14)


perftestdb=> SELECT LENGTH(CAST('12345     ' AS TEXT)) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

 

CHAR_LENGTH()でも同様に確認しておきましょう。LENGTH()でいいじゃん感じですけどもw。


perftestdb=> SELECT CHAR_LENGTH(null) AS "NULL";
NULL
--------
[null]

perftestdb=> SELECT CHAR_LENGTH('') AS "Empty String";
Empty String
--------------
0

perftestdb=> SELECT CHAR_LENGTH(' ') AS "SINGLE-BYTE SPACE";
SINGLE-BYTE SPACE
-------------------
1

perftestdb=> SELECT CHAR_LENGTH(' ') AS "MULTI-BYTE SPACE";
MULTI BYTE SPACE
------------------
1

perftestdb=> SELECT CHAR_LENGTH('12345') AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

perftestdb=> SELECT CHAR_LENGTH('あいうえお') AS "MULTI-BYTE CHARs";
MULTI-BYTE CHARs
------------------
5

perftestdb=> SELECT CHAR_LENGTH('12345 ') AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

perftestdb=> SELECT CHAR_LENGTH('あいうえお     ') AS "MULTI-BYTE CHARs + MLUTI-BYTE SPACEs";
MULTI-BYTE CHARs + MLUTI-BYTE SPACEs
--------------------------------------
10

perftestdb=> SELECT CHAR_LENGTH(CAST('12345' AS CHAR(10))) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

perftestdb=> SELECT CHAR_LENGTH(CAST('12345 ' AS CHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
"5

perftestdb=> SELECT CHAR_LENGTH(CAST('12345' AS VARCHAR(10))) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

perftestdb=> SELECT CHAR_LENGTH(CAST('12345 ' AS VARCHAR(10))) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

perftestdb=> SELECT CHAR_LENGTH(CAST('12345' AS TEXT)) AS "SINGLE-BYTE CHARs";
SINGLE-BYTE CHARs
-------------------
5

perftestdb=> SELECT CHAR_LENGTH(CAST('12345 ' AS TEXT)) AS "SINGLE-BYTE CHARs + SPACEs";
SINGLE-BYTE CHARs + SPACEs
----------------------------
10

 

CHAR型の空白の扱いが癖の元なので、表の列がCHAR型の場合の動きも少し見ておきましょう。
まずは、データの準備、Oracle Database/MySQL/PostgreSQLそれぞれで、CHAR(10)の列を1列持つ表とデータを用意しました。

Oracle Database


SCOTT@orclpdb1> l
1 CREATE TABLE str
2 (
3 memo VARCHAR2(40 CHAR)
4 , c CHAR(10 CHAR)
5* )
SCOTT@orclpdb1> /

表が作成されました。

SCOTT@orclpdb1> l
1 INSERT ALL
2 INTO str(memo, c) VALUES('NULL', null)
3 INTO str(memo, c) VALUES('Empty string', '')
4 INTO str(memo, c) VALUES('SINGLE-BYTE SPACE', ' ')
5 INTO str(memo, c) VALUES('SINGLE-BYTE 2 SPACEs', ' ')
6 INTO str(memo, c) VALUES('SINGLE-BYTE CHAR and SPACE', '1 ')
7 INTO str(memo, c) VALUES('SINGLE-BYTE CHARs', '12345')
8 INTO str(memo, c) VALUES('SINGLE-BYTE CHARs', '12345 ')
9* SELECT null FROM dual
SCOTT@orclpdb1> /

7行が作成されました。

SCOTT@orclpdb1> COMMIT;

コミットが完了しました。

 

MySQL


mysql> CREATE TABLE str
-> (
-> memo VARCHAR(40)
-> , c CHAR(10)
-> );
Query OK, 0 rows affected (0.29 sec)

mysql> INSERT
-> INTO str(memo, c)
-> VALUES('NULL', null)
-> ,('Empty string', '')
-> ,('SINGLE-BYTE SPACE', ' ')
-> ,('SINGLE-BYTE 2 SPACEs', ' ')
-> ,('SINGLE-BYTE CHAR and SPACE', '1 ')
-> ,('SINGLE-BYTE CHARs', '12345')
-> ,('SINGLE-BYTE CHARs', '12345 ')
-> ;
Query OK, 7 rows affected (0.03 sec)
Records: 7 Duplicates: 0 Warnings: 0

mysql>

 

PostgreSQL


perftestdb=> CREATE TABLE str
perftestdb-> (
perftestdb(> memo VARCHAR(40)
perftestdb(> , c CHAR(10)
perftestdb(> );
CREATE TABLE

perftestdb-> INTO str(memo, c)
perftestdb-> VALUES('NULL', null)
perftestdb-> ,('Empty string', '')
perftestdb-> ,('SINGLE-BYTE SPACE', ' ')
perftestdb-> ,('SINGLE-BYTE 2 SPACEs', ' ')
perftestdb-> ,('SINGLE-BYTE CHAR and SPACE', '1 ')
perftestdb-> ,('SINGLE-BYTE CHARs', '12345')
perftestdb-> ,('SINGLE-BYTE CHARs', '12345 ')
perftestdb-> ;
INSERT 0 7
perftestdb=>

 

リテラル値をキャストした場合とは少々異なる結果になりました。
MySQLとPostgreSQLは同一の挙動を示します。MySQLの場合、'12345 'をCHAR(10)へCASTした時の挙動の違い、やはり気になりますね。

Oracle Database


SCOTT@orclpdb1> SELECT memo, CASE WHEN c IS NULL THEN c ELSE '"'||c||'"' END AS "CHAR(10)", LENGTH(c) FROM str;

MEMO CHAR(10) LENGTH(C)
---------------------------------------- ------------------------------------ ----------
NULL [null] [null]
Empty string [null] [null]
SINGLE-BYTE SPACE " " 10
SINGLE-BYTE 2 SPACEs " " 10
SINGLE-BYTE CHAR and SPACE "1 " 10
SINGLE-BYTE CHARs "12345 " 10
SINGLE-BYTE CHARs "12345 " 10

 

MySQL


mysql> SELECT memo, CASE WHEN c IS NULL THEN c ELSE CONCAT('"', c, '"') END AS "CHAR(10)", CHAR_LENGTH(c) FROM str;
+----------------------------+----------+----------------+
| memo | CHAR(10) | CHAR_LENGTH(c) |
+----------------------------+----------+----------------+
| NULL | NULL | NULL |
| Empty string | "" | 0 |
| SINGLE-BYTE SPACE | "" | 0 |
| SINGLE-BYTE 2 SPACEs | "" | 0 |
| SINGLE-BYTE CHAR and SPACE | "1" | 1 |
| SINGLE-BYTE CHARs | "12345" | 5 |
| SINGLE-BYTE CHARs | "12345" | 5 |
+----------------------------+----------+----------------+

 

PostgreSQL


perftestdb=> SELECT memo, CASE WHEN c IS NULL THEN c ELSE '"'||c||'"' END AS "CHAR(10)", LENGTH(c) FROM str;
memo | CHAR(10) | length
----------------------------+----------+--------
NULL | [null] | [null]
Empty string | "" | 0
SINGLE-BYTE SPACE | "" | 0
SINGLE-BYTE 2 SPACEs | "" | 0
SINGLE-BYTE CHAR and SPACE | "1" | 1
SINGLE-BYTE CHARs | "12345" | 5
SINGLE-BYTE CHARs | "12345" | 5

 

明日の、JPOUG Advent Calendar 2024は、rkondoさん、MySQL Advent Calendar 2024は、今の所空いていますが誰かが書くはずw、そして、PostgreSQL Advent Calendar 2024は、kingtomo1122さん(シリーズ1)、yamatattsuさん(シリーズ2)です。

 

メーリークリスマスには少し早いですが、メリークリスマス!
良いお年をお迎えください。

では、また来年!(DTMのポストはまだする予定、DTMの全部俺アドベントカレンダーやってますのでw )

Enjoy! SQL!

 



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

 

 

 

| | | コメント (0)

2024年12月13日 (金)

実行計画は, SQL文のレントゲン写真だ! No.65 / JPOUG Advent Calendar 2024 / JSON-Relational Duality Views

本エントリーは、13日の金曜日のエントリーです。
昨日のポストは、Takayuki Nishio (Nisshii0)さんの「[Oracle] ここが違うよ Autonomous Database! 初めてデータ移行して気づいたこと」でした。




ということで、私のターン。

皆様の期待通り?、13日の金曜日なので、JSON を取り上げておきたいと思います。(実は元々別のながーーーいネタを用意していたのですが、13日の金曜日であることに気づきwww 以下略)
Oracle Database のJSON関連機能について、すでに多くの方がブログ等で書かれていることもあり、本エントリーでは、Mac De Oracleっぽくw、実行計画という名のレントゲン写真はどうなのかw を診ておきたいと思います。


Stable Diffusion WebUI
20241206-131544

対象とするのは、JSON-Relational Duality Views です。
この機能、名前の通り、リレーショナル表のままで、JSONにマッピングするVIEWを通して使えるようにしたものでOracle Database 23aiの新機能の一つです。(雑に解説すると。。)

気になりますよね。JSON-Relational Duality Viewsを介してアクセスした場合の実行計画!!!!

ちょっとわき道に逸れるのですが、Oracle Databaseには、RDFView という機能があります。リレーショナル表のままで、RDF Graphのトリプルとして参照する機能ですよね。これまた雑に解説すると。

実行計画は,SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 おまけ#3
トリプルとして扱うために、対象の列毎にSELECT文を作る必要がありかつ3列なのでUNIONも必要で、という想像通りのなかなかのレントゲン写真(実行計画でした)。
Rdfview


果たして、JSON-Relational Duality Views ではどうなるのか。。。。

早速、試してみましょう。

23aiを利用しています。

COTT@localhost:1521/freepdb1> select banner_full from v$version;

BANNER_FULL
--------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05


まず、JSON-Relational Duality ViewsはViewなので元になるリレーショナル表を決めちゃいます。dept表をもとにして department表を作って使いましょう。 dept表のままでも良いのですけどもw

SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc dept
Name Null? Type
----------------------------------------- -------- ----------------------------
DEPTNO NOT NULL NUMBER(2)
DNAME VARCHAR2(14)
LOC VARCHAR2(13)

SCOTT@localhost:1521/freepdb1> select * from dept;

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

SCOTT@localhost:1521/freepdb1> create table department as select * from dept;

Table created.

SCOTT@localhost:1521/freepdb1> alter table department add constraint pk_department primary key (deptno) using index;

Table altered.


department表の列をJSONのキーにマップしちゃうだけ(おまけで, UPDATE/INSERT/DELETEも許可していますが、今回はSELECTしかしませんw)
JSON RELATIONAL DUALITY VIEWを作成します。(見ると リレーショナル表の列とJSONのキーをマップしているだけですね。シンプル)

SCOTT@localhost:1521/freepdb1> l
1 CREATE JSON RELATIONAL DUALITY VIEW department_dv
2 AS
3 SELECT
4 JSON { '_id' : d.deptno,
5 'departmentName' : d.dname,
6 'location' : d.loc }
7 FROM
8 department d
9* WITH UPDATE INSERT DELETE
SCOTT@localhost:1521/freepdb1> /

View created.

SCOTT@localhost:1521/freepdb1> select collection_name,collection_type from user_json_collections;

COLLECTION_NAME COLLECTION_T
------------------------------ ------------
DEPARTMENT_DV DUALITY VIEW

SCOTT@localhost:1521/freepdb1> set linesize 400
COTT@localhost:1521/freepdb1> select * from user_json_duality_views;

VIEW_NAME JSON ROOT_TABLE_NAME ROOT_TABLE_OWNER ALLOW_INSER ALLOW_UPDAT ALLOW_DELET READ_ONLY JSON_SCHEMA STATUS
------------------------------ ---- ------------------------------ ------------------------------ ----------- ----------- ----------- ----------- ------------------------------ -------
DEPARTMENT_DV DATA DEPARTMENT SCOTT TRUE TRUE TRUE FALSE {"title":"DEPARTMENT_DV","dbOb VALID
ject":"SCOTT.DEPARTMENT_DV","d
bObjectType":"dualit


特に何も指定せず問い合わせると、まんまのJSONが返されます。

SCOTT@localhost:1521/freepdb1> set long 4000
SCOTT@localhost:1521/freepdb1> set longchunk 4000
SCOTT@localhost:1521/freepdb1> set linesize 400
SCOTT@localhost:1521/freepdb1> select * from department_dv;

DATA
--------------------------------------------------------------------------------------------------------------------------------------------------
{"_id":10,"_metadata":{"etag":"66F269F721C734BAE74D56D6A948D0F6","asof":"0000000001799313"},"departmentName":"ACCOUNTING","location":"NEW YORK"}
{"_id":20,"_metadata":{"etag":"8A0701C115BFECAB64C34E2FF406FFDA","asof":"0000000001799313"},"departmentName":"RESEARCH","location":"DALLAS"}
{"_id":30,"_metadata":{"etag":"A7CDA588F9052B35B56E00BB22B6EC6F","asof":"0000000001799313"},"departmentName":"SALES","location":"CHICAGO"}
{"_id":40,"_metadata":{"etag":"93AE902896310C0DFFCE3FC70E0479F6","asof":"0000000001799313"},"departmentName":"OPERATIONS","location":"BOSTON"}


さて、table full scanになると思いますが、とりあえず見てみましょう。

SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> select * from department_dv;

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------
Plan hash value: 826413278

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

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

SCOTT@localhost:1521/freepdb1> set autot off
SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc department_dv
Name Null? Type
----------------------------------------- -------- ----------------------------
DATA JSON

SCOTT@localhost:1521/freepdb1> set linesize 400
SCOTT@localhost:1521/freepdb1> col _id for a30
SCOTT@localhost:1521/freepdb1> col departmentName for a30
SCOTT@localhost:1521/freepdb1> col location for a30

...略...

リレーショナル表っぽいクエリーにもできます。

SCOTT@localhost:1521/freepdb1> r
1 select d.data."_id",
2 d.data."departmentName",
3 d.data."location"
4 from department_dv d
5* order by 1

_id departmentName location
------------------------------ ------------------------------ ------------------------------
10 "ACCOUNTING" "NEW YORK"
20 "RESEARCH" "DALLAS"
30 "SALES" "CHICAGO"
40 "OPERATIONS" "BOSTON"

では、次に、主キーにマップした _id 列でアクセスしてみましょう。おおおおお、普通に、INDEX UQNIQE SCANでしたね。(想像していた通りですがw)
Predicate Information に現れるアクセスパスを見ると、 access("D"."DEPTNO"=30) となっており、内部的にはリレーショナル表を問い合わせるSQL文に書き換えられているように見えますよね(まだ調べていないですがw)

SCOTT@localhost:1521/freepdb1> set autot trace exp stat
SCOTT@localhost:1521/freepdb1> select json_serialize(d.data pretty) from department_dv d where d.data."_id" = 30;

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3132674683

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

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

2 - access("D"."DEPTNO"=30)


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

SCOTT@localhost:1521/freepdb1> set autot off
SCOTT@localhost:1521/freepdb1> r
1* select json_serialize(d.data pretty) from department_dv d where d.data."location" = 'CHICAGO'

JSON_SERIALIZE(D.DATAPRETTY)
----------------------------------------------------------------------------------------------------
{
"_id" : 30,
"_metadata" :
{
"etag" : "A7CDA588F9052B35B56E00BB22B6EC6F",
"asof" : "0000000001799BF8"
},
"departmentName" : "SALES",
"location" : "CHICAGO"
}

...略...

SCOTT@localhost:1521/freepdb1> r
1 select
2 d.data."_id" as deptno
3 , d.data."departmentName" as department_name
4 , d.data."location" as location
5 from
6 department_dv d
7 where
8* d.data."location" = 'CHICAGO'

DEPTNO DEPARTMENT_NAME LOCATION
------------------------------ ------------------------------ ------------------------------
30 "SALES" "CHICAGO"


JSON-Relational Duality Viewsから問い合わせてもリレーショナル表を直接問い合わせるのと同じなんですねぇ。なんとなく安心w

13日の金曜日のJSONネタなので、恐ろしーい結果を期待していた方、ごめんなさいwwww JSON怖くないですw


明日のJPOUG Advent Calendar 2024は、ketsujiさんです。




Related article on Mac De Oracle

実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3
実行計画は, SQL文のレントゲン写真だ! No.64 / 先生、私のLEFT OUTER JOINが無いんです!!(Join Elimination番外編

| | | コメント (0)

2024年9月 1日 (日)

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

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

 

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

 

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

 

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

 

[oracle@localhost work]$ sqlplus scott@orclpdb1

...中略...

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

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

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

SCOTT@orclpdb1>

 

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

SCOTT@orclpdb1> @additional_example.sql

表が削除されました。

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

表が作成されました。

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

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

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

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

表が変更されました。

経過: 00:00:35.23

 

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

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

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

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

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

 

 

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

 

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

 

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

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

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

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

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

 

 

 

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

[oracle@localhost ~]$ sqlplus scott@orclpdb1

...中略...

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

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

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

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

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

経過: 00:00:01.03

 

 

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

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

索引が作成されました。

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

表が変更されました。

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

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

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

経過: 00:00:00.02

 

 

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

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

表が変更されました。

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

表が変更されました。

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

索引が作成されました。

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

表が変更されました。

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

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

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

経過: 00:00:00.03

 

 

 

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

 

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

 

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

 

と記されています。

 

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

 

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

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

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

...中略...

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

...中略...

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

...中略...

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

52行が選択されました。

経過: 00:00:00.40

 

 

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

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

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

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

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

 

 

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

 

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

[oracle@localhost ~]$ sqlplus scott@orclpdb1

...中略...

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

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

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

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

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

経過: 00:00:00.08

 

 

 

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

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

索引が作成されました。

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

表が変更されました。

経過: 00:00:00.07

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

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

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

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

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

経過: 00:00:00.01

 

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

 

ではまた。

 


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

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

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

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

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

 



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

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


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


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

 

 

 

| | | コメント (0)

2024年8月30日 (金)

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


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

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

 

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


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

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

 

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

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


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

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

 

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

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

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

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

 

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

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

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


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

 

 

 

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

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

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


SCOTT@orclpdb1> @why_do_you_think_it_is_slow_adding_the_primary_constraint.sql

表が削除されました。

経過: 00:00:01.25

表が削除されました。

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

表が作成されました。

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

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

表が作成されました。

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

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

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

経過: 00:11:42.62

 

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

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

表が変更されました。

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

表が変更されました。

経過: 00:00:36.92

 

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

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

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

表が変更されました。

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

索引が作成されました。

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

索引が作成されました。

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

表が変更されました。

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

表が変更されました。

経過: 00:00:00.02

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

経過: 00:00:18.53

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

経過: 00:00:15.50

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

経過: 00:00:00.26

 

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

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

経過: 00:00:00.41

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

1* ALTER TABLE table_1 MODIFY id2 NUMBER NOT NULL

表が変更されました。

経過: 00:00:03.14

 

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

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

 

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

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

表が変更されました。

経過: 00:00:34.65

 

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

  1* ALTER TABLE table_1 DROP PRIMARY KEY DROP INDEX

表が変更されました。

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

索引が作成されました。

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

表が変更されました。

経過: 00:00:00.02

 

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

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

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

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

表が作成されました。

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

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

表が作成されました。

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

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

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

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

索引が作成されました。

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

索引が作成されました。

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

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

...略...

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

表が変更されました。

経過: 00:00:07.12

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

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

...略...

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

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

表が変更されました。

経過: 00:00:00.05

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

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

表が削除されました。

表が削除されました。

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

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

[oracle@localhost ~]$ view ORCLCDB_ora_7254_10046_table_1_slow.txt

...略...

SQL ID: 48v4hxnmpykdy Plan Hash: 0

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

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

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

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

...略...


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

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

[oracle@localhost ~]$ view ORCLCDB_ora_7256_10046_table_2_fast.txt

...略...

SQL ID: 7x7hqgxcpkyky Plan Hash: 0

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

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

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

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

 

 

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

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

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

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

set line 400

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

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

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

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

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

@gather_tab_stats table_1
@gather_tab_stats table_2

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

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

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

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

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

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

 

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

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

 

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

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

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

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

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

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

disconnect

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

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

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

 


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

ではまた。

 

| | | コメント (0)

2024年8月21日 (水)

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

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

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

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

 

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

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

患者 (キョトン!)

私「ほら〜〜」

(以下、Oracle Databaseです)

SCOTT@orclpdb1> select banner_full from v$version;

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

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

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

解析されました。

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

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

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

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

19行が選択されました。

 


 

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

 

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

 

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

 

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

 

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

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

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

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

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

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

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

SCOTT@orclpdb1>
SCOTT@orclpdb1> select * from emp order by empno;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300

12行が選択されました。

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

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

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

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

 

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

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

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

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

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

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

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

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

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

perftestdb=>

 

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

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

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

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

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

 

では、また。

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


Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その3

 

| | | コメント (0)

2024年7月24日 (水)

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

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

 

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

 

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

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

表が作成されました。

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

表が作成されました。

SCOTT@orclpdb1> @gather_tab_stats foo2

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

SCOTT@orclpdb1> @gather_tab_stats bar2

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

 

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

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

 

 

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

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

解析されました。

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

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

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

8行が選択されました。

 

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

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

 

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

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

 

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

まとめると、

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

 

PostgreSQL (16以前では未確認)

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

 

MySQL

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

 

では、また。

Enjoy SQL! and Execution Plan!

 



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

 

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

 

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

 

oeユーザで。

OE@orclpdb1> grant select on customers to scott;

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

OE@orclpdb1> grant select on orders to scott;

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

 

 

以降、scottユーザで。

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

表が作成されました。

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

表が変更されました。

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

表が作成されました。

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

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

表が変更されました。

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

表が変更されました。

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

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

索引が作成されました。

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

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

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

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

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

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

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

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

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

SCOTT@orclpdb1> @gather_tab_stats.sql orders

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

SCOTT@orclpdb1> @gather_tab_stats.sql customers

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

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

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

 

 

 

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

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

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

 

 

 

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

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

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

....中略....

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

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

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

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

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

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

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

 

 

 

 



Related article on Mac De Oracle
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その2

| | | コメント (0)

2024年7月23日 (火)

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

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

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

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

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

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

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

表が作成されました。

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

表が作成されました。

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

SCOTT@orclpdb1> @gather_tab_stats foo

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

SCOTT@orclpdb1> @gather_tab_stats bar

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

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

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


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

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

解析されました。

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

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

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

8行が選択されました。


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

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

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

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

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

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


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


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




Related article on Mac De Oracle

実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?
実行計画は, SQL文のレントゲン写真だ! No.63 / Join Elimination (再び)その1

| | | コメント (0)

2024年7月22日 (月)

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

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

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

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

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

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


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


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

 

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

SCOTT@orclpdb1> select banner_full from v$version;

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

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

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

 

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

 

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

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

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

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

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

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

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

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

 

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

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

 

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

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

解析されました。

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

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

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

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

1 - access("ORDER_ID"<2400)

13行が選択されました。

 

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

SCOTT@orclpdb1> alter table orders disable constraint fk_orders_customers;

表が変更されました。

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

解析されました。

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

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

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

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

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

18行が選択されました。

 

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

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

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

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

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

 

 

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

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

 

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

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

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

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

 

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

海側も無茶苦茶暑いのだろうか、海風吹いてそうでもないのだろうか。。と湘南方面を見ながらw

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


Related article on Mac De Oracle
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ
実行計画は, SQL文のレントゲン写真だ! No.62 / ORDBMS機能であるコレクション型の列をアクセスする実行計画ってどうなるの?

| | | コメント (0)

2024年7月12日 (金)

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

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

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




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

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

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

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


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

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

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

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

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

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

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


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


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

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


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

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

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

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

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

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

解析されました。

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

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

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

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

2 - access("CUSTOMER_ID"=348)

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

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



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

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

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

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

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

....中略....

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


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

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

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

解析されました。

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

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

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

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

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


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

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

ではまた。



Related article on Mac De Oracle

・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は, SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は, SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は, SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は, SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は, SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は, SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方
・実行計画は, SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 1 / No.36 / INTERSECT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 2 / No.37 / MINUS ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 3 / No.38 / EXCEPT and EXCEPT ALL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 4 / No.39 / In-Memory Hybrid Scans
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 5 / No.40 / PIVOT and UNPIVOT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 6 / No.41 / In-Memory Vectorized Join
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 7 / No.42 / INDEX RANGE SCAN (MULTI VALUE)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 8 / No.43 / TABLE ACCESS BY INDEX ROWID BATCHED
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 9 / No.44 / COLLECTION ITERATOR PICKLER FETCH
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 10 / No.45 / MAT_VIEW REWRITE ACCESS FULL
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 11 / No.46 / GROUPING SETS, ROLLUP, CUBE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 12 / No.47 / TEMP TABLE TRANSFORMATION
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 13 / No.48 / MULTI-TABLE INSERT
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 14 / No.49 / the DUAL Table
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 15 / No.50 / REMOTE
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 16 / No.51 / Concurrent Execution of Union All and Union
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 17 / No.52 / Order by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 19 / No.54 / Group by Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 20 / No.55 / DISTINCT Elimination
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 21 / No.56 / INLIST ITERATOR と Sub Query と STATISTICS COLLECTOR
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 22 / No.57 / Subquery Unnesting
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 23 / No.58 / ANTI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 24 / No.59 / SQL MACRO (19.7〜)
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 25 / No.60 / ANSI JOIN
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.60 / ANSI JOINのおまけ
実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 / No.61 / ANSI JOINのおまけのおまけ

| | | コメント (0)

2024年7月11日 (木)

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

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

 

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

 

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

 

 

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

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

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


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

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


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

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

 

 

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

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


mysql> SELECT
-> empno
-> ,ename
-> FROM
-> (
-> SELECT
-> empno
-> ,ename
-> ,mgr
-> FROM
-> emp
-> ) iv_emp
-> WHERE
-> mgr IS NULL
-> ;
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.00 sec)


mysql> SELECT
-> /*+ NO_MERGE(iv_emp) */
-> empno
-> ,ename
-> FROM
-> (
-> SELECT
-> empno
-> ,ename
-> ,mgr
-> FROM
-> emp
-> ) iv_emp
-> WHERE
-> mgr IS NULL
-> ;
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.02 sec)

 

 

PostgreSQL (13.14) PostgreSQLもMySQLと同じ挙動ですね。
PostgreSQL 13.1文書 / 第7章 問い合わせ / 7.2. テーブル式 / 7.2.1. FROM句 / 7.2.1.3. 副問い合わせに、"7.2.1.3. 副問い合わせ 派生テーブルを指定する副問い合わせは括弧で囲む必要があります。 また、(7.2.1.2にあるように)必ずテーブル別名が割り当てられている必要があります。 例を示します。"とあります。

perftestdb=> SELECT
perftestdb-> empno
perftestdb-> ,ename
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> ,ename
perftestdb(> ,mgr
perftestdb(> FROM
perftestdb(> emp
perftestdb(> )
perftestdb-> WHERE
perftestdb-> mgr IS NULL
perftestdb-> ;
ERROR: subquery in FROM must have an alias
行 5: (
^
HINT: For example, FROM (SELECT ...) [AS] foo.

perftestdb=> SELECT
perftestdb-> empno
perftestdb-> ,ename
perftestdb-> FROM
perftestdb-> (
perftestdb(> SELECT
perftestdb(> empno
perftestdb(> ,ename
perftestdb(> ,mgr
perftestdb(> FROM
perftestdb(> emp
perftestdb(> ) iv_emp
perftestdb-> WHERE
perftestdb-> mgr IS NULL
perftestdb-> ;
empno | ename
-------+-------
7839 | KING
(1 行)

 

実際、この手の癖に遭遇したOracle Databaseなど表エイリアス不要の世界の方々は、「おまえは、なにをいっているんだぁ〜」という感じで一瞬固まったあとに、あ〜〜〜〜っ! 、なるとは思いますけどw

 

ところで、最近のdown pour半端ない感じがします。私の子供の頃でも台風以外でみたことのないような集中的な豪雨が頻発しています。用心しても来るものはきてしまうわけですが、正しい情報を入手して早めに避難するなりしましょうね。と。実家近くの川の水位が落ち着いたのをみて安堵する。。。

 

Enjoy SQL! and 癖。

 

 

 

 



関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!
帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある
帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)
帰ってきた! 標準はあるにはあるが癖の多いSQL #15 - 実行計画でスカラー副問合せの見せ方にも癖がでる

 

| | | コメント (0)

2024年7月10日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #15 - 実行計画でスカラー副問合せの見せ方にも癖がでる

さて、今日はまた、癖の話をしたいと思います!
今回のネタには標準があるわけではないですが、SELECTリストに記述するスカラー副問合せの実行計画上の見せ方の癖というか違いw

実は、このネタ、2020年ぐらいに、目黒方面(ご存知の方だけwww)にある某所で定期開催される内部勉強会的なLT大会で使ったネタだったのですが、そのあとゴタゴタしていて、ブログで書き漏らしていたことを、昨日ネタリストを纏めていた時に思い出した次いでに小ネタとして書いておきます。 (その時のKeynoteのタイトルページだけ載せておきますw)

20240710-141853


この癖を把握していれば、SELECTリストに記述されたスカラー副問合せチューニングするような案件に遭遇してしまったときでも何かの役に立つかもしれません。
(少なくとも実行計画を見ただけで、これはSELECTリストにスカラー副問合せがある! ということは一瞬で理解できるようになるはず。。。)

では早速見てみましょう。(Oracle Databaseではお馴染みの表とデータをMySQL/PostgreSQLでも事前に作成してあります)

SCOTT@orclpdb1> select * from dept;

DEPTNO DNAME LOC
---------- ------------------------------------------ ---------------------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON

SCOTT@orclpdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

実行計画で見えるSELECT中のスカラー副問合せの位置に注目してください。(赤字にしてあります)

Oracle Databaseでは本体のクエリーより上に表示されますが、PostgreSQL/MySQLでは逆で、下に表示されます。

このような見せ方の違いが逆になるのって以前もご紹介したの覚えているでしょうか?
そう、帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画の見せ方にも癖がでるで紹介した癖ですね。
HASH JOINのBUILD/PROBEは実行計画上、Oracle DatabaseとPostgreSQL/MySQLでは順序が逆に表現されていましたよね!

これに気づけば、あなたも、道にまようこともなく実行計画を追っていけるはず!! :)

Oracle Database (21c)
(このようにスカラー副問合せ部分が性能上ネックになりそうな場合、Oracle Databaseのオプティマイザは、スカラー副問合せを結合に書き換えて最適化することがあるため、この例ではそれを無効化するNO_UNNESTヒントを利用しています。)

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> !cat scalar_subquery_plan.sql
SELECT
deptno
,dname
,(
SELECT
/*+ NO_UNNEST */
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

SCOTT@orclpdb1> set autot trace exp stat
SCOTT@orclpdb1> @scalar_subquery_plan.sql

経過: 00:00:00.18

実行計画
----------------------------------------------------------
Plan hash value: 1445953226

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 65 | 9 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 4 | 28 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IX_DEPT | 4 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPT | 5 | 65 | 3 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_DEPT | 5 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("EMP"."DEPTNO"=:B1)


見ての通り、PostgreSQL/MySQLはスカラー副問合せ部分の実行計画の位置がOracle Databaseのそれとは異なることがわかると思います。:)
PostgreSQL(13.14)

perftestdb=> \! cat scalar_subquery_plan.sql
EXPLAIN ANALYZE
SELECT
deptno
,dname
,(
SELECT
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;
perftestdb=> \i scalar_subquery_plan.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Sort (cost=5.87..5.88 rows=4 width=46) (actual time=0.246..0.247 rows=4 loops=1)
Sort Key: dept.deptno
Sort Method: quicksort Memory: 25kB
-> Seq Scan on dept (cost=0.00..5.83 rows=4 width=46) (actual time=0.065..0.095 rows=4 loops=1)
SubPlan 1
-> Aggregate (cost=1.19..1.20 rows=1 width=32) (actual time=0.016..0.017 rows=1 loops=4)
-> Seq Scan on emp (cost=0.00..1.18 rows=5 width=5) (actual time=0.003..0.006 rows=4 loops=4)
Filter: (deptno = dept.deptno)
Rows Removed by Filter: 10
Planning Time: 1.916 ms
Execution Time: 0.843 ms
(11 行)


MySQL(8.0.36)

mysql> \! cat scalar_subquery_plan.sql
EXPLAIN FORMAT=tree
SELECT
deptno
,dname
,(
SELECT
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

mysql> \. scalar_subquery_plan.sql
+----------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------+
| -> Index scan on dept using PRIMARY (cost=0.65 rows=4)
-> Select #2 (subquery in projection; dependent)
-> Aggregate: max(emp.sal) (cost=1.28 rows=1)
-> Filter: (emp.deptno = dept.deptno) (cost=1.14 rows=1.4)
-> Table scan on emp (cost=1.14 rows=14)
|
+----------------------------------------------------------------------+
1 row in set, 1 warning (0.05 sec)
mysql> show warnings;
+-------+------+------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------+
| Note | 1276 | Field or reference 'perftestdb.dept.deptno' of SELECT #2 was resolved in SELECT #1 |
+-------+------+------------------------------------------------------------------------------------+
1 row in set (0.01 sec)


ただし、Oracle DatabaseだけはSELECTリストのスカラー副問合せをUNNESTして結合に書き換える最適化を行うこともあるので、実行計画だけだと元のSQL文に記述されているSELECTリスト中のスカラー副問合せに気付けないこともあります.
とはいえ、一般的には、そこに至るまでの間に、SQL文は抜き出せているでしょうから困ることはないでしょうね。(現場がリモートで、実行計画だけ送られてきた!なんてことでもなければw)

SELECTリスト中に記載したスカラー副問合せがUNNESTされてMERGE JOINに書き換えられた例(UNNESTヒント利用)
2013年、Oracle Database 12cR1で実装された最適化機能で、Scalar Subquery Unnesting Transformation (Oracle Database 12c R1 New Feature)でも説明していますので、詳しく知りたい方は参考にしてみてください。

SCOTT@orclpdb1> !cat scalar_subquery_unnest.sql
SELECT
deptno
,dname
,(
SELECT
/*+ UNNEST */
MAX(sal)
FROM
emp
WHERE
emp.deptno = dept.deptno
) AS max_sal
FROM
dept
ORDER BY
deptno
;

SCOTT@orclpdb1> @scalar_subquery_unnest.sql

経過: 00:00:00.17

実行計画
----------------------------------------------------------
Plan hash value: 2834279049

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 145 | 11 (19)| 00:00:01 |
| 1 | MERGE JOIN OUTER | | 5 | 145 | 11 (19)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 5 | 65 | 3 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_DEPT | 5 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 4 | 64 | 8 (25)| 00:00:01 |
| 5 | VIEW | VW_SSQ_1 | 4 | 64 | 7 (15)| 00:00:01 |
| 6 | HASH GROUP BY | | 4 | 28 | 7 (15)| 00:00:01 |
| 7 | TABLE ACCESS FULL | EMP | 14 | 98 | 6 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("ITEM_1"(+)="DEPT"."DEPTNO")
filter("ITEM_1"(+)="DEPT"."DEPTNO")


Enjoy SQL! and 癖

ではまた。






関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!
帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある
帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)

| | | コメント (0)

2024年7月 1日 (月)

帰ってきた! 標準はあるにはあるが癖の多いSQL #14 - コメントを書く位置にも癖がでる (SQL Clientにも癖がある)

Previously on Mac De Oracle
前回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖があるでした。
今回は、続編w とは言ってもMySQLが主役ではありませんし、DBMSのエンジンの癖でもなく、SQL Clentの癖のお話です。

 

前回利用したSQL文(SQLスクリプト化してありますが、スクリプト化せずコピペしても同じ)に以下のような位置に一行コメントを書いてみました。
MySQLの制限も考慮して、(--)の後には半角スペースを忘れずに!

 

MySQL (8.0.26) ふむふむ、という感じですよね。

[master@localhost ~]$ mysql -u scott -D perftestdb -p 
Enter password:

....略...

mysql> \! cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.
mysql>
mysql>
mysql> \. one_more_thing.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
+-------+-------+
1 row in set (0.01 sec)

mysql>

 

PostgreSQL (13.14) PostgreSQLもふむふむという感じ。

psql -d perftestdb -U discus -p 5432 -W -h localhost
パスワード:
psql (13.14)
"help"でヘルプを表示します。

....略...

perftestdb=> \! cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.
perftestdb=>
perftestdb=>
perftestdb=> \i one_more_thing.sql
empno | ename
-------+-------
7839 | KING
(1 行)

perftestdb=>

 

 

 

 

Oracle Database (23ai)

さて、お待ちかね、今回の癖の持ち主も実はOracle。といっても、SQL clientのSQL*Plusです。挙動を見てみましょう。
おやおや〜〜〜〜。結果が返らずスクリプトが終了しちゃいました。どういうこと?

[oracle@localhost ~]$ sqlplus -version

SQL*Plus: Release 23.0.0.0.0 - Production
Version 23.4.0.24.05

[oracle@localhost ~]$ sqlplus scott/tiger@localhost:1521/freepdb1

....略...

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.


SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

 

SQL文はしっかりSQL*Plusのバッファにあるに、どういうことでしょう。 r または / で再実行させると、、

え、え、エラ〜だぁ! 。。。。
いったい、おまえは、何をいっているんだぁ(ジョジョ風)

SCOTT@localhost:1521/freepdb1> l
1 SELECT
2 empno
3 ,ename
4 FROM
5 emp
6 WHERE
7* mgr IS NULL; -- It means president of the company.
SCOTT@localhost:1521/freepdb1> /
mgr IS NULL; -- It means president of the company.
*
ERROR at line 7:
ORA-03048: SQL reserved word ';' is not syntactically valid following '...FROM
emp
WHERE
mgr IS NULL'
Help: https://docs.oracle.com/error-help/db/ora-03048/

 

これ、一行コメントの位置がまずいんです。 SQL*PLusの癖せいです。(昔からの癖ですので。。。
(;)セミコロンの後に記述されている一行コメントを該当行の上において、セミコロンより前になるように修正してみましょう!

こんどは正しい結果が返ってきました! wwww SQL*Plusの癖もわかりにくいですね。癖の存在をしらないと。。。(マニュアルにも記載されているので参考にしてください)

SCOTT@localhost:1521/freepdb1> edit one_more_thing.sql

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
-- It means president of the company.
mgr IS NULL;

SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

EMPNO ENAME
---------- ----------
7839 KING

 

参考)

Oracle Database / Release 19 / ユーザーズ・ガイドおよびリファレンス / 5 SQL*Plusでのスクリプトの使用 / 文の終了記号(ピリオド、セミコロンまたはスラッシュ)の後に、コメントを挿入しないでください。

 

では、最後に、Oracle純正のSQL client、実は、最近はもう一つあるんです。 Oraclerならみなさんご存知の、SQLcl です。
こちら、SQL*Plusと異なり、前述した癖がありません。MySQL/PostgreSQL純正のSQL clientと同じ位置に一行コメントを書いても怒られることはありません。なかなか難しいですね。Oracle純正でも挙動が異なるのでご注意くださいね。。。(苦笑いw

この挙動の違い、私もついさっき気づいたんですけどね。wwwww このエントリー書きながらwwwww (本当ですw)

[oracle@localhost ~]$ sql -version

SQLcl: リリース24.1.0.0 Production ビルド: 24.1.0.087.0929

[oracle@localhost ~]$ sql scott/tiger@localhost:1521/freepdb1

....略...

SCOTT@localhost:1521/freepdb1> !cat one_more_thing.sql
SELECT
empno
,ename
FROM
emp
WHERE
mgr IS NULL; -- It means president of the company.

SCOTT@localhost:1521/freepdb1>
SCOTT@localhost:1521/freepdb1> @one_more_thing.sql

EMPNO ENAME
________ ________
7839 KING

 

SQL Client特有の癖もあるので本体以外でも癖には注意しましょうね。

Enjoy SQL! and 癖。

ではまた。

 



関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!
帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #13 - コメント書くにも癖がある

人間、なくて七癖。とか申しまして、わしゃ癖がない。といぅ人でも、七つぐらいは癖がある。と言ぅんですなぁ。
SQLも同じで、標準に対応しています。と言う割には、びっくりするぐらい癖があります。
(なぜか落語風w)


ということで、本日の癖、SQLにコメントを書くときは、どのタイプがお好きですかね? みなさん。
複数行コメントと単一行コメント併用していますが、知ってる人しかしらない(そりゃそうだ)w
コメントを書くにも、ちょっとした癖の持ち主もいたりします。

 

Oracle Database (23ai)

SCOTT@localhost:1521/freepdb1> !cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;

SCOTT@localhost:1521/freepdb1> @sqlcomment.sql

EMPNO ENAME
---------- ----------
7900 JAMES

 

 

PostgreSQL (13.14) Oracle Databaseと同じルールなのでそのまま使えます

perftestdb=> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;
perftestdb=> \i sqlcomment.sql
empno | ename
-------+-------
7900 | JAMES
(1 行)

 

MySQL (8.0.36) MySQLでは一行コメント(--)の後に半角スペースが必須となっているので注意なのね。

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename --foobar
FROM
emp
WHERE
empno = 7900;
mysql>
mysql> \. sqlcomment.sql
ERROR 1054 (42S22): Unknown column 'foobar' in 'field list'
mysql>

 

なかなか、いい癖もってますよね。MySQL

 

MySQLの場合、一行コメントとして、(#)もサポートされていて、こちらは、(#)の半角スペースは必須ではないと!!

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno
,ename #foobar
FROM
emp
WHERE
empno = 7900;
mysql> \. sqlcomment.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7900 | JAMES |
+-------+-------+
1 row in set (0.04 sec)

 

最後に、MySQLで有効なコメント記述を確認しておこう。(/* */)や(-- )という記述にしておけば、他のRDBMSで使う時にも楽そうではありますよね。(--)のスペースが必須なのは注意するとして。。

mysql> \! cat sqlcomment.sql
SELECT
/*
foo
bar
*/
empno -- Do not forget "white space" following "--"
,ename #foobar
FROM
emp
WHERE
empno = 7900;
mysql>
mysql>
mysql> \. sqlcomment.sql
+-------+-------+
| empno | ename |
+-------+-------+
| 7900 | JAMES |
+-------+-------+
1 row in set (0.01 sec)

 

おあとがよろしいようで。。。。

一行コメント(--)の後に空白を置くか置かないかってとこまで気を使う必要があるみたいなので、MySQLへ乗り換えたり、MySQLでも実行できるSQL文やスクリプトを用意する場合には注意したい部分ですすねー。ハマりそうなきもするけどw(一行コメントの部分もケアされているSQLコーディング標準がああって厳格に管理されていれば問題ないと思いますけども)

 

参考)

Oracle Database / Release 19 / SQL言語リファレンス / コメント
PostgreSQL 13.1文書 / 第4章 SQLの構文 / 4.1.5. コメント
MySQL 8.0 リファレンスマニュアル / 言語構造 / コメント


 

梅雨らしい雨の降る、ちがさき、、いや、東京よりw。
ではまた。

 



関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!

| | | コメント (0)

2024年6月29日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #12 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(後編)ー 列エイリアスの扱いにも癖がある!

Previously on Mac De Oracle
前回は、帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)でした、

引用符にも多少の方言が存在すること、NULLのソート方法にも同様に方言が存在することを確認しました。
今回はその後編です。

引用符で囲んだ識別子(列名、表名、列エイリアス、表エイリアス)を英語だと、Quoted Identifier と記載されシノニムはあまり見かけません。(SQL-1992などでは、delimited identifier と記されている程度ですかね)日本語のマニュアル等では翻訳影響だと思いますが、多少揺れていたりします。

Oraclerのみなさんだと、引用識別子 のほうが馴染み深い日本語訳だと思いますが、他のRDBMSの日本語マニュアルでは、引用符付き識別子 と記載されていたりします。とうのは前回にも書いてますが、Oracle Database/PostgreSQL/MySQLの間でも多少表現は違ったりします。

参考)
Database Oracle / Release 19 / SQL言語リファレンス / データベース・オブジェクト名および修飾子 / データベース・オブジェクトのネーミング規則
MySQL 8.0 リファレンスマニュアル / 言語構造 / スキーマオブジェクト名
PostgreSQL 13.1文書 / 第4章 SQLの構文 / 4.1. 字句の構造


という前置きはこれぐらいで。本日のお題。列エイリアスの扱いの癖!

今日のお題は、列エイリアスの扱いの癖 を見てみます(気づかないと意外にハマりますよーーw)

今回も前回同様、お馴染みの、emp表を、Oracle Database 23ai/PostgreSQL 13.14/MySQL 8.0.36 それぞれに作成してあります。(以下はOracle Database)

SCOTT@localhost:1521/freepdb1> desc emp
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

SCOTT@localhost:1521/freepdb1> select * from emp order by empno;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7788 SCOTT ANALYST 7566 87-04-19 3000 20
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7876 ADAMS CLERK 7788 87-05-23 1100 20
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

14 rows selected.


では、前回のおさらいから、MySQLだけ癖が強い結果となりましたが、問い合わせたい結果はどれも正しいですよね!(標準はあるにはあるが、癖の多いSQLらしい。素晴らしい結果ですよねw)

Oracle Database 23ai

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS "Boss's emp no."
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "Boss's emp no."
8 ORDER BY
9* "Boss's emp no." NULLS FIRST

Boss's emp no. HEAD_COUNTS
-------------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.

PostgreSQL 13.14

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST
;
perftestdb=> \i quoted_identification.sql
Boss's emp no. | head_counts
----------------+-------------
[null] | 1
7566 | 2
7698 | 5
7782 | 1
7788 | 1
7839 | 3
7902 | 1
(7 行)


MySQL 8.0.36

mysql> \! cat quoted_identification.sql
SELECT
mgr AS `Boss's emp no.`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`Boss's emp no.`
ORDER BY
`Boss's emp no.` IS NULL DESC
,`Boss's emp no.` ASC
;
mysql>
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.01 sec)


今日は、上記のクエリー列エイリアスにちょっと意地の悪い変更を行なって、その挙動の違いを見てみたいと思います。(おもしろいよ、それぞれの個性が出てて)
オリジナルでは、mgr AS "Boss's emp no." としていた列エイリアスですが、まずは、 mgr AS empno と、非識別引用子にして、かつ、emp表の列名である empno と同じ名称にしてあります。。。 AS 列エイリアス にはなっているので文法的には正しいです。。。よね。ちょっと嫌な予感はしますがw

(MySQLの場合、ORDER BY句の構文が異なります。以下、Oracle Database 23ai/PostgreSQL向け)

Before

SELECT
mgr AS Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST;


After

SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno NULLS FIRST;

では、早速、MySQLから実験してみましょう!

mysql> desc emp;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| empno | smallint | NO | PRI | NULL | |
| ename | varchar(10) | YES | | NULL | |
| job | varchar(10) | YES | | NULL | |
| mgr | smallint | YES | | NULL | |
| hiredate | date | YES | | NULL | |
| sal | decimal(7,2) | YES | | NULL | |
| comm | decimal(7,2) | YES | | NULL | |
| deptno | smallint | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
8 rows in set (0.05 sec)

mysql> \! cat quoted_identification2.sql
SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno IS NULL DESC
,empno ASC
;


おおおおおおおおおーーーーーーー。想定外の結果がががががー(いや、予想してましたよw)。

mysql> \. quoted_identification2.sql
+-------+-------------+
| empno | head_counts |
+-------+-------------+
| NULL | 1 |
| 7566 | 1 |
| 7566 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7902 | 1 |
+-------+-------------+
14 rows in set, 1 warning (0.02 sec)


ワーニングがでてますね。覗いてみると、どうやら、empno が曖昧だけど、実行しておいたから。。。。と。emp表のempnoと mgr列に対する列エイリアス、どちらか曖昧だけど、とりあえず、emp表のempno列の方でやっといたでーーー。ということみたいですね。まじか。。。 AS 列エイリアス とSQLで書いてるから列エイリアスとしてみてくれんの???(ちょっとわざとらしいセリフを入れてみましたw)

mysql> show warnings;
+---------+------+------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------+
| Warning | 1052 | Column 'empno' in group statement is ambiguous |
+---------+------+------------------------------------------------+
1 row in set (0.00 sec)


そうか、列エイリアスと表の列名が被ってて、曖昧だと。 では、 引用符を使って、これは、列エイリアスだーーーーーーとわかるように書けば良いのでは??  
ということで試してみる。

MySQLのデフォルトの引用符(`)バッククォートで囲った結果。。。だめだ。。。。引用識別子にしても、表の列側としてハンドリングされている。。。。。

mysql> \! cat quoted_identification2.sql
SELECT
mgr AS `empno`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`empno`
ORDER BY
`empno` IS NULL DESC
,`empno` ASC
;
mysql> \. quoted_identification2.sql
+-------+-------------+
| empno | head_counts |
+-------+-------------+
| NULL | 1 |
| 7566 | 1 |
| 7566 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7698 | 1 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7839 | 1 |
| 7902 | 1 |
+-------+-------------+
14 rows in set, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------+
| Warning | 1052 | Column 'empno' in group statement is ambiguous |
+---------+------+------------------------------------------------+
1 row in set (0.00 sec)

mysql>


気を取り直して、PostgreSQL ではどうなのでしょう?

perftestdb=> \d+ emp
テーブル"public.emp"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
----------+-----------------------+----------+---------------+------------+------------+----------+------
empno | numeric(4,0) | | not null | | main | |
ename | character varying(10) | | | | extended | |
job | character varying(9) | | | | extended | |
mgr | numeric(4,0) | | | | main | |
hiredate | date | | | | plain | |
sal | numeric(7,2) | | | | main | |
comm | numeric(7,2) | | | | main | |
deptno | numeric(2,0) | | | | main | |
インデックス:
"pk_emp" PRIMARY KEY, btree (empno)
"ix_deptno" btree (deptno)
外部キー制約:
"fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno)
アクセスメソッド: heap

perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS empno
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
empno
ORDER BY
empno NULLS FIRST
;

ギョギョ! サカナくんみたいな声がでますね!w MySQLと同じ挙動です!!!! なーーーーーーーんーーーーーーだってーーーーーーーっ。 MySQLとは異なりワーニングもなく、結果が想定結果を違うんだーー。というところから列エイリアスが列エイリアスと認識されていないことに気づいてあげないとならないですね。。。これだと。 難易度が上がったw
MySQL、意外と親切かも。ワーニングで教えてくれるなんて。。。

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \i quoted_identification.sql
empno | head_counts
--------+-------------
[null] | 1
7566 | 1
7566 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7782 | 1
7788 | 1
7839 | 1
7839 | 1
7839 | 1
7902 | 1
(14 行)

では、引用識別子に書き換えてみましょう。。。。。あ、まじで、PostgreSQLもMySQLと同じ挙動ですね。 列エイリアスだよーーーーーと明示しても、表の列としてのハンドリングを優先しています。。。むむむ。

perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification2.sql
SELECT
mgr AS "empno"
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"empno"
ORDER BY
"empno" NULLS FIRST
;
perftestdb=> \i quoted_identification2.sql
empno | head_counts
--------+-------------
[null] | 1
7566 | 1
7566 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7698 | 1
7782 | 1
7788 | 1
7839 | 1
7839 | 1
7839 | 1
7902 | 1
(14 行)

perftestdb=>


では、ここまでくると、わかりますよね。 前回のエントリーで、MySQL の立ち位置にいるのは、Oracle Database 23ai でーーーす。
なお、23aiからGROUP BYに列エイリアスが書けるようになってます。

結果を見てみましょうw

おおおおおーーーーーー。まじでーーーー。
MySQL/PostgreSQLと異なる挙動。列エイリアス(非引用識別子)と表の列名が被ってる影響で列エイリアスがエイリアスとして認識されていないためエラーとして扱っていますね。興味深い。いや、実はミスに気づきやすいのかも。。。

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> edit quoted_identification.sql

SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS empno
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 empno
8 ORDER BY
9* empno NULLS FIRST
mgr AS empno
*
ERROR at line 2:
ORA-00979: "MGR": must appear in the GROUP BY clause or be used in an aggregate function
Help: https://docs.oracle.com/error-help/db/ora-00979/


非識別引用子のままだと、表の列名である、empno と区別できない(AS empno と記載しても)ので、引用識別子にしてみます。PostgreSQL/Oracle Databaseの引用符は(")ダブルクォートですよね。

実行してみると。。。。。。あーーーーら不思議、MySQL/PostgreSQLとは異なり、引用識別子にしてあげることで、emp表のempno列とはことなる、列エイリアスと認識して、正しく処理しています。。。なんと。。。というか、この実装のほうが引用識別子の意味としては理解しやすくないですかね。。どうなんだろう。

SCOTT@localhost:1521/freepdb1> !cp quoted_identification.sql quoted_identification2.sql

SCOTT@localhost:1521/freepdb1> edit quoted_identification2.sql

SCOTT@localhost:1521/freepdb1> @quoted_identification2.sql
1 SELECT
2 mgr AS "empno"
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "empno"
8 ORDER BY
9* "empno" NULLS FIRST

empno HEAD_COUNTS
---------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.

Elapsed: 00:00:00.01
SCOTT@localhost:1521/freepdb1> set linesize 80
SCOTT@localhost:1521/freepdb1> desc emp
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

SCOTT@localhost:1521/freepdb1>


引用識別子/非引用識別子が既存の列名と同じ場合の評価の仕方は皆バラバラですねw。思いっきり実装依存の部分なので、列、表名に対するエイリアスを指定する場合には注意しましょう。
(個人的な感覚でしかないですがw

Oracle Databaseの実装のほうが直感的には理解しやすいきがします)

MySQLの場合、PostgreSQL同様に実行されますが、面白い特徴は、ワーニングという形で、"Warning 1052 | Column 'empno' in group statement is ambiguous" を示してくれることですね。MySQLでは warningのカウントに注意!!! 忘れないようにしたいですね。重要なコメントが書かれてたりしますw

この3者の中で、もっとも問題に気づきにくいのは、PostgreSQLでしょうか。。。
挙動がMySQL側に似ているので、MySQLのようにちょっと曖昧だけど、実行だけしておいたよ! 的なワーニングでも返してくれると、
MySQLのように気づきやすくなるかもしれませんね。> 各位


これだから、癖のあるSQLとの付き合いはやめられないwww


Enjoy SQL! そして、癖! もw

ではまた。





関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)
帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)

| | | コメント (0)

2024年6月22日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #11 - 引用符にも癖がでるし、NULLのソート構文にも癖がある!(前編)

Oracle ACE Program的に新年度に切り替わり。今期もOracle ACE Proに認定されました。:)

前置きはそれぐらいにして、今日の本題。

column expressionのaliasや、 table, view, subqueryなどのaliasを指定する際に利用することがある引用符、通常は (")ダブルクォートで囲むわけですが、そんな引用符にも癖があるというお話。
SQL-1992のドラフトではありますが以下のドキュメントを delimited identifier で検索すると見つけることができます。
( (Second Informal Review Draft) ISO/IEC 9075:1992, Database Language SQL- July 30, 1992 )

ついでに、世間ではいろいろ言われて肩身の狭い?想いをしているかもしれない NULL. そのNULLのソートが必要になってしまった場合にも、ソートの構文に癖がある!!

ほんと、みんな、癖多すぎますよね!(w
いい感じに差し支えない単語にすると、個性 があるというか... これだからSQLは楽しいって話もありますけども。人によるかなw


Oracle Database 23ai / PostgreSQL 13.14 / MySQL 8.0.36 のそれぞれで、どうなるか見てみましょう。

それぞれのデータベースに以下のような emp表を事前に作成しておきます。Oraclerにはお馴染みの表です:)

SCOTT@localhost:1521/freepdb1> select * from emp order by empno;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 80-12-17 800 20
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30
7521 WARD SALESMAN 7698 81-02-22 1250 500 30
7566 JONES MANAGER 7839 81-04-02 2975 20
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30
7698 BLAKE MANAGER 7839 81-05-01 2850 30
7782 CLARK MANAGER 7839 81-06-09 2450 10
7788 SCOTT ANALYST 7566 87-04-19 3000 20
7839 KING PRESIDENT 81-11-17 5000 10
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30
7876 ADAMS CLERK 7788 87-05-23 1100 20
7900 JAMES CLERK 7698 81-12-03 950 30
7902 FORD ANALYST 7566 81-12-03 3000 20
7934 MILLER CLERK 7782 82-01-23 1300 10

14 rows selected.

まず、Oracle Database 23ai
なぜ最新にしたかと言うと、GROUP BYで alias が利用可能になった最初のリリースだからなのですw (例で利用するクエリで利用する必要があるので)
GROUP BY列の別名または位置の指定が可能に! / 23ai〜 / SQL / FAQ

SCOTT@localhost:1521/freepdb1> select banner_full from v$version;

BANNER_FULL
-------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05


この例では、別名に空白などを含めているため引用符が必要になります。
Oracle Databaseの場合は昔から (") ダブルクォートですね。
一般的には引用符を必要としない記述にすることが多いのではないでしょうか。プログラムで扱うには面倒ですからね。(印字するだけの目的なら別ですが)
とはいえ、引用符の利用が必須のケースや、コーディング規約次第というところはあります。
SQL言語リファレンス/ データベース・オブジェクト名および修飾子/ データベース・オブジェクトのネーミング規則

NULLの位置が最初に来るようにソートするには、NULLS FIRSTですよね。みなさんもご存知のはず。

Oraclerのみなさんには GROUP BY でいきなりaliasを使う構文で目新しいですよね。すっきり書けるようになって嬉しい:)

SCOTT@localhost:1521/freepdb1> set null [null]
SCOTT@localhost:1521/freepdb1> @quoted_identification.sql
1 SELECT
2 mgr AS "Boss's emp no."
3 , COUNT(empno) AS head_counts
4 FROM
5 emp
6 GROUP BY
7 "Boss's emp no."
8 ORDER BY
9* "Boss's emp no." NULLS FIRST

Boss's emp no. HEAD_COUNTS
-------------- -----------
[null] 1
7566 2
7698 5
7782 1
7788 1
7839 3
7902 1

7 rows selected.


次は、PostgreSQL 
Oracle Database同様、引用符が必要な別名は、ダブルクォートを利用します。( PostgreSQL 16.0 / 4.1.1. 識別子とキーワード )

NULLS FIRSTでNULLをいい感じにソートする方法も同じですね。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

perftestdb=>
perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." NULLS FIRST;
;
perftestdb=> \i quoted_identification.sql
Boss's emp no. | head_counts
----------------+-------------
[null] | 1
7566 | 2
7698 | 5
7782 | 1
7788 | 1
7839 | 3
7902 | 1
(7 行)

さて、最後は、MySQLです。

気付いたかと思いますが、本日の癖の主役ですw

MySQLのデフォルトの引用符は、なんと、(`) バッククォートです。手癖で、ダブルクォートをタイプして、え!と一瞬固まる、Oraclerが。。> 俺だよw
( MySQL 8.0 リファレンスマニュアル / 言語構造 / スキーマオブジェクト名 )

また、NULLのソートも可能ですが、見たこともない構文でソートします。私まだよくわかってないですが、これで良いらしい。この癖に慣れる必要もありそう。。

+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS `Boss's emp no.`
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
`Boss's emp no.`
ORDER BY
`Boss's emp no.` IS NULL DESC
,`Boss's emp no.` ASC
;
mysql>
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.01 sec)

ところで、MySQLでも、(")ダブルクォートを引用符にすることができます。
( MySQL 8.0 リファレンスマニュアル / 5.1.11 サーバー SQL モード / ANSI_QUOTES 参照のこと )

sql_modeに ANSI_QUOTESを設定することで使えるようになります。。。。あ〜っ、スッキリ。NULLのソート構文以外はw

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> set session sql_mode = concat(@@sql_mode,',ANSI_QUOTES');
Query OK, 0 rows affected (0.01 sec)

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------------------+
| ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." IS NULL DESC
,"Boss's emp no." ASC
;
mysql> \. quoted_identification.sql
+----------------+-------------+
| Boss's emp no. | head_counts |
+----------------+-------------+
| NULL | 1 |
| 7566 | 2 |
| 7698 | 5 |
| 7782 | 1 |
| 7788 | 1 |
| 7839 | 3 |
| 7902 | 1 |
+----------------+-------------+
7 rows in set (0.00 sec)

mysql>


ANSI_QUOTESを無効にすると、GROUP BY で指定した alias 無効となり、デフォルトで有効化されているONLY_FULL_GROUP_BYのため、ERROR 1055 となります。なんとなく理解した!

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------------------+
| ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> set session sql_mode = replace(@@sql_mode,'ANSI_QUOTES,','');
Query OK, 0 rows affected (0.00 sec)

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat quoted_identification.sql
SELECT
mgr AS "Boss's emp no."
, COUNT(empno) AS head_counts
FROM
emp
GROUP BY
"Boss's emp no."
ORDER BY
"Boss's emp no." IS NULL DESC
,"Boss's emp no." ASC
;
mysql> \. quoted_identification.sql
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'perftestdb.emp.mgr' which is not functionally dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by


そういえば、
英語だと、大抵は、quoted identifier と書かれていますが、日本語表記だと 引用符付き識別子とか、引用識別子、 各社のドキュメントで微妙に違いがあったりして難しいなぁ。と思ったり。
quoted identifier ってカタカタにしたら長くてタイプするの面倒、結局、Oraclerなので、引用識別子/非引用識別子 で通りしゃったりしますけど。


ではでは。
次回へつづく。






関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)

| | | コメント (0)

2024年5月29日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)の おまけ - SQL*PlusのautotraceでSQL Analysis Reportが出力される! (23ai〜)

ちょっと意地悪してみました。

 

前回のエントリで、以下のようにindex only scanにできるBOOLEAN型の単列索引を作成したのを覚えていますか?

 

SCOTT@freepdb1> create index ix_bool_example2 on example2(b1);

Index created.

SCOTT@freepdb1> set autot trace exp stat
SCOTT@freepdb1> select count(1) from example2 where b1;

Execution Plan
----------------------------------------------------------
Plan hash value: 1159143718

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 1 | | |
|* 2 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
586 bytes sent via SQL*Net to client
108 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

 

この状態で、述語のBOOLEAN列(索引列)に暗黙型変換が発生する意地悪をしてみました。
BOOLEAN列を数値として計算した後に、falseと比較しているので、TO_NUMBER() の後に、TO_BOOLEAN()されています。当然、索引は使えなくなるので index only scan から table access fullに変わっています! (狙い通りですね。こんなことしないと思いますけどもw

 

で、みなさん、SQL*Plusのautoraceに見慣れない情報が出力されているのに気づきませんか!?

 

そう、Oracle Database 23ai free developerでは、SQL*PlusのautotraceでSQL Analysis reportが表示され、index range scan できるよう、述語の書き換えをご検討くさい! とレコメンドされています!(赤字部分)

 

このメッセージカスタマイズできたりしたらw 「チッ、少しは考えたSQL書けよな〜。」と上から目線のメッセージに変えてみたいw(無理でしょうけどもw) 

ということで、おまけでした!

 

SCOTT@freepdb1> select b1 from example2 where (b1 - 1) = false;

18 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1894430233

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EXAMPLE2 | 1 | 1 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_BOOLEAN(TO_NUMBER("B1")-1)=FALSE)

SQL Analysis Report (identified by operation id/Query Block Name/Object Alias):
-------------------------------------------------------------------------------

1 - SEL$1 / "EXAMPLE2"@"SEL$1"
- The following columns have predicates which preclude their
use as keys in index range scan. Consider rewriting the
predicates.
"B1"

Statistics
----------------------------------------------------------
9 recursive calls
6 db block gets
11 consistent gets
0 physical reads
1012 redo size
914 bytes sent via SQL*Net to client
133 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
18 rows processed

 

この例だと的確なレコメンドしてるんだけど、もっとムズイやつだとどうなるんだろうね。誰か本番で使って結果公開して欲しい :)

 

Enjoy SQL!

 

 



関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)

 

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #10、BOOLEAN型にも癖が出る(後編)

Previously on Mac De Oracle
前回は、BOOLEAN型に使える値の差異について、Oracle Database 23ai/PostgreSQL/MySQLで比較してみました。 なかなか癖がありますよね。それぞれw Oracle Database 23aiは、Db2やSnowflakeの仕様に類似していそうでしたよね。(未検証ですが)
ということで、5月もラストスパートwで、BOOLEANネタ後編で締めたいと思いますw

 

BOOLEAN/BOOL型どちらでもOKということなので、一応確認しておきます。

 

Oracle Database


SCOTT@freepdb1> create table sample3 (b1 boolean, b2 bool);

Table created.

SCOTT@freepdb1> desc sample3
Name Null? Type
----------------------------------------- -------- ----------------------------
B1 BOOLEAN
B2 BOOLEAN

SCOTT@freepdb1> drop table sample3 purge;Table dropped.

 

 

PostgreSQL


perftestdb=> create table sample3 (b1 boolean, b2 bool);
CREATE TABLE
perftestdb=> \d sample3
テーブル"public.sample3"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト
----+---------+----------+---------------+------------
b1 | boolean | | |
b2 | boolean | | |

perftestdb=> drop table sample3;
DROP TABLE
perftestdb=>

 

MySQL


mysql> create table sample3 (b1 boolean, b2 bool);
Query OK, 0 rows affected (0.27 sec)

mysql> desc sample3;
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| b1 | tinyint(1) | YES | | NULL | |
| b2 | tinyint(1) | YES | | NULL | |
+-------+------------+------+-----+---------+-------+
2 rows in set (0.04 sec)

mysql> drop table sample3;
Query OK, 0 rows affected (0.10 sec)

 

 

次は、データサイズ。
MySQL/PostgreSQLそれぞれマニュアルにて、1バイトと記載されていますが、Oracle Databaseでは発見できす(仕方ないので直接確認してみますw)
PostgreSQL - https://www.postgresql.jp/document/13/html/datatype-boolean.html MySQL BOOLEAN = TINYINT - https://dev.mysql.com/doc/refman/8.0/ja/other-vendor-data-types.html MySQL TINYINTのサイズ - https://dev.mysql.com/doc/refman/8.0/ja/integer-types.html

 

Oracleも1バイトのようですね!

 


SCOTT@freepdb1> set null [null]
SCOTT@freepdb1> col dump(b1) for a30
SCOTT@freepdb1> col vsize(b1) for 9990
SCOTT@freepdb1> select dump(b1),vsize(b1) from example2

DUMP(B1) VSIZE(B1)
------------------------------ ---------
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1
Typ=252 Len=1: 1 1

...中略...

Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
Typ=252 Len=1: 0 1
NULL [null]

33 rows selected.

 

 

BOOLEAN型を文字、数値へキャストするとどうなるでしょう?

 

Oracle Database BOOLEAN型の数値へのキャストは、1 (true) / 0 (false)、文字列へのキャストは、'TRUE' / 'FALSE' になるようですね。CHAR型へのキャストの場合、空白埋めされるようですね(マニュアルより)。

 


SCOTT@freepdb1> select id,b1,to_number(b1),memo from example2 order by id;

ID B1 TO_NUMBER(B1) MEMO
---------- ----------- ------------- ----------
1 TRUE 1 TRUE
2 TRUE 1 true
3 TRUE 1 True
4 TRUE 1 ON
5 TRUE 1 on
6 TRUE 1 On
7 TRUE 1 YES
8 TRUE 1 yes
9 TRUE 1 Yes
10 TRUE 1 T
11 TRUE 1 t
12 TRUE 1 Y
13 TRUE 1 y
14 TRUE 1 1
15 TRUE 1 0.01
16 TRUE 1 -0.01
17 TRUE 1 10
18 TRUE 1 -12
19 FALSE 0 FALSE
20 FALSE 0 false
21 FALSE 0 False
22 FALSE 0 OFF
23 FALSE 0 off
24 FALSE 0 Off
25 FALSE 0 NO
26 FALSE 0 no
27 FALSE 0 No
28 FALSE 0 F
29 FALSE 0 f
30 FALSE 0 N
31 FALSE 0 n
32 FALSE 0 0
33 [null] [null] null

33 rows selected.

SCOTT@freepdb1> select id,b1,to_char(b1),memo from example2 order by id;

ID B1 TO_CHA MEMO
---------- ----------- ------ ----------
1 TRUE TRUE TRUE
2 TRUE TRUE true
3 TRUE TRUE True
4 TRUE TRUE ON
5 TRUE TRUE on
6 TRUE TRUE On
7 TRUE TRUE YES
8 TRUE TRUE yes
9 TRUE TRUE Yes
10 TRUE TRUE T
11 TRUE TRUE t
12 TRUE TRUE Y
13 TRUE TRUE y
14 TRUE TRUE 1
15 TRUE TRUE 0.01
16 TRUE TRUE -0.01
17 TRUE TRUE 10
18 TRUE TRUE -12
19 FALSE FALSE FALSE
20 FALSE FALSE false
21 FALSE FALSE False
22 FALSE FALSE OFF
23 FALSE FALSE off
24 FALSE FALSE Off
25 FALSE FALSE NO
26 FALSE FALSE no
27 FALSE FALSE No
28 FALSE FALSE F
29 FALSE FALSE f
30 FALSE FALSE N
31 FALSE FALSE n
32 FALSE FALSE 0
33 [null] [null] null

33 rows selected.

 

PostgreSQL PostgreSQLは想定の範囲内ですね。'true'/'false', 1/0 になるようですね


perftestdb=> \pset null [null]
Null表示は"[null]"です。
perftestdb=>
perftestdb=> select id,b1,b1::varchar,memo from example2 order by id;
id | b1 | b1 | memo
----+--------+--------+-------
1 | t | true | TRUE
2 | t | true | true
3 | t | true | True
4 | t | true | ON
5 | t | true | on
6 | t | true | On
7 | t | true | YES
8 | t | true | yes
9 | t | true | Yes
10 | t | true | T
11 | t | true | t
12 | t | true | Y
13 | t | true | y
14 | t | true | 1
17 | t | true | 10
19 | f | false | FALSE
20 | f | false | false
21 | f | false | False
22 | f | false | OFF
23 | f | false | off
24 | f | false | Off
25 | f | false | NO
26 | f | false | no
27 | f | false | No
28 | f | false | F
29 | f | false | f
30 | f | false | N
31 | f | false | n
32 | f | false | 0
33 | [null] | [null] | null
(30 行)

perftestdb=> select id,b1,b1::integer,memo from example2 order by id;
id | b1 | b1 | memo
----+--------+--------+-------
1 | t | 1 | TRUE
2 | t | 1 | true
3 | t | 1 | True
4 | t | 1 | ON
5 | t | 1 | on
6 | t | 1 | On
7 | t | 1 | YES
8 | t | 1 | yes
9 | t | 1 | Yes
10 | t | 1 | T
11 | t | 1 | t
12 | t | 1 | Y
13 | t | 1 | y
14 | t | 1 | 1
17 | t | 1 | 10
19 | f | 0 | FALSE
20 | f | 0 | false
21 | f | 0 | False
22 | f | 0 | OFF
23 | f | 0 | off
24 | f | 0 | Off
25 | f | 0 | NO
26 | f | 0 | no
27 | f | 0 | No
28 | f | 0 | F
29 | f | 0 | f
30 | f | 0 | N
31 | f | 0 | n
32 | f | 0 | 0
33 | [null] | [null] | null
(30 行)

 

MySQL TINYINTのまま文字列に変換されちゃいますね。これはこれで癖が強いといえば強い

 


mysql> select id,b1,cast(b1 as char),memo from example2 order by id;
+----+------+------------------+-------+
| id | b1 | cast(b1 as char) | memo |
+----+------+------------------+-------+
| 1 | 1 | 1 | TRUE |
| 2 | 1 | 1 | true |
| 3 | 1 | 1 | True |
| 14 | 1 | 1 | 1 |
| 15 | 0 | 0 | 0.01 |
| 16 | 0 | 0 | -0.01 |
| 19 | 0 | 0 | FALSE |
| 20 | 0 | 0 | false |
| 21 | 0 | 0 | False |
| 32 | 0 | 0 | 0 |
| 33 | NULL | NULL | null |
+----+------+------------------+-------+

 

 

 

Boolean型は索引にも利用できるので、念の為、Boolean型の検索でリテラルとして使える記述がどう評価されるか確認しておきましょうね。
暗黙のキャストやらなんやらあるのかないのか。。。索引に含めた場合、キャストされて索引として利用できないないケースなど把握しておくと便利ですよー。:)

 

Oracle Database 23ai Oracleの場合、簡易な方法として、SQL*Plusのオートトレースを利用し、 Predicate Information(述語情報)を見て filterの際に、関数が適用され内部的にキャストされてしまうかどうかで判断していくことにします。
ついでに、boolean列に非ユニーク索引(単一列索引)を作成しておき、索引が利用されることも見ておきます。

 

全てのケースは実施していませんが、大文字小文字は無視される前提で、小文字で検証しています。
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0以外の数値は全て、TRUEと解釈される。e.g. -1,10,0.1,-1.5 など) TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の数値

 

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される) FALSE, 'OFF', 'NO', 'F', 'N', 0

 

以上が、OracleのBOOELAN型で利用できるリテラル値なので、

 

true, 'on', 'yes', 't', 'y', 1, 0以外の数値(10, 0.1, -2, -0.1)、全てにおいて、内部的にTRUEとしてハンドリングされていることがわかります。
(キャストされているわけではなく、TRUEとして評価されていることが、predicate information及び、index only scanになっているところでも判断できます)

 

ますます、 true/false, nullだけ使えばいいじゃんって感じがしますね。

 

以下、検証ログ


SCOTT@freepdb1> create index ix_bool_example2 on example2(b1);

Index created.

SCOTT@freepdb1> select b1 from example2 where b1 = true;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
7 recursive calls
4 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 't';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'y';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'yes';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 'on';

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 10;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = 0.1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

05:53:05 SCOTT@192.168.1.23:1521/freepdb1> select b1 from example2 where b1 = -2;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

SCOTT@freepdb1> select b1 from example2 where b1 = -0.1;

18 rows selected.

...中略...

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 16 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IX_BOOL_EXAMPLE2 | 16 | 16 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("B1"=TRUE)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets

...中略...

18 rows processed

 

 

PostgreSQL

 

TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、正の整数のみTRUEと解釈され、負の整数や少数はエラーとなる。 -1, 0.1, -1.5などはエラー) TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の正の整数のみ(ただし、booleanへのキャストが必要)

 

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される) FALSE, 'OFF', 'NO', 'F', 'N', 0

 

true, 'on', 'yes', 't', 'y', 1, 0以外の正の整数のみ(ただし、booleanへのキャスト必須)(10) 全てにおいて、内部的にTRUEとしてハンドリングされていることがわかります。
(キャストされているわけではなく、Index Cond:及び、、hintで矯正していますが、Index Scan uging..になっているところでも判断できます)

 

0以外の正の整数以外(0.1, -2, -0.1)は、エラーになっていることが確認できます。

 


perftestdb=> create index ix_bool_example2 on example2(b1);
CREATE INDEX
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = true;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.093..0.098 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 13.020 ms
Execution Time: 8.653 ms
(5 行)
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'on';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.038..0.046 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.259 ms
Execution Time: 0.142 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'yes';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.021..0.026 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.168 ms
Execution Time: 0.087 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 't';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.022..0.030 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.173 ms
Execution Time: 0.117 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 'y';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.017..0.021 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.119 ms
Execution Time: 0.066 ms
(5 行)

perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 1;
ERROR: operator does not exist: boolean = integer
行 1: ...example2 ix_bool_example2) */ b1 from example2 where b1 = 1;
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 1::boolean;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_bool_example2 on public.example2 (cost=0.14..8.40 rows=15 width=1) (actual time=0.010..0.015 rows=15 loops=1)
Output: b1
Index Cond: (example2.b1 = true)
Planning Time: 0.125 ms
Execution Time: 0.067 ms
(5 行)
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = 0.1::boolean;
ERROR: cannot cast type numeric to boolean
行 1: ..._bool_example2) */ b1 from example2 where b1 = 0.1::boolean;
^
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = -2::boolean;
ERROR: operator does not exist: - boolean
行 1: ... ix_bool_example2) */ b1 from example2 where b1 = -2::boolea...
^
HINT: No operator matches the given name and argument type. You might need to add an explicit type cast.
perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where b1 = -0.1::boolean;
ERROR: cannot cast type numeric to boolean
行 1: ...bool_example2) */ b1 from example2 where b1 = -0.1::boolean;


暗黙型変換ではないですが、暗黙変換同様に関数が利用された場合は、索引が利用できなくなることが確認できますよね。

 


perftestdb=> explain (analyze,verbose) select /*+ IndexScan(example2 ix_bool_example2) */ b1 from example2 where (b1::integer - 1)::boolean = false;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Seq Scan on public.example2 (cost=10000000000.00..10000000001.52 rows=20 width=1) (actual time=0.027..0.041 rows=15 loops=1)
Output: b1
Filter: (NOT (((example2.b1)::integer - 1))::boolean)
Rows Removed by Filter: 15
Planning Time: 0.171 ms
Execution Time: 2.737 ms
(6 行)

 

 

 

MySQL

 

TRUEとして解釈されるリテラル値
TRUE, 1のみ。(エラーにはならないが、0以外の数字は数字として整数として登録さえるので要注意)

 

FALSEとして解釈されるリテラル値
FALSE, 0のみ

 

なので、true,1 は 内部的に  true と扱われていることがわかります。
'yes'はやなりエラーですね。

 


mysql> create index ix_bool_example2 on example2(b1);
Query OK, 0 rows affected (1.28 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> explain analyze select b1 from example2 where b1 = true;
+---------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=true) (cost=0.651 rows=4) (actual time=0.04..0.0441 rows=4 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)

mysql> explain analyze select b1 from example2 where b1 = 1;
+--------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=1) (cost=0.651 rows=4) (actual time=0.0165..0.0203 rows=4 loops=1)
|
+--------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

 

文字列だとエラーになるかと思いきや。ワーニング扱いですね。文字列がトランケートされた結果、述語が b1 = 0 に変更されてしまっています。これはダメですね。


mysql> explain analyze select b1 from example2 where b1 = 'yes';
+--------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=0) (cost=0.851 rows=6) (actual time=0.0179..0.0222 rows=6 loops=1)
|
+--------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
mysql> show warnings;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'yes' |
+---------+------+-----------------------------------------+
1 row in set (0.01 sec)

 

整数の正の値、負の値。予想通りそのまま、数値として比較されています。エラーにならないのでこれは避けた方が良さそう


mysql> explain analyze select b1 from example2 where b1 = 100;
+---------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=100) (cost=0.35 rows=1) (actual time=0.0214..0.0214 rows=0 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> explain analyze select b1 from example2 where b1 = -100;
+------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------+
| -> Covering index lookup on example2 using ix_bool_example2 (b1=-(100)) (cost=0.35 rows=1) (actual time=0.0147..0.0147 rows=0 loops=1)
|
+------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

 

少数の場合もエラーにはなりません。述語は (Impossible WHERE) なことになってますね。これらも避けた方が良さそう。


mysql> explain analyze select b1 from example2 where b1 = 0.1;
+--------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------+
| -> Zero rows (Impossible WHERE) (cost=0..0 rows=0) (actual time=281e-6..281e-6 rows=0 loops=1)
|
+--------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> explain analyze select b1 from example2 where b1 = -0.1;
+--------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------+
| -> Zero rows (Impossible WHERE) (cost=0..0 rows=0) (actual time=176e-6..176e-6 rows=0 loops=1)
|
+--------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 

最後に、意地悪な検証を。

 

こんなことやらないと思いますけども、MySQL独特な挙動ですね。index only scanのまま、filterしてますね。TINYINTである影響なのでしょうね。面白いですが真似しないようにw。 この手の作り込んだら探すの大変そうだし。(データ積んでしっかり検証すると炙り出せるとは思うけどw)


mysql> explain analyze select b1 from example2 where (b1 - 1) = false;
+-------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: ((example2.b1 - 1) = false) (cost=1.35 rows=11) (actual time=1.78..1.8 rows=4 loops=1)
-> Covering index scan on example2 using ix_bool_example2 (cost=1.35 rows=11) (actual time=0.234..0.251 rows=11 loops=1)
|
+-------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)

 

 


BOOLEAN型の癖、総まとめ

 

BOOLEAN/BOOLどちらでも定義可能 (Oracle/PostgreSQL/MySQL)
(MySQLは内部的には、TINYINT型なのでそれ由来の癖がある)

 

データサイズは、1バイト (Oracle/PostgreSQL/MySQL)

 

利用可能なリテラル値(INSERT/UPDATEでセットする場合) TRUEとして扱われる値 TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 少数以下を切り捨てた整数として扱われるのでCHECK制約などで保護した方が安全なのではないだろうか。内部的にTINYINTである影響だろう)

 

 

FALSEとして扱われる値 FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

 

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 


利用可能なリテラル値(述語で利用する場合) TRUEとして扱われる値 TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'ON' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'YES' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'T' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い 。 sql_mode挙動が変わったりするのかは未確認)

 

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'Y' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 内部的にTINYINTである影響だろうか、エラーとはならず、数値として扱われたり、切り捨てられたりする。避けた方が良い。 e.g. WHERE boolean列 = 100や、WHERE boolean列 = 0.1 など。 sql_mode挙動が変わったりするのかは未確認)

 

FALSEとして扱われる値 FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

 

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'OFF' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'NO' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'F' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQL 利用すると値が切り捨てられ、WHERE boolean列 = 'N' が WHERE boolean列 = 0 に書き換えられてします。ワーニングなどで確認できるが、避けた方が良い。 sql_mode挙動が変わったりするのかは未確認)

 

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

 

全体的に、true/false (null) を使うように規約で制限したり、CHECK制約で保護しておいた方が混乱を避けられるのではないだろうか。。

 

 


癖ありすぎ! w w

 

では、また。

 



関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る

 

 

 

| | | コメント (0)

帰ってきた! 標準はあるにはあるが癖の多いSQL #9、BOOLEAN型にも癖が出る

PostgreSQL/MySQLには実装済みだった Boolean型がやっとOracle Database 23aiで追加されました。長ーい間実装されなかったデータ型なので、number型を使ったりして代替されていたわけですが、23ai以降は普通に使えるってことですね。
ただ、タイトルの通り、標準はあるものの、それぞれの実装には癖があります!!!!w


どのような癖があるのか、知っておきましょう。 (兼、自分用メモ)
Oracle/PostgreSQL/MySQLで違いを見てみます。

Oracle Database 23ai

SCOTT@freepdb1> select banner_full from v$version;

BANNER_FULL
--------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05

PostgreSQL
例によってw こちらの都合により、13.14を使っています。

perftestdb=> select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 13.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit
(1 行)

MySQL

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36 |
+-----------+
1 row in set (0.02 sec)



データ登録時、どの値がBOOLEANとして扱われるのかマニュアルから拾った値を元に確認しておきたいと思います。キャストが必要だったりする場合には、暗黙型変換とか気にしておいた方が良いですからね。索引にも使えるデータ型ですし。。。

Oracle Database 23ai
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0以外の数値は全て、TRUEと解釈される。e.g. -1,10,0.1,-1.5 など)
TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の数値

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される)
FALSE, 'OFF', 'NO', 'F', 'N', 0

この仕様、Db2SnowflakeのBOOLEAN型と同じように見えますね。(試してないですけどw)

マニュアルに記載されている通りの挙動。。。数値を指定した時の解釈もマニュアル通りなのですが、こんな使い方したくないですよねw 挙動確認なので試してはいますが。。。。

SCOTT@freepdb1> @boolean_literals.sql

Table dropped.
Table created.
1 row created.
1 row created.

...中略...

1 row created.
1 row created.
Commit complete.

ID B1 MEMO
---------- ----------- ----------
1 TRUE TRUE
2 TRUE true
3 TRUE True
4 TRUE ON
5 TRUE on
6 TRUE On
7 TRUE YES
8 TRUE yes
9 TRUE Yes
10 TRUE T
11 TRUE t
12 TRUE Y
13 TRUE y
14 TRUE 1
15 TRUE 0.01
16 TRUE -0.01
17 TRUE 10
18 TRUE -12
19 FALSE FALSE
20 FALSE false
21 FALSE False
22 FALSE OFF
23 FALSE off
24 FALSE Off
25 FALSE NO
26 FALSE no
27 FALSE No
28 FALSE F
29 FALSE f
30 FALSE N
31 FALSE n
32 FALSE 0
33 [null] null

33 rows selected.




PostgreSQL 13.14
TRUEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、正の整数のみTRUEと解釈され、負の整数や少数はエラーとなる。 -1, 0.1, -1.5などはエラー)
TRUE. 'ON', 'YES', 'T', 'Y', 1, 0以外の正の整数のみ(ただし、booleanへのキャストが必要)

FALSEとして解釈されるリテラル値 (文字列は大文字小文字は無視される。数値は、0のみFALSEと解釈される)
FALSE, 'OFF', 'NO', 'F', 'N', 0

Oracleに類似していますが、数値の扱いが微妙に違いますね。

perftestdb=> \i boolean_literals.sql
DROP TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1

...中略...

INSERT 0 1
INSERT 0 1
INSERT 0 1
psql:boolean_literals.sql:19: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(14, 1, '1');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
psql:boolean_literals.sql:21: ERROR: column "b1" is of type boolean but expression is of type numeric
行 1: insert into example2 values(15, 0.01, '0.01');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:22: ERROR: cannot cast type numeric to boolean
行 1: insert into example2 values(15, 0.01::boolean, '0.01');v ^
psql:boolean_literals.sql:23: ERROR: column "b1" is of type boolean but expression is of type numeric
行 1: insert into example2 values(16, -0.1, '-0.01');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:24: ERROR: cannot cast type numeric to boolean
行 1: insert into example2 values(16, -0.1::boolean, '-0.01');
^
psql:boolean_literals.sql:25: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(17, 10, '10');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
psql:boolean_literals.sql:27: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(18, -12, '-12');
^
HINT: You will need to rewrite or cast the expression.
psql:boolean_literals.sql:28: ERROR: operator does not exist: - boolean
行 1: insert into example2 values(18, -12::boolean, '-12');
^
HINT: No operator matches the given name and argument type. You might need to add an explicit type cast.
INSERT 0 1
INSERT 0 1

...中略...

INSERT 0 1
INSERT 0 1
psql:boolean_literals.sql:44: ERROR: column "b1" is of type boolean but expression is of type integer
行 1: insert into example2 values(32, 0, '0');
^
HINT: You will need to rewrite or cast the expression.
INSERT 0 1
INSERT 0 1
Null表示は"[null]"です。
id | b1 | memo
----+--------+-------
1 | t | TRUE
2 | t | true
3 | t | True
4 | t | ON
5 | t | on
6 | t | On
7 | t | YES
8 | t | yes
9 | t | Yes
10 | t | T
11 | t | t
12 | t | Y
13 | t | y
14 | t | 1
17 | t | 10
19 | f | FALSE
20 | f | false
21 | f | False
22 | f | OFF
23 | f | off
24 | f | Off
25 | f | NO
26 | f | no
27 | f | No
28 | f | F
29 | f | f
30 | f | N
31 | f | n
32 | f | 0
33 | [null] | null
(30 行)

perftestdb=>



MySQL 8.0.32
実データ型がTINYINTであるためですが、
数値の場合0,1以外ではエラーは出ないようで注意が必要ですよね。CHECK制約で保護するとかだろうか。。。

TRUEとして解釈されるリテラル値
TRUE, 1のみ。(エラーにはならないが、0以外の数字は数字として整数として登録さえるので要注意)

FALSEとして解釈されるリテラル値
FALSE, 0のみ

Oracle/PostgreSQLとも違い、最低限に絞ってるって感じですが、数値を指定した時の挙動には要注意ですね。内部の型はTINYINTなので。。。文字列を全て受け付けないのは、これもTINYINTである影響ですかね?

mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> \. boolean_literals.sql
Query OK, 0 rows affected (0.05 sec)

...中略...

Query OK, 1 row affected (0.01 sec)
ERROR 1366 (HY000): Incorrect integer value: 'ON' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'on' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'On' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'YES' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'yes' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Yes' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'T' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 't' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Y' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'y' for column 'b1' at row 1
Query OK, 1 row affected (0.01 sec)

...中略...

Query OK, 1 row affected (0.01 sec)
ERROR 1366 (HY000): Incorrect integer value: 'OFF' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'off' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'Off' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'NO' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'no' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'No' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'F' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'f' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'N' for column 'b1' at row 1
ERROR 1366 (HY000): Incorrect integer value: 'n' for column 'b1' at row 1
Query OK, 1 row affected (0.01 sec)
Query OK, 1 row affected (0.01 sec)

+----+------+-------+
| id | b1 | memo |
+----+------+-------+
| 1 | 1 | TRUE |
| 2 | 1 | true |
| 3 | 1 | True |
| 14 | 1 | 1 |
| 15 | 0 | 0.01 |
| 16 | 0 | -0.01 |
| 17 | 10 | 10 |
| 18 | -12 | -12 |
| 19 | 0 | FALSE |
| 20 | 0 | false |
| 21 | 0 | False |
| 32 | 0 | 0 |
| 33 | NULL | null |
+----+------+-------+
13 rows in set (0.00 sec)



長くなりそうなので、次回へ続く!w 

今回のまとめ

Oracle > PostgreSQL > MySQLのような感じで使える値の種類はサブセットになってる感じですね。 なので、true/false , nullだけ使ってればどこに行っても問題はなさそう。。ではあります。

Oracle Database 23ai/MySQL/PostgreSQL共通
BOOLEAN/BOOL型として定義可能。
(ただし、MySQLでは実態はTINYINTなので、TINYINT由来の癖があるので注意

TRUEとして扱われる値
TRUE 
(Oracle/PostgreSQL/MySQL, No case sensitive)

'ON'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'YES'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'T'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'Y'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

1
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

0以外の数値
(Oracle 少数及び負の整数も含む)
(PostgreSQL 正の整数のみ)
(MySQL 少数以下を切り捨てた整数として扱われるのでCHECK制約などで保護した方が安全なのではないだろうか。内部的にTINYINTである影響だろう)


FALSEとして扱われる値
FALSE
(Oracle/PostgreSQL/MySQL, No case sensitive)

'OFF'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'NO'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'F'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

'N'
(Oracle/PostgreSQL, No case sensitive)
(MySQLでは使えない)

0
(Oracle/MySQL)
(PostgreSQL booleanへのキャストが必要)

全体的に、true/false (null) を使うように規約等、場合によってはCHECK制約で保護しておいた方が、何かと混乱を避けられるのではないだろうか。。



補足)

この検証に利用したスクリプト
Oracle Database 23ai

SCOTT/freepdb1> !cat boolean_literals.sql
drop table if exists example2;
create table example2 (id number primary key, b1 boolean, memo varchar2(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(18, -12, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');

-- NULL
insert into example2 values(33, null, 'null');

commit;

-- check
set NULL [null]
select * from example2 order by id;


PostgreSQL 13.14

erftestdb=> \! cat boolean_literals.sql
drop table if exists example2;
create table example2 (id integer primary key, b1 boolean, memo varchar(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(14, 1::boolean, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(15, 0.01::boolean, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(16, -0.1::boolean, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(17, 10::boolean, '10');
insert into example2 values(18, -12, '-12');
insert into example2 values(18, -12::boolean, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');
insert into example2 values(32, 0::boolean, '0');

-- NULL
insert into example2 values(33, null, 'null');

-- check
\pset null [null]
select * from example2 order by id;


MySQL 8.0.32

mysql> \! cat boolean_literals.sql
drop table if exists example2;
create table example2 (id integer primary key, b1 boolean, memo varchar(10));

-- TRUE
insert into example2 values(1, TRUE, 'TRUE');
insert into example2 values(2, true, 'true');
insert into example2 values(3, True, 'True');
insert into example2 values(4, 'ON', 'ON');
insert into example2 values(5, 'on', 'on');
insert into example2 values(6, 'On', 'On');
insert into example2 values(7, 'YES', 'YES');
insert into example2 values(8, 'yes', 'yes');
insert into example2 values(9, 'Yes', 'Yes');
insert into example2 values(10, 'T', 'T');
insert into example2 values(11, 't', 't');
insert into example2 values(12, 'Y', 'Y');
insert into example2 values(13, 'y', 'y');
insert into example2 values(14, 1, '1');
insert into example2 values(15, 0.01, '0.01');
insert into example2 values(16, -0.1, '-0.01');
insert into example2 values(17, 10, '10');
insert into example2 values(18, -12, '-12');

-- FALSE
insert into example2 values(19, FALSE, 'FALSE');
insert into example2 values(20, false, 'false');
insert into example2 values(21, False, 'False');
insert into example2 values(22, 'OFF', 'OFF');
insert into example2 values(23, 'off', 'off');
insert into example2 values(24, 'Off', 'Off');
insert into example2 values(25, 'NO', 'NO');
insert into example2 values(26, 'no', 'no');
insert into example2 values(27, 'No', 'No');
insert into example2 values(28, 'F', 'F');
insert into example2 values(29, 'f', 'f');
insert into example2 values(30, 'N', 'N');
insert into example2 values(31, 'n', 'n');
insert into example2 values(32, 0, '0');

-- NULL
insert into example2 values(33, null, 'null');

-- check
select * from example2 order by id;


ここまで見た感じ、true/false, 必要があれば null を使う側のルールとしておくと、みんな幸せになれる気がするよね。BOOLEAN型って。


参考資料)
Oracle Database 23 / 開発者ガイド / 外部データ型
"https://docs.oracle.com/cd/F82042_01/lnoci/data-types.html#GUID-D69455D9-CE01-44CC-B5A9-E541C7774805

Oracle Database 23 / SQL言語リファレンス / ブールデータ型
https://docs.oracle.com/cd/F82042_01/sqlrf/Data-Types.html#GUID-285FFCA8-390D-4FA9-9A51-47B84EF5F83A

Oracle Database 23 / SQL言語リファレンス / Oracleの組込みデータ型
https://docs.oracle.com/cd/F82042_01/sqlrf/Data-Types.html#GUID-7B72E154-677A-4342-A1EA-C74C1EA928E6

PostgreSQL / データ型 / 論理値データ型
https://www.postgresql.jp/document/13/html/datatype-boolean.html

マニュアルによると、PostgreSQLのBOOLEAN型もサイズは1バイト
PostgreSQL 13 / 8.6. 論理値データ型
https://www.postgresql.jp/document/13/html/datatype-boolean.html

MySQL その他のデータベースエンジンのデータ型の使用  - BOOLEAN/BOOL
https://dev.mysql.com/doc/refman/8.0/ja/other-vendor-data-types.html

MySQL 数値型の記憶域要件 - TINYINT
https://dev.mysql.com/doc/refman/8.0/ja/storage-requirements.html#data-types-storage-reqs-numeric

MySQLのBOOLEANはTINYINT型で1バイトということですね。Boolean型のサイズはMySQL/Oracle/PostgreSQLどれも1バイト。
MySQL 8.0 / 11.1.6 Boolean Literals
https://dev.mysql.com/doc/refman/8.0/en/boolean-literals.html



長くなりそうなので、次回へ続くw

こういうことで、この手の SQLの癖w なかなか厳しいなw  Enjoy SQL!






関連エントリー
標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い
帰ってきた! 標準はあるにはあるが癖の多いSQL #6 - Hash Joinの結合ツリーにも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #7 - Hash Joinの実行計画にも癖がでる
帰ってきた! 標準はあるにはあるが癖の多いSQL #8 - Hash Joinさせるにも癖が出る

| | | コメント (0)

2024年5月19日 (日)

構文図で見る、SELECT文の構文拡張の歴史 w

2015年にイベント開催のコメントおまけで、Oracle DatabaseのSQLからSELECT文の構文拡張の歴史をOracle Database 7.3と12.1版のマニュアルに記載されている構文図の長さを使って可視化したことがあったのですが、覚えているでしょうか?w (多分、忘れてますよねw)

 

JPOUG> SET EVENTS 20151017 を開催します!

 

Oracle Database 23aiがリリースされてSQLシンタックスの拡張をマニュアルをつらつら読んでて思ったのですが、色々拡張されてますよね!
ということで、Oracle Database 7.3 / 8i 8.1.6 / 12cR1 12.1 / 23ai それぞれのSELECT文の拡張の歴史を構文図の長さを使って、今一度、可視化して残しておこうと思います。

 

みなさん、SQLの進化というか拡張に、追いつけていますよね。。。ね。。。。ね!? (大変ですけどもw)

 

各バージョンのSELECT文の構文図のソースは以下です。みなさんもマニュアルのページ数の増加や構文図の拡張に着目しつつ追ってみるのも楽しいかもしれません。  

 

Oracle7 Server SQL Reference Manual - SELECT
Oracle8i SQL Reference Release 2 (8.1.6) - SELECT and Subqueries
Database SQL Language Reference 12c 12.1 - SELECT
SQL Language Reference - Oracle Database 23ai - SELECT

 

 

 

Oracle Database 9i, 10g, 11gのダイアグラムは端折ってますが、これぐらい差分があったほうが、インパクトがあっていいかなぁ。と思いあえて載せていません :)

 

12cR1以降長すぎてこれぐらい小さくしないと収まりませんw 

 

オチも何もないですが、現場からは以上です! (なお、実際のリリース年は多少前後しているかもしれません)
Select78i12cr123ai

 

Enjoy SQL!

 

ではまた。

| | | コメント (0)

2024年5月16日 (木)

GROUP BY列の別名または位置の指定が可能に! / 23ai〜 / SQL / FAQ

23c 改め、23ai になった Oracle Database 23aiですが、SQLの使い勝手の改善がいくつか。
有名なのは、from dual を書かなくても良くなったこと。ですが、有名すぎるのであえて書きません!w

ということで、ちょっとマイナーだけど便利ですよね! という 「GROUP BY列の別名または位置の指定が可能に! / 23ai〜」 というお話。

参考 ー Oracle Databaseリリース23cの変更点
https://docs.oracle.com/cd/F82042_01/sqlrf/Changes-in-This-Release-for-Oracle-Database-SQL-Language-Reference.html

自分用アップデートメモでもありますw

まずこれまでのおさらいということで、Oracle Database 21c EEで挙動を確認しておきます。

SCOTT@orclpdb1> select banner_full from v$version;

BANNER_FULL
-----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0


SCOTT@orclpdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.

group by 別名指定。。見事にエラーになります!

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 year
8 ORDER BY
9* year
SCOTT@orclpdb1> /
year
*
行7でエラーが発生しました。:
ORA-00904: "YEAR": 無効な識別子です。


group by 位置指定。。これも間違いなくエラーです!。。。

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9* year
SCOTT@orclpdb1> /
TO_CHAR(hiredate,'YYYY') AS year
*
行2でエラーが発生しました。:
ORA-00979: GROUP BYの式ではありません。


ということで、これまではこんな面倒が書き方してたわけです。はい。。。

SCOTT@orclpdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 TO_CHAR(hiredate,'YYYY')
8 ORDER BY
9* year
SCOTT@orclpdb1> /

YEAR HIRED
------------ ----------
1980 1
1981 10
1982 1
1987 2


これまでは、こんな感じ。まあ面倒臭いですよね。


しかーーーーし、23ai以降では、そんな面倒は忘れてください。

SCOTT/freepdb1> select banner_full from v$version;

BANNER_FULL
-------------------------------------------------------------------------------
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.4.0.24.05


SCOTT/freepdb1> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.


group by 別名指定。おおおおおおーーぅ!  できた。

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 year
8 ORDER BY
9* year
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2


group by 位置指定....? 

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9 year
10*
SCOTT/freepdb1> /
TO_CHAR(hiredate,'YYYY') AS year
*
ERROR at line 2:
ORA-03162: "HIREDATE": must appear in the GROUP BY clause or be used in an aggregate function as 'group_by_position_enabled' is FALSE
Help: https://docs.oracle.com/error-help/db/ora-03162/


なんと、23ai free developerではデフォルトで無効化されてる!?。。。とは言っても、位置指定は、order by でも使わない場合が多いので、デフォルトオフでも影響はないですかね。一般的には。

SCOTT/freepdb1> show parameter group_by_position_enabled
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
group_by_position_enabled boolean FALSE

SCOTT/freepdb1> alter session set group_by_position_enabled = true;

Session altered.

SCOTT/freepdb1> show parameter group_by_position_enabled

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
group_by_position_enabled boolean TRUE

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 1
8 ORDER BY
9 year
10*
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2


strong>最後に、従来の面倒臭い構文の確認。

SCOTT/freepdb1> l
1 SELECT
2 TO_CHAR(hiredate,'YYYY') AS year
3 , COUNT(1) AS hired
4 FROM
5 emp
6 GROUP BY
7 TO_CHAR(hiredate,'YYYY')
8 ORDER BY
9* year
SCOTT/freepdb1> /

YEAR HIRED
---- ----------
1980 1
1981 10
1982 1
1987 2

Enjoy SQL!

では、また

| | | コメント (0)

2023年11月22日 (水)

帰ってきた! 標準はあるにはあるが癖の多いSQL #5 - Optimizer Hint でも癖が多い

癖の多いSQLシリーズネタは沢山あるわけですが、今回は、Oracle/PostgreSQL/MySQLで利用可能なオプティマイザヒントのざっくりしたまとめ。

というか、Advent Calendar向けの準備運動的ななエントリーその1. 意味の同じオプティマイザーヒントを使った何かをネタにしようとしてまーすw(その2は、どうするか検討中w)

各DBの Optimizer Hint (PostgreSQLは、pg_hint_plan拡張機能を利用)の数(特定の最適化の有効化、無効化ヒントはそれぞれ1ヒントとしてカウント。e.g. INDEX, NO_INDEXで2つとカウントする)

 

Oracle Database 21c : 388

(圧倒的に多いヒント。まあ、Oracle Database 8.1ぐらいからありますからねぇw 様々な最適化を制御できるようになっているということでもあるわけですけども。それにしても多いですね)

 

PostgreSQL with pg_hint_plan : 26

(pg_hint_planのドキュメントから拾って数得ました。これぐらいなんですね。)

 

MySQL 8.0.x : 37

(MySQL 8.0の Optimizer Hint には、HASH_JOIN/NO_HASH_JOINのように特定のリリースでしか使えないものなども含まれる)

 

MySQL系のAurora MySQL compatible, TiDBなどは、独自のヒントをいくつか追加しているものもある。e.g. HASH JOINのBUILD/PROBE表を制御するものなど。。ただし、Aurora MySQLの場合は、Vanila MySQLでHASH_JOINヒントが廃止された影響で、HASH_JOIN_BUILDINGなどの関連ヒントが廃止されるなど、各DB毎に多少温度差のある対応があり、Vanila以外のMySQLを利用する場合には利用可能な Optimizer Hint は個別に調査動作確認しておくことをお勧めする。

 

参考 Aurora MySQL hints(独自拡張あり)

https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Reference.Hints.html

 

TiDB(独自拡張あり)

https://docs.pingcap.com/ja/tidb/stable/optimizer-hints

 

YagabyteDB (pg_hint_planを使うようなので、Vanila PostgreSQLと同じ!)

https://docs.yugabyte.com/preview/explore/query-1-performance/pg-hint-plan/

 

 

Oracleのマニュアルでは、全量が解説されているわけではないので、v$sql_hintビューを問い合わせた結果も載せておきます。(ドキュメントのリンク貼っても、OracleさんのドキュメントURLって簡単にリンク切れになるのですが、取り敢えず貼っときますw)
YOracle Database 21c SQL Language Reference / Table 2-23 Hints by Functional Category

https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E__BABEAFGF

中には、黒魔術すぎた影響でw 指定しても無視(ヒントレポートでは、常に Error 扱い)されるヒントもあります( 参考: https://blogs.oracle.com/otnjp/post/tsushima-hakushi-11 )


(10gぐらいまでのSQLチューニング経験者なら一度ぐらいは聞いたことがあるかもしれねい、BYPASS_UJVC ヒントが有名ですね. ちなみに、このヒント単体で使うと単純に無視されヒントレーポートにも現れません。なお、v$sql_hintには残されていますw)


DISCUS@ORCLCDB> select * from v$sql_hint order by name;

NAME SQL_FEATURE CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OU
---------------------------------------- ------------------------------ ------------------------------ ------------------------------ ------------ ---------- ---------- ----------
ADAPTIVE_PLAN QKSFM_ADAPTIVE_PLAN ADAPTIVE_PLAN NO_ADAPTIVE_PLAN 1 16 12.1.0.2 12.1.0.2
ALL_ROWS QKSFM_ALL_ROWS MODE 1 16 8.1.0 10.2.0.1
ANALYTIC_VIEW_SQL QKSFM_COMPILATION ANALYTIC_VIEW_SQL 2 0 20.1.0
AND_EQUAL QKSFM_AND_EQUAL ACCESS 4 304 8.1.0 8.1.7
ANSI_REARCH QKSFM_ANSI_REARCH ANSI_REARCH NO_ANSI_REARCH 2 16 12.1.0.2 12.1.0.2
ANSWER_QUERY_USING_STATS QKSFM_ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS NO_ANSWER_QUERY_USING_STATS 2 16 18.1.0 18.1.0
ANTIJOIN QKSFM_TRANSFORMATION ANTIJOIN 2 16 9.0.0
APPEND QKSFM_CBO APPEND NOAPPEND 1 0 8.1.0
APPEND_VALUES QKSFM_CBO APPEND_VALUES NOAPPEND 1 0 11.2.0.1
AUTO_REOPTIMIZE QKSFM_AUTO_REOPT AUTO_REOPTIMIZE NO_AUTO_REOPTIMIZE 1 0 12.1.0.1
AV_CACHE QKSFM_EXECUTION AV_CACHE 2 0 18.1.0
BATCH_TABLE_ACCESS_BY_ROWID QKSFM_EXECUTION BATCH_TABLE_ACCESS_BY_ROWID NO_BATCH_TABLE_ACCESS_BY_ROWID 4 272 12.1.0.1 12.1.0.1
BIND_AWARE QKSFM_CURSOR_SHARING BIND_AWARE NO_BIND_AWARE 1 0 11.1.0.7
BITMAP QKSFM_CBO BITMAP 2 256 8.1.0 8.1.5
BITMAP_AND QKSFM_BITMAP_TREE BITMAP_AND 4 48 12.1.0.1 12.1.0.1
BITMAP_TREE QKSFM_BITMAP_TREE ACCESS 4 304 10.2.0.1 10.2.0.1
BUFFER QKSFM_CBO BUFFER NO_BUFFER 2 0 8.1.5
BUSHY_JOIN QKSFM_BUSHY_JOIN BUSHY_JOIN NO_BUSHY_JOIN 2 16 12.2.0.1 12.2.0.1
BYPASS_RECURSIVE_CHECK QKSFM_ALL BYPASS_RECURSIVE_CHECK 2 0 9.0.0
BYPASS_UJVC QKSFM_CBO BYPASS_UJVC 2 0 8.1.5
CACHE QKSFM_EXECUTION CACHE NOCACHE 4 256 8.1.0
CACHE_CB QKSFM_CBO CACHE_CB NOCACHE 4 256 8.1.5
CARDINALITY QKSFM_STATS CARDINALITY 14 272 9.0.0
CHANGE_DUPKEY_ERROR_INDEX QKSFM_DML CHANGE_DUPKEY_ERROR_INDEX 4 288 11.1.0.7
CHECK_ACL_REWRITE QKSFM_CHECK_ACL_REWRITE CHECK_ACL_REWRITE NO_CHECK_ACL_REWRITE 1 0 11.1.0.6
CHOOSE QKSFM_CHOOSE MODE 1 16 8.1.0
CLUSTER QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7
CLUSTERING QKSFM_CLUSTERING CLUSTERING NO_CLUSTERING 1 0 12.1.0.1 12.1.0.1
CLUSTER_BY_ROWID QKSFM_CBO CLUSTER_BY_ROWID NO_CLUSTER_BY_ROWID 4 272 12.1.0.1 12.1.0.1
COALESCE_SQ QKSFM_COALESCE_SQ COALESCE_SQ NO_COALESCE_SQ 2 16 11.2.0.1 11.2.0.1
COLUMN_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
CONNECT_BY_CB_WHR_ONLY QKSFM_TRANSFORMATION CONNECT_BY_CB_WHR_ONLY NO_CONNECT_BY_CB_WHR_ONLY 2 16 10.2.0.5 10.2.0.5
CONNECT_BY_COMBINE_SW QKSFM_ALL CONNECT_BY_COMBINE_SW NO_CONNECT_BY_COMBINE_SW 2 16 10.2.0.4 10.2.0.4
CONNECT_BY_COST_BASED QKSFM_TRANSFORMATION CONNECT_BY_COST_BASED NO_CONNECT_BY_COST_BASED 2 16 10.2.0.2 10.2.0.2
CONNECT_BY_ELIM_DUPS QKSFM_ALL CONNECT_BY_ELIM_DUPS NO_CONNECT_BY_ELIM_DUPS 2 16 11.2.0.1 11.2.0.1
CONNECT_BY_FILTERING QKSFM_ALL CONNECT_BY_FILTERING NO_CONNECT_BY_FILTERING 2 16 10.2.0.2 10.2.0.2
CONTAINERS QKSFM_ALL CONTAINERS 1 0 12.2.0.1 12.2.0.1
COST_XML_QUERY_REWRITE QKSFM_COST_XML_QUERY_REWRITE COST_XML_QUERY_REWRITE NO_COST_XML_QUERY_REWRITE 1 0 11.1.0.6 11.1.0.6
CPU_COSTING QKSFM_CPU_COSTING CPU_COSTING NO_CPU_COSTING 2 16 9.0.0
CUBE_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 12.1.0.1 12.1.0.1
CUBE_GB QKSFM_CBO CUBE_GB 2 0 8.1.5
CUBE_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 12.1.0.1 12.1.0.1
CURRENT_INSTANCE QKSFM_ALL CURRENT_INSTANCE 1 0 18.1.0
CURSOR_SHARING_EXACT QKSFM_CBO CURSOR_SHARING_EXACT 2 0 9.0.0
DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS NO_DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
DATA_SECURITY_REWRITE_LIMIT QKSFM_DATA_SECURITY_REWRITE DATA_SECURITY_REWRITE_LIMIT NO_DATA_SECURITY_REWRITE 1 0 12.1.0.1 12.1.0.1
DATA_VALIDATE QKSFM_EXECUTION DATA_VALIDATE 1 0 12.2.0.1
DBMS_STATS QKSFM_DBMS_STATS DBMS_STATS 1 0 10.2.0.1
DB_VERSION QKSFM_ALL DB_VERSION 1 272 11.1.0.6 11.1.0.6
DECORRELATE QKSFM_DECORRELATE DECORRELATE NO_DECORRELATE 2 16 12.1.0.1 12.1.0.1
DENORM_AV QKSFM_COMPILATION DENORM_AV 2 0 20.1.0
DEREF_NO_REWRITE QKSFM_ALL DEREF_NO_REWRITE 1 0 8.1.0
DISABLE_PARALLEL_DML QKSFM_DML ENABLE_PARALLEL_DML ENABLE_PARALLEL_DML 1 0 11.2.0.4
DIST_AGG_PROLLUP_PUSHDOWN QKSFM_PQ DIST_AGG_PROLLUP_PUSHDOWN NO_DIST_AGG_PROLLUP_PUSHDOWN 2 16 12.2.0.1 12.2.0.1
DML_UPDATE QKSFM_CBO DML_UPDATE 1 0 9.0.0
DOMAIN_INDEX_FILTER QKSFM_CBO DOMAIN_INDEX_FILTER NO_DOMAIN_INDEX_FILTER 4 304 11.1.0.6 11.1.0.6
DOMAIN_INDEX_NO_SORT QKSFM_CBO DOMAIN_INDEX_SORT DOMAIN_INDEX_SORT 2 0 8.1.5 10.2.0.1
DOMAIN_INDEX_SORT QKSFM_CBO DOMAIN_INDEX_SORT DOMAIN_INDEX_NO_SORT 2 0 8.1.5 10.2.0.1
DRIVING_SITE QKSFM_ALL DRIVING_SITE 4 256 8.1.0 8.1.7
DST_UPGRADE_INSERT_CONV QKSFM_ALL DST_UPGRADE_INSERT_CONV NO_DST_UPGRADE_INSERT_CONV 1 0 11.2.0.1
DYNAMIC_SAMPLING QKSFM_DYNAMIC_SAMPLING DYNAMIC_SAMPLING 6 272 9.2.0
DYNAMIC_SAMPLING_EST_CDN QKSFM_DYNAMIC_SAMPLING_EST_CDN DYNAMIC_SAMPLING_EST_CDN 4 272 9.2.0
ELIMINATE_JOIN QKSFM_TABLE_ELIM ELIMINATE_JOIN NO_ELIMINATE_JOIN 4 16 10.2.0.1 10.2.0.1
ELIMINATE_OBY QKSFM_OBYE ELIMINATE_OBY NO_ELIMINATE_OBY 2 16 10.2.0.1 10.2.0.1
ELIMINATE_SQ QKSFM_ELIMINATE_SQ ELIMINATE_SQ NO_ELIMINATE_SQ 2 16 12.2.0.1 12.2.0.1
ELIM_GROUPBY QKSFM_TRANSFORMATION ELIM_GROUPBY NO_ELIM_GROUPBY 2 16 12.1.0.2 12.1.0.2
ENABLE_PARALLEL_DML QKSFM_DML ENABLE_PARALLEL_DML DISABLE_PARALLEL_DML 1 0 11.2.0.4
EXPAND_GSET_TO_UNION QKSFM_TRANSFORMATION EXPAND_GSET_TO_UNION NO_EXPAND_GSET_TO_UNION 2 0 9.2.0 10.1.0
EXPAND_TABLE QKSFM_TABLE_EXPANSION EXPAND_TABLE NO_EXPAND_TABLE 4 16 11.2.0.1 11.2.0.1
EXPR_CORR_CHECK QKSFM_CBO EXPR_CORR_CHECK 1 0 8.0.0
FACT QKSFM_STAR_TRANS FACT NO_FACT 4 272 8.1.0 8.1.7
FACTORIZE_JOIN QKSFM_JOINFAC FACTORIZE_JOIN NO_FACTORIZE_JOIN 2 16 11.2.0.1 11.2.0.1
FBTSCAN QKSFM_CBO FBTSCAN 1 0 10.1.0.3
FIRST_ROWS QKSFM_FIRST_ROWS MODE 1 16 8.1.0 10.2.0.1
FORCE_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM NO_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
FORCE_XML_QUERY_REWRITE QKSFM_XML_REWRITE FORCE_XML_QUERY_REWRITE NO_XML_QUERY_REWRITE 1 0 9.2.0 11.1.0.6
FRESH_MV QKSFM_MVIEWS FRESH_MV 1 0 12.2.0.1
FULL QKSFM_FULL ACCESS 4 272 8.1.0 8.1.7
FULL_OUTER_JOIN_TO_OUTER QKSFM_CBO FULL_OUTER_JOIN_TO_OUTER NO_FULL_OUTER_JOIN_TO_OUTER 4 272 11.2.0.3 11.2.0.3
GATHER_OPTIMIZER_STATISTICS QKSFM_DBMS_STATS GATHER_OPTIMIZER_STATISTICS NO_GATHER_OPTIMIZER_STATISTICS 1 0 12.1.0.1
GATHER_PLAN_STATISTICS QKSFM_GATHER_PLAN_STATISTICS GATHER_PLAN_STATISTICS 1 0 10.1.0.3
GBY_CONC_ROLLUP QKSFM_TRANSFORMATION GBY_CONC_ROLLUP 2 0 9.0.0
GBY_PUSHDOWN QKSFM_ALL GBY_PUSHDOWN NO_GBY_PUSHDOWN 2 16 10.2.0.5 10.2.0.5
HASH QKSFM_ALL ACCESS 4 272 8.1.0 8.1.7
HASHSET_BUILD QKSFM_EXECUTION HASHSET_BUILD 2 16 21.1.0 21.1.0
HASH_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.1.0 8.1.7
HASH_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.1.0 8.1.7
HWM_BROKERED QKSFM_CBO HWM_BROKERED 2 0 9.0.0
IGNORE_OPTIM_EMBEDDED_HINTS QKSFM_ALL IGNORE_OPTIM_EMBEDDED_HINTS 1 0 10.1.0.3 10.2.0.1
IGNORE_ROW_ON_DUPKEY_INDEX QKSFM_DML IGNORE_ROW_ON_DUPKEY_INDEX 4 288 11.1.0.7
IGNORE_WHERE_CLAUSE QKSFM_ALL IGNORE_WHERE_CLAUSE 1 0 9.2.0
INCLUDE_VERSION QKSFM_ALL INCLUDE_VERSION 1 0 10.1.0.3
INDEX QKSFM_INDEX ACCESS NO_INDEX 4 304 8.0.0 8.1.7
INDEX_ASC QKSFM_INDEX_ASC ACCESS NO_INDEX 4 304 8.1.0
INDEX_COMBINE QKSFM_INDEX_COMBINE ACCESS 4 432 8.1.0 8.1.7
INDEX_DESC QKSFM_INDEX_DESC ACCESS NO_INDEX 4 304 8.1.0 8.1.7
INDEX_FFS QKSFM_INDEX_FFS ACCESS 4 304 8.1.0 8.1.7
INDEX_JOIN QKSFM_INDEX_JOIN ACCESS 4 304 8.1.5 10.1.0.3
INDEX_RRS QKSFM_CBO ACCESS 4 304 9.0.0
INDEX_RS_ASC QKSFM_INDEX_RS_ASC ACCESS 4 304 11.1.0.6 11.1.0.6
INDEX_RS_DESC QKSFM_INDEX_RS_DESC ACCESS 4 304 11.1.0.6 11.1.0.6
INDEX_SS QKSFM_INDEX_SS ACCESS NO_INDEX_SS 4 304 9.0.0 10.2.0.1
INDEX_SS_ASC QKSFM_INDEX_SS_ASC ACCESS NO_INDEX_SS 4 304 9.0.0
INDEX_SS_DESC QKSFM_INDEX_SS_DESC ACCESS NO_INDEX_SS 4 304 9.0.0 10.2.0.1
INDEX_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
INLINE QKSFM_TRANSFORMATION INLINE MATERIALIZE 2 16 9.0.0 18.1.0
INLINE_XMLTYPE_NT QKSFM_ALL INLINE_XMLTYPE_NT 1 0 10.2.0.1
INMEMORY QKSFM_EXECUTION INMEMORY NO_INMEMORY 6 64 12.1.0.2 12.1.0.2
INMEMORY_PRUNING QKSFM_EXECUTION INMEMORY_PRUNING NO_INMEMORY_PRUNING 6 64 12.1.0.2 12.1.0.2
JSON_LENGTH QKSFM_EXECUTION JSON_LENGTH 1 0 19.1.0
LEADING QKSFM_JOIN_ORDER LEADING 8 272 8.1.6 10.1.0.3
LOCAL_INDEXES QKSFM_CBO LOCAL_INDEXES 2 0 9.0.0
MATERIALIZE QKSFM_TRANSFORMATION INLINE INLINE 2 16 9.0.0 18.1.0
MEMOPTIMIZE_WRITE QKSFM_EXECUTION MEMOPTIMIZE_WRITE 1 0 18.1.0
MERGE QKSFM_CVM MERGE NO_MERGE 6 16 8.1.0 10.1.0
MERGE_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.1.0 8.1.7
MERGE_CONST_ON QKSFM_CBO MERGE_CONST_ON 1 0 8.0.0
MERGE_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.1.0 8.1.7
MODEL_COMPILE_SUBQUERY QKSFM_TRANSFORMATION MODEL_COMPILE_SUBQUERY 2 0 10.2.0.1
MODEL_DONTVERIFY_UNIQUENESS QKSFM_TRANSFORMATION MODEL_DONTVERIFY_UNIQUENESS 2 0 10.1.0.3
MODEL_DYNAMIC_SUBQUERY QKSFM_TRANSFORMATION MODEL_DYNAMIC_SUBQUERY 2 0 10.2.0.1
MODEL_MIN_ANALYSIS QKSFM_TRANSFORMATION MODEL_MIN_ANALYSIS 2 0 10.1.0.3
MODEL_NO_ANALYSIS QKSFM_ALL MODEL_MIN_ANALYSIS 2 0 10.1.0.3
MODEL_PUSH_REF QKSFM_TRANSFORMATION MODEL_PUSH_REF NO_MODEL_PUSH_REF 2 0 10.1.0.3
MONITOR QKSFM_ALL MONITOR NO_MONITOR 1 0 11.1.0.6
MV_MERGE QKSFM_TRANSFORMATION MV_MERGE 2 0 9.0.0
NATIVE_FULL_OUTER_JOIN QKSFM_ALL NATIVE_FULL_OUTER_JOIN NO_NATIVE_FULL_OUTER_JOIN 2 16 10.2.0.3 10.2.0.3
NESTED_TABLE_FAST_INSERT QKSFM_ALL NESTED_TABLE_FAST_INSERT 1 0 10.1.0.3
NESTED_TABLE_GET_REFS QKSFM_ALL NESTED_TABLE_GET_REFS 1 0 8.1.0
NESTED_TABLE_SET_SETID QKSFM_ALL NESTED_TABLE_SET_SETID 1 0 8.1.5
NLJ_BATCHING QKSFM_EXECUTION ACCESS NO_NLJ_BATCHING 4 272 11.1.0.6 11.1.0.6
NLJ_PREFETCH QKSFM_EXECUTION NLJ_PREFETCH NO_NLJ_PREFETCH 4 272 11.1.0.6 11.1.0.6
NL_AJ QKSFM_JOIN_METHOD ANTIJOIN 2 16 8.0.0
NL_SJ QKSFM_JOIN_METHOD SEMIJOIN 2 16 8.0.0
NOAPPEND QKSFM_CBO APPEND APPEND 1 0 8.1.0
NOCACHE QKSFM_EXECUTION CACHE CACHE 4 256 8.1.0
NOPARALLEL QKSFM_PARALLEL SHARED SHARED 5 256 8.1.0
NO_ACCESS QKSFM_ALL NO_ACCESS 4 256 8.1.5 8.1.7
NO_ADAPTIVE_PLAN QKSFM_ADAPTIVE_PLAN ADAPTIVE_PLAN ADAPTIVE_PLAN 1 16 12.1.0.2 12.1.0.2
NO_ANSI_REARCH QKSFM_ANSI_REARCH ANSI_REARCH ANSI_REARCH 2 16 12.1.0.2 12.1.0.2
NO_ANSWER_QUERY_USING_STATS QKSFM_ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS ANSWER_QUERY_USING_STATS 2 16 18.1.0 18.1.0
NO_AUTO_REOPTIMIZE QKSFM_AUTO_REOPT AUTO_REOPTIMIZE AUTO_REOPTIMIZE 1 0 12.1.0.1
NO_BASETABLE_MULTIMV_REWRITE QKSFM_ALL REWRITE REWRITE 2 16 10.1.0.3 10.1.0.3
NO_BATCH_TABLE_ACCESS_BY_ROWID QKSFM_EXECUTION BATCH_TABLE_ACCESS_BY_ROWID BATCH_TABLE_ACCESS_BY_ROWID 4 272 12.1.0.1 12.1.0.1
NO_BIND_AWARE QKSFM_CURSOR_SHARING BIND_AWARE BIND_AWARE 1 0 11.1.0.7
NO_BUFFER QKSFM_CBO BUFFER BUFFER 2 0 8.1.5
NO_BUSHY_JOIN QKSFM_BUSHY_JOIN BUSHY_JOIN BUSHY_JOIN 2 16 12.2.0.1 12.2.0.1
NO_CARTESIAN QKSFM_ALL NO_CARTESIAN 4 336 10.2.0.1
NO_CHECK_ACL_REWRITE QKSFM_CHECK_ACL_REWRITE NO_CHECK_ACL_REWRITE CHECK_ACL_REWRITE 1 0 11.1.0.6
NO_CLUSTERING QKSFM_CLUSTERING CLUSTERING CLUSTERING 1 0 12.1.0.1 12.1.0.1
NO_CLUSTER_BY_ROWID QKSFM_CBO CLUSTER_BY_ROWID CLUSTER_BY_ROWID 4 272 12.1.0.1 12.1.0.1
NO_COALESCE_SQ QKSFM_COALESCE_SQ COALESCE_SQ COALESCE_SQ 2 16 11.2.0.1 11.2.0.1
NO_CONNECT_BY_CB_WHR_ONLY QKSFM_TRANSFORMATION CONNECT_BY_CB_WHR_ONLY CONNECT_BY_CB_WHR_ONLY 2 16 10.2.0.5 10.2.0.5
NO_CONNECT_BY_COMBINE_SW QKSFM_ALL CONNECT_BY_COMBINE_SW CONNECT_BY_COMBINE_SW 2 16 10.2.0.4 10.2.0.4
NO_CONNECT_BY_COST_BASED QKSFM_TRANSFORMATION CONNECT_BY_COST_BASED CONNECT_BY_COST_BASED 2 16 10.2.0.2 10.2.0.2
NO_CONNECT_BY_ELIM_DUPS QKSFM_ALL CONNECT_BY_ELIM_DUPS CONNECT_BY_ELIM_DUPS 2 16 11.2.0.1 11.2.0.1
NO_CONNECT_BY_FILTERING QKSFM_ALL CONNECT_BY_FILTERING CONNECT_BY_FILTERING 2 16 10.2.0.2 10.2.0.2
NO_COST_XML_QUERY_REWRITE QKSFM_COST_XML_QUERY_REWRITE NO_COST_XML_QUERY_REWRITE COST_XML_QUERY_REWRITE 1 0 11.1.0.6 11.1.0.6
NO_CPU_COSTING QKSFM_CPU_COSTING CPU_COSTING CPU_COSTING 2 16 9.0.0
NO_DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
NO_DATA_SECURITY_REWRITE QKSFM_DATA_SECURITY_REWRITE DATA_SECURITY_REWRITE_LIMIT DATA_SECURITY_REWRITE_LIMIT 1 0 12.1.0.1 12.1.0.1
NO_DECORRELATE QKSFM_DECORRELATE DECORRELATE DECORRELATE 2 16 12.1.0.1 12.1.0.1
NO_DIST_AGG_PROLLUP_PUSHDOWN QKSFM_PQ DIST_AGG_PROLLUP_PUSHDOWN DIST_AGG_PROLLUP_PUSHDOWN 2 16 12.2.0.1 12.2.0.1
NO_DOMAIN_INDEX_FILTER QKSFM_CBO NO_DOMAIN_INDEX_FILTER DOMAIN_INDEX_FILTER 4 304 11.1.0.6 11.1.0.6
NO_DST_UPGRADE_INSERT_CONV QKSFM_ALL DST_UPGRADE_INSERT_CONV DST_UPGRADE_INSERT_CONV 1 0 11.2.0.1 21.1.0.1
NO_ELIMINATE_JOIN QKSFM_TABLE_ELIM ELIMINATE_JOIN ELIMINATE_JOIN 4 16 10.2.0.1 10.2.0.1
NO_ELIMINATE_OBY QKSFM_OBYE ELIMINATE_OBY ELIMINATE_OBY 2 16 10.2.0.1 10.2.0.1
NO_ELIMINATE_SQ QKSFM_ELIMINATE_SQ ELIMINATE_SQ ELIMINATE_SQ 2 16 12.2.0.1 12.2.0.1
NO_ELIM_GROUPBY QKSFM_TRANSFORMATION ELIM_GROUPBY ELIM_GROUPBY 2 16 12.1.0.2 12.1.0.2
NO_EXPAND QKSFM_USE_CONCAT OR_EXPAND USE_CONCAT 2 16 8.1.0 8.1.7
NO_EXPAND_GSET_TO_UNION QKSFM_TRANSFORMATION EXPAND_GSET_TO_UNION EXPAND_GSET_TO_UNION 2 0 9.2.0 10.1.0
NO_EXPAND_TABLE QKSFM_TABLE_EXPANSION EXPAND_TABLE EXPAND_TABLE 4 16 11.2.0.1 11.2.0.1
NO_FACT QKSFM_STAR_TRANS FACT FACT 4 272 8.1.0 8.1.7
NO_FACTORIZE_JOIN QKSFM_JOINFAC FACTORIZE_JOIN FACTORIZE_JOIN 2 16 11.2.0.1 11.2.0.1
NO_FULL_OUTER_JOIN_TO_OUTER QKSFM_CBO FULL_OUTER_JOIN_TO_OUTER FULL_OUTER_JOIN_TO_OUTER 4 272 11.2.0.3 11.2.0.3
NO_GATHER_OPTIMIZER_STATISTICS QKSFM_DBMS_STATS GATHER_OPTIMIZER_STATISTICS GATHER_OPTIMIZER_STATISTICS 1 0 12.1.0.1
NO_GBY_PUSHDOWN QKSFM_ALL GBY_PUSHDOWN GBY_PUSHDOWN 2 16 10.2.0.5 10.2.0.5
NO_INDEX QKSFM_INDEX NO_INDEX INDEX 4 304 8.1.5 8.1.7
NO_INDEX_FFS QKSFM_INDEX_FFS NO_INDEX_FFS INDEX_FFS 4 304 10.1.0.3 10.1.0.3
NO_INDEX_SS QKSFM_INDEX_SS NO_INDEX_SS INDEX_SS 4 304 10.1.0.3 10.1.0.3
NO_INMEMORY QKSFM_EXECUTION INMEMORY INMEMORY 6 64 12.1.0.2 12.1.0.2
NO_INMEMORY_PRUNING QKSFM_EXECUTION INMEMORY_PRUNING INMEMORY_PRUNING 6 64 12.1.0.2 12.1.0.2
NO_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM FORCE_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
NO_LOAD QKSFM_EXECUTION NO_LOAD 1 0 11.1.0.6
NO_MERGE QKSFM_CVM MERGE MERGE 6 16 8.0.0 10.1.0
NO_MODEL_PUSH_REF QKSFM_ALL MODEL_PUSH_REF MODEL_PUSH_REF 2 0 10.1.0.3
NO_MONITOR QKSFM_ALL MONITOR MONITOR 1 0 11.1.0.6
NO_MONITORING QKSFM_ALL NO_MONITORING 1 0 8.0.0
NO_MULTIMV_REWRITE QKSFM_ALL REWRITE REWRITE 2 16 10.1.0.3 10.1.0.3
NO_NATIVE_FULL_OUTER_JOIN QKSFM_ALL NATIVE_FULL_OUTER_JOIN NATIVE_FULL_OUTER_JOIN 2 16 10.2.0.3 10.2.0.3
NO_NLJ_BATCHING QKSFM_EXECUTION ACCESS NLJ_BATCHING 4 272 11.1.0.6 11.1.0.6
NO_NLJ_PREFETCH QKSFM_EXECUTION NLJ_PREFETCH NLJ_PREFETCH 4 272 11.1.0.6 11.1.0.6
NO_OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
NO_ORDER_ROLLUPS QKSFM_TRANSFORMATION NO_ORDER_ROLLUPS 2 0 8.0.0
NO_OR_EXPAND QKSFM_CBQT_OR_EXPANSION OR_EXPAND OR_EXPAND 2 16 12.2.0.1 12.2.0.1
NO_OUTER_JOIN_TO_ANTI QKSFM_CBO OUTER_JOIN_TO_ANTI OUTER_JOIN_TO_ANTI 4 272 11.2.0.3 11.2.0.3
NO_OUTER_JOIN_TO_INNER QKSFM_OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER 6 16 11.1.0.6 11.1.0.6
NO_PARALLEL QKSFM_CBO SHARED SHARED 5 256 10.1.0.3
NO_PARALLEL_INDEX QKSFM_PQ PARALLEL_INDEX PARALLEL_INDEX 4 288 8.1.0
NO_PARTIAL_COMMIT QKSFM_CBO NO_PARTIAL_COMMIT 1 0 10.1.0.3
NO_PARTIAL_JOIN QKSFM_PARTIAL_JOIN PARTIAL_JOIN PARTIAL_JOIN 4 272 12.1.0.1 12.1.0.1
NO_PARTIAL_ROLLUP_PUSHDOWN QKSFM_PQ PARTIAL_ROLLUP_PUSHDOWN PARTIAL_ROLLUP_PUSHDOWN 2 16 12.1.0.1 12.1.0.1
NO_PLACE_DISTINCT QKSFM_DIST_PLCMT PLACE_DISTINCT PLACE_DISTINCT 2 16 11.2.0.1 11.2.0.1
NO_PLACE_GROUP_BY QKSFM_PLACE_GROUP_BY PLACE_GROUP_BY PLACE_GROUP_BY 2 16 11.1.0.6 11.1.0.6
NO_PQ_CONCURRENT_UNION QKSFM_PQ PQ_CONCURRENT_UNION PQ_CONCURRENT_UNION 3 0 12.1.0.1 12.1.0.1
NO_PQ_EXPAND_TABLE QKSFM_TABLE_EXPANSION PQ_EXPAND_TABLE PQ_EXPAND_TABLE 4 16 19.1.0 19.1.0
NO_PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0
NO_PQ_REPLICATE QKSFM_PQ_REPLICATE PQ_REPLICATE PQ_REPLICATE 4 272 12.1.0.1 12.1.0.1
NO_PQ_SKEW QKSFM_PQ PQ_SKEW PQ_SKEW 4 272 12.1.0.1 12.1.0.1
NO_PRUNE_GSETS QKSFM_TRANSFORMATION NO_PRUNE_GSETS 2 0 9.0.0
NO_PULL_PRED QKSFM_PULL_PRED PULL_PRED PULL_PRED 4 16 10.2.0.1 10.2.0.1
NO_PUSH_HAVING_TO_GBY QKSFM_EXECUTION PUSH_HAVING_TO_GBY PUSH_HAVING_TO_GBY 2 0 18.1.0 18.1.0
NO_PUSH_PRED QKSFM_FILTER_PUSH_PRED PUSH_PRED PUSH_PRED 6 16 8.1.0 8.1.5
NO_PUSH_SUBQ QKSFM_TRANSFORMATION PUSH_SUBQ PUSH_SUBQ 2 16 9.2.0 10.2.0.5
NO_PX_FAULT_TOLERANCE QKSFM_PQ PX_FAULT_TOLERANCE PX_FAULT_TOLERANCE 1 0 12.1.0.1 12.1.0.1
NO_PX_JOIN_FILTER QKSFM_PX_JOIN_FILTER PX_JOIN_FILTER PX_JOIN_FILTER 4 336 10.2.0.1 11.1.0.6
NO_QKN_BUFF QKSFM_CBO NO_QKN_BUFF 2 0 9.2.0
NO_QUERY_TRANSFORMATION QKSFM_TRANSFORMATION NO_QUERY_TRANSFORMATION 1 16 10.1.0.3
NO_REF_CASCADE QKSFM_CBO REF_CASCADE_CURSOR REF_CASCADE_CURSOR 1 0 9.2.0
NO_REORDER_WIF QKSFM_PARTITION REORDER_WIF REORDER_WIF 2 0 18.1.0 18.1.0
NO_RESULT_CACHE QKSFM_EXECUTION RESULT_CACHE RESULT_CACHE 2 0 11.1.0.6
NO_REWRITE QKSFM_TRANSFORMATION REWRITE REWRITE 2 16 8.1.5 8.1.7
NO_SEMIJOIN QKSFM_TRANSFORMATION SEMIJOIN SEMIJOIN 2 16 9.0.0
NO_SEMI_TO_INNER QKSFM_CBO NO_SEMI_TO_INNER SEMI_TO_INNER 4 272 11.2.0.3 11.2.0.3
NO_SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
NO_SET_TO_JOIN QKSFM_SET_TO_JOIN SET_TO_JOIN SET_TO_JOIN 2 16 10.1.0.3 10.1.0.3
NO_SQL_TUNE QKSFM_ALL NO_SQL_TUNE 1 0 10.2.0.1
NO_STAR_TRANSFORMATION QKSFM_STAR_TRANS STAR_TRANSFORMATION STAR_TRANSFORMATION 6 16 10.1.0.3 10.1.0.3
NO_STATEMENT_QUEUING QKSFM_PARALLEL STATEMENT_QUEUING STATEMENT_QUEUING 1 0 11.2.0.1
NO_STATS_GSETS QKSFM_ALL NO_STATS_GSETS 2 0 8.0.0
NO_SUBQUERY_PRUNING QKSFM_CBO SUBQUERY_PRUNING SUBQUERY_PRUNING 4 272 11.1.0.6 11.1.0.6
NO_SUBSTRB_PAD QKSFM_EXECUTION NO_SUBSTRB_PAD 1 0 11.2.0.1
NO_SWAP_JOIN_INPUTS QKSFM_CBO SWAP_JOIN_INPUTS SWAP_JOIN_INPUTS 4 272 10.1.0.3 10.1.0.3
NO_TABLE_LOOKUP_BY_NL QKSFM_TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL 4 16 11.2.0.2 11.2.0.2
NO_TRANSFORM_DISTINCT_AGG QKSFM_TRANSFORMATION TRANSFORM_DISTINCT_AGG TRANSFORM_DISTINCT_AGG 2 0 11.2.0.1 11.2.0.1
NO_UNNEST QKSFM_UNNEST UNNEST UNNEST 2 16 8.1.6 10.1.0
NO_USE_CUBE QKSFM_USE_CUBE JOIN USE_CUBE 4 336 12.1.0.1 12.1.0.1
NO_USE_DAGG_UNION_ALL_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS USE_DAGG_UNION_ALL_GSETS 2 0 12.2.0.1 12.2.0.1
NO_USE_HASH QKSFM_USE_HASH NO_USE_HASH USE_HASH 4 336 10.1.0.3 10.1.0.3
NO_USE_HASH_AGGREGATION QKSFM_ALL USE_HASH_AGGREGATION USE_HASH_AGGREGATION 2 0 10.2.0.1 10.2.0.5
NO_USE_HASH_GBY_FOR_DAGGPSHD QKSFM_ALL USE_HASH_GBY_FOR_DAGGPSHD USE_HASH_GBY_FOR_DAGGPSHD 2 0 12.2.0.1 12.2.0.1
NO_USE_HASH_GBY_FOR_PUSHDOWN QKSFM_ALL USE_HASH_GBY_FOR_PUSHDOWN USE_HASH_GBY_FOR_PUSHDOWN 2 0 11.2.0.2 11.2.0.2
NO_USE_INVISIBLE_INDEXES QKSFM_INDEX USE_INVISIBLE_INDEXES USE_INVISIBLE_INDEXES 1 0 11.1.0.6 11.1.0.6
NO_USE_MERGE QKSFM_USE_MERGE NO_USE_MERGE USE_MERGE 4 336 10.1.0.3 10.1.0.3
NO_USE_NL QKSFM_USE_NL NO_USE_NL USE_NL 4 336 10.1.0.3 10.1.0.3
NO_USE_PARTITION_WISE_DISTINCT QKSFM_PARTITION USE_PARTITION_WISE_DISTINCT USE_PARTITION_WISE_DISTINCT 2 0 12.2.0.1 12.2.0.1
NO_USE_PARTITION_WISE_GBY QKSFM_PARTITION USE_PARTITION_WISE_GBY USE_PARTITION_WISE_GBY 2 0 12.2.0.1 12.2.0.1
NO_USE_PARTITION_WISE_WIF QKSFM_PARTITION USE_PARTITION_WISE_WIF USE_PARTITION_WISE_WIF 2 0 18.1.0 18.1.0
NO_USE_SCALABLE_GBY_INVDIST QKSFM_PQ USE_SCALABLE_GBY_INVDIST USE_SCALABLE_GBY_INVDIST 2 0 19.1.0 19.1.0
NO_USE_VECTOR_AGGREGATION QKSFM_VECTOR_AGG USE_VECTOR_AGGREGATION USE_VECTOR_AGGREGATION 2 16 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM QKSFM_VECTOR_AGG VECTOR_TRANSFORM VECTOR_TRANSFORM 2 16 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM_DIMS QKSFM_VECTOR_AGG VECTOR_TRANSFORM_DIMS VECTOR_TRANSFORM_DIMS 4 80 12.1.0.2 12.1.0.2
NO_VECTOR_TRANSFORM_FACT QKSFM_VECTOR_AGG VECTOR_TRANSFORM_FACT VECTOR_TRANSFORM_FACT 4 80 12.1.0.2 12.1.0.2
NO_XDB_FASTPATH_INSERT QKSFM_ALL XDB_FASTPATH_INSERT XDB_FASTPATH_INSERT 1 0 11.2.0.2
NO_XMLINDEX_REWRITE QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE XMLINDEX_REWRITE 1 0 11.1.0.6 11.1.0.6
NO_XMLINDEX_REWRITE_IN_SELECT QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE XMLINDEX_REWRITE_IN_SELECT 1 0 11.1.0.6 11.1.0.6
NO_XML_DML_REWRITE QKSFM_XML_REWRITE NO_XML_DML_REWRITE 1 0 10.2.0.1 11.1.0.6
NO_XML_QUERY_REWRITE QKSFM_XML_REWRITE FORCE_XML_QUERY_REWRITE FORCE_XML_QUERY_REWRITE 1 0 9.2.0 11.1.0.6
NO_ZONEMAP QKSFM_ZONEMAP ZONEMAP ZONEMAP 4 256 12.1.0.1 12.1.0.1
NUM_INDEX_KEYS QKSFM_CBO ACCESS 4 304 10.2.0.3 10.2.0.3
OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE NO_OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
OLD_PUSH_PRED QKSFM_OLD_PUSH_PRED OLD_PUSH_PRED 6 16 10.2.0.1 10.2.0.1
OPAQUE_TRANSFORM QKSFM_TRANSFORMATION OPAQUE_TRANSFORM 1 0 10.1.0.3
OPAQUE_XCANONICAL QKSFM_TRANSFORMATION OPAQUE_XCANONICAL 1 0 10.1.0.3
OPTIMIZER_FEATURES_ENABLE QKSFM_ALL OPTIMIZER_FEATURES_ENABLE 1 272 10.1.0.3 10.2.0.1
OPT_ESTIMATE QKSFM_OPT_ESTIMATE OPT_ESTIMATE 14 272 10.1.0.3
OPT_PARAM QKSFM_ALL OPT_PARAM 1 272 10.2.0.1 10.2.0.1
ORDERED QKSFM_CBO ORDERED 2 16 8.1.0 8.1.7
ORDERED_PREDICATES QKSFM_CBO ORDERED_PREDICATES 2 16 8.0.0
ORDER_KEY_VECTOR_USE QKSFM_VECTOR_AGG ORDER_KEY_VECTOR_USE 2 272 21.1.0 21.1.0
ORDER_SUBQ QKSFM_TRANSFORMATION ORDER_SUBQ 2 16 12.2.0.1 12.2.0.1
OR_EXPAND QKSFM_CBQT_OR_EXPANSION OR_EXPAND NO_OR_EXPAND 2 16 12.2.0.1 12.2.0.1
OSON_GET_CONTENT QKSFM_JSON OSON_GET_CONTENT 1 0 21.1.0 21.1.0
OUTER_JOIN_TO_ANTI QKSFM_CBO OUTER_JOIN_TO_ANTI NO_OUTER_JOIN_TO_ANTI 4 272 11.2.0.3 11.2.0.3
OUTER_JOIN_TO_INNER QKSFM_OUTER_JOIN_TO_INNER OUTER_JOIN_TO_INNER NO_OUTER_JOIN_TO_INNER 6 16 11.1.0.6 11.1.0.6
OUTLINE QKSFM_ALL OUTLINE 2 0 10.2.0.1 10.2.0.1
OUTLINE_LEAF QKSFM_ALL OUTLINE_LEAF 2 0 10.2.0.1 10.2.0.1
OVERFLOW_NOMOVE QKSFM_CBO OVERFLOW_NOMOVE 2 0 9.0.0
PARALLEL_INDEX QKSFM_PQ PARALLEL_INDEX NO_PARALLEL_INDEX 4 288 8.1.0
PARTIAL_JOIN QKSFM_PARTIAL_JOIN PARTIAL_JOIN NO_PARTIAL_JOIN 4 272 12.1.0.1 12.1.0.1
PARTIAL_ROLLUP_PUSHDOWN QKSFM_PQ PARTIAL_ROLLUP_PUSHDOWN NO_PARTIAL_ROLLUP_PUSHDOWN 2 16 12.1.0.1 12.1.0.1
PDB_LOCAL_ONLY QKSFM_DML PDB_LOCAL_ONLY 1 0 18.1.0
PIV_GB QKSFM_ALL PIV_GB 2 0 8.1.0
PIV_SSF QKSFM_ALL PIV_SSF 2 0 8.1.0
PLACE_DISTINCT QKSFM_DIST_PLCMT PLACE_DISTINCT NO_PLACE_DISTINCT 2 16 11.2.0.1 11.2.0.1
PLACE_GROUP_BY QKSFM_PLACE_GROUP_BY PLACE_GROUP_BY NO_PLACE_GROUP_BY 2 16 11.1.0.6 11.1.0.6
PQ_CONCURRENT_UNION QKSFM_PQ PQ_CONCURRENT_UNION NO_PQ_CONCURRENT_UNION 3 0 12.1.0.1 12.1.0.1
PQ_DISTRIBUTE QKSFM_PQ_DISTRIBUTE PQ_DISTRIBUTE 4 272 8.1.5 8.1.7
PQ_DISTRIBUTE_WINDOW QKSFM_PQ PQ_DISTRIBUTE_WINDOW 2 16 12.1.0.1 12.1.0.1
PQ_EXPAND_TABLE QKSFM_TABLE_EXPANSION PQ_EXPAND_TABLE NO_PQ_EXPAND_TABLE 4 16 19.1.0 19.1.0
PQ_FILTER QKSFM_PQ PQ_FILTER 2 0 12.1.0.1 12.1.0.1
PQ_MAP QKSFM_PQ_MAP PQ_MAP PQ_NOMAP 4 272 9.0.0 10.2.0.1
PQ_NOMAP QKSFM_PQ_MAP PQ_MAP PQ_MAP 4 272 9.0.0 10.2.0.1
PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW NO_PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0
PQ_REPLICATE QKSFM_PQ_REPLICATE PQ_REPLICATE NO_PQ_REPLICATE 4 272 12.1.0.1 12.1.0.1
PQ_SKEW QKSFM_PQ PQ_SKEW NO_PQ_SKEW 4 272 12.1.0.1 12.1.0.1
PRECOMPUTE_SUBQUERY QKSFM_TRANSFORMATION PRECOMPUTE_SUBQUERY 2 0 10.2.0.1
PRESERVE_OID QKSFM_ALL PRESERVE_OID 1 0 10.2.0.1
PULL_PRED QKSFM_PULL_PRED PULL_PRED NO_PULL_PRED 4 16 10.2.0.1 10.2.0.1
PUSH_HAVING_TO_GBY QKSFM_EXECUTION PUSH_HAVING_TO_GBY NO_PUSH_HAVING_TO_GBY 2 0 18.1.0 18.1.0
PUSH_PRED QKSFM_FILTER_PUSH_PRED PUSH_PRED NO_PUSH_PRED 6 16 8.1.0 8.1.5
PUSH_SUBQ QKSFM_TRANSFORMATION PUSH_SUBQ NO_PUSH_SUBQ 2 16 8.1.0 10.2.0.5
PX_FAULT_TOLERANCE QKSFM_PQ PX_FAULT_TOLERANCE NO_PX_FAULT_TOLERANCE 1 0 12.1.0.1 12.1.0.1
PX_JOIN_FILTER QKSFM_PX_JOIN_FILTER PX_JOIN_FILTER NO_PX_JOIN_FILTER 4 336 10.2.0.1 11.1.0.6
QB_NAME QKSFM_ALL QB_NAME 2 256 10.1.0.3
QUARANTINE QKSFM_EXECUTION QUARANTINE 1 0 19.1.0
QUEUE_CURR QKSFM_CBO ACCESS 4 256 8.0.0
QUEUE_ROWP QKSFM_CBO ACCESS 4 256 8.0.0
RBO_OUTLINE QKSFM_RBO RBO_OUTLINE 1 0 10.2.0.1 10.2.0.1
REF_CASCADE_CURSOR QKSFM_CBO REF_CASCADE_CURSOR NO_REF_CASCADE 1 0 9.2.0
REMOTE_MAPPED QKSFM_ALL REMOTE_MAPPED 2 272 8.1.0
REORDER_WIF QKSFM_PARTITION REORDER_WIF NO_REORDER_WIF 2 0 18.1.0 18.1.0
RESERVOIR_SAMPLING QKSFM_EXECUTION RESERVOIR_SAMPLING 1 0 12.1.0.2
RESTORE_AS_INTERVALS QKSFM_CBO RESTORE_AS_INTERVALS 2 0 8.1.5
RESTRICT_ALL_REF_CONS QKSFM_ALL RESTRICT_ALL_REF_CONS 1 0 10.1.0.3
RESULT_CACHE QKSFM_EXECUTION RESULT_CACHE NO_RESULT_CACHE 2 0 11.1.0.6
RETRY_ON_ROW_CHANGE QKSFM_DML RETRY_ON_ROW_CHANGE 1 0 11.1.0.7
REWRITE QKSFM_TRANSFORMATION REWRITE NO_REWRITE 2 16 8.1.5 8.1.7
REWRITE_OR_ERROR QKSFM_TRANSFORMATION REWRITE 2 0 10.1.0.3
ROWID QKSFM_CBO ACCESS 4 272 8.0.0 8.1.7
RULE QKSFM_RBO MODE 1 16 8.1.0 8.1.5
SAVE_AS_INTERVALS QKSFM_CBO SAVE_AS_INTERVALS 2 0 8.1.5
SCN_ASCENDING QKSFM_ALL SCN_ASCENDING 1 0 8.1.5
SEMIJOIN QKSFM_TRANSFORMATION SEMIJOIN NO_SEMIJOIN 2 16 9.0.0
SEMIJOIN_DRIVER QKSFM_CBO SEMIJOIN_DRIVER 2 16 8.1.0 8.1.7
SEMI_TO_INNER QKSFM_CBO SEMI_TO_INNER NO_SEMI_TO_INNER 4 272 11.2.0.3 11.2.0.3
SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN NO_SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
SET_TO_JOIN QKSFM_SET_TO_JOIN SET_TO_JOIN NO_SET_TO_JOIN 2 16 10.1.0.3 10.1.0.3
SHARED QKSFM_PARALLEL SHARED NO_PARALLEL 5 256 8.1.0
SKIP_EXT_OPTIMIZER QKSFM_CBO SKIP_EXT_OPTIMIZER 2 16 9.0.0
SKIP_PROXY QKSFM_ALL SKIP_PROXY 1 0 18.1.0
SKIP_UNQ_UNUSABLE_IDX QKSFM_CBO SKIP_UNQ_UNUSABLE_IDX 1 0 10.1.0.3
SQLLDR QKSFM_CBO SQLLDR 1 0 9.0.0
SQL_SCOPE QKSFM_COMPILATION SQL_SCOPE 1 0 12.2.0.1
STAR QKSFM_STAR_TRANS STAR 2 16 8.1.0
STAR_TRANSFORMATION QKSFM_STAR_TRANS STAR_TRANSFORMATION NO_STAR_TRANSFORMATION 6 16 8.1.0 8.1.7
STATEMENT_QUEUING QKSFM_PARALLEL STATEMENT_QUEUING NO_STATEMENT_QUEUING 1 0 11.2.0.1
STREAMS QKSFM_CBO STREAMS 1 0 10.1.0.3
SUBQUERY_PRUNING QKSFM_CBO SUBQUERY_PRUNING NO_SUBQUERY_PRUNING 4 272 11.1.0.6 11.1.0.6
SUPPRESS_LOAD QKSFM_DDL SUPPRESS_LOAD 1 0 18.1.0
SWAP_JOIN_INPUTS QKSFM_CBO SWAP_JOIN_INPUTS NO_SWAP_JOIN_INPUTS 4 272 8.1.0 8.1.7
SYSTEM_STATS QKSFM_ALL SYSTEM_STATS 1 272 18.1.0
SYS_DL_CURSOR QKSFM_CBO SYS_DL_CURSOR 1 0 9.2.0
SYS_PARALLEL_TXN QKSFM_CBO SYS_PARALLEL_TXN 2 0 8.1.6
SYS_RID_ORDER QKSFM_ALL SYS_RID_ORDER 2 0 9.2.0
TABLE_LOOKUP_BY_NL QKSFM_TABLE_LOOKUP_BY_NL TABLE_LOOKUP_BY_NL NO_TABLE_LOOKUP_BY_NL 4 16 11.2.0.2 11.2.0.2
TABLE_STATS QKSFM_STATS TABLE_STATS 1 272 10.1.0.3
TIV_GB QKSFM_ALL PIV_GB 2 0 8.1.0
TIV_SSF QKSFM_ALL PIV_SSF 2 0 8.1.0
TRACING QKSFM_EXECUTION TRACING 1 0 10.1.0.3
TRANSFORM_DISTINCT_AGG QKSFM_TRANSFORMATION TRANSFORM_DISTINCT_AGG NO_TRANSFORM_DISTINCT_AGG 2 0 11.2.0.1 11.2.0.1
UNNEST QKSFM_UNNEST UNNEST NO_UNNEST 2 16 8.1.6 10.1.0
USE_ANTI QKSFM_CBO USE_ANTI 4 272 8.1.0
USE_CONCAT QKSFM_USE_CONCAT OR_EXPAND NO_EXPAND 2 16 8.1.0 8.1.7
USE_CUBE QKSFM_USE_CUBE JOIN NO_USE_CUBE 4 336 12.1.0.1 12.1.0.1
USE_DAGG_UNION_ALL_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS NO_USE_DAGG_UNION_ALL_GSETS 2 0 12.2.0.1 12.2.0.1
USE_HASH QKSFM_USE_HASH JOIN NO_USE_HASH 4 464 8.1.0 8.1.7
USE_HASH_AGGREGATION QKSFM_ALL USE_HASH_AGGREGATION NO_USE_HASH_AGGREGATION 2 0 10.2.0.1 10.2.0.5
USE_HASH_GBY_FOR_DAGGPSHD QKSFM_ALL USE_HASH_GBY_FOR_DAGGPSHD NO_USE_HASH_GBY_FOR_DAGGPSHD 2 0 12.2.0.1 12.2.0.1
USE_HASH_GBY_FOR_PUSHDOWN QKSFM_ALL USE_HASH_GBY_FOR_PUSHDOWN NO_USE_HASH_GBY_FOR_PUSHDOWN 2 0 11.2.0.2 11.2.0.2
USE_HIDDEN_PARTITIONS QKSFM_PARTITION USE_HIDDEN_PARTITIONS 2 0 12.1.0.1
USE_INVISIBLE_INDEXES QKSFM_INDEX USE_INVISIBLE_INDEXES NO_USE_INVISIBLE_INDEXES 1 0 11.1.0.6 11.1.0.6
USE_MERGE QKSFM_USE_MERGE JOIN NO_USE_MERGE 4 336 8.1.0 8.1.7
USE_MERGE_CARTESIAN QKSFM_USE_MERGE_CARTESIAN JOIN 4 336 11.1.0.6 11.1.0.6
USE_NL QKSFM_USE_NL JOIN NO_USE_NL 4 336 8.1.0 8.1.7
USE_NL_WITH_INDEX QKSFM_USE_NL_WITH_INDEX USE_NL_WITH_INDEX NO_USE_NL 4 304 10.1.0.3
USE_PARTITION_WISE_DISTINCT QKSFM_PARTITION USE_PARTITION_WISE_DISTINCT NO_USE_PARTITION_WISE_DISTINCT 2 0 12.2.0.1 12.2.0.1
USE_PARTITION_WISE_GBY QKSFM_PARTITION USE_PARTITION_WISE_GBY NO_USE_PARTITION_WISE_GBY 2 0 12.2.0.1 12.2.0.1
USE_PARTITION_WISE_WIF QKSFM_PARTITION USE_PARTITION_WISE_WIF NO_USE_PARTITION_WISE_WIF 2 0 18.1.0 18.1.0
USE_SCALABLE_GBY_INVDIST QKSFM_PQ USE_SCALABLE_GBY_INVDIST NO_USE_SCALABLE_GBY_INVDIST 2 0 19.1.0 19.1.0
USE_SEMI QKSFM_CBO USE_SEMI 4 272 8.1.0
USE_TTT_FOR_GSETS QKSFM_TRANSFORMATION USE_TTT_FOR_GSETS 2 0 9.0.0
USE_VECTOR_AGGREGATION QKSFM_VECTOR_AGG USE_VECTOR_AGGREGATION NO_USE_VECTOR_AGGREGATION 2 16 12.1.0.2 12.1.0.2
USE_WEAK_NAME_RESL QKSFM_ALL USE_WEAK_NAME_RESL 1 0 10.1.0.3
VECTOR_READ QKSFM_CBO VECTOR_READ 1 0 10.1.0.3
VECTOR_READ_TRACE QKSFM_CBO VECTOR_READ_TRACE 1 0 10.1.0.3
VECTOR_TRANSFORM QKSFM_VECTOR_AGG VECTOR_TRANSFORM NO_VECTOR_TRANSFORM 2 16 12.1.0.2 12.1.0.2
VECTOR_TRANSFORM_DIMS QKSFM_VECTOR_AGG VECTOR_TRANSFORM_DIMS NO_VECTOR_TRANSFORM_DIMS 4 80 12.1.0.2 12.1.0.2
VECTOR_TRANSFORM_FACT QKSFM_VECTOR_AGG VECTOR_TRANSFORM_FACT NO_VECTOR_TRANSFORM_FACT 4 80 12.1.0.2 12.1.0.2
WITH_PLSQL QKSFM_ALL WITH_PLSQL 1 0 12.1.0.1
XDB_FASTPATH_INSERT QKSFM_ALL XDB_FASTPATH_INSERT NO_XDB_FASTPATH_INSERT 1 0 11.2.0.2
XMLINDEX_REWRITE QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE NO_XMLINDEX_REWRITE 1 0 11.1.0.6 11.1.0.6
XMLINDEX_REWRITE_IN_SELECT QKSFM_XMLINDEX_REWRITE XMLINDEX_REWRITE NO_XMLINDEX_REWRITE_IN_SELECT 1 0 11.1.0.6 11.1.0.6
XMLINDEX_SEL_IDX_TBL QKSFM_ALL XMLINDEX_SEL_IDX_TBL 1 0 11.2.0.1
XMLTSET_DML_ENABLE QKSFM_ALL XMLTSET_DML_ENABLE 1 0 12.2.0.1
XML_DML_RWT_STMT QKSFM_XML_REWRITE XML_DML_RWT_STMT 1 0 11.1.0.6 11.1.0.6
X_DYN_PRUNE QKSFM_CBO X_DYN_PRUNE 2 0 10.1.0.3
ZONEMAP QKSFM_ZONEMAP ZONEMAP NO_ZONEMAP 4 256 12.1.0.1 12.1.0.1

388行が選択されました。

 

 

PostgreSQL / pg_hint_plan (22-Nov-2023時点)

pg_hint_planのヒントもOracleの影響を受けているところもあるけども、PostgreSQL独特の言い回しを使う傾向はありますよね。面白いのは、LEADINGヒントで(a b)みたいなペアで優先度を記述する部分。これかなり慣れが必要な気がします。

https://github.com/ossc-db/pg_hint_plan

https://github.com/ossc-db/pg_hint_plan/blob/master/docs/hint_list.md

Hint list

  Format Description
Scan method SeqScan(table) Forces sequential scan on the table.
  TidScan(table) Forces TID scan on the table.
  IndexScan(table[ index...]) Forces index scan on the table. Restricts to specified indexes if any.
  IndexOnlyScan(table[ index...]) Forces index-only scan on the table. Restricts to specified indexes if any. Index scan may be used if index-only scan is not available.
  BitmapScan(table[ index...]) Forces bitmap scan on the table. Restricts to specified indexes if any.
  IndexScanRegexp(table[ POSIX Regexp...])
IndexOnlyScanRegexp(table[ POSIX Regexp...])
BitmapScanRegexp(table[ POSIX Regexp...])
Forces index scan, index-only scan (For PostgreSQL 9.2 and later) or bitmap scan on the table. Restricts to indexes that matches the specified POSIX regular expression pattern.
  NoSeqScan(table) Forces to not do sequential scan on the table.
  NoTidScan(table) Forces to not do TID scan on the table.
  NoIndexScan(table) Forces to not do index scan and index-only scan on the table.
  NoIndexOnlyScan(table) Forces to not do index only scan on the table.
  NoBitmapScan(table) Forces to not do bitmap scan on the table.
Join method NestLoop(table table[ table...]) Forces nested loop for the joins on the tables specified.
  HashJoin(table table[ table...]) Forces hash join for the joins on the tables specified.
  MergeJoin(table table[ table...]) Forces merge join for the joins on the tables specified.
  NoNestLoop(table table[ table...]) Forces to not do nested loop for the joins on the tables specified.
  NoHashJoin(table table[ table...]) Forces to not do hash join for the joins on the tables specified.
  NoMergeJoin(table table[ table...]) Forces to not do merge join for the joins on the tables specified.
Join order Leading(table table[ table...]) Forces join order as specified.
  Leading(<join pair>) Forces join order and directions as specified. A join pair is a pair of tables and/or other join pairs enclosed by parentheses, which can make a nested structure.
Behavior control on Join Memoize(table table[ table...]) Allows the topmost join of a join among the specified tables to Memoize the inner result. Not enforced.
  NoMemoize(table table[ table...]) Inhibits the topmost join of a join among the specified tables from Memoizing the inner result.
Row number correction Rows(table table[ table...] correction) Corrects row number of a result of the joins on the tables specified. The available correction methods are absolute (#), addition (+), subtract (-) and multiplication (*). should be a string that strtod() can understand.
Parallel query configuration Parallel(table <# of workers> [soft|hard]) Enforces or inhibits parallel execution of the specified table. <# of workers> is the desired number of parallel workers, where zero means inhibiting parallel execution. If the third parameter is soft (default), it just changes max_parallel_workers_per_gather and leaves everything else to the planner. Hard enforces the specified number of workers.
GUC Set(GUC-param value) Sets GUC parameter to the value defined while planner is running.

 

MySQL 8.0

https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html

Table 8.2 Optimizer Hints Available

MySQLのOptimizer HintsはOracleにもある同一用途のヒントもちらほら。ヒント名も同一で:)
Hint Name Description Applicable Scopes
BKA, NO_BKA Affects Batched Key Access join processing Query block, table
BNL, NO_BNL Prior to MySQL 8.0.20: affects Block Nested-Loop join processing; MySQL 8.0.18 and later: also affects hash join optimization; MySQL 8.0.20 and later: affects hash join optimization only Query block, table
DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN Use or ignore the derived condition pushdown optimization for materialized derived tables (Added in MySQL 8.0.22) Query block, table
GROUP_INDEX, NO_GROUP_INDEX Use or ignore the specified index or indexes for index scans in GROUP BY operations (Added in MySQL 8.0.20) Index
HASH_JOIN, NO_HASH_JOIN Affects Hash Join optimization (MySQL 8.0.18 only Query block, table
INDEX, NO_INDEX Acts as the combination of JOIN_INDEX, GROUP_INDEX, and ORDER_INDEX, or as the combination of NO_JOIN_INDEX, NO_GROUP_INDEX, and NO_ORDER_INDEX (Added in MySQL 8.0.20) Index
INDEX_MERGE, NO_INDEX_MERGE Affects Index Merge optimization Table, index
JOIN_FIXED_ORDER Use table order specified in FROM clause for join order Query block
JOIN_INDEX, NO_JOIN_INDEX Use or ignore the specified index or indexes for any access method (Added in MySQL 8.0.20) Index
JOIN_ORDER Use table order specified in hint for join order Query block
JOIN_PREFIX Use table order specified in hint for first tables of join order Query block
JOIN_SUFFIX Use table order specified in hint for last tables of join order Query block
MAX_EXECUTION_TIME Limits statement execution time Global
MERGE, NO_MERGE Affects derived table/view merging into outer query block Table
MRR, NO_MRR Affects Multi-Range Read optimization Table, index
NO_ICP Affects Index Condition Pushdown optimization Table, index
NO_RANGE_OPTIMIZATION Affects range optimization Table, index
ORDER_INDEX, NO_ORDER_INDEX Use or ignore the specified index or indexes for sorting rows (Added in MySQL 8.0.20) Index
QB_NAME Assigns name to query block Query block
RESOURCE_GROUP Set resource group during statement execution Global
SEMIJOIN, NO_SEMIJOIN Affects semijoin strategies; beginning with MySQL 8.0.17, this also applies to antijoins Query block
SKIP_SCAN, NO_SKIP_SCAN Affects Skip Scan optimization Table, index
SET_VAR Set variable during statement execution Global
SUBQUERY Affects materialization, IN-to-EXISTS subquery strategies Query block

 

いよいよ、Advent Calendar 2023の季節が近くなってきました。今年は、全部俺シリーズは、やらずに、Oracle/PostgreSQL/MySQLのカレンダーへクロスポストすることだけは決めてます〜 :)
全部俺では無いけど、いくつかエントリーは書く予定ではいます。このエントリーもAdvent Calendarネタへ繋がるネタなのですけどね。w

では、また。



関連エントリー


Oracle Database 20c 20.1.0以降〜21c 21.1.0で v$sql_hintに追加されたヒント/ FAQ

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる

 

 

 

| | | コメント (0)

2023年10月11日 (水)

Oracle SQL Hinting Tips / #JoelKallmanDay


Joel Kallman Day 2023 : Announcement
の通り、本ポストは、Joel Kallman Day 2023 向けのブログポストでもあります。詳細はTimeのブログ参照のこと。


Previously on Mac De Oracle
前回は、rebuild index後のindexサイズって結局、create indexのexplainで見積もれるんだよね、というお話でした。

今回から数回、OracleのSQL ヒントの使い方テクニックでも紹介して行こうかなと思っています。
まず、最近のヒント周りの劇的な進化というか便利になったこと、最近といっても、数ヶ月とかいう単位ではないのですけどもw

Oracle 19c から hint usage report が表示されるようになりました。

19.3.3.4 ヒント使用状況レポート: 例
https://docs.oracle.com/cd/F19136_01/tgsql/influencing-the-optimizer.html#GUID-1697E7CA-9DD0-4C0D-9BC9-E4E17334C0AA


これ、今までなんでなかったの? という感じはあります。(全くなかったわけではないのですが、よほどのことがなけれそこまでトレースしなかったというのが一番の理由でしょうね。取得および、確認方法が面倒だったのでw)

最近は、ヒントの種類も、使用頻度も多くなってきたことが影響しているのでしょうか。。。ヒント使用状況レポートがほしいという要望が多くなったのでしょうかね? 

ただ、hint usage report で簡単に確認できるようになったのは良いことだと思うのですが、その副作用というような現象に気づくことも、それなりに多くなってきました。。。

その副作用とは、ヒントが、Unusedとなるような記述は避けるべき! のような話です
(風の便り程度で実際そうなっている状況に遭遇はしていないのですが)

それ以来、
ヒントの書き方の標準を決める際に、過度に、strictになって、逆にヒントを使いこなせない状況になったりしていないだろうか?
もしくは、その兆候があるのではないかと心配になることがあります。
昔から使われてきた柔軟なヒント記述方法が、変に制限されたりすることのないようにしてほしいものだとは思います。
(そう意味も込めて、マニュアルにも書いてるからね、この方法というのも点も強調しつつ、簡単な検証方法も合わせて紹介しています)

USE_HASHヒント
https://docs.oracle.com/cd/F19136_01/sqlrf/Comments.html#GUID-FA1147B3-BCAA-41F9-B6A2-8DEDABF1C021

USE_NLヒント
https://docs.oracle.com/cd/F19136_01/sqlrf/Comments.html#GUID-56DAA0EC-54BB-4E9D-9049-BCEA934F7A89


マニュアルにも例が記載されていますが、USE_NLやUSE_HASHヒントには、内部表(HASH結合の場合は、プローブ表)を指定します、単一表指定、複数表指定どちらも可能です。

例えば、A表とB表を2表を結合する例で言えば、
Nested Loop Join(NLJ)の駆動表、Hash Join(HJ)のビルド表が、A表だとして、

/*+ LEADING(A) USE_NL(B) */
/*+ LEADING(A) USE_HASH(B) */

のようにヒントを書くことができます。LEADINGヒントで駆動表(NLJの場合)または、ビルド表(HJの場合)を指定し、内部表または、ビルド表をUSE_NL(NLJの場合)または、USE_HASH(HJの場合)で指定する。

では、マニュアルにも記載されている以下のような記述は、どのような意味なのか、みなさん、お分かりでしょうか?

/*+ USE_NL(A B) */ 同じ意味ですが、 /*+ USE_NL(A) USE_NL(B) */
/*+ USE_HASH(A B) */ 同じ意味ですが、 /*+ USE_HASH(A) USE_HASH(B) */

A表とB表の2表ですから、どちらかが、駆動表(NLJの場合)、または、ビルド表(HJの場合)で、どちらかが、内部表(NLJの場合)、または、プローブ表(HJの場合)となります。
これらのヒントに指定した表は、指定しても無視される表(駆動表または、ビルド表)、つまり、Unused となる表も含めて記載しています。(エラーではないく、使われないだけだからですが)

あえて、そうする意味はなんでしょう。みなさんお分かりでしょうか?

答えは、結合順序はオプティマイザの判断に任せ、結合方法だけを、NLJ や HJ にしたい!!!!!

ということです。


意外にこのような状況は多くあります。
要するに、駆動表や、ビルド表は状況に応じて柔軟に変えてもらって良いが、結合方法だけは、絶対、NLJにしたい。もしくは、HJにしたいというケースですね!
(チューニングで呼ばれて行った先で、業務観点からどの表が駆動表や、ビルド表ですか? と聞いて、即答してくれないと、おじさん困ってしまうんですねw わからんと言われてたら、リスク覚悟で現状のデータ量から決めるか、このように決めないで、オプティマイザに任せる。みなさんもオプティマイザを信じてください! と、言ったもののw、統計情報取得止められてたりすると、まあ、信じられないみたいな状況もあるわけです。はい。wwwww)


他のTech Tipsとして、結合順は状況に応じて人の手で書き換えること(前述したように担当者も結合順を把握していないとか、一旦、固定したけども、やはり間違ってたケースや、経年で傾向が変わったので変更したい etc.)を想定して、ヒント修正によるミスのリスクを最小化するため。(修正範囲をLEADINGヒントだけにして、変更箇所を最小にしたい場合です

駆動表または、ビルド表の変更前
/*+ LEADING(A B) USE_NL(A B) */
/*+ LEADING(A B) USE_HASH(A B) */
/*+ LEADING(A) USE_NL(A B) */
/*+ LEADING(A) USE_HASH(A B) */

駆動表または、ビルド表の変更後
/*+ LEADING(B A) USE_NL(A B) */
/*+ LEADING(B A) USE_HASH(A B) */
/*+ LEADING(B) USE_NL(A B) */
/*+ LEADING(B) USE_HASH(A B) */

これらのヒントは、LEADINGヒントの結合順を変更するだけで、駆動表やビルド表を切り替えることができます。USE_NL、USE_HASHヒントは変更する必要がありません。(ヒント指定時のミスの発生箇所を最小化できるわけです=変更箇所を少なくした)

しかし、以下のように記述していた場合はどうでしょう?
駆動表や、ビルド表を変更する場合、LEADING/USE_NL/USE_HASH全てのヒントを適切に修正する必要があります。

変更前
/*+ LEADING(A) USE_NL(B) */
/*+ LEADING(A) USE_HASH(B) */

変更後
/*+ LEADING(B) USE_NL(A) */
/*+ LEADING(B) USE_HASH(A) */

変更し忘れたことによりヒントが無効となり、遅延に繋がってしまったということは意外に多いです。修正しているのは、人ですからね。間違いはあります。修正する箇所と量を最小化すれば、ミスの発生リスクは減らせます。
もう一つの効果として、可読性が向上する(個人的な感覚かもしれませんが)のではないかと考えています。


ということで長い前置きはこれぐらいにして挙動を確認してみましょう。


結合順はオプティマイザ任せにして固定せず、結合方法だけをHJにした場合、Hint Usage Reportにはどうのようにレポートされるのか、また実行計画は想定通りなのか等を確認してみましょう。


Oracle Database 21cを利用して検証します。ちなみに、この方法は、Oracle 11gの頃から実戦で利用されている方法です。(マニュアルにも例が記載されているので、将来的にも挙動が変わることはないでしょう。影響でかいですからね。挙動が変わるとw)
なお、隠しパラメータのカスタマイズはせず、インストール時のままです。

SCOTT@orclpdb1> select banner from v$version;

BANNER
-----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production


検証用の表とデータ登録、統計情報取得、データ件数確認
(なお、本検証で利用したスクリプトはこのブログの後半に記載してあります)

SCOTT@orclpdb1> @hinting_tech1

表が削除されました。

経過: 00:00:00.84

表が作成されました。

経過: 00:00:00.19

表が削除されました。

経過: 00:00:00.51

表が作成されました。

経過: 00:00:00.07

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:06.41

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:13.93
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.71
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.16
1* SELECT COUNT(1) FROM table_a

COUNT(1)
----------
50000

経過: 00:00:00.01
1* SELECT COUNT(1) FROM table_b

COUNT(1)
----------
100000

経過: 00:00:00.01

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.03


何もチューニングしていないオリジナルのSQLと実行計画を確認しておきます。少量のデータに絞って、結合(索引あり)しています。
駆動表がTABLE_Aで、NLJしていることが確認できます。

  1  SELECT
2 *
3 FROM
4 table_a a
5 INNER JOIN table_b b
6 ON
7 a.id = b.id
8* AND a.id BETWEEN :s AND :e

経過: 00:00:00.04

実行計画
----------------------------------------------------------
Plan hash value: 1883513077

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 203 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_A | 125 | 244K| 78 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | SYS_C009317 | 225 | | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | SYS_C009318 | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TABLE_B | 1 | 2006 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
5 - access("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
6 - access("A"."ID"="B"."ID")
filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

Note
-----
- this is an adaptive plan


統計
----------------------------------------------------------
73 recursive calls
21 db block gets
143 consistent gets
8 physical reads
4108 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
11 sorts (memory)
0 sorts (disk)
5 rows processed


NLJをヒント使って、HJに強制します。索引アクセスを行わないようにFULLヒントも併用していますが、LEADINGヒントは利用せず、ビルド表の決定はオプティマイザに任せています。
実行結果とヒント使用状況レポートを確認すると、オプティマイザの判断により、TABLE_Aがビルド表となっため、USE_HASH(A)がUnusedとしてレポートされてますが、残るヒントは利用され、NLJからHJへ変更が行われています。
Unusedは最終的に利用されなかったという意味で、ヒント構文場問題があるわけではなくヒントとしては正しいが、最終的に利用されなかった。このケースではビルド表となった表TABLE_Aを指定していた、USE_HASH(A)が利用されなかったということですね。
理由は、USE_HASH/USE_NLに指定する表は、プローブ表または、内部表となっているためで、このケースではオプティマイザが最終的にTABLE_Aをビルド表としたためUSE_HASH(A)が利用されなかったということを意味しています。

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a)
6 USE_HASH(b)
7 */
8 *
9 FROM
10 table_a a
11 INNER JOIN table_b b
12 ON
13 a.id = b.id
14* AND a.id BETWEEN :s AND :e

経過: 00:00:00.24

実行計画
----------------------------------------------------------
Plan hash value: 3176705182

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_A | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_B | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
4 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "A"@"SEL$1"
U - USE_HASH(a)


統計
----------------------------------------------------------
30 recursive calls
0 db block gets
50366 consistent gets
50282 physical reads
0 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
5 rows processed


USE_HASH(a b)という記載ですが、前述のヒントと同じ意味です。この例のような指定方法だと、ヒント使用状況レポートの読み方の理解が重要になります。
U - USE_HASH(a b) というレポートがありますが、これだけだと、ヒント全体が利用されなかったのか? と思われるかもしれませんが、
もう一つ上の、3 - SEL$58A6D7F6 / "A"@"SEL$1"に着目する必要があります。 該当ヒントの A が利用されなかったということを意味しています。

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a b)
6 */
7 *
8 FROM
9 table_a a
10 INNER JOIN table_b b
11 ON
12 a.id = b.id
13* AND a.id BETWEEN :s AND :e

経過: 00:00:00.23

実行計画
----------------------------------------------------------
Plan hash value: 3176705182

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_A | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_B | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))
4 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "A"@"SEL$1"
U - USE_HASH(a b)


統計
----------------------------------------------------------
24 recursive calls
0 db block gets
50298 consistent gets
50282 physical reads
0 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed


この検証では、LEADINGヒントを利用せず、オプティマイザ任せにしてあります。もう一つの検証として、オプティマイザが駆動表というかビルド表にする表を変えたケースも確認しておきたいですよね! ヒント指定の思惑通りの挙動になるか。。。確認は必要ですよからねw (実案件ではここまで確認はしませんけどもw 本記事の目的がその確認ですのでw)

データをからにして、行数を逆にして、駆動表というかビルド表が変わるように仕掛けておきます。

  1* truncate table table_a

表が切り捨てられました。

経過: 00:00:00.44
1* truncate table table_b

表が切り捨てられました。

経過: 00:00:00.13

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:07.02

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:13.49
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:03.19
1 BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.54
1* SELECT COUNT(1) FROM table_a

COUNT(1)
----------
100000

経過: 00:00:00.02
1* SELECT COUNT(1) FROM table_b

COUNT(1)
----------
50000

経過: 00:00:00.01


何もしていないオプティマイザ任せの実行計画は、NLJで、狙い通りにTABLE_Bが駆動表に切り替わっています。(うまく行ってよかったw)

  1  SELECT
2 *
3 FROM
4 table_a a
5 INNER JOIN table_b b
6 ON
7 a.id = b.id
8* AND b.id BETWEEN :s AND :e

経過: 00:00:00.05

実行計画
----------------------------------------------------------
Plan hash value: 3627193911

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 203 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 125 | 489K| 203 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_B | 125 | 244K| 78 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | SYS_C009318 | 225 | | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | SYS_C009317 | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TABLE_A | 1 | 2006 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
5 - access("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
6 - access("A"."ID"="B"."ID")
filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

Note
-----
- this is an adaptive plan


統計
----------------------------------------------------------
86 recursive calls
0 db block gets
113 consistent gets
7 physical reads
0 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
9 sorts (memory)
0 sorts (disk)
5 rows processed


先ほどと同じヒントを指定しました。LEADINGヒントはないので、オプティマイザは、TABLE_Bをビルド表として選択しました。狙い通りですね。
ということは、今度は、USE_HASH(B)がUnusedになるはずです。ビルド表になりましたからね。先ほどは、プローブ表だったわけですが。

ヒント使用状況レポートの詳細にはいかのようにレポートされました。USE_HASH(A B)という表よりは直感的に理解しやすいと思いますが、B Unusedですよということですね。
3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(b)

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a)
6 USE_HASH(b)
7 */
8 *
9 FROM
10 table_a a
11 INNER JOIN table_b b
12 ON
13 a.id = b.id
14* AND b.id BETWEEN :s AND :e

経過: 00:00:00.22

実行計画
----------------------------------------------------------
Plan hash value: 3989351480

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_B | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_A | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
4 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(b)


統計
----------------------------------------------------------
113 recursive calls
0 db block gets
50787 consistent gets
50526 physical reads
0 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
25 sorts (memory)
0 sorts (disk)
5 rows processed


USE_HASH(a b)という表記にした例です意味は同じですが、USE_HASH(a b)全体がUnusedなのかと勘違いしそうですよね。よーく見てみましょう。SEL$58A6D7F6 / "B"@"SEL$1" とあり、 USE_HASH(a b)のうち、B が Unusedであることが確認できます。
3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(a b)

  1  SELECT
2 /*+
3 FULL(a)
4 FULL(b)
5 USE_HASH(a b)
6 */
7 *
8 FROM
9 table_a a
10 INNER JOIN table_b b
11 ON
12 a.id = b.id
13* AND b.id BETWEEN :s AND :e

経過: 00:00:00.23

実行計画
----------------------------------------------------------
Plan hash value: 3989351480

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 125 | 489K| 13783 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| TABLE_B | 125 | 244K| 4678 (1)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| TABLE_A | 250 | 489K| 9105 (1)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(TO_NUMBER(:E)>=TO_NUMBER(:S))
2 - access("A"."ID"="B"."ID")
3 - filter("B"."ID">=TO_NUMBER(:S) AND "B"."ID"<=TO_NUMBER(:E))
4 - filter("A"."ID">=TO_NUMBER(:S) AND "A"."ID"<=TO_NUMBER(:E))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------

3 - SEL$58A6D7F6 / "B"@"SEL$1"
U - USE_HASH(a b)


統計
----------------------------------------------------------
5 recursive calls
0 db block gets
50541 consistent gets
50526 physical reads
0 redo size
8944 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed



本検証で利用したスクリプト( hinting_tech1.sql )

drop table table_a purge
/
create table table_a
(
id number primary key
, dummy_str varchar2(2000)
)
/

drop table table_b purge
/
create table table_b
(
id number primary key
, dummy_str varchar2(2000)
)
/


BEGIN
FOR i IN 1..50000 LOOP
INSERT INTO table_a VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/


BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO table_b VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
END;
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
END;
.
l
/


SELECT COUNT(1) FROM table_a
.
l
/
SELECT COUNT(1) FROM table_b
.
l
/

REM **** Original - No Hint - 1-0 ****
VARIABLE s NUMBER
VARIABLE e NUMBER
BEGIN
:s := 1;
:e := 5;
END;
.
/


SELECT
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 1-1 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a)
USE_HASH(b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 1-2 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND a.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off



truncate table table_a
.
l
/

truncate table table_b
.
l
/


BEGIN
FOR i IN 1..50000 LOOP
INSERT INTO table_b VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/


BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO table_a VALUES(i,LPAD('*',2000,'*'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_A', cascade=>true, no_invalidate=>false);
END;
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'TABLE_B', cascade=>true, no_invalidate=>false);
END;
.
l
/

SELECT COUNT(1) FROM table_a
.
l
/
SELECT COUNT(1) FROM table_b
.
l
/

REM **** Original - No Hint - 2-0 ****
SELECT
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 2-1 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a)
USE_HASH(b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


REM **** pattern 2-2 ****
SELECT
/*+
FULL(a)
FULL(b)
USE_HASH(a b)
*/
*
FROM
table_a a
INNER JOIN table_b b
ON
a.id = b.id
AND b.id BETWEEN :s AND :e
.
l

set autot trace exp stat
/
set autot off


やっと、涼しくなってきたけど、涼しくなるのが急すぎて、まじで季節の変化が急激になってきたなぁと。

では、また。

| | | コメント (0)

2023年9月28日 (木)

explain plan for文で、rebuild 後の索引サイズも見積れる! / FAQ

Jonathan Lewisが、索引rebuild後のサイズ見積もりで昔からある方法を紹介していた. そういえば、私が以前紹介していた方法で見積もれるよね!
ということで、簡単な例で紹介しておきますね。

この方法、通常は、create indexの時にしか使えないのですが、結局のところ、alter index ...rebuildも、create indexもindexを作成することには違いはないので、rebuilddでもcreateでも最終的な索引サイズには同じだよね。という単純な発想です。どうせ、ballpark figureなわけで、厳密性不要なわけですし、リーズナブルだと思います。

参考
explain plan文 De 索引サイズ見積 / FAQ


では、試してみましょう!


索引の無い表を作ります!

  1  CREATE TABLE foobar
2 (
3 key_code CHAR(10) NOT NULL
4* )

表が作成されました。

データを登録します。この例では 10万行登録してあります。(単純なぐるぐる方式ですが、大した量ではないので気にしないでくださいw)

 1  BEGIN
2 FOR i IN 1..100000 LOOP
3 INSERT INTO foobar VALUES(TO_CHAR(i,'FM0000000009'));
4 IF MOD(i,100) = 0
5 THEN
6 COMMIT;
7 END IF;
8 END LOOP;
9* END;

PL/SQLプロシージャが正常に完了しました。


統計情報を取得します。

  1  BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。


まずは、create index文で作成される索引サイズの見積もりを行います。(見積もりだけなので実際には作成されません!)
3145KBとの見積もりです

  1* EXPLAIN PLAN FOR CREATE INDEX ix_foobar ON foobar(key_code)

解析されました。

@?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 4144366834

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | CREATE INDEX STATEMENT | | 100K| 1074K| 143 (1)| 00:00:01 |
| 1 | INDEX BUILD NON UNIQUE| IX_FOOBAR | | | | |
| 2 | SORT CREATE INDEX | | 100K| 1074K| | |
| 3 | TABLE ACCESS FULL | FOOBAR | 100K| 1074K| 69 (2)| 00:00:01 |
------------------------------------------------------------------------------------

Note
-----
- estimated index size: 3145K bytes

14行が選択されました。


では、見積もりとの乖離を確認するために実際に索引を作成して、セグメントサイズを確認してみましょう。
3072KBとなりました。見積もりは、3145KBでした。大きな差はないですよね。これで十分でしょう。

  1* CREATE INDEX ix_foobar ON foobar(key_code)

索引が作成されました。


統計情報を取得して、セグメントサイズを確認します。

  1  BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。

1* select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type

SEGMENT_NAME SEGMENT_TYPE KB
------------------------------ ------------------------------------------------------ ----------
IX_FOOBAR INDEX 3072

次に rebuild したサイズを見積もるため、50%程度のデータを削除します。


1* DELETE FROM foobar WHERE key_code BETWEEN '0000000000' AND '0000050000'

50000行が削除されました。

1* COMMIT

コミットが完了しました。


統計を取得します。

  1  BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
3* END;

PL/SQLプロシージャが正常に完了しました。


50%削除しましたが、rebuild前なのでサイズはcreate index時と同じですよね。

  1* select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type

SEGMENT_NAME SEGMENT_TYPE KB
------------------------------ ------------------------------------------------------ ----------
IX_FOOBAR INDEX 3072


この状態で、alter index .... rebuild文ではなく、create index文でサイズを見積もります。
2097KBになると見積もられました。

  1* EXPLAIN PLAN FOR CREATE INDEX ix_foobar ON foobar(key_code)

解析されました。

@?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 4144366834

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | CREATE INDEX STATEMENT | | 50000 | 537K| 80 (0)| 00:00:01 |
| 1 | INDEX BUILD NON UNIQUE| IX_FOOBAR | | | | |
| 2 | SORT CREATE INDEX | | 50000 | 537K| | |
| 3 | TABLE ACCESS FULL | FOOBAR | 50000 | 537K| 43 (0)| 00:00:01 |
------------------------------------------------------------------------------------

Note
-----
- estimated index size: 2097K bytes

14行が選択されました。


alter index ... rebuildは、expla plan forでは索引サイズを見積もれないことも、念の為に確認しておきましょう。
見積もられないですよね。間違いなく!

  1* EXPLAIN PLAN FOR ALTER INDEX ix_foobar REBUILD

解析されました。

@?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 4144366834

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | ALTER INDEX STATEMENT | | 50000 | 537K| 43 (0)| 00:00:01 |
| 1 | INDEX BUILD NON UNIQUE| IX_FOOBAR | | | | |
| 2 | SORT CREATE INDEX | | 50000 | 537K| | |
| 3 | TABLE ACCESS FULL | FOOBAR | 50000 | 537K| 43 (0)| 00:00:01 |
------------------------------------------------------------------------------------

10行が選択されました。


最後に、実際に alter index ... rebuildしてみるとサイズはどうなるでしょうか! 確認してみましょう!
はい!  2048KBとreuildしてコンパクトになりました!。 見積もりサイズは、2097KB でした。この程度の誤差は問題にもならないでしょう。

  1* ALTER INDEX ix_foobar REBUILD

索引が変更されました。

1* select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type

SEGMENT_NAME SEGMENT_TYPE KB
------------------------------ ------------------------------------------------------ ----------
IX_FOOBAR INDEX 2048

今回使用したSQLスクリプト

[oracle@localhost ~]$ cat estimate_rebuild_index_size.sql
DROP TABLE foobar PURGE
.
l
/

CREATE TABLE foobar
(
key_code CHAR(10) NOT NULL
)
.
l
/

BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO foobar VALUES(TO_CHAR(i,'FM0000000009'));
IF MOD(i,100) = 0
THEN
COMMIT;
END IF;
END LOOP;
END;
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
END;
.
l
/


EXPLAIN PLAN FOR CREATE INDEX ix_foobar ON foobar(key_code)
.
l
/
@?/rdbms/admin/utlxpls

CREATE INDEX ix_foobar ON foobar(key_code)
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
END;
.
l
/

select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type
.
l
/

DELETE FROM foobar WHERE key_code BETWEEN '0000000000' AND '0000050000'
.
l
/

COMMIT
.
l
/

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT', tabname=>'FOOBAR', cascade=>true, no_invalidate=>false);
END;
.
l
/

select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type
.
l
/

EXPLAIN PLAN FOR CREATE INDEX ix_foobar ON foobar(key_code)
.
l
/
@?/rdbms/admin/utlxpls

EXPLAIN PLAN FOR CREATE INDEX ix_foobar ON foobar(key_code)
.
l
/
@?/rdbms/admin/utlxpls

EXPLAIN PLAN FOR ALTER INDEX ix_foobar REBUILD
.
l
/
@?/rdbms/admin/utlxpls

ALTER INDEX ix_foobar REBUILD
.
l
/

select segment_name,segment_type,sum(bytes)/1024 AS "KB" from user_segments where segment_name = 'IX_FOOBAR' group by segment_name,segment_type
.
l
/

DROP TABLE foobar PURGE
.
l
/


ちょっと、涼しくなったと思ったら、残暑がキツイ。

では、また。


| | | コメント (0)

2023年9月23日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #4 Optimizer Traceの取得でも癖がでる

Previously on Mac De Oracle
前回は、MySQLのHash Join を取り上げました。MySQL 8.0.32では NLJに使えるINDEXが存在していても、Hash Joinをヒントで強制することができる!(オプティマイザが選択することがある!)
(MySQLの生い立ちを考えると、Hash Joinとか言われると、MySQLもHash Joinが必要な時代になったのか〜。と遠くを見る自分がいるw)

ということで、今回は、前々回とりあげた、
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)で思い出したネタで、オプティマイザがパースに悩むというか、考えすぎている姿を、時間ではなく、オプティマイザトレースのサイズから見える化して比較!!! してみたいと思います。


実は、
10億年以上前に、Oracle Databaseで同じことやってましたw

MySQLへ話を戻すと、
MySQLのオプティマイザトレースは、Oracle Databaseのそれとは少々ことなり、パラメータで指定されたメモリー上にJSON形式で記録され、INFORMATION_SCHEMA経由で問い合わせて確認します。
パラメータで指定されたメモリーサイズを超えるトレースは切り捨てられてしまいます。Oracle Databaseのオプティマイザトレースとは異なる注意点ですね。

また、面白い特徴もあります。
オプティマイザトレースの出力サイズを事前に予測することはできないわけですが、メモリー内に記録しきれず切り捨てたバイト数をレポートしてくれます。
その機能を利用し、切り捨てたれたトレースのバイト数とトレースを記録するために指定したメモリーのバイト数の合計からトレースのサイズを確認することはできます! 面白い仕組みを提供してくれてますよね。

余談
Oracle/MySQLは、オプティマイザトレースを明示的に取得できますが、PostgreSQLって、オプティマイザトレースだけを取得するOracleの10053トレースMySQLのoptimizer_trace=onに類似する方法はなかったはず。
(DB側の機能の一部としてはないという認識なので、もし勘違いしていたら、ツッコミ🙇よろしくお願いします)



細けーことはこれぐらいにして、比較してみましょう。

まず、前回もネタにした、パース時間のながーーーーーーい、クエリー。
explainだけで、29秒 です!!  11! の組み合わせをみているわけですから、デフォルト設定のMySQL、ものすごーく考えてますよね。
OracleとかPostgreSQLならとっくに諦めている数です。

参考)
検証で利用しているMySQLの表及び索引定義とSQLスクリプトは以下ブログ参照のこと。
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)


mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (29.05 sec)

MySQLでオプティマイザトレースを取得するには、optimizer_trace を on 、optimizer_trace_max_mem_size に オプティマイザトレース記録に必要なメモリーサイズを指定します。
その後、explain文でオプティマイザトレースを取得します。なお、今回は、explainでパース部分だけをトレースして比較します。
https://dev.mysql.com/doc/refman/8.0/en/information-schema-optimizer-trace-table.html


optimizer_trace_max_mem_size を 100 bytesにして、確実に切り捨てが起きるように設定します。

mysql> show variables like 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name | Value |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.18 sec)

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.01 sec)

mysql> show variables like 'optimizer_trace';
+-----------------+-------------------------+
| Variable_name | Value |
+-----------------+-------------------------+
| optimizer_trace | enabled=on,one_line=off |
+-----------------+-------------------------+
1 row in set (0.00 sec)

mysql> show variables like 'optimizer_trace_max_mem_size';
+------------------------------+---------+
| Variable_name | Value |
+------------------------------+---------+
| optimizer_trace_max_mem_size | 1048576 |
+------------------------------+---------+
1 row in set (0.00 sec)

mysql> SET optimizer_trace_max_mem_size = 100;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'optimizer_trace_max_mem_size';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| optimizer_trace_max_mem_size | 100 |
+------------------------------+-------+
1 row in set (0.00 sec)

オプティマイザトレースの準備ができたので、explainしてみましょう!

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (7 min 3.67 sec)

次に、INFORMATION_SCHEMA.OPTIMIZER_TRACEからオプティマイザトレースを取得してみます。

2147483647 bytes (切り捨てられたトレースのサイズ)+ 100 bytes (optimizer_trace_max_mem_sizeパラメータに指定したサイズ)= 2,147,483,747 bytes およそ 2GB です

mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: explain format=tree
select
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `master`.`id` AS `id`,`master`.`dummya` AS `dummya`,`detail`.`id` AS `id`,`detail`.`subid` AS `subid`,`detail`.`dummya` AS `dummya`,`t2`.`id` AS `id`,
`t2`.`subid` AS `subid`,`t2`.`dummya` AS `dummya`,`t3`.`id` AS `id`,`t3`.`subid` AS `subid`,`t3`.`dummya` AS `dummya`,`t4`.`id` AS `id`,`t4`.`subid` AS `subid`,`t4`.`dummya` AS `dummya`,
`t5`.`id` AS `id`,`t5`.`subid` AS `subid`,`t5`.`dummya` AS `dummya`,`t6`.`id` AS `id`,`t6`.`subid` AS `subid`,`t6`.`dummya` AS `dummya`,`t7`.`id` AS `id`,`t7`.`subid` AS `subid`,
`t7`.`dummya` AS `dummya`,`t8`.`id` AS `id`,`t8`.`subid` AS `subid`,`t8`.`dummya` AS `dummya`,`t9`.`id` AS `id`,`t9`.`subid` AS `subid`,`t9`.`dummya` AS `dummya`,`t10`.`id` AS `id`,
`t10`.`subid` AS `subid`,`t10`.`dummya` AS `dummya` from ((((((((((`master` join `detail` on((`master`.`id` = `detail`.`id`))) join `detail` `t2` on((`t2`.`id` = `detail`.`id`)))
join `detail` `t3` on((`t3`.`id` = `t2`.`id`))) join `detail` `t4` on((`t4`.`id` = `t3`.`id`))) join `detail` `t5` on((`t5`.`id` = `t4`.`id`))) join `detail` `t6` on((`t6`.`id` = `t5`.`id`)))
join `detail` `t7` on((`t7`.`id` = `t6`.`id`))) join `detail` `t8` on((`t8`.`id` = `t7`.`id`))) join `detail` `t9` on((`t9`.`id` = `t8`.`id`))) join `detail` `t10` on((`t10`.`id` = `t9`.`id`)))"
},
{
"transformations_to_nested_joins": {
"transformations": [
"JOIN_condition_to_WHERE",
"parenthesis_removal"
],
"expanded_query": "/* select#1 */ select `master`.`id` AS `id`,`master`.`dummya` AS `dummya`,`detail`.`id` AS `id`,`detail`.`subid` AS `subid`,`detail`.`dummya` AS `dummya`,
`t2`.`id` AS `id`,`t2`.`subid` AS `subid`,`t2`.`dummya` AS `dummya`,`t3`.`id` AS `id`,`t3`.`subid` AS `subid`,`t3`.`dummya` AS `dummya`,`t4`.`id` AS `id`,`t4`.`subid` AS `subid`,
`t4`.`dummya` AS `dummya`,`t5`.`id` AS `id`,`t5`.`subid` AS `subid`,`t5`.`dummya` AS `dummya`,`t6`.`id` AS `id`,`t6`.`subid` AS `subid`,`t6`.`dummya` AS `dummya`,`t7`.`id` AS `id`,
`t7`.`subid` AS `subid`,`t7`.`dummya` AS `dummya`,`t8`.`id` AS `id`,`t8`.`subid` AS `subid`,`t8`.`dummya` AS `dummya`,`t9`.`id` AS `id`,`t9`.`subid` AS `subid`,
`t9`.`dummya` AS `dummya`,`t10`.`id` AS `id`,`t10`.`subid` AS `subid`,`t10`.`dummya` AS `dummya` from `master` join `detail` join `detail` `t2` join `detail` `t3`
join `detail` `t4` join `detail` `t5` join `detail` `t6` join `detail` `t7` join `detail` `t8` join `detail` `t9` join `detail` `t10`
where ((`t10`.`id` = `t9`.`id`) and (`t9`.`id` = `t8`.`id`) and (`t8`.`id` = `t7`.`id`) and (`t7`.`id` = `t6`.`id`) and (`t6`.`id` = `t5`.`id`) and (`t5`.`id` = `t4`.`id`)
and (`t4`.`id` = `t3`.`id`) and (`t3`.`id` = `t2`.`id`) and (`t2`.`id` = `detail`.`id`) and (`master`.`id` = `detail`.`id`))"
}
}
]
}
},


....中略....

},
"condition_filtering_pct": 100,
"rows_for_plan": 10240,
"cost_for_plan": 7163,
"pruned_by_cost": true
}

MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 2147483647
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.26 sec)

mysql>


次に、考え過ぎているオプティマイザに、
JOIN_ORDERヒントを利用し、結合順を 考えるな! 感じろ! 作戦でオプティマイザに考えさせないように。。。パースも速いし、オプティマイザトレースのJSONのサイズも小さい!

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| PLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


オプティマイザトレース結果のJSONサイズは、

57379 bytes (切り捨てられたトレースのバイト数) + 100 bytes(optimizer_trace_max_mem_sizeパラメータに指定したサイズ) = 57,479 bytes 、約 56 KBとなりました。

2GB vs 56KB

オプティマイザトレースのJSONサイズからオプティマイザの仕事量を覗いてみるのも面白いですよね。(俺だけか喜んでるのw)

mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
TRACE:
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 57379
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

ということで、 MySQLのオプティマイザトレースはメモリー中に記録され、メモリーに記録できないトレースは切り捨てられる。
また、切り捨てられたサイズ+オプティマイザトレース向けメモリーサイズからトレースを完全に取得するためのメモリーサイズを確認できる。
ただし、必要がメモリーの空き次第ではありそうですね。ということで、Oracle Databaseの 10053トレースとはちょっと違う注意点もあるな。

という知見を得た :)

Oracle Database の 10053トレースでのトレースによる考えている仕事量の見える化同様に、MySQLのオプティマイザトレースでも、オプティマイザが考えすぎていると様子をトレースサイズで見える化してみるという検証はここまで。
MySQLのオプティマイザトレースをなんとなく眺めて、なぜ、その判断をしたのだなどを追うのはまた別の機会に;)


10月も近いのに、muggy な気候の続く、東京より。

ではまた。





関連エントリー
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)
悩ませ過ぎは及ばざるがごとし #7 - おまけ



標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る

| | | コメント (0)

2023年9月 7日 (木)

MySQL 8.0.32では NLJに使えるINDEXが存在していても、Hash Joinをヒントで強制することができる!(オプティマイザが選択することがある!)

Previously on Mac De Oracle
前回は悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)で、パース時間が長い場合、ヒントを使うことでパース時間を短縮させることが可能なケースもあるというという話でした。Oracleでも同様のケースがあることもお話ししました。が、その中で、MySQL 8.0.32 ではオプティマイザが、NLJで利用するINDEXが存在していても、HJを選択することがあることに気づいた!(どうやら、該当表がバッファープールに乗っているとNLJを選択しやすくなる傾向もありそう)という、予告編までw

ということで、今日は、MySQL 8.0.32 では、NLJに利用するINDEXが存在していても、オプティマイザは、HJを選択することもあるし、ヒントによってHJを矯正できるのか? ということを確認してみました。


これまでの、MySQL 8.0.x台のHash Joinの適用範囲および挙動は以下のブログで取り上げられています。(非常に参考になりました。ありがたいですね。本当に ;)

MySQL 8.0.18のHASH JOINを試した(tom__bo’s Blog)
https://tombo2.hatenablog.com/entry/2019/10/14/212100

MySQL 8.0.20 のハッシュジョイン(Hash Join)を INDEX があるテーブルで試してみる(Qiita @hmatsu47)
https://qiita.com/hmatsu47/items/e473a3e566b910d61f5b

MySQL 8.0.20 でHASH JOINが効くケースが拡大した (mita2 database life)
https://mita2db.hateblo.jp/entry/2020/05/03/174101

と、NLJ可能な索引があるとなぜか、HJにならなかった。ついでに、HASH_JOINというヒントは、8.0.18でしか効果がない!
https://dev.mysql.com/doc/refman/8.0/ja/optimizer-hints.html#optimizer-hints-table-level


MySQL 8.0 Manual
https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html

"8.2.1.4 Hash Join Optimization By default, MySQL (8.0.18 and later) employs hash joins whenever possible. It is possible to control whether hash joins are employed using one of the BNL and NO_BNL optimizer hints, or by setting block_nested_loop=on or block_nested_loop=off as part of the setting for the optimizer_switch server system variable.

Note
MySQL 8.0.18 supported setting a hash_join flag in optimizer_switch, as well as the optimizer hints HASH_JOIN and NO_HASH_JOIN. In MySQL 8.0.19 and later, none of these have any effect any longer.

Beginning with MySQL 8.0.18, MySQL employs a hash join for any query for which each join has an equi-join condition, and in which there are no indexes that can be applied to any join conditions, such as this one:"


ななな、なーーーん、だってーーーーーーっ!。


だが、しかし、8.0.32 になるとマニュアルの記載では特に見かけなかったが、NLJに使える索引がある場合でも等価結合でHash Joinになるではありませんか。みなさん!

まじでーーーというか、実際、そうなるもん!

以下、前回のブログ参照のこと。
悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編) (Mac De Oracle)
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2023/09/post-bee50f.html


ただし、 HASH_JOINヒントは使えないので、どのようにヒントを書けば Hash Joinを強制できるのだろう? 8.0.32編 ということで、試してみた!

結論から言ってしまうと

MySQL 8.0.32 では NLJに使えるINDEXが存在していても、Hash Joinを強制することができる! (いつから変わったかは不明だが、8.0.32では可能!)
MySQL 8.0.32 では NLJに使えるINDEXが存在していても、Hash Joinを強制することができる! (いつから変わったかは不明だが、8.0.32では可能!)


詳細は、以下の検証を。比較的気楽にヒントでHJを強制できるようです(だたしちょっと分かりずらいね。Oracleとかと比べると)

表定義等は前々回のエントリー参照のこと。帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る

起動直後、バッファキャッシュが空だと(未検証)Hash Joinが選択されるので、まずは、オプティマイザが Hash Join (等価結合でNLJに利用できる索引が存在する状況でも)が選択されている(前回のブログのおさらい)

[master@localhost ~]$ sudo service mysqld restart
Redirecting to /bin/systemctl restart mysqld.service
[master@localhost ~]$ mysql -u scott -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 Source distribution

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)

mysql>
mysql> \. test1.sql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t10.id = `master`.id) (cost=20489.43 rows=10240)
-> Table scan on t10 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t9.id = `master`.id) (cost=10238.74 rows=5120)
-> Table scan on t9 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t8.id = `master`.id) (cost=5113.38 rows=2560)
-> Table scan on t8 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t7.id = `master`.id) (cost=2550.45 rows=1280)
-> Table scan on t7 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t6.id = `master`.id) (cost=1268.60 rows=640)
-> Table scan on t6 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t5.id = `master`.id) (cost=627.24 rows=320)
-> Table scan on t5 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t4.id = `master`.id) (cost=306.09 rows=160)
-> Table scan on t4 (cost=0.02 rows=20)
-> Hash
-> Inner hash join (t3.id = `master`.id) (cost=145.03 rows=80)
-> Table scan on t3 (cost=0.03 rows=20)
-> Hash
-> Inner hash join (t2.id = `master`.id) (cost=64.01 rows=40)
-> Table scan on t2 (cost=0.06 rows=20)
-> Hash
-> Inner hash join (detail.id = `master`.id) (cost=23.00 rows=20)
-> Table scan on detail (cost=0.12 rows=20)
-> Hash
-> Index scan on master using ix_master (cost=2.00 rows=10)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

関連する表をアクセスしてバッファープールに乗った状態にすると。オプティマイザは、INDEXを利用したNLJを選択するようになった。(前回のブログのおさらい)

mysql> 
mysql> select * from master;
+----+--------+
| id | dummya |
+----+--------+
| 1 | 1 |
| 10 | 10 |

....中略....

| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
+----+--------+
10 rows in set (0.01 sec)

mysql> select * from detail;
+----+-------+--------+
| id | subid | dummya |
+----+-------+--------+
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
| 2 | 2 | 22 |

....中略....

| 9 | 1 | 91 |
| 9 | 2 | 92 |
| 10 | 1 | 101 |
| 10 | 2 | 102 |
+----+-------+--------+
20 rows in set (0.01 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7163.00 rows=10240)
-> Nested loop inner join (cost=3579.00 rows=5120)
-> Nested loop inner join (cost=1787.00 rows=2560)
-> Nested loop inner join (cost=891.00 rows=1280)
-> Nested loop inner join (cost=443.00 rows=640)
-> Nested loop inner join (cost=219.00 rows=320)
-> Nested loop inner join (cost=107.00 rows=160)
-> Nested loop inner join (cost=51.00 rows=80)
-> Nested loop inner join (cost=23.00 rows=40)
-> Nested loop inner join (cost=9.00 rows=20)
-> Index scan on master using ix_master (cost=2.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


オプティマイザがNLJを選択するようになった状況で、Hash Join となるヒントを追加(ヒント等は後半のスクリプト test1_hj.sql を参照のこと)すると、NLJで利用可能なINDEXが存在していたとしても、HJを強制できる。(8.0.20ごろまでは、HJは選択されなかったようなので、8.0.21から8.0.32の間でHJの適用範囲がさらに拡大されたのでしょうね。想像ですけども)

mysql> 
mysql> \. test1_hj.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t10.id = `master`.id) (cost=20468.86 rows=10240)
-> Table scan on t10 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t9.id = `master`.id) (cost=10226.19 rows=5120)
-> Table scan on t9 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t8.id = `master`.id) (cost=5104.85 rows=2560)
-> Table scan on t8 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t7.id = `master`.id) (cost=2544.11 rows=1280)
-> Table scan on t7 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t6.id = `master`.id) (cost=1263.65 rows=640)
-> Table scan on t6 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t5.id = `master`.id) (cost=623.31 rows=320)
-> Table scan on t5 (cost=0.00 rows=20)
-> Hash
-> Inner hash join (t4.id = `master`.id) (cost=303.02 rows=160)
-> Table scan on t4 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t3.id = `master`.id) (cost=142.76 rows=80)
-> Table scan on t3 (cost=0.01 rows=20)
-> Hash
-> Inner hash join (t2.id = `master`.id) (cost=62.50 rows=40)
-> Table scan on t2 (cost=0.02 rows=20)
-> Hash
-> Inner hash join (detail.id = `master`.id) (cost=22.25 rows=20)
-> Table scan on detail (cost=0.05 rows=20)
-> Hash
-> Table scan on master (cost=2.00 rows=10)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)


ついでなので、NLJになるようにがっつりヒントで固めてみた場合はどうなるか? 想定通り、NLJに倒せますね。(前回のブログのおさらい)

mysql> 
mysql> \. test1_nlj.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7173.00 rows=10240)
-> Nested loop inner join (cost=3589.00 rows=5120)
-> Nested loop inner join (cost=1797.00 rows=2560)
-> Nested loop inner join (cost=901.00 rows=1280)
-> Nested loop inner join (cost=453.00 rows=640)
-> Nested loop inner join (cost=229.00 rows=320)
-> Nested loop inner join (cost=117.00 rows=160)
-> Nested loop inner join (cost=61.00 rows=80)
-> Nested loop inner join (cost=33.00 rows=40)
-> Nested loop inner join (cost=19.00 rows=20)
-> Index scan on master using ix_master (cost=12.00 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>


検証で利用した 表と索引定義は前回のブログ参照のこと。

以下、今回検証で利用したスクリプト

オリジナル。結合順だけ JOIN_ORDERで制御。オプティマイザが、NLJ/HJをその時の気分で(嘘w)、その時の状況で判断して、NLJ/HJのいずれかを選択する(ことがわかっている 8.0.32 では)

[master@localhost ~]$ cat test1.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;


JOIN_ORDER/NO_INDEX/NO_JOIN_INDEXヒントを利用して、結合順と索引スキャンの抑止、そして、NLJで利用する索引の利用を抑止することで HJ になるようにしたスクリプト

[master@localhost ~]$ cat test1_hj.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
NO_INDEX(master ix_master)
NO_JOIN_INDEX(detail primary)
NO_JOIN_INDEX(t2 primary)
NO_JOIN_INDEX(t3 primary)
NO_JOIN_INDEX(t4 primary)
NO_JOIN_INDEX(t5 primary)
NO_JOIN_INDEX(t6 primary)
NO_JOIN_INDEX(t7 primary)
NO_JOIN_INDEX(t8 primary)
NO_JOIN_INDEX(t9 primary)
NO_JOIN_INDEX(t10 primary)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;


最後に、前回も利用した NLJ に倒すためのガチガチヒントバージョンのスクリプト

[master@localhost ~]$ cat test1_nlj.sql
use perftestdb;


explain format=tree
select
/*+
JOIN_ORDER(master,detail,t2,t3,t4,t5,t6,t7,t8,t9,t10)
INDEX(master ix_master)
JOIN_INDEX(detail primary)
JOIN_INDEX(t2 primary)
JOIN_INDEX(t3 primary)
JOIN_INDEX(t4 primary)
JOIN_INDEX(t5 primary)
JOIN_INDEX(t6 primary)
JOIN_INDEX(t7 primary)
JOIN_INDEX(t8 primary)
JOIN_INDEX(t9 primary)
JOIN_INDEX(t10 primary)
*/
*
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

ということで、まだまだまだ、蒸し暑いし、台風直撃しそうな、東京からお送りしました。台風に注意しつつ週末を過ごすしかなさそう。

では、また。





悩ませ過ぎは及ばざるがごとし (MySQL 8.0.32編)

| | | コメント (0)

2023年9月 2日 (土)

帰ってきた! 標準はあるにはあるが癖の多いSQL #3 オプティマイザの結合順評価テーブル数上限にも癖が出る

Previously on Mac De Oracle前回の癖はw
帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る
でした。
実は、今日の癖は、その検証時に気づいた、MySQLで結合表数が多いと、パース時間長くなってね? というか長いよね! という点。

オプティマイザは、皆さんの難しい(いや、難しい必要はないですが)SQLを無駄なく、そのSQLに見合った最短の時間で結果を返そうと努力して実行計画を立てています。これを解析フェーズとかパース時間とか、ハードパース時間とかいくつかのバリエーションで表現していたりします。
今回は、中をとってw パース時間としますね。SQL文の実行計画を立てる、オプティマイザがあれやこれや考えて実行計画を立て終えるまでの時間のことです。

先日の検証で、10表結合したのですが、MySQLでは、20秒以上要していました。実行計画でオプティマイザ考えすぎて帰ってこない。SQL文の実行ではなく実行計画を立てるところで時間がかかるケースはいくつか有名なのがありますが。
そうならないように各データベースは閾値を設けています。今回は、結合順評価テーブル数上限 ってやつです。結合する表の順序の組み合わせをどこまで検討するかということなのですが、これが、票数が増えると鰻登りに増加します。
今回のケースだと、1011表なので、10!11!ですね。2表をを繰り返し結合していてもしっかり考えてくれちゃいます。

ただ、何も考えずにオプティマイザに時間を与えていると、パース時間だけでとんでもない時間になったり、性能要件に見合わないといったケースが出てきます。なので、適当なところで手を打って、オプティマイザに考えさせ過ぎないようしています。
その閾値のデフォルト値が、MySQLとOracle/PostgreSQLとでは結構違うみたい。というところが今回の癖!

ちなみに、Oracleの例ですが、結合以外にINリストに大量にリテラルがあり、かつ考慮する必要のある索引も多いケースでは、悩ませ過ぎは及ばざるがごとし #7 - おまけなんてことも起こりますw オプティマイザを悩ませすぎると大変なので、そんな時は、SQLヒントなどを使って、オプティマイザに考えさせないという治療で回避するのが特効薬ですw

余談はこれぐらいにして、本題へ。

 

Note:
(MySQL/PostgreSQL/Oracle 23c freeそれぞれ、同一Virtualbox VMを利用して検証しているため、CPUリソース等の差異はありません)

最初は、ネタの発端になったMySQL 8.0.32から


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)

mysql> show tables;
+----------------------+
| Tables_in_perftestdb |
+----------------------+
| detail |
| master |
+----------------------+
2 rows in set (0.00 sec)

mysql> desc detail;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| subid | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> show indexes from detail;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| detail | 0 | PRIMARY | 1 | id | A | 10 | NULL | NULL | | BTREE | | | YES | NULL |
| detail | 0 | PRIMARY | 2 | subid | A | 20 | NULL | NULL | | BTREE | | | YES | NULL |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.02 sec)

mysql> desc master;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | MUL | NULL | |
+--------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> show indexes from master;
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| master | 0 | PRIMARY | 1 | id | A | 10 | NULL | NULL | | BTREE | | | YES | NULL |
| master | 1 | ix_master | 1 | dummya | A | 10 | NULL | NULL | YES | BTREE | | | YES | NULL |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.02 sec)

mysql> analyze table master;
+-------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------------------+---------+----------+----------+
| perftestdb.master | analyze | status | OK |
+-------------------+---------+----------+----------+
1 row in set (0.04 sec)

mysql> analyze table detail;
+-------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------------------+---------+----------+----------+
| perftestdb.detail | analyze | status | OK |
+-------------------+---------+----------+----------+
1 row in set (0.02 sec)

mysql>

 

 

表定義とデータは以前のエントリーも参考に
MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?

 

では、

MySQL 8.0.32で、パースに時間を要するケースの再現。optimizer_search_depthパラメータはデフォルトのまま

 

MySQL : optimizer_search_depth optimizer_search_depth


mysql> show variables like 'optimizer_search_depth';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| optimizer_search_depth | 62 |
+------------------------+-------+
1 row in set (0.00 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7162.25 rows=10240)
-> Nested loop inner join (cost=3578.25 rows=5120)
-> Nested loop inner join (cost=1786.25 rows=2560)
-> Nested loop inner join (cost=890.25 rows=1280)
-> Nested loop inner join (cost=442.25 rows=640)
-> Nested loop inner join (cost=218.25 rows=320)
-> Nested loop inner join (cost=106.25 rows=160)
-> Nested loop inner join (cost=50.25 rows=80)
-> Nested loop inner join (cost=22.25 rows=40)
-> Nested loop inner join (cost=8.25 rows=20)
-> Index scan on master using ix_master (cost=1.25 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (21.37 sec)

パースに 21.37 sec 要していますね。 62!まで結合順を探査する設定ですから、このケースだと、10!まで行っているということなのでしょうね。

では、Oracleなどで設けられてる閾値と同等の 6 にせってしてみた場合のパース時間はどうでしょうね。このケースで実行計画を間違うことはない(単純なので)ですが、複雑な結合だと、実行計画をミスする率は多くなりそうですけども、Oracleとかだとその程度で使ってますし。。この程度でも良いのかもしれません。時間かかってもいいよというケースも当然あるので、非機能要件次第ではありますけどね。


mysql> set optimizer_search_depth = 6;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'optimizer_search_depth';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| optimizer_search_depth | 6 |
+------------------------+-------+
1 row in set (0.00 sec)

mysql> \. test1.sql
Database changed
+----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=7162.25 rows=10240)
-> Nested loop inner join (cost=3578.25 rows=5120)
-> Nested loop inner join (cost=1786.25 rows=2560)
-> Nested loop inner join (cost=890.25 rows=1280)
-> Nested loop inner join (cost=442.25 rows=640)
-> Nested loop inner join (cost=218.25 rows=320)
-> Nested loop inner join (cost=106.25 rows=160)
-> Nested loop inner join (cost=50.25 rows=80)
-> Nested loop inner join (cost=22.25 rows=40)
-> Nested loop inner join (cost=8.25 rows=20)
-> Index scan on master using ix_master (cost=1.25 rows=10)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.52 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.27 sec)

はい! パース時間は、一気短縮され、 0.27 sec になりました。Oracleのハードパースでもこれぐらいなこんな程度だとは思います。

 

次に、PostgreSQLで確認してみましょ。 PostgreSQLの場合は、Oracleに近かったはず。表定義とデータ量は同じです。

PostgreSQLでは、結合順序評価上限は以下join_collapse_limitパラメータで制御するようですね。あまり気にしてなかったので脳のしわが一つ増えた! defaultは、8 でOracleより多少、多いというところですね。
join_collapse_limit

 


perftestdb=> select version();
version
--------------------------------------------------------------------------------------------------------
PostgreSQL 13.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4), 64-bit
(1 行)

perftestdb=> show join_collapse_limit;
join_collapse_limit
---------------------
8
(1 行)

perftestdb=> \d+ master
テーブル"public.master"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
--------+-----------------------+----------+---------------+------------+------------+----------+------
id | integer | | not null | | plain | |
dummya | character varying(10) | | | | extended | |
インデックス:
"master_pkey" PRIMARY KEY, btree (id)
アクセスメソッド: heap

perftestdb=> \d+ detail
テーブル"public.detail"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 統計目標 | 説明
--------+-----------------------+----------+---------------+------------+------------+----------+------
id | integer | | not null | | plain | |
subid | integer | | not null | | plain | |
dummya | character varying(10) | | | | extended | |
インデックス:
"pk_detail" PRIMARY KEY, btree (id, subid)
アクセスメソッド: heap

perftestdb=> analyze verbose master;
INFO: analyzing "public.master"
INFO: "master": scanned 1 of 1 pages, containing 10 live rows and 0 dead rows; 10 rows in sample, 10 estimated total rows
ANALYZE
perftestdb=> analyze verbose detail;
INFO: analyzing "public.detail"
INFO: "detail": scanned 1 of 1 pages, containing 20 live rows and 0 dead rows; 20 rows in sample, 20 estimated total rows
ANALYZE
perftestdb=>

 

では試してみましょう。


perftestdb=> \timing on
タイミングは on です。
perftestdb=>
perftestdb=>
perftestdb=> \i ./test1_pg.sql
QUERY PLAN
-----------------------------------------------------------------------------------------------
Hash Join (cost=19.53..156.40 rows=10240 width=116)
Hash Cond: (master.id = t8.id)
-> Hash Join (cost=12.93..31.40 rows=1280 width=83)
Hash Cond: (master.id = detail.id)
-> Hash Join (cost=6.33..10.00 rows=160 width=50)
Hash Cond: (master.id = t4.id)
-> Hash Join (cost=2.67..4.45 rows=40 width=28)
Hash Cond: (master.id = t7.id)
-> Hash Join (cost=1.23..2.50 rows=20 width=17)
Hash Cond: (t6.id = master.id)
-> Seq Scan on detail t6 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.10..1.10 rows=10 width=6)
-> Seq Scan on master (cost=0.00..1.10 rows=10 width=6)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t7 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=3.15..3.15 rows=40 width=22)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (t4.id = t5.id)
-> Seq Scan on detail t4 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t5 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=5.60..5.60 rows=80 width=33)
-> Hash Join (cost=2.90..5.60 rows=80 width=33)
Hash Cond: (detail.id = t3.id)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (detail.id = t2.id)
-> Seq Scan on detail (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t2 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t3 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=5.60..5.60 rows=80 width=33)
-> Hash Join (cost=2.90..5.60 rows=80 width=33)
Hash Cond: (t8.id = t10.id)
-> Hash Join (cost=1.45..3.15 rows=40 width=22)
Hash Cond: (t8.id = t9.id)
-> Seq Scan on detail t8 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t9 (cost=0.00..1.20 rows=20 width=11)
-> Hash (cost=1.20..1.20 rows=20 width=11)
-> Seq Scan on detail t10 (cost=0.00..1.20 rows=20 width=11)
(41 行)

時間: 29.117 ミリ秒
perftestdb=>

 

MySQLで上限を制限した時間と似たような時間でパースが行われていますね。ただ、HASH JOINが選択されていますがw この辺りも、MySQLの生い立ちと、PostgreSQL、そしてOracleの違いとして現れてきます。MySQL 8のHash Joinって、MySQL 8.0.20 でHASH JOINが効くケースが拡大したという元々、Block Nested Loopの置き換えとしての意味が強かったという生い立ちが影響しているようにも見えます。なので、等価結合のINNER JOIN(今回の例)の場合、MySQL 8.0.32でもNLJが選択されているということのようですね。似てるようで似てないRDBMSの世界。まだまだ、私も学びが必要ですね。ちょっと曲のあるMySQLのHash Joinの発動条件。。いずれPostgreSQLやOracleっぽくHash Joinをカジュアルに使えちゃう日が来るのだろうか。。。

 

最後に真打w Oracle Database

 

今回は、23c Freeで試します。ただし、前回のエントリーにあるように、23c freeでは、"_optimizer_max_permutations" = 300と少なく設定されています。
今回の確認では、正式リリースされれば、通常の 2000 になるだろう。という想定で、 2000 にしてハードパース時間を確認しておきます。


SCOTT@freepdb1> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 23c Free, Release 23.0.0.0.0 - Developer-Release

 

デフォルトの設定はいかですが、.sqlスクリプト内で、 _optimizer_max_permutations を 2000 に設定して実行します!


parameter name                   parameter value
-------------------------------- ------------------------------
_optimizer_max_permutations 300
_optimizer_search_limit 5


SCOTT@freepdb1> desc master
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
DUMMYA VARCHAR2(10)

SCOTT@freepdb1> desc detail
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
SUBID NOT NULL NUMBER
DUMMYA VARCHAR2(10)

SCOTT@freepdb1> select table_name,index_name,column_name from user_ind_columns where table_name in ('MASTER','DETAIL') order by table_name,index_name,column_position;

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
DETAIL PK_DETAIL ID
DETAIL PK_DETAIL SUBID
MASTER SYS_C008265 ID

SCOTT@freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'MASTER',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.08
SCOTT@freepdb1> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'DETAIL',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.13

では、試してみましょう!


SCOTT@freepdb1> @test1_ora

セッションが変更されました。

経過: 00:00:00.01

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_optimizer_max_permutations integer 2000
1* alter system flush shared_pool

システムが変更されました。

経過: 00:00:00.68

解析されました。

経過: 00:00:00.27

 

一般的なOracleの_optimizer_max_permutationsパラメータの設定で、 0.27 sec となりました. こんなもんでしょうね。- this is an adaptive plan となっているので、ACTUAL PLANを見ないと確定できないケースですが、多分
Hash joinが選ばれていることでしょうw(なので確認まではしませんが)

 

ついでなので、実行計画(見積もり)を見ておきましょう。 hash joinになってますね。一部、SORT MERGE JOINになってますが、


SCOTT@freepdb1> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------
Plan hash value: 3620033460

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10240 | 950K| 33 (4)| 00:00:01 |
|* 1 | HASH JOIN | | 10240 | 950K| 33 (4)| 00:00:01 |
| 2 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 5120 | 430K| 30 (4)| 00:00:01 |
| 4 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 2560 | 192K| 27 (4)| 00:00:01 |
| 6 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 7 | HASH JOIN | | 1280 | 87040 | 24 (5)| 00:00:01 |
| 8 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 9 | HASH JOIN | | 640 | 37760 | 21 (5)| 00:00:01 |
| 10 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 11 | HASH JOIN | | 320 | 16000 | 18 (6)| 00:00:01 |
| 12 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
|* 13 | HASH JOIN | | 160 | 6560 | 15 (7)| 00:00:01 |
|* 14 | HASH JOIN | | 80 | 2560 | 12 (9)| 00:00:01 |
|* 15 | HASH JOIN | | 40 | 920 | 9 (12)| 00:00:01 |
| 16 | MERGE JOIN | | 20 | 280 | 6 (17)| 00:00:01 |
| 17 | TABLE ACCESS BY INDEX ROWID| MASTER | 10 | 50 | 2 (0)| 00:00:01 |
| 18 | INDEX FULL SCAN | SYS_C008265 | 10 | | 1 (0)| 00:00:01 |
|* 19 | SORT JOIN | | 20 | 180 | 4 (25)| 00:00:01 |
| 20 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | DETAIL | 20 | 180 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------


Note
-----
- this is an adaptive plan

174行が選択されました。

経過: 00:00:00.71

MySQL/PostgreSQL/Oracleそれぞれ、ハードパースの結合順評価テーブル数上限にも癖というか違いがあって、かつ、それが、パース時間の差になってみたり。癖が多いですよね。それぞれの特徴というか。
ついでに、実行計画、MySQLのHash Joinの発動ケース。現状OracleやPostgreSQLのようには使えないので、その辺りは、認識しておいた方が良いですよね。癖として ;)

8月末に東北に居たのですが、例年だと朝晩は涼しくて、過ごしやすいのに、今年は、ダメですね。東京と同じです。農作物への影響が気になりますね。寒暖の差が美味しさに影響するのもありますからね。。。

 

では、また。

 

次回は、OracleのSQLヒントネタでもしようかと思ってます。Oracleのヒントレポートが出力されるようになって、そこ気になる、なぜ。みたいな方も多かったり、それにより無用な議論があったりと聞いているのでw 一応書いておこうかなと。

 



MySQL/PostgreSQL/Oracleで利用したスクリプト(テーブル、索引定義およびデータ取得と統計取得コマンドは本文参照のこと)

MySQL


[master@localhost ~]$ cat test1.sql
use perftestdb;


explain
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

PostgreSQL


[master@localhost ~]$ cat test1_pg.sql
explain
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 

Oracle


[master@localhost ~]$ cat test1_ora.sql
alter session set "_optimizer_max_permutations"=2000;
show parameter "_optimizer_max_permutations"

alter system flush shared_pool
.
l
/


explain plan for
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on
t2.id = detail.id
inner join detail t3
on
t3.id = t2.id
inner join detail t4
on
t4.id = t3.id
inner join detail t5
on
t5.id = t4.id
inner join detail t6
on
t6.id = t5.id
inner join detail t7
on
t7.id = t6.id
inner join detail t8
on
t8.id = t7.id
inner join detail t9
on
t9.id = t8.id
inner join detail t10
on
t10.id = t9.id
;

 



関連エントリー
Oracle Database 23c Free Developer Releaseの”_optimizer_max_permutations” parameterの設定値について

 


標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る

| | | コメント (0)

2023年9月 1日 (金)

Oracle Database 23c Free Developer Releaseの”_optimizer_max_permutations” parameterの設定値について

Previously on Mac De Oracle..
前回のお話は、帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出るでした。Oracle/PostgreSQL/MySQLそれぞれ、再帰問合せで試してみましたが、興味深い違いが出ました。思わず、PostgreSQLのソースコード読み始めてしまいましたwwww

ということで、本日は、その流れで気づいてOracle 23c Freeのひみつ!


たまたま気づいたのですけど、 Oracle Database 23c Free Developer Release で、
under scored parameterの "_optimizer_max_permutations" って、 300なんですね。随分少ない設定にされていました。

これ

5! (120) < 6! (720) ということを意味するので、6表以上の結合では、通常のOracleより実行計画をミスりやすいということを意味しています。
(なぜなのでしょう、Free Developer Releaseだからではないか? というコメントももらいましたが、そうなのですかねぇ。でもそうかもしれないw。理由はなぜなのかわからんので何とも言えないですね)
なので、多数の結合を伴うSQLの検証には注意した方が良いですね。もしくは、通常の 2000 ぐらいまであげて試すとかしておいた方が良いかもね。
FreeのDeveloper Releaseだから 300 になっているのか確認できる資料は見つからなかったけど。


念の為、調べてみるとやはり、23c Free Developer Releaseのみ少なく設定されていました。

SYS@free> select banner from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 23c Free, Release 23.0.0.0.0 - Developer-Release

parameter name parameter value
-------------------------------- ------------------------------
_optimizer_max_permutations 300
_optimizer_search_limit 5



BANNER
----------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
PL/SQL Release 12.2.0.1.0 - Production
CORE 12.2.0.1.0 Production
TNS for Linux: Version 12.2.0.1.0 - Production
NLSRTL Version 12.2.0.1.0 - Production

parameter name parameter value
------------------------------ ------------------------------
_optimizer_max_permutations 2000
_optimizer_search_limit 5


Oracle 23c の正式版がリリースされたら皆さんも、2000になっているか確認しましょうね!(多分、2000になっていると思うけどw)

東京の8月が毎日真夏日だったなんで、話題。来年は毎日、猛暑日じゃなければ良いのですけども。。。
残暑厳しい東京からお送りしました。

では、また。

| | | コメント (0)

2023年8月29日 (火)

帰ってきた! 標準はあるにはあるが癖の多いSQL #2 Actual Plan取得中のキャンセルでも癖が出る

前々回のエントリーで、MySQL 8.0.32のActual Plan取得中にキャンセルしてもキャンセルされるまでのActual Planを取得できることを確認した。

では、OracleやPostgreSQLではどうなのだろう?(Oracleは皆さんご存知だと思いますが、確認の意味も含めて)

Oracle Database 21c と PostgreSQL 13.6 (私の環境の都合上w)で確認しておこうと思います。

Actual Plan欲しいけど、本番環境でしか試せないとか、実行に数時間以上要するような状況と大人の事情で、泣く泣くキャンセルしなければいけない。
でも、途中まででもいいからActual Plan返してくれたら、原因特定できる。。。かもしれないし。。。と思いますよね。

MySQL 8.0.32では、キャンセルしても途中まで取得できました。
(SQLパースフェーズの場合を除く。これはOracleでも、PostgreSQLでも同じでしょう。だって、実行前の解析フェーズでキャンセルされたらActual Planなんて返しようがないですからね)

 

では、Oracle Database 21cから見てみましょう!
(ご存知の方も多いと思いますので最初に答えを言っちゃうと、Oracleの場合、Actual Plan取得でも、SQLモニターの場合でもキャンセルしても途中までの結果を見ることができます!)

 

再帰問合せを利用した連番生成を行う方法で試してみます。


SYS@ORCLCDB> select banner_full from v$version;

BANNER_FULL
------------------------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production Version 21.3.0.0.0

 

gather_plan_statisticsヒントを利用した、Actual Plan取得(キャンセルせず最後まで実行した例です)


SCOTT@freepdb1> @actual
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ gather_plan_statistics */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums

 


SCOTT@freepdb1>  @actual_plan 6n3sc0n82t9dq

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 6n3sc0n82t9dq, child number 0
-------------------------------------
WITH gen_nums(v) AS ( SELECT /*+ gather_plan_statistics */ 1 FROM
dual UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <=
10000000 ) SELECT v from gen_nums

Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Writes | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 4 (100)| | 10M|00:07:57.97 | 105M| 28831 | | | |
| 1 | VIEW | | 1 | 2 | 26 | 4 (0)| 00:00:01 | 10M|00:07:57.97 | 105M| 28831 | | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | 1 | | | | | 10M|00:07:53.30 | 105M| 28831 | 2048 | 2048 | 97M (0)|
| 3 | FAST DUAL | | 1 | 1 | | 2 (0)| 00:00:01 | 1 |00:00:00.01 | 0 | 0 | | | |
| 4 | RECURSIVE WITH PUMP | | 10M| | | | | 9999K|00:00:16.81 | 0 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

同SQLを途中でキャンセルした例です。
A-Rows/A-Timeが途中まで実行されたことを示しています。


SCOTT@freepdb1> @actual
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ gather_plan_statistics */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums
^CSCOTT@freepdb1>

 


SCOTT@freepdb1> @actual_plan 6n3sc0n82t9dq

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 6n3sc0n82t9dq, child number 0
-------------------------------------
WITH gen_nums(v) AS ( SELECT /*+ gather_plan_statistics */ 1 FROM
dual UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <=
10000000 ) SELECT v from gen_nums

Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | Writes | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 4 (100)| | 7479K|00:06:29.48 | 76M| 21535 | | | |
| 1 | VIEW | | 1 | 2 | 26 | 4 (0)| 00:00:01 | 7479K|00:06:29.48 | 76M| 21535 | | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | 1 | | | | | 7479K|00:06:25.85 | 76M| 21535 | 2048 | 2048 | 2048 (0)|
| 3 | FAST DUAL | | 1 | 1 | | 2 (0)| 00:00:01 | 1 |00:00:00.01 | 0 | 0 | | | |
| 4 | RECURSIVE WITH PUMP | | 7479K| | | | | 7479K|00:00:12.40 | 0 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

次に、SQL監視の例を見てみます。まず、キャンセルせず、最後まで実行した場合の例


Global Information
------------------------------
Status : DONE (ALL ROWS)
Instance ID : 1
Session : SCOTT (379:17125)
SQL ID : 8m5nwydj0rk2t

...中略...

Global Stats
============================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
============================================================================
| 294 | 195 | 3.56 | 95 | 100K | 105M | 1126 | 225MB |
============================================================================

SQL Plan Monitoring Details (Plan Hash Value=1492144221)
============================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
============================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 411 | +0 | 1 | 10M | | | . | . | | |
| 1 | VIEW | | 2 | 4 | 411 | +0 | 1 | 10M | | | . | . | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 411 | +0 | 1 | 10M | 1126 | 225MB | 98MB | 226MB | 95.44 | Cpu (248) |
| | | | | | | | | | | | | | | direct path write temp (3) |
| 3 | FAST DUAL | | 1 | 2 | 1 | +0 | 1 | 1 | | | . | . | | |
| 4 | RECURSIVE WITH PUMP | | | | 411 | +0 | 10M | 10M | | | . | . | 1.90 | Cpu (5) |
============================================================================================================================================================================================

 

次に途中でキャンセルした場合の例。 410sec要していたので、10sec後ぐらいにキャンセルしました。どちらの方法でもキャンセルした場合でも途中までのActual Planを取得できました。MySQLは、Actual Plan取得時のキャンセルの挙動をOracle Database側にあわせたのでしょうかね?

 

SQLモニターの場合、Global informationのStatusにも着目してください。全行取得できたのか、途中で止められたのか、エラーで途中終了したのかなどの情報も確認できます。


  1  WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ MONITOR */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10000000
12 )
13* SELECT v from gen_nums
^CSCOTT@orclpdb1>

 


Global Information
------------------------------
Status : DONE (FIRST N ROWS)
Instance ID : 1
Session : SCOTT (379:17125)
SQL ID : 8m5nwydj0rk2t

...中略...

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 10 | 5.49 | 4.45 | 7216 | 2M |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1492144221)
=========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 19 | +0 | 1 | 722K | . | | |
| 1 | VIEW | | 2 | 4 | 19 | +0 | 1 | 722K | . | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 19 | +0 | 1 | 722K | 34MB | 83.33 | Cpu (5) |
| 3 | FAST DUAL | | 1 | 2 | 1 | +0 | 1 | 1 | . | | |
| 4 | RECURSIVE WITH PUMP | | | | 19 | +0 | 722K | 722K | . | | |
=========================================================================================================================================================

 

次にPostgreSQL


perftestdb=> select version();
version
--------------------------------------------------------------------------------------------------------
PostgreSQL 13.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4), 64-bit
(1 行)

 

キャンセルせず、最後まで実行した例


perftestdb=> explain (analyze, buffers, verbose) 
perftestdb-> WITH RECURSIVE gen_nums(v)
perftestdb-> AS
perftestdb-> (
perftestdb(> SELECT 1
perftestdb(> UNION ALL
perftestdb(> SELECT v + 1
perftestdb(> FROM
perftestdb(> gen_nums
perftestdb(> WHERE v + 1 <= 100000000
perftestdb(> )
perftestdb-> SELECT v from gen_nums;

QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
CTE Scan on gen_nums (cost=3.21..3.83 rows=31 width=4) (actual time=0.007..98357.448 rows=100000000 loops=1)
Output: gen_nums.v
Buffers: temp written=170898
CTE gen_nums
-> Recursive Union (cost=0.00..3.21 rows=31 width=4) (actual time=0.005..67023.784 rows=100000000 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=1)
Output: 1
-> WorkTable Scan on gen_nums gen_nums_1 (cost=0.00..0.26 rows=3 width=4) (actual time=0.000..0.000 rows=1 loops=100000000)
Output: (gen_nums_1.v + 1)
Filter: ((gen_nums_1.v + 1) <= 100000000)
Rows Removed by Filter: 0
Planning Time: 0.090 ms
Execution Time: 103968.625 ms
(13 行)

 

キャンセルした例。
Planning Time: 0.090 ms なので、パースフェーズ中にキャンセルする方が難しいですね。103秒ほど要するので、60秒ぐらいでキャンセルしましたが、PostgreSQLの場合は、Actual Plan取得中のキャンセルでは何も出力されません。
XにPostしたところ、篠田さんから、本体側で対応しないと返せなようだ、とコメントをもらいコードを見始めたり() 機能として、あったら便利かもしれないですね。PostgreSQLでも。キャンセルしたところまでのActual Planを返してくれたら問題解決の糸口になることもあるだろうし。


perftestdb=> explain (analyze, buffers, verbose) 
WITH RECURSIVE gen_nums(v)
AS
(
SELECT 1
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 <= 100000000
)
SELECT v from gen_nums;
^Cキャンセル要求を送信しました
ERROR: canceling statement due to user request
perftestdb=>

 


 

温泉三昧と、いつも満席で諦めてた蕎麦屋のいくつかは食べ歩けた遅いお盆休みも終わり。もう9月が目の前ですね。

 

では、また。

 

 



標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)
標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw
帰ってきた! 標準はあるにはあるが癖の多いSQL #1 SQL de ROT13 やるにも癖が出るw

| | | コメント (0)

2023年8月 4日 (金)

MySQL 8.0.32 / explain analyze 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの?

本年度一発目のw Databaseネタは、なんと、MySQLネタからのスタートですw

MySQL 8.0.18以降、Actualプランが取得できるようになってた。
また、MySQL 8.0.20 以降、KILL QUERY または CTRL-C を使用してこのステートメントを終了できるとある。
ただ、マニュアルに記載されているのはここまでで、キャンセルできることは記載されているが、キャンセルした場合のActual Planはどこまで出力されるのだろう? or 全く出力されない?

OracleのリアルタイムSQL監視だとキャンセルすると不完全ではあるけども、なんとなーく判断できる程度の情報は途中まで取得できるが、
GATHER_PLAN_STATISTICSヒントなどを使ったActualプランは最後まで実行しないとアcつあlはしゅとくできない。どちらのタイプに似ているのでしょうね?

長時間(数日とかw)かかってしまうようになったSQLのActualプラン取得するの意外と難しいケースも場合によってはあったりするわけで。。。そんなとき、Actualみたいけど、キャンセルしたらどうなるのだろうと。。
途中まででも取得できるのか、それとも、Nothingなのか。。。

MySQLのActualプランの取得はどうなんだろう。。。ということで、試してみた。

EXPLAIN ANALYZE による情報の取得
https://dev.mysql.com/doc/refman/8.0/ja/explain.html#explain-analyze





結論から書くと、
MySQL 8.0.32での検証だが、

explain analyzeで取得できるActual Planは、対応しているSQL文の実行中にCTRL-Cでキャンセルしても、その時点までの、Actual Planを表示してくれる!!!!!!

ただし、パース時間が異常に長めのSQL文(結合数めちゃ多いとか)だと、実行以前に、パース時間が長いため、パースフェーズでキャンセルしてしまうの何も表示されない。。と(最初はこちらを引いてしまったので、常に表示されないものかと思い込んでしまった)
他の方法で実行をキャンセルした場合も同じだろう。とは思う。

いや、他にも出力されないケースがあるとか、その検証方法だから出力されているだけどか、MySQLのexplain analyzeのディープなツッコミがありましたら、よろしくお願いします。:)

MySQLの8.0台ってマイナー番号変わっても機能追加されたり変化しているので、一応。 MySQL 8.0.32 上では。ということにしておく。




以下、検証の記録的なもの。

確認に使用した MySQLのバージョン

[master@localhost ~]$ mysql -u scott -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 Source distribution

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)

事前に準備したクエリ向け表と索引など

Database changed
mysql> use perftestdb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql>
mysql> desc master;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

mysql> desc detail;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| subid | int | NO | PRI | NULL | |
| dummya | varchar(10) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> show indexes from master;
+--------+------------+----------+--------------+-------------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Index_type |
+--------+------------+----------+--------------+-------------+------------+
| master | 0 | PRIMARY | 1 | id | BTREE |
+--------+------------+----------+--------------+-------------+------------+
1 row in set (0.02 sec)

mysql> show indexes from detail;
+--------+------------+----------+--------------+-------------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Index_type |
+--------+------------+----------+--------------+-------------+------------+
| detail | 0 | PRIMARY | 1 | id | BTREE |
| detail | 0 | PRIMARY | 2 | subid | BTREE |
+--------+------------+----------+--------------+-------------+------------+
2 rows in set (0.02 sec)

mysql> select * from master;
+----+--------+
| id | dummya |
+----+--------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 10 |
+----+--------+
10 rows in set (0.01 sec)

mysql> select * from detail;
+----+-------+--------+
| id | subid | dummya |
+----+-------+--------+
| 1 | 1 | 11 |
| 1 | 2 | 12 |
| 2 | 1 | 21 |
| 2 | 2 | 22 |
| 3 | 1 | 31 |
...略...
| 7 | 1 | 71 |
| 7 | 2 | 72 |
| 8 | 1 | 81 |
| 8 | 2 | 82 |
| 9 | 1 | 91 |
| 9 | 2 | 92 |
| 10 | 1 | 101 |
| 10 | 2 | 102 |
+----+-------+--------+
20 rows in set (0.01 sec)

検証用に作ったSQL。どのぐらいのElapsed Timeかanaluyzeをつけて実行して、キャンセルしやすい程度のElapsed Timeか確認しておきます。

mysql> explain analyze
-> select *
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.115..37.938 rows=10240 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.108..18.747 rows=5120 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.102..9.267 rows=2560 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.097..4.689 rows=1280 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.091..2.336 rows=640 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.086..1.203 rows=320 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.080..0.630 rows=160 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.075..0.337 rows=80 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.069..0.189 rows=40 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.063..0.110 rows=20 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.041..0.047 rows=10 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.005..0.006 rows=2 loops=10)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.003..0.004 rows=2 loops=20)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.002..0.003 rows=2 loops=40)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=80)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=160)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=320)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=640)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=1280)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=2560)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.002..0.003 rows=2 loops=5120)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (21.18 sec)


では、explain analyseを途中でキャンセルしてみます。elapsed timeが21秒ほどなので、20秒後にキャンセルしてみます。

mysql> explain analyze
select *
from
master inner join detail
on master.id = detail.id
inner join detail t2
on t2.id = master.id
inner join detail t3
on t3.id = master.id
inner join detail t4
on t4.id = master.id
inner join detail t5
on t5.id = master.id
inner join detail t6
on t6.id = master.id
inner join detail t7
on t7.id = master.id
inner join detail t8
on t8.id = master.id
inner join detail t9
on t9.id = master.id
inner join detail t10
on t10.id = master.id
;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql>


! やはり、実行途中のキャンセルだとActual Planは何も出力されないみたい。。。。

??ん? いや、なんか違うな。これw!

SQL文の実行時間はたいしたことない。人間がこの時間内にピンポイントでキャンセルするには短すぎるよね。
SQL文の実行時間は、約38ミリ秒!!! こっちだw 最終に表示されている21秒には、SQLのパース時間も含まれているはず。。。。見逃した。俺としたことが。。。

| -> Nested loop inner join  (cost=5729.85 rows=8192) (actual time=0.115..37.938 rows=10240 loops=1)

キャンセルしたのは、SQL実行後、おおよそ、20秒後。SQLのActualタイムを見ると38ミリ秒なので、ギリギリ、パース時間中に被ってそう。

1 row in set (21.00 sec)

だとしたら、まず、そこから確認。パースにどの程度要しているか、explainだけにすれば、良さそうだよね。Oracle Databaseでもそうだし。(PostgreSQLはパース時間も表示してくれたりするけども。あれ意外に便利なんだよ)

mysql> explain format=tree    -> select *
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192)
-> Nested loop inner join (cost=2862.65 rows=4096)
-> Nested loop inner join (cost=1429.05 rows=2048)
-> Nested loop inner join (cost=712.25 rows=1024)
-> Nested loop inner join (cost=353.85 rows=512)
-> Nested loop inner join (cost=174.65 rows=256)
-> Nested loop inner join (cost=85.05 rows=128)
-> Nested loop inner join (cost=40.25 rows=64)
-> Nested loop inner join (cost=17.85 rows=32)
-> Nested loop inner join (cost=6.65 rows=16)
-> Table scan on master (cost=1.05 rows=8)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (21.84 sec)


お! 予想通り、explainでパースだけさせたら、explain analyze(SQL文の実行を含む)とほぼ同じ 21秒台。想定は正しそう。

だとすると、今回確認したいことを実現するには、..... SQLの実行時間をもっと長くして、人がw キャンセルしやすい程度に実行時間を間延びさせておく必要がある。。
とはいえ、大量のデータを用意する時間もVMのストレージもないと。。。

さて、どうするか。。。。

***** ここで、 一休さん、考え中。。。 木魚の音。ポク、ポク、ポク。。。。w *****


***** ここで、 一休さん、閃いた。。。 鐘の音、チーーーーーん *****
 
sleep()関数を使って、眠らせると時間稼ぎできるのでは?  やってみよう!
おおおおお、いい感じだ。

mysql> explain analyze
-> select *, sleep(0.01)
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.170..232.399 rows=10240 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.164..75.457 rows=5120 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.159..37.659 rows=2560 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.154..17.709 rows=1280 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.149..9.612 rows=640 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.143..4.658 rows=320 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.138..2.285 rows=160 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.132..1.174 rows=80 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.126..0.625 rows=40 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.120..0.347 rows=20 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.097..0.122 rows=10 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.017..0.021 rows=2 loops=10)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.010..0.013 rows=2 loops=20)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.010..0.013 rows=2 loops=40)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.013 rows=2 loops=80)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.014 rows=2 loops=160)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.011..0.015 rows=2 loops=320)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.009..0.012 rows=2 loops=640)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.011..0.015 rows=2 loops=1280)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.014 rows=2 loops=2560)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.010..0.028 rows=2 loops=5120)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (2 min 47.64 sec)


Elapsed Timeがパース時間より長くなったので、できそう。 60秒後にキャンセルすれば余裕はありそう。

1 row in set (2 min 47.64 sec)

explain analyzeを実行して、60秒後にキャンセルした!

mysql> explain analyze
-> select *, sleep(0.01)
-> from
-> master inner join detail
-> on master.id = detail.id
-> inner join detail t2
-> on
-> t2.id = detail.id
-> inner join detail t3
-> on
-> t3.id = t2.id
-> inner join detail t4
-> on
-> t4.id = t3.id
-> inner join detail t5
-> on
-> t5.id = t4.id
-> inner join detail t6
-> on
-> t6.id = t5.id
-> inner join detail t7
-> on
-> t7.id = t6.id
-> inner join detail t8
-> on
-> t8.id = t7.id
-> inner join detail t9
-> on
-> t9.id = t8.id
-> inner join detail t10
-> on
-> t10.id = t9.id
-> ;

^C^C -- query aborted

+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join (cost=5729.85 rows=8192) (actual time=0.113..92.382 rows=2639 loops=1)
-> Nested loop inner join (cost=2862.65 rows=4096) (actual time=0.106..29.480 rows=1320 loops=1)
-> Nested loop inner join (cost=1429.05 rows=2048) (actual time=0.101..14.323 rows=660 loops=1)
-> Nested loop inner join (cost=712.25 rows=1024) (actual time=0.096..7.161 rows=330 loops=1)
-> Nested loop inner join (cost=353.85 rows=512) (actual time=0.090..3.507 rows=165 loops=1)
-> Nested loop inner join (cost=174.65 rows=256) (actual time=0.085..1.624 rows=83 loops=1)
-> Nested loop inner join (cost=85.05 rows=128) (actual time=0.079..0.867 rows=42 loops=1)
-> Nested loop inner join (cost=40.25 rows=64) (actual time=0.073..0.352 rows=21 loops=1)
-> Nested loop inner join (cost=17.85 rows=32) (actual time=0.068..0.210 rows=11 loops=1)
-> Nested loop inner join (cost=6.65 rows=16) (actual time=0.062..0.122 rows=6 loops=1)
-> Table scan on master (cost=1.05 rows=8) (actual time=0.040..0.044 rows=3 loops=1)
-> Index lookup on detail using PRIMARY (id=`master`.id) (cost=0.53 rows=2) (actual time=0.021..0.024 rows=2 loops=3)
-> Index lookup on t2 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.011..0.014 rows=2 loops=6)
-> Index lookup on t3 using PRIMARY (id=`master`.id) (cost=0.51 rows=2) (actual time=0.009..0.012 rows=2 loops=11)
-> Index lookup on t4 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.020..0.024 rows=2 loops=21)
-> Index lookup on t5 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.014..0.017 rows=2 loops=42)
-> Index lookup on t6 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.022 rows=2 loops=83)
-> Index lookup on t7 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.014..0.021 rows=2 loops=165)
-> Index lookup on t8 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.016..0.021 rows=2 loops=330)
-> Index lookup on t9 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.022 rows=2 loops=660)
-> Index lookup on t10 using PRIMARY (id=`master`.id) (cost=0.50 rows=2) (actual time=0.015..0.044 rows=2 loops=1320)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (1 min 3.57 sec)


SQLのActualタイムは、92ミリ秒、処理した行数が、2629行。

| -> Nested loop inner join  (cost=5729.85 rows=8192) (actual time=0.113..92.382 rows=2639 loops=1)

以下を見ると、loopsが、1320回となっている。

    -> Index lookup on t10 using PRIMARY (id=`master`.id)  (cost=0.50 rows=2) (actual time=0.015..0.044 rows=2 loops=1320)

やった〜〜〜。 MySQLの explain analyzeは、"SQLの実行途中"(パース時間が長い割に、実行時間が短い場合は、実行中にキャンセルするのが難しいので表示されないケースはあるので注意)でキャンセルしても、途中までのActual Planを返してくれる!


念の為、sleep()関数を使ったトリッキーな再現方法ではなく、実際にSQLの実行中にキャンセルする方法を思いついたので、さらに、試してみる!


お分かりだろうか。 みなさん大好き(?)、 再起問い合わせ再帰問合せでシーケンス番号を生成するクエリーだ。100000000ぐらい生成すれば、いい感じの実行時間になるだろうと、思われるので、まず、このSQLのパース時間だけ確認。


mysql> SET SESSION cte_max_recursion_depth = 100000000;
Query OK, 0 rows affected (0.00 sec)

パース時間はほぼかかってないですね。先ほどの例は、結合する表が多いので、パース時間は長くなる傾向があるので、シンプルなSQLだけど、実行時間は、なげ〜〜〜〜ぞ〜というのにしてみた。

mysql> explain format=tree
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3)
-> Rows fetched before execution (cost=0.00..0.00 rows=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2)
-> Scan new records on gen_nums (cost=2.73 rows=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


explain analyzeの実時間を確認

mysql> explain analyze
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3) (actual time=139082.095..178091.268 rows=100000000 loops=1)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (actual time=139081.959..139081.959 rows=100000000 loops=1)
-> Rows fetched before execution (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2) (actual time=0.003..29453.367 rows=50000000 loops=2)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..21852.139 rows=50000000 loops=2)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (3 min 8.46 sec)

Actual timeは、178秒なので、60秒後にキャンセルすればよさそうですね。

| -> Table scan on gen_nums  (cost=3.87..5.56 rows=3) (actual time=139082.095..178091.268 rows=100000000 loops=1)

では、キャンセルでどうなるか検証!!

mysql> explain analyze
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 100000000
-> )
-> SELECT v from gen_nums;
^C^C -- query aborted
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (cost=3.87..5.56 rows=3) (never executed)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (never executed)
-> Rows fetched before execution (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 100000000) (cost=2.73 rows=2) (actual time=0.004..27789.355 rows=47924088 loops=1)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..20555.788 rows=47924088 loops=1)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (1 min 3.94 sec)


60行でキャンセルしたので、ほぼ同じ。

1 row in set, 1 warning (1 min 3.94 sec

以下の行ソースを見ると、Actual timeが21秒ほど、rowsも47924088行。


-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.002..20555.788 rows=47924088 loops=1)

興味深い部分は、以下の2行の行ソース。never execute と表示されている。

| -> Table scan on gen_nums  (cost=3.87..5.56 rows=3) (never executed)
-> Materialize recursive CTE gen_nums (cost=3.03..3.03 rows=3) (never executed)
¥


これを見る限り、MySQLのexplain analyzeは、SQL文の実行フェース中にキャンセルすると、その時点までの Actual Planを出力してくれる。これ、結構嬉しいよね。長時間実行で、仕方なくキャンセルするにしても途中の状態。運が良ければ、詰まっている部分が見えるかもしれないわけで。。。


いや、他にも出力されないケースがあるとか、その検証方法だから出力されているだけどか、MySQLのexplain analyzeのディープなツッコミがありましたら、よろしくお願いします。:) (大事なので、2度書いておくw)


以上、explain analyze のブラックボックステスト。 実行途中でキャンセルできるみたいだけど、キャンセルしたら、Actual Plan、途中まで出るの? の巻。終わり。


似てるようで、似てない。それぞれのRDBMSの世界w。 ではまた。

 

参考)
EXPLAIN ANALYZE による情報の取得
https://dev.mysql.com/doc/refman/8.0/ja/explain.html#explain-analyze


| | | コメント (0)

2023年2月 2日 (木)

join elimination(結合の排除)のバリエーション / FAQ

Previously on Mac De Oracle
SQL*Plus -Fastオプション / FAQでした。

 

今日は、結合排除のバリエーションをいくつか紹介しておこうと思います。
なんどか説明していた無駄に結合してないですよね? の例は、参照整合性制約を頼りに結合排除が行われるものでした。

今日はそれに加えて、典型的な例を2つ紹介しておきたいと思います。(ここ” ”、試験に出ませんよ!w でないけど大切:)

一つ目は、結合列がそれぞれユニークキーかプライマリキーで一意で、1 : 0..1 で外部結合されるケース
結合の排除は、その結合を排除しても結果に影響しなことが自明な場合に発動するので、条件を満たしています。ただ、参照整合性制約のパターン同様に、SQL文を見ただけでは気づけないですよね。
あれ、実行計画にSQL文に記載されている表がない!? で制約を見てみて、あ”〜〜〜〜理解。みたいなw

実行計画ではSQLモニターも含め、Join Eliminationしたことを明示的に示すコメント等はありません。結合が消えていることで気づくことが多いわけですw(よーく考えたら、その結合イラねーじゃんというわけですけどもw0

  1* CREATE TABLE foo (id NUMBER PRIMARY KEY, note VARCHAR2(100))

表が作成されました。

1* CREATE TABLE bar (id NUMBER PRIMARY KEY, memo VARCHAR2(100))

表が作成されました。

1 SELECT
2 foo.id
3 , foo.note
4 FROM
5 foo
6 LEFT OUTER JOIN bar
7 ON
8* foo.id = bar.id

実行計画
----------------------------------------------------------
Plan hash value: 1245013993

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 65 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| FOO | 1 | 65 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

この手の制約による結合の排除は、前提になる制約が結合排除の条件からはずれると排除されなくなります。
たとえば、前述の例で、それぞれの表を主キーで 1:0..1 で外部結合していましたよね?
それが仕様変更で、片方の方の主キーが複合主キーになってしまい。1:* の外部結合になってしまうと。。。。結合排除できなくなります。排除した場合、クエリー結果に影響するから。

  1  CREATE TABLE foo2 (
2 id NUMBER
3 , note VARCHAR2(100)
4 , PRIMARY KEY (id)
5* )

表が作成されました。

1 CREATE TABLE bar2 (
2 id NUMBER
3 , sq NUMBER NOT NULL
4 , memo VARCHAR2(100)
5 , PRIMARY KEY (id, sq)
6* )

表が作成されました。

1 SELECT
2 foo2.id
3 , foo2.note
4 FROM
5 foo2
6 LEFT OUTER JOIN bar2
7 ON
8* foo2.id = bar2.id

実行計画
----------------------------------------------------------
Plan hash value: 3679270243

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 78 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 1 | 78 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| FOO2 | 1 | 65 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SYS_C009222 | 1 | 13 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("FOO2"."ID"="BAR2"."ID"(+))

 

では、結合の排除のもう一つの例。これは制約というより、排除を狙った意図的な方法です。
動的なSQlが利用されているアプリケーションで使われていることが多い印象ですが、この方法は好き嫌い激しいかもしれないですね。私は好きじゃないですw が偶にみます。

このクエリは、1:* の外部結合なのですが、WHERE 1=0 によって結果を返しません。なのでクエリー結果に影響しないことが自明なので排除されている。ということになります。

   1  SELECT
2 foo2.id
3 , foo2.note
4 FROM
5 foo2
6 LEFT OUTER JOIN
7 (
8 SELECT * FROM bar2 WHERE 1=0
9 ) bar2
10 ON
11* foo2.id = bar2.id

実行計画
----------------------------------------------------------
Plan hash value: 2844017661

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 65 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| FOO2 | 1 | 65 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

 

ということで、今年も残すところあと11ヶ月ですねw
長いことやってたプロジェクトが一つ形になったみたいな、ならないような、hear through the grapevine.

 

では、また。

 



・Join Elimination(結合の排除)と 参照整合性制約 / FAQ
・実行計画は, SQL文のレントゲン写真だ! Oracle Database (全部俺)Advent Calendar 2022 Day 18 / No.53 / Join Elimination

 

| | | コメント (0)

2023年1月27日 (金)

SQL*Plus -Fastオプション / FAQ

Previously on Mac De Oracle.
前回は、アドベントカレンダーのおまけのおまけwでした

今日は、そこで仕込んでおいたネタを使い、SQL*Plusも機能拡張されてたのすっかり忘れていた! ので、
高Fetch圧症の話に絡めてSQL*Plusの-F[ast]オプション書いておこうと思います。

軽めですが。

Fetch回数削減に効果があるので、多数の行をFetchするような時は思い出すと良いですね。
Client/Server間のrount tripが減ることに繋がるわけで、そこが慢性病の原因なら少しでも楽になれたら良いと思いますし。
(ということで、Fetch Sizeも忘れないでね。という気持ちを込めて。)

最初は、-Fastオプションなしで。arraysizeのデフォルトは 15です。なお、この -F[ast]オプションは、Oracle Database 12c 12.2以降でサポートされています。

[oracle@localhost ~]$ sqlplus scott/tiger@orclpdb1

...略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> @dayx2
1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

1000001行が選択されました。

経過: 00:02:18.83

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 1451M| | 119K (1)| 00:00:05 |
|* 1 | HASH JOIN OUTER | | 1000K| 1451M| 497M| 119K (1)| 00:00:05 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 1000K| 486M| | 19593 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 750K| 723M| | 38796 (1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)


統計
----------------------------------------------------------
1297 recursive calls
0 db block gets
229599 consistent gets
374715 physical reads
0 redo size
1551273711 bytes sent via SQL*Net to client
735146 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
---------------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key ,description
FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL ) SELECT /*+ MONITOR */ *
FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------

...略...

Duration : 173s

...略...

Fetch Calls : 66668

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 17 | 15 | 1.59 | 0.84 | 66668 | 230K | 6878 | 3GB | 5169 | 1GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 172 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 119K | 173 | +1 | 1 | 1M | 5169 | 1GB | 5169 | 1GB | 184MB | 1GB | 76.92 | Cpu (9) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (1) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 1 | +2 | 1 | 1M | 575 | 559MB | | | . | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 38796 | 47 | +2 | 1 | 1M | 1134 | 1GB | | | . | . | 15.38 | Cpu (2) |
==============================================================================================================================================================================================================


次に、-F[ast]オプションで接続します。このオプションにより、ARRAYSIZE = 100に設定されます。それ以外にも3.5.1.5 FASTオプションいくつかの設定が変更されます。

[oracle@localhost ~]$ sqlplus -Fast scott/tiger@orclpdb1

...略...

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SCOTT@orclpdb1> @dayx2
1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

1000001行が選択されました。

経過: 00:01:55.03

実行計画
----------------------------------------------------------
Plan hash value: 2223315184

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 1451M| | 119K (1)| 00:00:05 |
|* 1 | HASH JOIN OUTER | | 1000K| 1451M| 497M| 119K (1)| 00:00:05 |
| 2 | TABLE ACCESS FULL| SUPERTYPE | 1000K| 486M| | 19593 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| NIKOICHI_MITAINA_SUBTYPE | 750K| 723M| | 38796 (1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ST"."PKEY"=CASE WHEN (ROWID(+) IS NOT NULL) THEN CASE WHEN ("COL2"(+) IS
NULL) THEN "COL1"(+) ELSE "COL2"(+) END ELSE NULL END )
3 - filter("COL1"(+) IS NOT NULL OR "COL2"(+) IS NOT NULL)


統計
----------------------------------------------------------
1297 recursive calls
0 db block gets
216797 consistent gets
374715 physical reads
0 redo size
1539940308 bytes sent via SQL*Net to client
110262 bytes received via SQL*Net from client
10001 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000001 rows processed


1 WITH
2 t1 AS
3 (
4 SELECT
5 pkey
6 , CASE
7 WHEN col2 IS NULL
8 THEN col1
9 ELSE col2
10 END AS join_key
11 ,description
12 FROM
13 nikoichi_mitaina_subtype
14 WHERE
15 col1 IS NOT NULL
16 OR col2 IS NOT NULL
17 )
18 SELECT /*+ MONITOR */ *
19 FROM
20 supertype st
21 LEFT OUTER JOIN t1
22 ON
23* st.pkey = t1.join_key

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>NULL,TYPE=>'TEXT')
----------------------------------------------------------------
SQL Monitoring Report

SQL Text
------------------------------
WITH t1 AS ( SELECT pkey , CASE WHEN col2 IS NULL THEN col1 ELSE col2 END AS join_key ,description
FROM nikoichi_mitaina_subtype WHERE col1 IS NOT NULL OR col2 IS NOT NULL ) SELECT /*+ MONITOR */ *
FROM supertype st LEFT OUTER JOIN t1 ON st.pkey = t1.join_key

Global Information
------------------------------

...略...

Duration : 146s

...略...

Fetch Calls : 10001

Global Stats
============================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
============================================================================================
| 17 | 14 | 1.76 | 1.72 | 10001 | 216K | 6878 | 3GB | 5169 | 1GB |
============================================================================================

SQL Plan Monitoring Details (Plan Hash Value=2223315184)
==============================================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
==============================================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 145 | +2 | 1 | 1M | | | | | . | . | | |
| 1 | HASH JOIN OUTER | | 1M | 119K | 146 | +1 | 1 | 1M | 5169 | 1GB | 5169 | 1GB | 184MB | 1GB | 100.00 | Cpu (14) |
| | | | | | | | | | | | | | | | | SQL*Net more data to client (4) |
| 2 | TABLE ACCESS FULL | SUPERTYPE | 1M | 19593 | 1 | +2 | 1 | 1M | 575 | 559MB | | | . | . | | |
| 3 | TABLE ACCESS FULL | NIKOICHI_MITAINA_SUBTYPE | 750K | 38796 | 42 | +2 | 1 | 1M | 1134 | 1GB | | | . | . | | |
==============================================================================================================================================================================================================


-Fastオプションなしの場合と比較してみるとSQL*Net roundtrips to/from clientなど減ってますよね:)
このround tripは、待機イベントSQL*Net more data to clientなどで現れます。(SQL監視のActivity Detailsにも現れていますので、覚えておくと良いと思います)

-Fastオプションなし(auto trace)

1551273711  bytes sent via SQL*Net to client
735146 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client

-Fastオプションあり(auto trace)

1539940308  bytes sent via SQL*Net to client
110262 bytes received via SQL*Net from client
10001 SQL*Net roundtrips to/from client

-Fastオプションなし(SQL監視)

Global Information
------------------------------
...略...
Duration : 173s
...略...
Fetch Calls : 66668

-Fastオプションあり(auto trace)

Global Information
------------------------------
...略...
Dration : 146s
...略...
Fetch Calls : 10001

早く、ポカポカ陽気にならないかなぁ。

と思う寒い日々。

では、また。

| | | コメント (0)

2022年12月31日 (土)

iMovieでクロマキー合成する / FAQ

久々の、Macネタです

最近、GarageBandで曲を作った後、iMovieでフリー映像コンテンツと合わせYouTubeで公開しているのですが、クロマキー合成できることを知った(何周遅れだろうw)

では、試してみましょう。

いくつか試したところ白黒のもできることに気づく!

20221230-82546

使い方は簡単で、背景になる動画の上部にグリーン、ブルー、ブラックの動画を置く。ベースとなる動画と繋がるリンクが現れるのでわかりやすいはず。
その後、赤丸で囲ったドロップダウンメニューから、「ブルー/グリーン」を選択すれば、グリーンとブルー背景の動画は簡単に合成できる。

ブラック背景の動画はそれだけではうまく合成できず、横に並んでいる「クリーンアップ」から消しゴムアイコンを選択して動画の背景になる部分をクリックするとブラック背景の動画もいい感じで合成してくれる。簡単!!

 

フリー動画等はpixabayを使っています。

https://pixabay.com/ja/

| | | コメント (0)

2022年11月23日 (水)

Oracle Database 20c 20.1.0以降〜21c 21.1.0で v$sql_hintに追加されたヒント/ FAQ


Oracle Database 21c 21.1.0 EEで。20.1.0以降で追加されたSQLヒントをみておく。

SYS@ORCLCDB> select * from v$sql_hint where version in ('20.1.0', '21.1.0')  order by version,name;

NAME SQL_FEATURE CLASS INVERSE TARGET_LEVEL PROPERTY VERSION VERSION_OU
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------ ---------- ---------- ----------
ANALYTIC_VIEW_SQL QKSFM_COMPILATION ANALYTIC_VIEW_SQL 2 0 20.1.0
DENORM_AV QKSFM_COMPILATION DENORM_AV 2 0 20.1.0
FORCE_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM NO_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
NO_JSON_TABLE_TRANSFORM QKSFM_JSON_REWRITE FORCE_JSON_TABLE_TRANSFORM FORCE_JSON_TABLE_TRANSFORM 1 0 20.1.0 20.1.0
NO_SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
SET_GBY_PUSHDOWN QKSFM_ALL SET_GBY_PUSHDOWN NO_SET_GBY_PUSHDOWN 2 16 20.1.0 20.1.0
DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS NO_DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
HASHSET_BUILD QKSFM_EXECUTION HASHSET_BUILD 2 16 21.1.0 21.1.0
NO_DAGG_OPTIM_GSETS QKSFM_GROUPING_SET_XFORM DAGG_OPTIM_GSETS DAGG_OPTIM_GSETS 2 0 21.1.0 21.1.0
NO_OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
NO_PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0
OBY_GBYPD_SEPARATE QKSFM_PQ OBY_GBYPD_SEPARATE NO_OBY_GBYPD_SEPARATE 2 16 21.1.0 21.1.0
ORDER_KEY_VECTOR_USE QKSFM_VECTOR_AGG ORDER_KEY_VECTOR_USE 2 272 21.1.0 21.1.0
OSON_GET_CONTENT QKSFM_JSON OSON_GET_CONTENT 1 0 21.1.0 21.1.0
PQ_NONLEAF_SKEW QKSFM_PQ PQ_NONLEAF_SKEW NO_PQ_NONLEAF_SKEW 4 272 21.1.0 21.1.0

15行が選択されました。

| | | コメント (0)

2022年7月 1日 (金)

explain plan文 De 索引サイズ見積 / FAQ

久々の投稿ですw

というか、Oracle ACEのKPIを考えるとどうしても、こうなってしまう大人の事情。

今期一発目の投稿は、意外と知られていない? explain plan文 De 索引サイズ見積。

統計情報などに依存はしますが、100億年に一度ぐらい、索引サイズどれぐらいになるかねぇ。みたいな聞かれかたしたときに、サクっとタイプして、ほれ!

と、Slackでなげかえしちゃって、飲みに行きましょうね。そこ必死にやるところじゃない時代なわけで。

では、21cもあるのですが、データ仕込むのめんどくさかったので、ありもの 19cの環境で試してみましょう。ちなみに、explain plan で索引サイズを見積もるのは私の記憶によると10gぐらいから使ってた記憶はあるので、昔からのOraclerだと知ってる方は多いはず。(もっと前からあるよーというツッコミ歓迎w)

表の存在とデータを大量に登録してあるtest表を使います。統計は最新化

[oracle@localhost ~]$ sqlplus scott/tiger

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Jun 29 22:59:46 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle. All rights reserved.

Last Successful login time: Mon Jun 06 2022 21:39:58 -04:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> desc test
Name   Null? Type
------- ------------------------ -------- ----------------------------
NUM NUMBER

SQL> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'TEST',cascade=>true,no_invalidate=>false);

PL/SQL procedure successfully completed.

explain plan文でcreate index文を解析します。索引は作成されないので、躊躇なくタイプしちゃってくださいw
解析が終わったら、utlxpls.sqlを実行すれば見積もりサイズを確認できます。

10m行登録してるのでそれなりのサイズになるようですね。243MB という見積もりがでました!

SQL> explain plan for create index test on test(num);

Explained.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2829245909

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | CREATE INDEX STATEMENT | | 10M| 57M| 9958 (1) | 00:00:01 |
| 1 | INDEX BUILD NON UNIQUE| TEST | | | | |
| 2 | SORT CREATE INDEX | | 10M| 57M| | |
| 3 | TABLE ACCESS FULL | TEST | 10M| 57M| 4414 (2) | 00:00:01 |
-------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Note
-----
- estimated index size: 243M bytes

14 rows selected.

SQL>

Explain plan文以外では、使い勝手が悪いというかタイプする文字数多くて嫌いな、DBMS_SPACE.CREATE_INDEX_COST() があります。
以下のような無名PL/SQLブロックを書いておくか、あらかじめ俺俺関数(UDFね)として登録しておくと便利ですが、explain planでいいかなぁ。私はw

set serveroutput on
DECLARE
used_bytes NUMBER;
segment_bytes NUMBER;
BEGIN
DBMS_SPACE.CREATE_INDEX_COST (
ddl=> 'CREATE INDEX test ON test(num)'
, used_bytes => used_bytes
, alloc_bytes => segment_bytes
);
DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
END;
/

Explain planより情報量は多いですが、セグメントサイズがどれぐらいになるか知りたいわけなので、他の情報は捨てちゃうことが多い感じはします。
DBMS_SPACE.CREATE_INDEX_COST()パッケージプロシージャでは 232MB という見積もり結果となりました。

SQL> set serveroutput on
SQL> l
1 DECLARE
2 used_bytes NUMBER;
3 segment_bytes NUMBER;
4 BEGIN
5 DBMS_SPACE.CREATE_INDEX_COST (
6 ddl=> 'CREATE INDEX test ON test(num)'
7 , used_bytes => used_bytes
8 , alloc_bytes => segment_bytes
9 );
10 DBMS_OUTPUT.PUT_LINE('Segment Size (MB) :'||segment_bytes/1024/1024);
11 DBMS_OUTPUT.PUT_LINE('Index data Size (MB) :'||used_bytes/1024/1024);
12* END;
SQL> /
Segment Size (MB) :232
Index data Size (MB) :57.220458984375

PL/SQL procedure successfully completed.

SQL>

実際のセグメントサイズはどれぐらいでしょう? 実際に索引を作ったあとセグメントサイズをみてみました。

SQL> select segment_name,bytes/1024/1024 "MB" from user_segments where segment_name='TEST' and segment_type='INDEX';

SEGMENT_NAME MB
------------------------------ ----------
TEST 192

SQL>



今年も半年すぎたけど、アドベントカレンダー全部俺をやるべきか悩む。まとめてアウトプットするので、ちびちびアウトプットするのとどちらがよいか。。。w

では、また。

| | | コメント (0)

2022年5月28日 (土)

bashからzshへdefault shellが変わったんだった / FAQ

zsh を Mac のデフォルトシェルとして使う

https://support.apple.com/ja-jp/HT208050

 

ということで、変えた時のメモ。Terminal開いたら、↑そう言われたので。

chsh -s /bin/zsh

を実行

Bash1 

パスワード聞かれるので、入力

Bash2

Terminal終了して、起動し直すと、zsh切り替わり

Zsh1

ということで、無事に変更できました。と。

| | | コメント (0)

2022年4月11日 (月)

実行計画は、SQL文のレントゲン写真だ! No.35 - 似て非なるもの USE_CONCAT と OR_EXPANDヒントとパラレルクエリー

Previously on Mac De Oracle
前回は、その前のエントリーの流れから、標準はあるにはあるが癖の多いSQL - #27 LNNVL is 何? と思った方向けでちょいと脱線してました。
今日は、話を元に戻しますw

USE_CANTATとOR_EXPAND、レントゲン(実行計画)をみて、どこがどう違うのかは理解できたのではないかと思います。ではなぜ、今後使うとしたら、OR_EXPANDなのかは、USE_CONCATとより言うことを聞いてくれやすいという他にもう一つあるのですが、それは何かわかりますか?
大人の事情で、しばらく関わりが薄かった時期(w にこのヒントの効果を知ったのですが、もう一つのメリットまでは知らなかったんですよw。 斜め読みだけしてると取りこぼしちゃいますねw

答えはパラレルクエリーにした場合の違い。

OR_EXPANDによる書き換えとUNION-ALLへの内部的な書き換えの効果で、パラレルクエリーとの相性が良くなっているんですよね。

早速、レントゲンをみてみましょう :)
(あ、書き忘れてましたが、Oracle Database 21cを使ってます)

USE_CONCATを使ってCONCATENATION(Id=1のoperation)を強制してかつパラレルクエリーにしています。PX COORDINATOR が Id=2とId=9に現れているのでUNIONの各SELECT文はシリアルに実行されているようですね。この挙動は変わってなさそうです。

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 use_concat
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.44

実行計画
----------------------------------------------------------
Plan hash value: 1305058436

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 8 (0)| 00:00:01 | | | |
| 1 | CONCATENATION | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ20001 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q2,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 6 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | | | |
| 9 | PX COORDINATOR | | | | | | | | |
| 10 | PX SEND QC (RANDOM) | :TQ10001 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 13 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 14 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | | S->P | HASH (BLOCK|
|* 15 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | | | |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

8 - access("SUB_ITEM_CODE"='0001000000')
15 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

OR_EXPANDでU内部的にUNION-ALLに書き換えてパラレルクエrーにすると。。。。。。おーーーーー。違う!!! Id=1にあるPX COORDINATOR だけになってますね。各SELECT文もパラレル化されているようです。:)
結構違いますね。やはり、使うなら、USE_CANTATよりOR_EXPANDのようが良さそうですね。これで思い出した! ORDERED と LEADINGヒントのような感じですかねー。同じ機能を持つ後発ヒントの方が色々と使い勝手が良くなってることって意外に多いです!

SCOTT@orclpdb1> r
1 select
2 /*+
3 parallel(4)
4 or_expand
5 */
6 *
7 from
8 tab311
9 where
10 unique_id= 1
11* or sub_item_code = '0001000000'

経過: 00:00:00.14

実行計画
----------------------------------------------------------
Plan hash value: 3317360125

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 8 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | VW_ORE_5F0E22D2 | 4 | 1160 | 8 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 1 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 1 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 1 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 1 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


前々回手動でunionに書き換えたSQLをパラレルにするとどうなるだろう。。。
ほう。

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

実行計画
----------------------------------------------------------
Plan hash value: 3983264199

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (19)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10003 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | P->S | QC (RAND) |
| 3 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,03 | PCWP | |
| 5 | PX SEND HASH | :TQ10002 | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | P->P | HASH |
| 6 | HASH UNIQUE | | 5 | 1345 | 11 (19)| 00:00:01 | Q1,02 | PCWP | |
| 7 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 8 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 10 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 11 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 12 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 13 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 2 | 538 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
---------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

12 - access("UNIQUE_ID"=1)
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

同じく、前々回手動でunion all + フィルタ条件追加に書き換えたSQLをパラレルにするとどうなるだろう。。。
おおおおおーーーーーーっと。これはCONCATENATIONの実行計画にそっくりですね。CONCATENATIONの部分がUNION-ALLになっている程度の違い。2つのPX COORDINATOR がある点も共通しています。。。むむ。

このSQLをOR_EXPANDの実行計画と同じようにするには......あ! あれだ!

select
/*+
parallel(4)
*/
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

実行計画
----------------------------------------------------------
Plan hash value: 1844591072

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1076 | 9 (0)| 00:00:01 | | | |
| 1 | UNION-ALL | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10001 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | P->S | QC (RAND) |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,01 | PCWP | |
| 5 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 6 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,01 | PCWP | |
| 7 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 8 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 9 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
| 10 | PX COORDINATOR | | | | | | | | |
| 11 | PX SEND QC (RANDOM) | :TQ20001 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | P->S | QC (RAND) |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q2,01 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q2,01 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q2,01 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ20000 | 2 | | 3 (0)| 00:00:01 | Q2,00 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q2,00 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q2,00 | SCWP | |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

9 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint


ということで、
前々回手動でunion all + フィルタ条件追加に書き換えたSQLを単純にパラレルクエリーにしてもイマイチだったので、OR_EXPANDのような実行計画にするために、インラインビューにしてみました!!! どうでしょう? OR_EXPANDの実行計画と同じようになりました。
ポイントは、前々回のOR_EXPANDの実行計画中に現れるインラインビュー VW_ORE_5F0E22D2 です。内部的にインラインビューを追加してるんですよね! OR_EXPANDのUNION ALL書き換え。
インラインビュー化したことで、Id=4にビューが登場しています。OR_EXPANDでは、VW_ORE_* と名付けられるOR_EXPANDトランスフォームにより追加されるインラインビューと同じ役割を持っていますが、内部的に書き換えられて追加されるインラインビューとは異なり動的に名称が付加されません。

インラインビューが決めて! というか、意外と忘れがちなので注意しないとね。

select
/*+
parallel(4)
*/
*
from
(
select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1)
);

経過: 00:00:00.03

実行計画
----------------------------------------------------------
Plan hash value: 3706965944

-------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 1160 | 9 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | BUFFER SORT | | 4 | 1160 | | | Q1,02 | PCWP | |
| 4 | VIEW | | 4 | 1160 | 9 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | UNION-ALL | | | | | | Q1,02 | PCWP | |
| 6 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 3 | 807 | 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 7 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 8 | PX RECEIVE | | 3 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 9 | PX SEND HASH (BLOCK ADDRESS) | :TQ10000 | 3 | | 3 (0)| 00:00:01 | Q1,00 | S->P | HASH (BLOCK|
| 10 | PX SELECTOR | | | | | | Q1,00 | SCWC | |
|* 11 | INDEX RANGE SCAN | TAB311_PK | 3 | | 3 (0)| 00:00:01 | Q1,00 | SCWP | |
|* 12 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 5 (0)| 00:00:01 | Q1,02 | PCWP | |
| 13 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 14 | PX RECEIVE | | 2 | | 3 (0)| 00:00:01 | Q1,02 | PCWP | |
| 15 | PX SEND HASH (BLOCK ADDRESS) | :TQ10001 | 2 | | 3 (0)| 00:00:01 | Q1,01 | S->P | HASH (BLOCK|
| 16 | PX SELECTOR | | | | | | Q1,01 | SCWC | |
|* 17 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 2 | | 3 (0)| 00:00:01 | Q1,01 | SCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("UNIQUE_ID"=1)
12 - filter(LNNVL("UNIQUE_ID"=1))
17 - access("SUB_ITEM_CODE"='0001000000')

Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 4 because of hint

4月はじめだと言うのに、夏日とか、北の方面の友人からは31度だとか、最近の異常気象ほんとに農家泣かせな感じ。最近は天気予想が細かい範囲ででるので以前より対応しやすいのかもしれないけど。
こんな、陽気だとぶらりと湘南あたりからリモートワークしたいw

ではまた。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS
・実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

| | | コメント (0)

2022年4月 9日 (土)

標準はあるにはあるが癖の多いSQL - #27 LNNVL is 何? と思った方向け

Previously on Mac De Oracle.
前回のエントリで使った関数覚えてますか? LNNVL関数。 

Oracle純正の方言で、他のデータベースがネイティブでサポートしてるのって無さそうと思いつつ、気になったので軽くしらべてみた。

LNNVL
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LNNVL.html#GUID-FBCCE9B1-614E-45FA-8EE1-DFAA4F936867


基本的に移行対応や互換目的ぐらいですね。

Oracle Database の LNNVL を PostgreSQL で実現する
https://taityo-diary.hatenablog.jp/entry/2018/04/30/222335

PolarDB for Oracle にはある模様ですね。互換ということなので、だよね。というところですね。
https://www.alibabacloud.com/help/en/polardb-for-oracle/latest/lnnvl-function

折角なので、↑の例題の答えあわせしてみました。


SCOTT@orclpdb1>  set null [null]
SCOTT@orclpdb1> select * from account where lnnvl(year is not null);

NAME YEAR
------------------------------------------------------------ ----------
peter2007 [null]

SCOTT@orclpdb1> select * from account where lnnvl(year<2003);

NAME YEAR
------------------------------------------------------------ ----------
peter2003 2003
peter2004 2004
peter2005 2005
peter2006 2006
peter2007 [null]

SCOTT@orclpdb1> select * from account where lnnvl(year is null);

NAME YEAR
------------------------------------------------------------ ----------
peter2001 2001
peter2002 2002
peter2003 2003
peter2004 2004
peter2005 2005
peter2006 2006

6行が選択されました。

SCOTT@orclpdb1> select * from account where lnnvl(year=2008);

NAME YEAR
------------------------------------------------------------ ----------
peter2001 2001
peter2002 2002
peter2003 2003
peter2004 2004
peter2005 2005
peter2006 2006
peter2007 [null]

7行が選択されました。

SCOTT@orclpdb1> select * from account where lnnvl(year! =2008);

NAME YEAR
------------------------------------------------------------ ----------
peter2007 [null]

合ってそう。

PostgreSQLのExtensionである、oraface ではサポートしてますね。移行需要多いですからね。
https://github.com/orafce/orafce/search?q=LNNVL

たまたま見つけたのですが、Apache Spark。コメントのやりとりみて、まあ、そうですよねーーーというオチだったw
https://issues.apache.org/jira/browse/SPARK-21931

NVL2ほどは見当たらない、かなり強めの方言ですからね。 RedshiftやSnowflakeでもないね。これww 
では、また。

 



標準はあるにはあるが癖の多い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 全部俺 #26 おまけ SQL de 湯婆婆やるにも癖がでるw

 

| | | コメント (0)

2022年4月 7日 (木)

実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS


Previously on Mac De Oracle
前回は外部表特有のoperationであるEXTERNAL TABLE ACCESS FULL / INMEMORY FULL のレントゲンでした。

今日は、昔からあるBITMAP CONVERSION TO ROWIDSを見てみたいと思います。

SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換
SQLチューニング・ガイド 8.4.2 ビットマップのROWIDへの変換 / 21c


このオペレーションは、複数の索引からbitmapを生成しその結果のrowidを用いて表をアクセスするところにあります。通常一つの索引が利用されますが、この場合は複数の索引が利用されるところが特徴です。
ただ、bitmapに変換コストより、unionに書き換えたり(内部的な書き換えも含む)したほうが効率が良かったりします。なので意外と嫌われてたりw なので、STAR TRANSFORM などで見るぐららいで、結構それ以外の方向へチューニングされているケースのほうが多いかもしれません。でもこれで問題なければそのままでも問題はないわけですが。

あ、そういえば、以前、CONCATENATIONのレントゲンを紹介していましたね。
ちょうどよいので、CONCATENATIONのレントゲン撮影時と同じ表とSQL文を使って BITMAP CONVERSION TO ROWIDS のレントゲンを見てみましょう :)

SCOTT@orclpdb1> desc tab311
名前 NULL? 型
----------------------------------------- -------- ----------------------------
UNIQUE_ID NOT NULL NUMBER(10)
SUB_ITEM_CODE NOT NULL CHAR(10)
FOO NOT NULL VARCHAR2(500)
IS_DELETE NOT NULL NUMBER(1)

SCOTT@orclpdb1> select count(1) from tab311

COUNT(1)
----------
2000000

経過: 00:00:00.09

実行計画を見てわかると思いますが、 2つの索引(TAB311_PK, TAB311_IX_SUB_ITEM_CODE)のROWIDからBITMAPを作り(Id=3,7)、それを BITMAP OR (SQL文の7行目 Id=3)した結果をROWIDへ変換(Id=2)、複数のROWIDをまとめ、IOリクエストを少なくするための ROWID BATCHED(Id=1)で表(TAB311)をアクセスしていことが読み取れます。
ROWIDでアクセスするので、基本的に少量の行にアクセスする場合には有利ではあります。ただ、BITMAPへの変換コスト次第というところではあるわけです。なので、BITMAPの変換のないタイプのトランスフォームを狙ったHINTを利用したり、SQL文自体を書き換えたりするケースは少なくありません。意外に嫌いな方が多くてw 大抵チューニングされてしまい、あまり見かけることはないかもしれませんw 

SCOTT@orclpdb1> r
1 select
2 *
3 from
4 tab311
5 where
6 unique_id= 1
7* or sub_item_code = '0001000000'

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1263461875

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
1103 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

ちなみに、CONCATENATIONのエントリーを見ていただくのがよいとは思いますが、これも比較的古くからある、CONCATENATIONを使ったSQL変換のレントゲンも改めて載せておきます。
(USE_CONCATヒントで強制しています。みなさん、知っているとは思いますが、NO_EXPANDヒントが逆のヒントです)

実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 Day 17 / CONCATENATION
UNION のような実行計画ですが、UNIONとは出てませんw これはまた別の機会に。ただ、ほぼ同等の意味で、OR条件でそれぞれに最適な索引を使うことでindex range scanやindex unique scanを効かせて高速にアクセスしようとしています。
BITMAPとの相互変換などが無い分、安定して早いケースは経験的にも多いのは確かです。どちらを選ぶかはやはり、登録されているデータの傾向と検索条件次第ではあります。ただ一般的BITMAP変換を避ける傾向が強いのは確かではありますね。

SCOTT@orclpdb1> r
1 select
2 /*+
3 use_concat
4 */
5 *
6 from
7 tab311
8 where
9 unique_id= 1
10* or sub_item_code = '0001000000'

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 1344230703

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1091 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

GWも間近だ。そろそろ予定考えないとな。その前にACEのKPIはクリアしておかないと。追い込み追い込みw


ということで、次回へつづく。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

| | | コメント (0)

2022年4月 1日 (金)

実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL

Previously on Mac De Oracle
前回は、ルーティーンとなったw、19cと20cのパラメータ差分チェックでした。

今日は、元の路線に戻り、「実行計画は、SQL文のレントゲン写真だ!」シリーズです :)

前回のパラメータ差分チェックで、外部表を利用していたので、18c以降で変更された In-Memory External Tables とそれまでの External Tableのレントゲンを見ておこうと思います。

利用するのはOaracle Database 21cですが、In-Memory External Tablesは、18c以降であれば使える機能なので使えるはず!


12cまでのnon In-Memory External Tablesなころのレントゲンからです。

外部表は EXTERNAL TABLE ACCESS FULL というオペレーションになっています。CSVファイルを全て読み込んでいることを表ています。ここ大切なので、覚えておきましょう。

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 2727K| | 629 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 8168 | 2727K| 2984K| 629 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 8168 | 2727K| | 30 (4)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 8168 | 2727K| | 30 (4)| 00:00:01 |
| 4 | VIEW | | 838 | 139K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 838 | 58660 | | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_11_1_0_7_0 | 8168 | 1363K| | 29 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

外部表をフルスキャンしている。実際にはcsvファイルをまるっと読んで、ですよねーー。という感じ。外部表を利用されたことのある方であれば、ふむふむというところだと思います。

では21cの環境に切り替えて、前回Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden paramsを行った sysユーザーに接続して違いを見ていきましょう。

まずは、インメモリーを使わない状態で見てみます。(rpmでインストールしてconfigureしただけの21cデフォルトの状態。。のはずw。 こちらではカスタマイズしてないので)

ビルド表とプローブ表が12cR1の実行計画と逆になってますが、まあ気にしないw
外部表は、12cR1と同様に EXTERNAL TABLE ACCESS FULL ですね。

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 Version 21.3.0.0.0

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5997 | 2002K| | 457 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 5997 | 2002K| 2192K| 457 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 5997 | 2002K| | 15 (7)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 5997 | 1399K| | 15 (7)| 00:00:01 |
| 4 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 5412 | 359K| | 14 (0)| 00:00:01 |
| 5 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 6 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
----------------------------------------------------------------------------------------------------------


では、外部表をインメモリー対応に作り変えます。
INMEMORY句を追加してる箇所がポイントですね。

DROP TABLE ksppi_19_3_0_0_0;
CREATE TABLE ksppi_19_3_0_0_0 (
ksppinm VARCHAR2(80)
,ksppdesc VARCHAR2(255)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '|'
(
ksppinm
,ksppdesc
)
)
LOCATION (
'19.3.0.0.0.ksppi.csv'
)
)
INMEMORY MEMCOMPRESS FOR CAPACITY
;


おおお?? ビルド表とプローブ表が入れ替わりましたが、外部表は、EXTERNAL TABLE ACCESS FULL のままですね。
なにか設定し忘れているようです。

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

ん、外部表自体のインメモリー定義は問題なさそうですね。

SYS@ORCLCDB> r
1 select table_name, inmemory, inmemory_compression
2* from user_tables where EXTERNAL = 'YES'

TABLE_NAME INMEMORY INMEMORY_COMPRESSION
------------------------------ ------------------------ ---------------------------------------------------
OPATCH_XML_INV DISABLED
KSPPI_19_3_0_0_0 ENABLED FOR CAPACITY LOW


ポピュレーションされてないのか?

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


あ”〜〜っ! Oracle ACEのKPI入力期限まであと3ヶ月ある。あせるなwwww 大丈夫だw(謎

では、手動で。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。


ん? なんで?
あれか!

SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 0


やはり、だよね〜w デフォルトのままだもの。そりゃそうだ。

では、変更しましょう。inmemory_sizeは、最低 100MBは指定しないといけないので、今回は最低サイズ(十分だと思うので)に。

SYS@ORCLCDB> create pfile from spfile;

ファイルが作成されました。

SYS@ORCLCDB> alter system set inmemory_size = 100m scope=spfile;

システムが変更されました。

SYS@ORCLCDB> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@ORCLCDB> startup
ORACLEインスタンスが起動しました。

Total System Global Area 5586811432 bytes
Fixed Size 9697832 bytes
Variable Size 956301312 bytes
Database Buffers 4496293888 bytes
Redo Buffers 7077888 bytes
In-Memory Area 117440512 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@ORCLCDB> show parameter inmemory_size

NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
inmemory_size big integer 112M


よし!! これで大丈夫なはず。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

レコードが選択されませんでした。

SYS@ORCLCDB> exec dbms_inmemory.populate('SYS','KSPPI_19_3_0_0_0');

PL/SQLプロシージャが正常に完了しました。

SYS@ORCLCDB> select SEGMENT_NAME,INMEMORY_SIZE,BYTES_NOT_POPULATED,POPULATE_STATUS,IS_EXTERNAL from v$im_segments;

SEGMENT_NAME INMEMORY_SIZE BYTES_NOT_POPULATED POPULATE_STATUS IS_EXTERNAL
------------------------------ ------------- ------------------- --------------------------------------- ---------------
KSPPI_19_3_0_0_0 1179648 0 COMPLETED TRUE

では、こんどこそ!!

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

あれ〜〜〜〜〜〜〜っ。 もう一つ忘れてた。query_rewrite_integrity.

Database Reference 2.294 QUERY_REWRITE_INTEGRITY
https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/QUERY_REWRITE_INTEGRITY.html

SYS@ORCLCDB> alter session set query_rewrite_integrity=stale_tolerated;

セッションが変更されました。


外部表のオペレーションが EXTERNAL TABLE ACCESS INMEMORY FULL に変化しました。

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 102K| 33M| | 7807 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 102K| 33M| 36M| 7807 (1)| 00:00:01 |
| 2 | VIEW | VW_FOJ_0 | 102K| 33M| | 341 (1)| 00:00:01 |
|* 3 | HASH JOIN FULL OUTER | | 102K| 33M| | 341 (1)| 00:00:01 |
| 4 | VIEW | | 5997 | 1001K| | 1 (100)| 00:00:01 |
|* 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 415K| | 1 (100)| 00:00:01 |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL| KSPPI_19_3_0_0_0 | 102K| 16M| | 341 (1)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------------


まとめ

18cで登場した In-Memory External Tables のレントゲンは以下の通り。
EXTERNAL TABLE ACCESS INMEMORY FULL というオペレーションがポイントです。
また、INMEMORY_SIZEパラメータの設定や、QUERY_REWRITE_INTEGRITYパラメータをstale_toleratedにする必要あります。忘れがち?!


21cでの non in-memory external tables と in-memory external tables のレントゲンをSQL MONITORの実行計画を。

non in-memory external tables : EXTERNAL TABLE ACCESS FULL

Global Stats
=====================================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
=====================================================================================================
| 0.06 | 0.05 | 0.00 | 0.00 | 0.00 | 406 | 121 | 1 | 512KB | 16 | 683 |
=====================================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (%) | (# samples) |
========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +1 | 1 | 6066 | | | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 1 | +1 | 1 | 6066 | | | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +1 | 1 | 6066 | | | | | 2MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +1 | 1 | 5997 | | | | | . | | |
| 6 | EXTERNAL TABLE ACCESS FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 1 | +1 | 1 | 5412 | 1 | 512KB | 16 | | . | | |
========================================================================================================================================================================================

in-memory external tables : EXTERNAL TABLE ACCESS INMEMORY FULL

Global Stats
======================================================================================
| Elapsed | Cpu | IO | PL/SQL | Other | Fetch | Buffer | Write | Write |
| Time(s) | Time(s) | Waits(s) | Time(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
======================================================================================
| 0.04 | 0.04 | 0.00 | 0.00 | 0.00 | 406 | 48 | 16 | 683 |
======================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1973289935)
==================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Write | Write | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 2 | +0 | 1 | 6066 | | | . | | |
| 1 | SORT ORDER BY | | 102K | 7807 | 2 | +0 | 1 | 6066 | | | 676KB | | |
| 2 | VIEW | VW_FOJ_0 | 102K | 341 | 1 | +0 | 1 | 6066 | | | . | | |
| 3 | HASH JOIN FULL OUTER | | 102K | 341 | 1 | +0 | 1 | 6066 | | | 3MB | | |
| 4 | VIEW | | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 5 | FIXED TABLE FULL | X$KSPPI | 5997 | 1 | 1 | +0 | 1 | 5997 | | | . | | |
| 6 | EXTERNAL TABLE ACCESS INMEMORY FULL | KSPPI_19_3_0_0_0 | 102K | 341 | 2 | +0 | 1 | 5412 | 16 | | . | | |
==================================================================================================================================================================================

レントゲンは大切 !!!! :)


東京は一気に葉桜になりつつある。。。ではまた。





Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

| | | コメント (0)

2022年3月31日 (木)

Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden params

Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0) - including hidden params

Previously on Mac De Oracle
前回は、21cではお約束のパラメータ増加傾向を確認しました。今回もお約束のパラメータ差分をw

新しいメジャーリリースでたらやっとかないとねw

利用しているSQLなどは、昔のエントリーにサンプルがあるので参考にしてみてください。(うまく動作しないなどあれば、コメント頂けると幸いです。細かく確認してないので)

作成した19cと21cのパラメータ差分は↓↓↓↓↓↓↓
Difference of Initialization Parameters between 19c (19.3.0.0.0) and 21c (21.3.0.0.0)

作成方法は、みなさん知っていると思うので省略してもいいかもしれないですがw 軽めに書いておきます。
19cのパラメータをcsv化して、21cでは外部表として使います(詳細は前述のリンク先エントリーで確認ください)

適当なディレクトリに19cのパラメータのcsvファイルを置いて、ディレクトリオブジェクトを作成したあとに、外部表を作成しています。

[oracle@localhost ~]$ pwd
/home/oracle
[oracle@localhost ~]$ mkdir exttab
[oracle@localhost ~]$
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 水 3月 30 18:39:26 2022
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
SYS@ORCLCDB>
SYS@ORCLCDB> !ls -l exttab
合計 2688
-rw-r--r--. 1 oracle oinstall 2749046 3月 30 18:38 19.3.0.0.0.ksppi.csv

SYS@ORCLCDB>
SYS@ORCLCDB>
SYS@ORCLCDB> CREATE DIRECTORY ext_tab AS '/home/oracle/exttab';
SYS@ORCLCDB>
SYS@ORCLCDB> @cre_19c_paramtab
SYS@ORCLCDB>
SYS@ORCLCDB> !cat cre_19c_paramtab.sql
CREATE TABLE ksppi_19_3_0_0_0 (
ksppinm VARCHAR2(80)
,ksppdesc VARCHAR2(255)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '|'
(
ksppinm
,ksppdesc
)
)
LOCATION (
'19.3.0.0.0.ksppi.csv'
)
)
/

できたみたいなので、外部表を問い合わせてみましょう! 

SYS@ORCLCDB> select * from ksppi_19_3_0_0_0 fetch first 10 rows only;

KSPPINM KSPPDESC
------------------------------------------- -------------------------------------------------------------------
_appqos_qt System Queue time retrieval interval
_appqos_po_multiplier Multiplier for PC performance objective value
_appqos_cdb_setting QoSM CDB Performance Class Setting
_ior_serialize_fault inject fault in the ior serialize code
_shutdown_completion_timeout_mins minutes for shutdown operation to wait for sessions to complete
_inject_startup_fault inject fault in the startup code
_session_modp_list send session's modified parameter list to client
_wait_outlier_detection_enable Enable wait outlier detection module?
_wait_outlier_event_names Wait events to watch for outliers
_wait_outlier_lambda_x1000 Lambda (in thousands) to compute outliers

10行が選択されました。


最後に、htmlファイル形式で 差分を取った結果を出力します :)
簡単ですよね :)

以前作ったものを多少変更しただけ

[oracle@localhost ~]$ cat get_diff.sql
col "19c 19.3.0.0.0" for a17
col "21c 21.3.0.0.0" for a17
set pagesize 10000
set timi off
set feed off
set markup html on spool on
spo param-diff.html
SELECT
CASE
WHEN prev.ksppinm = curr.ksppinm
OR (
prev.ksppinm IS NULL
AND curr.ksppinm IS NOT NULL
)
THEN curr.ksppinm
ELSE prev.ksppinm
END AS ksppinm
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL THEN '○'
ELSE 'n/a'
END AS "19c 19.3.0.0.0"
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN '○'
ELSE 'n/a'
END AS "21c 21.3.0.0.0"
,CASE
WHEN prev.ksppdesc = curr.ksppdesc THEN curr.ksppdesc
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN curr.ksppdesc
WHEN prev.ksppinm iS NOT NULL AND curr.ksppinm IS NULL
THEN prev.ksppdesc
ELSE prev.ksppdesc
END AS kspdesc
,CASE
WHEN prev.ksppdesc = curr.ksppdesc
OR (prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL)
OR (prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL)
THEN NULL
ELSE curr.ksppdesc
END AS "New description"
FROM
(
SELECT * FROM x$ksppi WHERE con_id = 0
) curr
FULL OUTER JOIN ksppi_19_3_0_0_0 prev
ON
curr.ksppinm = prev.ksppinm
ORDER BY
1
;
spool off
set feed on
set markup html off

実行するとこんな感じで HTML ファイルの出来上がり!!

SYS@ORCLCDB> @get_diff.sql
...略...
are XML DB events enabled
</td>
<td>
 
</td>
</tr>
</table>
<p>
SYS@ORCLCDB>


さて、次は、再び、実行計画というレントゲンのお話に戻りますかw 



関連エントリー
21cの初期化パラメータ数や隠しパラメータ数変化
Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0) - including hidden params
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた
Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params
Difference of Initialization Parameters between 11g and 12c #2

| | | コメント (0)

21cの初期化パラメータ数や隠しパラメータ数変化

もうお約束になった感じですが、
21c環境を作ったので、お約束のパラメータ数差分確認を。増えることはあっても減ることは無いですよね。

09:31:44 SYS@ORCLCDB> select banner_full from v$version;

BANNER_FULL
------------------------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production Version 21.3.0.0.0


いい感じで、隠しパラメータが増加してますよね。もう手に負えない数ですけども。 それ以外は、びっくりするほどの増加ではないですね。増えてはいますが。

CATEGORY                                 Num Of Parameters
---------------------------------------- -----------------
1. Single underscore parameters 5486
2. Double underscore parameters 32
3. Non hidden parameters 479
-----------------
Total 5997

リリース毎の各パラメータ数の変化をまとめたグラフ
20220330-225912


さて、次回は、19cと21cのパラメータ差分。(新バージョンリリースされたあとのルーティーンになってきたw



関連エントリー
Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0) - including hidden params
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた
Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params
Difference of Initialization Parameters between 11g and 12c #2

| | | コメント (0)

2022年3月30日 (水)

Oracle Database 21c EE for Linux on VirtualBox 6.1

諸事情w によりまだやってなかった Oracle Database 21c お遊び環境構築しました。今の所 VirtualBox向けrebuildなのはなさそうなので、400億年振りにw Linuxインストールから実施w とはいっても、インストール含め楽すぎて良いですね。(preinstallな方しか試して無いですが)

これで、またパラメータ数比較もできる:)


環境
VirtualBox (現時点の最新版 6.1 for macOS intel)
https://www.virtualbox.org/

Oracle Linux Installation Media (今回は8.4を使った)
https://yum.oracle.com/oracle-linux-isos.html

Oracle Database 21c (21.3) for Linux x86-64 (RPM)
https://www.oracle.com/database/technologies/oracle21c-linux-downloads.html

VirtualBoxのバージョン確認

discus-mother:~ oracle$ VBoxManage -v
6.1.32r149290

Oracle Linux 8.4のVM作成(ここは本題じゃないので省略)

Oracle Linuxのバージョン確認

[master@localhost ~]$ uname -rv
5.4.17-2102.201.3.el8uek.x86_64 #2 SMP Fri Apr 23 09:05:57 PDT 2021
[master@localhost ~]$
[master@localhost ~]$ cat /etc/redhat-release
Red Hat Enterprise Linux release 8.4 (Ootpa)
[master@localhost ~]$ cat /etc/oracle-release
Oracle Linux Server release 8.4
[master@localhost ~]$


Oracle Database 21c EE 21.3.0.0.0のインストール

大人の事情w インストールからやるのかなり久々な上に、rpmでやるのは初!!!! しかも環境変数のあたり、ちょいと忘れているので怪しい(でツッコミ歓迎w)

では、first attempting... rpmは事前にダウンロード済み


master@localhost ~]$ ll -h oracle-database-ee-21c-1.0-1.ol8.x86_64.rpm
-rw-r--r--. 1 master master 2.6G 3月 29 03:07 oracle-database-ee-21c-1.0-1.ol8.x86_64.rpm

Install

[master@localhost ~]$ sudo dnf install oracle-database-ee-21c-1.0-1.ol8.x86_64.rpm
メタデータの期限切れの最終確認: 1:30:07 時間前の 2022年03月29日 02時07分01秒 に実施しました。
依存関係が解決しました。
======================================================================================================================
パッケージ アーキテクチャー バージョン リポジトリー サイズ
======================================================================================================================
インストール:
oracle-database-ee-21c x86_64 1.0-1 @commandline 2.6 G
依存関係のインストール:
compat-openssl10 x86_64 1:1.0.2o-3.el8 ol8_appstream 1.1 M
ksh x86_64 20120801-254.0.1.el8 ol8_appstream 927 k
libnsl x86_64 2.28-151.0.1.el8 ol8_baseos_latest 102 k
oracle-database-preinstall-21c x86_64 1.0-1.el8 ol8_appstream 30 k

トランザクションの概要
======================================================================================================================
インストール 5 パッケージ

合計サイズ: 2.6 G
インストール後のサイズ: 7.1 G
これでよろしいですか? [y/N]: y
パッケージのダウンロード:
[SKIPPED] libnsl-2.28-151.0.1.el8.x86_64.rpm: Already downloaded
[SKIPPED] compat-openssl10-1.0.2o-3.el8.x86_64.rpm: Already downloaded
[SKIPPED] ksh-20120801-254.0.1.el8.x86_64.rpm: Already downloaded
[SKIPPED] oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm: Already downloaded
----------------------------------------------------------------------------------------------------------------
合計 217 MB/s | 2.2 MB 00:00
トランザクションの確認を実行中
トランザクションの確認に成功しました。
トランザクションのテストを実行中
トランザクションのテストに成功しました。
トランザクションを実行中
準備 : 1/1
インストール中 : ksh-20120801-254.0.1.el8.x86_64 1/5
scriptletの実行中: ksh-20120801-254.0.1.el8.x86_64 1/5
インストール中 : compat-openssl10-1:1.0.2o-3.el8.x86_64 2/5
scriptletの実行中: compat-openssl10-1:1.0.2o-3.el8.x86_64 2/5
/sbin/ldconfig: /etc/ld.so.conf.d/kernel-5.4.17-2102.201.3.el8uek.x86_64.conf:6: hwcap directive ignored

インストール中 : libnsl-2.28-151.0.1.el8.x86_64 3/5
インストール中 : oracle-database-preinstall-21c-1.0-1.el8.x86_64 4/5
scriptletの実行中: oracle-database-ee-21c-1.0-1.x86_64 5/5
インストール中 : oracle-database-ee-21c-1.0-1.x86_64 5/5
scriptletの実行中: oracle-database-ee-21c-1.0-1.x86_64 5/5
[INFO] Executing post installation scripts...
[INFO] Oracle home installed successfully and ready to be configured.
To configure a sample Oracle Database you can execute the following service
configuration script as root: /etc/init.d/oracledb_ORCLCDB-21c configure

scriptletの実行中: oracle-database-preinstall-21c-1.0-1.el8.x86_64 5/5
scriptletの実行中: oracle-database-ee-21c-1.0-1.x86_64 5/5
/sbin/ldconfig: /etc/ld.so.conf.d/kernel-5.4.17-2102.201.3.el8uek.x86_64.conf:6: hwcap directive ignored

検証 : libnsl-2.28-151.0.1.el8.x86_64 1/5
検証 : compat-openssl10-1:1.0.2o-3.el8.x86_64 2/5
検証 : ksh-20120801-254.0.1.el8.x86_64 3/5
検証 : oracle-database-preinstall-21c-1.0-1.el8.x86_64 4/5
検証 : oracle-database-ee-21c-1.0-1.x86_64 5/5

インストール済み:
compat-openssl10-1:1.0.2o-3.el8.x86_64 ksh-20120801-254.0.1.el8.x86_64
libnsl-2.28-151.0.1.el8.x86_64 oracle-database-ee-21c-1.0-1.x86_64
oracle-database-preinstall-21c-1.0-1.el8.x86_64

完了しました!
[master@localhost ~]$


Configure
今回は、preinstallを構成します。カスタムな構成は今の所必要ないので。

[root@localhost ~]# cd /etc/init.d
[root@localhost init.d]# ll
合計 40
-rw-r--r--. 1 root root 1161 5月 19 2021 README
-rw-r--r--. 1 root root 18434 2月 15 2021 functions
-rwx------. 1 root root 1281 7月 13 2021 oracle-database-preinstall-21c-firstboot
-r-xr-xr-x. 1 root root 11307 7月 27 2021 oracledb_ORCLCDB-21c
[root@localhost init.d]#
[root@localhost ~]# /etc/init.d/oracledb_ORCLCDB-21c configure
Configuring Oracle Database ORCLCDB.
DB操作の準備
8%完了
データベース・ファイルのコピー中
31%完了
Oracleインスタンスの作成および起動中
32%完了
36%完了
40%完了
43%完了
46%完了
データベース作成の完了
51%完了
54%完了
プラガブル・データベースの作成
58%完了
77%完了
構成後アクションの実行
100%完了
データベースの作成が完了しました。詳細は、次の場所にあるログ・ファイルを参照してください:
/opt/oracle/cfgtoollogs/dbca/ORCLCDB。
データベース情報:
グローバル・データベース名:ORCLCDB
システム識別子(SID):ORCLCDB
詳細はログ・ファイル"/opt/oracle/cfgtoollogs/dbca/ORCLCDB/ORCLCDB.log"を参照してください。

Database configuration completed successfully. The passwords were auto generated,
you must change them by connecting to the database using 'sqlplus / as sysdba' as the oracle user.
[root@localhost ~]#


おおおおおおーーー、起動してるーーーー!! 21c 楽だw
20220329-182633

いろいろ見ていくと、oracleユーザーや必要なグループの作成等はおこなわれているが、なぜか、home directoryが存在しなかった(ご愛嬌ってところかw)

ということで、なぜか存在しなかったホームディレクトリ作成とパスワードを変更して、.bash_profileに、ほぼ忘れかけてた環境変数設定など。(漏れてないかな。。w)

[root@localhost ~]# cat /etc/passwd | grep oracle
oracle:x:54321:54321::/home/oracle:/bin/bash
[root@localhost ~]#
[root@localhost ~]# mkdir -p /home/oracle
[root@localhost ~]# chown oracle:oinstall /home/oracle
[root@localhost ~]# passwd oracle
ユーザー oracle のパスワードを変更。
新しいパスワード:
新しいパスワードを再入力してください:
passwd: すべての認証トークンが正しく更新できました。
[root@localhost ~]# su - oracle
[oracle@localhost ~]$ cat .bash_profile
export ORACLE_BASE=/opt/oracle
export ORACLE_BASE_CONFIG=$ORACLE_BASE/dbs
export ORACLE_HOME=$ORACLE_BASE/product/21c/dbhome_1
export PATH=$PATH:$ORACLE_HOME/bin
export ORACLE_SID=ORCLCDB
export NLS_LANG=Japanese_Japan.UTF8
export ORA_NLS10=$ORACLE_HOME/nls/data
[oracle@localhost ~]$ . .bash_profile
[oracle@localhost ~]$


そして、忘れる前にやっときましょうw glogin.sqlでSQL*Plusの環境を整えると。

[oracle@localhost ~]$ cat $ORACLE_HOME/sqlplus/admin/glogin.sql
--
-- Copyright (c) 1988, 2005, Oracle. All Rights Reserved.
--
-- NAME
-- glogin.sql
--
-- DESCRIPTION
-- SQL*Plus global login "site profile" file
--
-- Add any SQL*Plus commands here that are to be executed when a
-- user starts SQL*Plus, or uses the SQL*Plus CONNECT command.
--
-- USAGE
-- This script is automatically run
--
set linesize 300
set pagesize 1000
set timi on
set time on
set tab off
define _EDITOR=vi
set sqlp "_user""@""_connect_identifier> "
[oracle@localhost ~]$

SQL*Plusで接続!!!

[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 火 3月 29 06:14:52 2022
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.


Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
06:14:52 SYS@ORCLCDB> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 ORCLPDB1 READ WRITE NO
06:14:57 SYS@ORCLCDB>
06:14:58 SYS@ORCLCDB>
06:14:58 SYS@ORCLCDB>
06:15:26 SYS@ORCLCDB> select banner_full from v$version;

BANNER_FULL
-------------------------------------------------------------------------------
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0


経過: 00:00:00.00
06:15:40 SYS@ORCLCDB> exit

Lisenerの状態も見ておきましょう

[oracle@localhost ~]$ lsnrctl status

LSNRCTL for Linux: Version 21.0.0.0.0 - Production on 29-3月 -2022 07:09:29

Copyright (c) 1991, 2021, Oracle. All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 21.0.0.0.0 - Production
開始日 29-3月 -2022 03:57:55
稼働時間 0 日 3 時間 11 分 34 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/oracle/homes/OraDBHome21cEE/network/admin/listener.ora
ログ・ファイル /opt/oracle/diag/tnslsnr/localhost/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=localhost)(PORT=5500))
(Security=(my_wallet_directory=/opt/oracle/admin/ORCLCDB/xdb_wallet))(Presentation=HTTP)(Session=RAW))
サービスのサマリー...
サービス"ORCLCDB"には、1件のインスタンスがあります。
インスタンス"ORCLCDB"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"ORCLCDBXDB"には、1件のインスタンスがあります。
インスタンス"ORCLCDB"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"db57eedbd976b81ce055000000000001"には、1件のインスタンスがあります。
インスタンス"ORCLCDB"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"orclpdb1"には、1件のインスタンスがあります。
インスタンス"ORCLCDB"、状態READYには、このサービスに対する1件のハンドラがあります...
コマンドは正常に終了しました。
[oracle@localhost ~]$

停止してみましょう!

おっと、systemctlで起動停止というところまではやってくれないのか... ということで、自分で止めましょうw あとで作ろう。。w

[master@localhost ~]$ sudo service oracle stop
[sudo] master のパスワード:
Redirecting to /bin/systemctl stop oracle.service
Failed to stop oracle.service: Unit oracle.service not loaded.
[master@localhost ~]$ sudo su - oracle
[oracle@localhost ~]$ sqlplus / as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on 火 3月 29 07:16:55 2022
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle. All rights reserved.

Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
に接続されました。
07:16:55 SYS@ORCLCDB> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
07:17:01 SYS@ORCLCDB>

インストール楽すぎて、ワロタ。



一気に桜が咲いた。がいつまでもつのだろう。今年は東北方面も開花が早そうで、GWはもう葉桜だろうな。
では

| | | コメント (0)

2022年3月26日 (土)

小ネタ : Live SQL! でもexplain plan を使う

Oracle Live SQL

でauto traceはできないけど、explain plan for文で解析した後に

 

SELECT plan_table_output FROMTABLE(DBMS_XPLAN.DISPLAY(upper('plan_table'),null,'typical'));


を実行すればできます!
新たな気づき :)

PLAN_TABLEは大文字にするところがポイントです!

ちなみに、

誰がどんな名前のペットを飼っているのかな? 解答編 / JPOUG Advent Calendar Day 23 
のSQL文は全て実行できた!
ただ、まだ 21cではなくて、19cですね 21c版出るのだろうか...

20220326-114537

| | | コメント (0)

実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)

さて、実行計画のバリエーションの数だけ、レントゲンはありますよー(まだネタには余裕があるw)

ということで、今回は、12c前後で変化したところを見ておきたいと思います。

タイトルにも書いた

TEMP TABLE TRANSFORMATION
LOAD AS SELECT (CURSOR DURATION MEMORY)

は、12c以降のリリースから見られるWITH CTEで繰り返し利用されることが自明で性能改善につながると想定される場合に、一時表にマテリアライズされたときのオペレーションですよね。
(みなさんご存知だとおもいます。

12c以降のリリースしか利用したことのない方は、気づかないと思いますが、11gまで少々違いました。

TEMP TABLE TRANSFORMATION
  LOAD AS SELECT


違いといっても、CURSOR DURATION MEMORY があるかないかなのですがw、細かい改善の一つでだよね。と。


ちょうど良いネタなので、昨年末のAdvent CalendarのSQL文と実行計画でバージョン間のレントゲン写真の差を確認しておきましょう:)

まず、一つまえの 19cから。
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Id=2のオペレーションは、LOAD AS SELECT (CURSOR DURATION MEMORY) ですね。

もう一点、Predicate Informationセクションには、/*+ CACHE ("T1") */ というヒントが内部的に利用されていますね。ふーむ。
また、アダプティブな挙動もレポートされていないようですね。計画自体もindex only scanですし、まあ、想定通りというところですね。

参考までに、OPTIMIZER_DYNAMIC_SAMPLING = 2 と。デフォルトのままです。

CURSOR DURATION MEMORYの挙動については、
SQLチューニング・ガイド cursor-duration一時表

にあるように、シリアル実行では、PGAを利用するようですね。パラレルだと違うのか... でいずれもメモリ上に乗らなくなると、一時セグメントがストレージ上に確保されると記載されているのでTEMP表領域が利用されそうですね。
メモリに余裕があり、かつ、繰り返し参照されるケースでは効果はありそうですよね。ストレージに落ちてしまうと、direct path read from tempが発生するでしょうし、ストレージを繰り返しアクセスするかしないかの違いは大きいかも。
とはいえ、WITH句のCTEで性能改善を狙うケースでポイントになるところは同じなので、その点は忘れないようにしておきたいところですね。(例外は巨大なSQLで可読性向上を目的とした場合ぐらい)

Execution Plan
----------------------------------------------------------
Plan hash value: 3964084889

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 553 | 12 (0)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6640_6FF953 | | | | |
| 3 | WINDOW SORT | | 7 | 119 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 119 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 7 | 77 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0012896 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0012900 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0012898 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D6641_6FF953 | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6640_6FF953 | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | VIEW | | 7 | 553 | 2 (0)| 00:00:01 |
| 19 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 20 | FILTER | | | | | |
| 21 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 23 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 24 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
|* 25 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 26 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6641_6FF953 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - filter("NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "NAME"='Steve' AND "ANIMALS"."KIND"<>'Dog' AND
"ANIMALS"."KIND"<>'Snake' OR "NAME"='Hiro' AND "ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR
"NAME"='Larry' AND "ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("NAME"='Wendy' AND "KIND"='Dog' OR "NAME"='Tiger' AND "KIND"<>'Dog' AND
"KIND"<>'Turtle' OR "NAME"='Lisa' AND "KIND"<>'Snake' AND "KIND"<>'Dog' OR "NAME"='Taro' AND "KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND
NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2"
"PET_NAME","C3" "ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6640_6FF953" "T1") "UNKNOWN_PET_OWNERS" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
20 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT
EXISTS (SELECT 0 FROM (SELECT /*+ CACHE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6641_6FF953" "T1") "TEMP_PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
23 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
922 bytes sent via SQL*Net to client
2090 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed

では次、12R1の場合です。(12R2は19cと同じなので省略)
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

基本的にデフォルト設定のままなので、 OPTIMIZER_DYNAMIC_SAMPLING = 2 でもろもろ止めたりしていませんw

12cR2以降との違いの1つめ!
12cR1では、CURSOR DURATION MEMORY オペレーションがありません!! 

12cR2以降との違いの2つめ!
/*+ CACHE ("T1") */ではなく、/*+ CACHE_TEMP_TABLE ("T1") */ という一時表専用のヒントが担っている点。
CACHE_TEMP_TABLEヒントって解説がないヒントなのですが、12cR2以降はCACHEヒントという解説のある通常の表と同じヒントに置き換えられいますね。一時表だけ特別なわけではないので統一したのでしょうか(中の人のみぞ知るw)

Note部分にもでてますが、動的統計とプランディレクティブが動いてますね。再起コールが減らないのもその影響のようです。19cの挙動とは興味深い違いですね。データ量が多くなった場合にどう変化するのかなという気はしますがw

ちなみに、この挙動は、11cR2とくらべて、プランディレクティブが無い以外の挙動は同じ。


Execution Plan
----------------------------------------------------------
Plan hash value: 3787387246

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6647_566AED | | | | |
| 3 | WINDOW SORT | | 8 | 136 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 8 | 136 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 8 | 88 | 4 (0)| 00:00:01 |
| 6 | INDEX FULL SCAN | SYS_C0014925 | 4 | 24 | 1 (0)| 00:00:01 |
|* 7 | INDEX FAST FULL SCAN | SYS_C0014929 | 2 | 10 | 1 (0)| 00:00:01 |
|* 8 | INDEX FAST FULL SCAN | SYS_C0014927 | 1 | 6 | 1 (0)| 00:00:01 |
| 9 | LOAD AS SELECT | SYS_TEMP_0FD9D6648_566AED | | | | |
| 10 | WINDOW SORT | | 7 | 371 | 2 (0)| 00:00:01 |
|* 11 | FILTER | | | | | |
| 12 | VIEW | | 7 | 371 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 14 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
|* 16 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6647_566AED | 7 | 119 | 2 (0)| 00:00:01 |
| 18 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 19 | FILTER | | | | | |
| 20 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |
|* 24 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 25 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6648_566AED | 7 | 217 | 2 (0)| 00:00:01 |

--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
8 - filter(("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger') AND ("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR
"PETS"."NAME"='Tiger' AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR
"PETS"."NAME"='Lisa' AND "ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR
"PETS"."NAME"='Taro' AND "ANIMALS"."KIND"<>'Dog'))
11 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6647_566AED" "T1") "PET_OWNER_UNKNOWN" WHERE
"ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
14 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
16 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
19 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1
AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */
"C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6648_566AED" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
22 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
24 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- 5 Sql Plan Directives used for this statement

Statistics
----------------------------------------------------------
4 recursive calls
36 db block gets
126 consistent gets
2 physical reads
1160 redo size
811 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


そして、最後は、私の環境で最も古いOracle 11.1.0.7

Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production

わかっちゃいたけど、オプティマイザの進化が見えて嬉しいですねw

なかなか苦しい実行計画ですね。これは。。。w

Index only Scanにはなっていますが、マテリアライズする部分の実行計画が.. オプティマイザありがとう。(主に最新版のほうですけどw)

本題にもどると、このリリースではCURSOR DURATION MEMORYがないのは当然ですが、CACHE_TEMP_TABLEヒントが利用されているという点に関しては、12cR1と同じ
そして、アダプティブな挙動も一切ない自体の実行計画ですね。ここには戻りたくないですよね。みなさんw

アダプティブな挙動はないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw

Plan hash value: 3067991639

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 14 (15)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 8 | 240 | 9 (12)| 00:00:01 |
| 4 | CONCATENATION | | | | | |
| 5 | NESTED LOOPS | | 7 | 210 | 7 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 9 | 225 | 7 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 5 | 100 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 9 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 10 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 11 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 12 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 13 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 14 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 15 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 16 | NESTED LOOPS | | 1 | 30 | 1 (0)| 00:00:01 |
| 17 | NESTED LOOPS | | 2 | 50 | 1 (0)| 00:00:01 |
| 18 | NESTED LOOPS | | 1 | 20 | 0 (0)| 00:00:01 |
| 19 | NESTED LOOPS | | 1 | 15 | 0 (0)| 00:00:01 |
| 20 | NESTED LOOPS | | 1 | 10 | 0 (0)| 00:00:01 |
|* 21 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 22 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
|* 23 | INDEX UNIQUE SCAN | SYS_C009962 | 4 | 20 | 0 (0)| 00:00:01 |
|* 24 | INDEX UNIQUE SCAN | SYS_C009960 | 4 | 20 | 0 (0)| 00:00:01 |
|* 25 | INDEX FULL SCAN | SYS_C009964 | 2 | 10 | 1 (0)| 00:00:01 |
|* 26 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
| 27 | LOAD AS SELECT | | | | | |
| 28 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 29 | FILTER | | | | | |
| 30 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 32 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 33 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 34 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 35 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660C_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 36 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 37 | FILTER | | | | | |
| 38 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 39 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 40 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 41 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 42 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660D_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
12 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
13 - access("PETS"."NAME"="PETS"."NAME")
filter("PETS"."NAME"<>'Tiger')
14 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
15 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"<>'Hiro')
21 - access("PETS"."NAME"='Tiger')
22 - access("OWNERS"."NAME"='Hiro')
filter(LNNVL("OWNERS"."NAME"<>'Hiro') OR LNNVL("PETS"."NAME"<>'Tiger'))
23 - access("PETS"."NAME"="PETS"."NAME")
24 - access("OWNERS"."NAME"="OWNERS"."NAME")
25 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
26 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
29 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660C_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
32 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
34 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
37 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D660D_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND "NUM_OF_ROWS"=1))
40 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
42 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
85 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


アダプティブな挙動がないにしても、再起コールがおおいし、実行計画が行けてないのでヒントで補正してみたくなりますよねw
NO_EXPANDヒントでCONCATENATIONを抑止してみましたw
それでもイマイチだ。古いオプティマイザとの挙動の違いをみると、ほんと、最新版のオプテマイザの良さが身にしみますw

Plan hash value: 2841796482

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 448 | 13 (8)| 00:00:01 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | LOAD AS SELECT | | | | | |
| 3 | WINDOW SORT | | 7 | 210 | 8 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 7 | 210 | 8 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 12 | 300 | 8 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 7 | 140 | 4 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 7 | 105 | 4 (0)| 00:00:01 |
| 8 | NESTED LOOPS | | 4 | 40 | 1 (0)| 00:00:01 |
| 9 | INDEX FULL SCAN | SYS_C009964 | 4 | 20 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | SYS_C009964 | 1 | 5 | 0 (0)| 00:00:01 |
|* 11 | INDEX FAST FULL SCAN| SYS_C009962 | 2 | 10 | 1 (0)| 00:00:01 |
|* 12 | INDEX UNIQUE SCAN | SYS_C009962 | 1 | 5 | 0 (0)| 00:00:01 |
|* 13 | INDEX FAST FULL SCAN | SYS_C009960 | 2 | 10 | 1 (0)| 00:00:01 |
|* 14 | INDEX UNIQUE SCAN | SYS_C009960 | 1 | 5 | 0 (0)| 00:00:01 |
| 15 | LOAD AS SELECT | | | | | |
| 16 | WINDOW SORT | | 7 | 217 | 2 (0)| 00:00:01 |
|* 17 | FILTER | | | | | |
| 18 | VIEW | | 7 | 217 | 2 (0)| 00:00:01 |
| 19 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 20 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
|* 22 | VIEW | | 1 | 30 | 1 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_11BDA4 | 7 | 210 | 2 (0)| 00:00:01 |
| 24 | SORT ORDER BY | | 7 | 448 | 3 (34)| 00:00:01 |
|* 25 | FILTER | | | | | |
| 26 | VIEW | | 7 | 448 | 2 (0)| 00:00:01 |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 28 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
|* 30 | VIEW | | 7 | 210 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6625_11BDA4 | 7 | 217 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

10 - access("ANIMALS"."KIND"="ANIMALS"."KIND")
11 - filter("PETS"."NAME"='Wendy' AND "ANIMALS"."KIND"='Dog' OR "PETS"."NAME"='Tiger' AND
"ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Turtle' OR "PETS"."NAME"='Lisa' AND
"ANIMALS"."KIND"<>'Snake' AND "ANIMALS"."KIND"<>'Dog' OR "PETS"."NAME"='Taro' AND
"ANIMALS"."KIND"<>'Dog')
12 - access("PETS"."NAME"="PETS"."NAME")
13 - filter("OWNERS"."NAME"='Scott' AND "ANIMALS"."KIND"='Snake' OR "OWNERS"."NAME"='Steve'
AND "ANIMALS"."KIND"<>'Dog' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Hiro' AND
"ANIMALS"."KIND"<>'Turtle' AND "ANIMALS"."KIND"<>'Snake' OR "OWNERS"."NAME"='Larry' AND
"ANIMALS"."KIND"<>'Snake')
14 - access("OWNERS"."NAME"="OWNERS"."NAME")
filter("OWNERS"."NAME"='Hiro' AND "PETS"."NAME"='Tiger' OR "OWNERS"."NAME"<>'Hiro' AND
"PETS"."NAME"<>'Tiger')
17 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE
"PET_NAME"=:B1 AND "NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE
("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6624_11BDA4" "T1") "PET_OWNER_UNKNOWN" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
20 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
22 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)
25 - filter("NUM_OF_ROWS"=1 OR "NUM_OF_ROWS">1 AND NOT EXISTS (SELECT 0 FROM (SELECT /*+
CACHE_TEMP_TABLE ("T1") */ "C0" "NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3"
"ANIMAL_KIND" FROM "SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "PET_NAME"=:B1 AND
"NUM_OF_ROWS"=1) AND NOT EXISTS (SELECT 0 FROM (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
"NUM_OF_ROWS","C1" "OWNER_NAME","C2" "PET_NAME","C3" "ANIMAL_KIND" FROM
"SYS"."SYS_TEMP_0FD9D6625_11BDA4" "T1") "PET_OWNERS" WHERE "ANIMAL_KIND"=:B2 AND
"NUM_OF_ROWS"=1))
28 - filter("PET_NAME"=:B1 AND "NUM_OF_ROWS"=1)
30 - filter("ANIMAL_KIND"=:B1 AND "NUM_OF_ROWS"=1)

統計
----------------------------------------------------------
4 recursive calls
16 db block gets
84 consistent gets
2 physical reads
1156 redo size
807 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
4 rows processed


SQLのレントゲン写真、バージョンと共に基本形wが変化していくので、常に差分と最新の内容を把握しておきたいですよね。いざ、診療するとなったときには役に立つ、はず!!

今回利用したSQL文や表定義およびデータは、誰がどんな名前のペットを飼っているのかな? 解答編 / JPOUG Advent Calendar Day 23を参照ください。

では、また。






Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT

| | | コメント (0)

2022年3月21日 (月)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #13

随分間が空いてしまったのです、覚えてますかねw 前回のネタ。私もほぼ忘れてました orz. w

思い出しながら進めましょう:)

Previously on Mac De Oracle
前回は、CTASだと、内部的にdirect path writeされてしまうのでクリーンアウトが必要な状況にはならないという動きを確認しました。

scattered read同様に、sequential readだとどうなの?(という遠くから声が聞こえたきがしたのでw)

とはいえ、遅延ブロッククリーンアウトって、自分の理解だと、全表走査時の動きだと思っているので、多分、起きないよなーと。頭の上に雲型の吹き出した出た状態でイメージしているところ。


では、試してみましょう。

これまでと異なる箇所は、sequential read でぐるぐるループさせたいので、該当表に主キー索引を作成します。また、table full scanではなく、index unique scan で全行アクセスさせてみます。(この動きが大きく異なる部分です)





strong>0) 対象表のdrop/create と主キー作成

対象表のHOGE2は削除して作り直し。このテストケースでは主キーアクセスさせるため、合わせて主キーも作成しておきます。
SCOTT@orcl> @droppurge_create_hoge2_with_pk.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.51
1* create table hoge2 (id number, data varchar2(2000), constraint pk_hoge2 primary key (id) using index)

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.13


1) 統計をクリアするためOracle再起動


$ sudo service oracle restart

[sudo] password for oracle:

Restarting oracle (via systemctl): [ OK ]



2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略! (思い出したいかたはこの辺りを参照ください。。


4) PDBのSCOTTユーザーでNOAPPENDヒント付きIASを実行(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ)/ 未コミット

NOAPPENDヒントを利用している理由は前回までの内容を見ていただければわかりますが、direct path writeさせたないためです。これによりコミットクリーンアウトではクリーンアウトできない状況、つまり、遅延ブロッククリーンアウトが必要となる状態を作っています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:19.63
14:32:40 SCOTT@orcl>


5) CDBのSYSで統計取得(未コミット)

検証中に統計値が変動したもののみ記載

狙い通りdirect path writeは抑止できていることは確認できます。
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22,918
sysstat DBWR checkpoints 72
sysstat DBWR thread checkpoint buffers written 22,918
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 983
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 1,601
sysstat commit cleanouts successfully completed 1,601
sysstat consistent gets 122,604
sysstat db block changes 564,554
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 70,607
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 1,752
sysstat no work - consistent read gets 67,108
sysstat physical reads 66,750
sysstat physical reads direct 66,709
sysstat physical writes 22,918
sysstat physical writes from cache 22,918
sysstat physical writes non checkpoint 22,918
sysstat redo blocks written 967,108
sysstat redo size 479,412,088
sysstat redo writes 378

(PDB) SCOTTのセッション統計
こちらから見てもdirect path writeは発生していません

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1,589
sesstat commit cleanouts successfully completed 1,589
sesstat consistent gets 122,204
sesstat db block changes 564,506
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 70,595
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 1,748
sesstat no work - consistent read gets 66,884
sesstat physical reads 66,740
sesstat physical reads direct 66,709
sesstat redo size 478,945,336


該当期間の待機イベント記録されていないですよね。狙い通りなので準備としては問題なしです。

EVENT                                                            WAIT_CLASS
---------------------------------------------------------------- -----------------
SQL*Net message from client Idle
log buffer space Configuration
events in waitclass Other Other
direct path read User I/O
log file switch (private strand flush incomplete) Configuration
log file switch completion Configuration
log file sync Commit
db file sequential read User I/O
SQL*Net message to client Network
Disk file operations I/O User I/O
db file scattered read User I/O

6) CDB or PDBのSYSユーザで接続し、buffer cacheをflushする

SYS@orclcdb> @flush_buffercache.sql
1* alter system flush buffer_cache

System altered.

Elapsed: 00:00:09.04
14:36:26 SYS@orclcdb>


7) CDBのSYSで統計取得(buffer cacheをflush後)
strong>(CDB)システム統計

検証中に統計値が変動したもののみ記載

バッファキャッシュをフラッシュしたのでキャッシュから書き出されたことを示す physical writes from cache および関連する統計値が上昇しています。これも想定通りの挙動ですね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 8
sysstat DBWR undo block writes 1912
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 43
sysstat commit cleanouts successfully completed 43
sysstat consistent gets 20793
sysstat db block changes 429
sysstat deferred (CURRENT) block cleanout applications 21
sysstat free buffer requested 1013
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 9
sysstat no work - consistent read gets 12321
sysstat physical reads 977
sysstat physical reads direct 0
sysstat physical writes 47900
sysstat physical writes from cache 47900
sysstat physical writes non checkpoint 47900
sysstat redo blocks written 2017
sysstat redo size 994652
sysstat redo writes 23

(PDB) SCOTTのセッション統計

キャッシュをフラッシュしただけなので、該当セッションの統計には変化がありません。(まあ、そうですよねw)


SOURCE NAME VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistentread gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 0
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 0
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 0
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 0
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 0

8) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.

Elapsed: 00:00:00.01


9) CDBのSYSで統計取得(コミット後)
strong>(CDB)システム統計
コミット時点ではクリーンアウト仕切れないほどの更新ブロック数かつ、コミット前にバッファキャッシュをフラッシュしているので、コミット時のクリーンアウトもできない状態なっています(これも事前の準備の通りの結果。なので問題なし)
commit cleanouts と commit cleanout failures: block lost がポイントです。コミット時にクリーンアウトできる量(過去の記事を参照のこと)の閾値以下の量ですら、事前にキャッシュからフラッシュしまっているので、クリーンアウトに失敗(できないようにしたので当然ですが)しています。両方の統計値が同一であるとくことは、コミットクリーンアウトしようとした全てでくりアウトできなかった。理由はキャッシュに該当ブロックがキャッシュから落ちていたから(意図的に落としたわけですが)ということを意味します。予定通りですね。

コミットの時点で最低現おこなうクリーンアウトもさせていないですが、このケースでは 11140 ブロックをコミット時にクリーンアウトしようとしているので、コミット時のクリーンアウトが行われていたとしても 50,000 ブロック以上は遅延されていたことになりますね。今回は、 66,667 ブロックがク後続のSQLで参照されるついでにクリーンアウトされることになります。

検証中に統計値が変動したもののみ記載

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 0
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 0
sysstat DBWR undo block writes 0
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 11140
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 11140
sysstat commit cleanouts successfully completed 0
sysstat consistent gets 34
sysstat db block changes 1
sysstat deferred (CURRENT) block cleanout applications 0
sysstat free buffer requested 8
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 0
sysstat no work - consistent read gets 5
sysstat physical reads 8
sysstat physical reads direct 0
sysstat physical writes 0
sysstat physical writes from cache 0
sysstat physical writes non checkpoint 0
sysstat redo blocks written 3
sysstat redo size 456
sysstat redo writes 3

(PDB) SCOTTのセッション統計
セッションレベルでみるよりはっきり見るとことができます。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 11140
sesstat commit cleanouts 11140
sesstat commit cleanouts successfully completed 0
sesstat consistent gets 0
sesstat db block changes 1
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 1
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 0
sesstat physical reads 1
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 176


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表を主キーによる一意検索で全行アクセス)

さて、事前に想定している実行計画になっているかプランだけ確認しておきましょう。主キーによる索引ユニークスキャンであることが確認できます。想定通りです。この実行計画で、200,000 行をグルグル参照した場合、遅延されたブロッククリーンアウトが行われるでしょうか。。。。(してくれないと思いますが。。。試してみましょう)

SCOTT@orcl> set autot trace exp
14:41:47 SCOTT@orcl> select * from hoge2 where id = 1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3319133482

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1015 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE2 | 1 | 1015 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_HOGE2 | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

SCOTT@orcl> set autot off
SCOTT@orcl>
SCOTT@orcl> @table_unique_scan_hoge2.sql
1 begin
2 for i in 1..&1 loop
3 for hoge2_rec in (select * from hoge2 where id = i) loop
4 null;
5 end loop;
6 end loop;
7* end;
Enter value for 1: 200000
old 2: for i in 1..&1 loop
new 2: for i in 1..200000 loop

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.94
14:39:36 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

クリーンアウトが行われていれば、データブロックの総数 66,667 ブロックは少なくともクリーンアップの対象になっているはずですが。。。。。
cleanout系の統計値に動きはありませんよね。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 21
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 5
sysstat DBWR undo block writes 5
sysstat cleanouts only - consistent read gets 0
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 366
sysstat commit cleanouts successfully completed 351
sysstat consistent gets 632775
sysstat db block changes 7848
sysstat deferred (CURRENT) block cleanout applications 169
sysstat free buffer requested 76662
sysstat immediate (CR) block cleanout applications 0
sysstat immediate (CURRENT) block cleanout applications 169
sysstat no work - consistent read gets 21822
sysstat physical reads 69501
sysstat physical reads direct 0
sysstat physical writes 21
sysstat physical writes from cache 21
sysstat physical writes non checkpoint 10
sysstat redo blocks written 5132
sysstat redo size 2536084
sysstat redo writes 28

(PDB) SCOTTのセッション統計

physical reads は、67706 ブロックなのでほ、HOGE2表の索引と表のセグメントサイズほどは物理読み込みされたということになります。ただ、cleanouts only - consistent read gets などのクリーンアウトを示す統計値はほぼ動いていません。ようするに、db file sequential readとなる index unique scan ではブロッククリーンアウトは発生しないということを意味しています。

おおおーーー。だったら、グルグル系の処理で一行ごとに処理するのが最高じゃん! という単純な話ではないので要注意ですからねw

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 0
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 4
sesstat commit cleanouts successfully completed 4
sesstat consistent gets 600005
sesstat db block changes 14
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 74540
sesstat immediate (CR) block cleanout applications 0
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 0
sesstat physical reads 67706
sesstat physical reads direct 0
sesstat physical writes 0
sesstat physical writes direct 0
sesstat physical writes from cache 0
sesstat physical writes non checkpoint 0
sesstat redo blocks written 0
sesstat redo size 2152


10) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(sacattered read)

single block readを行うindex unique scanではクリーンアウトが発生しなかったということは、全表走査させる table full scan かつ scattered readさせれば発生するのではないでしょうか? という確認です。
auto traceで統計情報をみると、なにやら、REDOログが大量に生成されてますよね。。。これは!!!

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.55

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
10 recursive calls
1 db block gets
147157 consistent gets
377 physical reads
5866968 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

14:56:56 SCOTT@orcl>


11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表を主キーによる一意検索で全行アクセス)
strong>(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 1121
sysstat DBWR checkpoints 0
sysstat DBWR thread checkpoint buffers written 0
sysstat DBWR transaction table writes 36
sysstat DBWR undo block writes 347
sysstat cleanouts only - consistent read gets 66676
sysstat commit cleanout failures: block lost 0
sysstat commit cleanout failures: callback failure 0
sysstat commit cleanouts 806
sysstat commit cleanouts successfully completed 806
sysstat consistent gets 174296
sysstat db block changes 80773
sysstat deferred (CURRENT) block cleanout applications 383
sysstat free buffer requested 2095
sysstat immediate (CR) block cleanout applications 66676
sysstat immediate (CURRENT) block cleanout applications 75
sysstat no work - consistent read gets 27953
sysstat physical reads 1842
sysstat physical reads direct 0
sysstat physical writes 1121
sysstat physical writes from cache 1121
sysstat physical writes non checkpoint 981
sysstat redo blocks written 16550
sysstat redo size 8074260
sysstat redo writes 351

(PDB) SCOTTのセッション統計
HOGE2の表のクリーンアウト対象ブロック数、66,667 きっちり同じ数字が現れました。しかも cleanoutに関わる統計でかつ、大量のREDOログも生成されています。でました〜〜〜w クリーンアウト。全表走査かつ、scattered readで。

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 66667
sesstat commit cleanout failures: block lost 0
sesstat commit cleanouts 1
sesstat commit cleanouts successfully completed 1
sesstat consistent gets 153731
sesstat db block changes 66718
sesstat deferred (CURRENT) block cleanout applications 0
sesstat free buffer requested 690
sesstat immediate (CR) block cleanout applications 66667
sesstat immediate (CURRENT) block cleanout applications 0
sesstat no work - consistent read gets 16317
sesstat physical reads 680
sesstat physical reads direct 0
sesstat redo size 5877252




まとめ

index unique scanでグルグルさせつつ、全行アクセスしても発生しないですよね!
single block readになるunique index scanでは、遅延ブロッククリーンアウトは発生しない。(想像通りでよかったw)
で、そのあとで全表走査(scattered read)させてみると、やはり、遅延ブロッククリーンアウトは発生するわけですよ。面白いです。ほんと。



さあ、5月末まで追い込みだw ブログ書かないとw (頑張って、アドベントカレンダー全部俺やっとくほうが実は楽なのかもしれないw)

では、また。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12



| | | コメント (0)

2021年9月25日 (土)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #12

Previously on Mac De Oracle
前回、前々回とIASでdirect path writeであるか否かで、そのコミットおよび遅延ブロッククリーンアウトの有無が決まるという動きを確認しました。

今回は、DDLですが、親戚みたいな挙動のCTASではどうなるか確認しておきます。CTASはDDLなのでコミットは不要ですよね。また、 direct path writeが前提になっていることも皆さんご存知の通り。

ということは、IAS + APPENDヒントで、direct path write させた時と同じような挙動になるはず。。。。ですよね。

手順はいつもの図でご確認ください。(DDLなので手順も単純になっています)
Ctas_steps




0) 対象表のdrop
対象表のHOGE2は削除しておきます。CTASで作成することになるので。

SCOTT@orcl> @droppurge_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:02.79


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでCTAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ

SCOTT@orcl> @ctas_from_hoge.sql
1* create table hoge2 as select * from hoge

Table created.

Elapsed: 00:00:07.41


5) CDBのSYSで統計取得(CTAS後)
DDLなのでコミットはありませんが、念の為に確認すると、commit cleanouts、commit cleanouts successfully completed はほんの少しだけ。このテストケースではノイズ程度の量です。
DDL終了時にクリーンアウトは発生していないと読み取れます。(この後の手順で遅延クリーンアウトも発生していなければ。direct path writeではクリーンアウトが必要な状態にはならないということは間違いないと判断するできますよね)

参考程度ですが、physical writes 、physical writes direct、physical writes non checkpoint が同数です。物理書き込みが発生し、かつ、direct path writeでチェックポイントで書き出されたものではないということが確認できます。想定通り、CTASは direct path writeで書き出されているということがわかります。
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは以前確認した通りの値です)

また、physical reads と physical reads direct は、HOGE表のブロック数以上あり、HOGE表は direct path readでお読み込まれていることがわかります。このときのfree buffer requested は非常に低いことは、バッファキャッシュを介さず、ストレージへ書き出されていることを示しています。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 21
sysstat commit cleanouts successfully completed 21
sysstat consistent gets 72654
sysstat db block changes 3898
sysstat deferred (CURRENT) block cleanout applications 7
sysstat free buffer requested 819
sysstat immediate (CURRENT) block cleanout applications 8
sysstat no work - consistent read gets 69569
sysstat physical reads 67150
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 21
sesstat commit cleanouts successfully completed 21
sesstat consistent gets 72366
sesstat db block changes 3898
sesstat deferred (CURRENT) block cleanout applications 7
sesstat free buffer requested 818
sesstat immediate (CURRENT) block cleanout applications 8
sesstat no work - consistent read gets 69415
sesstat physical reads 67149
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

IASで direct path writeさせた場合同様に、direct path writeで書き出された場合、クリーンアウトするブロックは存在しないため、遅延ブロッククリーンアウトも発生しないという状況になります。
REDOは多少生成あれていますが、recursive call 等によるものと考えられ、このテストケースではノイズの類程度です。システム/セッション統計値をみることでその点も確認できます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.66

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
14 recursive calls
12 db block gets
80022 consistent gets
66669 physical reads
2108 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


7) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
deferred (CURRENT) block cleanout applications や、immediate (CURRENT) block cleanout applicationsなど遅延ブロッククリーンアウトで動く統計に極わずかに動きがありますが、数ブロックなので気にする程度ではないです。遅延ブロッククリーンアウトは発生していないと読み取れます。

また、physical reads は該当表のブロック数程度のブロック数となっており、physical reads direct が発生していないことから、 scattered read でバッファキャッシュに載せられたことが確認できます。free buffer requested もほぼ同じ値になっていることからも同様のことが言えます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105725
sysstat db block changes 255
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 67608
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 94138
sysstat physical reads 67567

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86719
sesstat db block changes 43
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66791
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82976
sesstat physical reads 66783




まとめ

CTASはDDLで、 direct path write を伴うため、IASのAPPEND同様、対象データブロックはクリーンアウトが必要な状態にはならず、コミットおよび遅延ブロッククリーンアウトは発生しない!
ことが確認できました。 :)

次回は、一旦中締めにしますか。

つづく。


目がショボショボしてるから、なにか浮遊物の影響を受けてる気がする。ブタクサやヨモギの季節だもんな。(目だけなんだよなー。アレルギーの酷方のように鼻水でたりとかではなくて。疲れ目用の目薬では、効かないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11



| | | コメント (0)

2021年9月24日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #11

Previously on Mac De Oracle
前回は、クリーンアウトの後始末をさせられる側ではなく、そもそも、後始末不要な状況もあるという確認でした。
IAS(Insert as Select)でdirect path writeで書き込まれたブロックは、クリーンアウトする必要がない状態なので、コミットクリーンアウトも遅延ブロッククリーンアウトも発生していないことはシステム統計やセッション統計からも明らかでした。

今回は、今一度確認ということで、IASでも非direct path writeだったら、やはりコミットクリーンアウトや遅延ブロッククリーンアウトは発生するよね! というところだけは見ておこうと思います。

手順は前回と同じ。前回と異なる点は、4) の部分。IASで NOAPPEND ヒントを使い direct path writeを抑止している部分のみ。
Iasstep





0) 対象表のdrop/create

SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.67
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.05
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.16


1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは 非direct path write でINSERTしたいので NOAPPEND ヒントで direct path write を抑止しています。

SCOTT@orcl> @ias_noappend_from_hoge.sql
1* insert /*+ noappend */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:16.79


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へ非direct path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは 非direct path write していることが読み取れます。
free buffer requested で読み込みブロック相当のブロックがバッファキャッシュへ載せられているように見えますが、読み込みは direct path read なのでバッファキャッシュには載りません。ようするに、HOGE2表向けのデータと考えることができますよね。このキャッシュされたブロックが本当にHOGE2表向けブロックであるかどうかは、これ以降の操作で物理読み込みが発生しないということで確認することができます。


(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前々回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 19158
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 19158
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 299
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 110098
sysstat db block changes 443304
sysstat deferred (CURRENT) block cleanout applications 5
sysstat free buffer requested 67998
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 66823
sysstat physical reads 66730
sysstat physical reads direct 66709
sysstat physical writes 19158
sysstat physical writes from cache 19158
sysstat physical writes non checkpoint 19158
sysstat transaction tables consistent reads - undo records applied 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 109971
sesstat db block changes 443304
sesstat deferred (CURRENT) block cleanout applications 5
sesstat free buffer requested 67996
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 66781
sesstat physical reads 66730
sesstat physical reads direct 66709


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
APPENDモードのIASと異なり、NOAAPENDモードでは、はやり、コミットクリーンアウトが発生しています。バッファキャッシュの10%-15%の範囲のブロックがコミット時にクリーンアウトされている状況が commit cleanouts successfully completed および、commit cleanouts から確認することができます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 55709
sysstat commit cleanouts successfully completed 55709
sysstat consistent gets 18547
sysstat db block changes 289
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 791
sysstat no work - consistent read gets 10937
sysstat physical reads 758
sysstat transaction tables consistent reads - undo records applied 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

direct path writeと非direct path writeの違いはハッキリでました。REDOログは大量に生成され、遅延ブロッククリーンアウトは発生しているようです。
また、物理読み込みは発生していないので、バッファキャッシュにヒットしているという状況も読み取れます。

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:05.74

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 172K| 167M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 172K| 167M| 18223 (1)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
19 recursive calls
13 db block gets
91462 consistent gets
2 physical reads
967388 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed
¥
set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
実行統計の示す通り、遅延ブロッククリーンアウトが発生していることは、immediate (CR) block cleanout applications、cleanouts only - consistent read getsのブロック数からもハッキリ確認することができます!(コミットクリーンアウトできなかったブロック数です)

physical readsはほんの少しありますが、発生していないとみなしても良い程度です。このケースはscattered readが発生していない。つまり、非direct path writeでバッファキャッシュに載ったブロックにヒットしていることで、scattered readの必要がなかった! ということを意味しています。もし、この時、バッファキャッシュから該当表のブロックがある程度落ちていれば、physical readsが表のブロック数程度まで増加していたはずです。(多数の同時実行トランザクションが存在する状況であればキャッシュからエージアウトされ、物理読み込みが大量に発生するというケースは珍しくありません。その分処理時間も長くなるわけで、程度とデータサイズサイズしだいで処理時間も延びることが予想できます。場合よっては、処理時間がかかりすぎて、ザワザワしたり。。。w)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 19
sysstat commit cleanouts successfully completed 19
sysstat consistent gets 98619
sysstat db block changes 11065
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 180
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 72340
sysstat physical reads 169

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98015
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat free buffer requested 121
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72013
sesstat physical reads 113




まとめ

IASで 非direct path write してINSERTされた場合、ブロッククリーンアウトが通常通り発生する(コミット時でも遅延でも)
direct path write時との統計値の違いからも明らかですね。

前回のIAS(APPEND)時の遅延ブロッククリーンアウトがなかったケースの統計を再掲しておきます。統計値の違いをよーく確認してみてください。(試験に。。。でないですけどw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770

次回は、CTAS ( create table as select ) ではどうなるか確かめます。(まだいくつかの関連統計を動かせていないケースありw)

次回へつづく。


東京は、自転車での移動や買い物のほうが渋滞や混雑のストレスがなくていいな。久々に coutry side側をドライブしてたが、ノーストレレスだった。一転機能は、買い物でドライブするも、いちいち渋滞w
WfHも年単位になると、すでに都心の満員電車や渋滞に耐えられない感じになってる気がしないでもないw (渋滞でスタックしている時間がもったいないw)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10



| | | コメント (0)

2021年9月22日 (水)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #10

Previously on Mac De Oracle
前回は、SELECT文であっても遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログも生成される。ただし、direct path read で読み込まれた場合を除く。
という動きをみました。

今回もdirect pathがらみです。といっても direct path write だったら、コミットクリーンアウトや、遅延ブロッククリーンアウトはどういう扱いになるのだろう。。。と
(このシリーズの初回で紹介したいろいろなブログに答えはあるのですがw)

システム統計やセッション統計の統計値から、それをどう読み取るか。γGTP高いから肝臓あたりに問題があるか、検査前日に酒飲んじゃったでしょ! 的なところを診て、なにがおきているか診察していくシリーズなので、それぞれの統計と、auto traceによる実行計画と実行統計を診ていくわけですが。。w


ということで、今回は、前回利用していた表を元にIASで別表を作成し、コミット前後の状況を診ていきたいとおもいます。IASと言っても direct path write になるケースと従来型のロードがあるのは皆さんご存知だと思いますが、まずは、 direct path writeの方から挙動を診ていくことにします。手順はざっとこんな感じ
Iasstep

そういえば、以前、IASで、direct path write かどうか判別しやすくなったよねーというネタをやってましたね。実行計画だけでも違いがわかりやすくなっているので便利になりました。:)

実行計画は、SQL文のレントゲン写真だ! No.30より、LOAD TABL CONVENTIONAL vs. LOAD AS SELECTの実行計画の違い。
20210213-150833


20210213-150416





0) 対象表のdrop/create
SCOTT@orcl> @droppurge_create_hoge2.sql
1* drop table hoge2 purge

Table dropped.

Elapsed: 00:00:00.74
1* create table hoge2 (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.07
1* select segment_name,blocks from user_segments where segment_name like '%HOGE2%'

no rows selected

Elapsed: 00:00:00.15

1) 統計をクリアするためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでIAS(データサイズは、コミットクリーンアウトではクリーンアウト仕切れないサイズ、コミットなし)
このケースでは direct path write でINSERTしたいので APPEND ヒントで direct path write を強制しています。 direct path read とは異なり、ヒントで制御できるのは楽w

SCOTT@orcl> @ias_from_hoge.sql
1* insert /*+ append */ into hoge2 select * from hoge

200000 rows created.

Elapsed: 00:00:10.42


5) CDBのSYSで統計取得(APPENDヒント付きのIAS後、未コミット)

IASでHOGE表を読み込み、HOGE2表へdirect path write している様子が確認できますよね。HOGE表の読み込みは direct path read 、HOGE2表へは direct path write している状況がはっきりでていてわかりやすい結果を得られました :)
(HOGE表のデータが載っているブロック数は、66667 ブロックであることは前回も確認した通りの値です)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 72460
sysstat db block changes 3131
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 792
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 69328
sysstat physical reads 67121
sysstat physical reads direct 66709
sysstat physical writes 66667
sysstat physical writes direct 66667
sysstat physical writes non checkpoint 66667

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 16
sesstat commit cleanouts successfully completed 16
sesstat consistent gets 72373
sesstat db block changes 3131
sesstat deferred (CURRENT) block cleanout applications 12
sesstat free buffer requested 792
sesstat immediate (CURRENT) block cleanout applications 3
sesstat no work - consistent read gets 69291
sesstat physical reads 67121
sesstat physical reads direct 66709
sesstat physical writes 66667
sesstat physical writes direct 66667
sesstat physical writes non checkpoint 66667


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
direct path writeでバッファキャッシュを経由せず書き出されたブロックはコミット時にはクリーンアウトの対象にはなっていないようですね。。。。統計をみる限りノイズ程度ですね。
ということは全てのブロックが遅延ブロッククリーンアウト対象になってしまうのか、または、その逆で、最初からクリーンアウト対象にもなっていないかということになります。次の全表走査の結果でどちらであるか、わかるはずです!!!!(ニヤニヤ、それ、ねらってやってるので、答えは知っているわけですがw)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 25
sysstat commit cleanouts successfully completed 25
sysstat consistent gets 12104
sysstat db block changes 1557
sysstat deferred (CURRENT) block cleanout applications 12
sysstat free buffer requested 714
sysstat immediate (CURRENT) block cleanout applications 4
sysstat no work - consistent read gets 7192
sysstat physical reads 705

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 864
sesstat db block changes 1474
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 37
sesstat no work - consistent read gets 351
sesstat physical reads 32


8) PDBのSCOTTユーザーで遅延ブロッククリーンアウト有無確認(対象表をscattered readで全表走査)

REDOログは多少生成されていますが、実際に遅延ブロッククリーンアウトが発生した場合REDOログ量この程度では少なすぎますよね。
今回の検証目的からするとノイズの類ですね。むむむ。これは。。。。
(物理読み込みは発生しているので、direct path read か、scattered readのどちらかということにはなります。期待している動きは、Scattered read 。)

SCOTT@orcl> @table_full_scan_hoge2.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge2

200000 rows selected.

Elapsed: 00:00:06.84

Execution Plan
----------------------------------------------------------
Plan hash value: 1530105727

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 382M| 18174 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE2 | 200K| 382M| 18174 (1)| 00:00:01 |
---------------------------------------------------------------------------

Statistics
----------------------------------------------------------
13 recursive calls
12 db block gets
80022 consistent gets
66668 physical reads
2284 redo size
406775148 bytes sent via SQL*Net to client
147265 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認。対象表をscattered readで全表走査)
physical reads は想定通りのブロック以上になっていますが、physical reads direct は発生していないので、scattered readによる全表走査であると読み取ることができます。
ただ、この状態でも、遅延ブロッククリーンアウトの発生を示す統計値はノイズ程度の値です。

つまり、direct path write でINSERTされたデータブロックはクリーンアウトが必要な状態だということになりますね。興味深い動きですよね。メモしておいたほうが良さそうです :)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 1
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 105046
sysstat db block changes 225
sysstat deferred (CURRENT) block cleanout applications 9
sysstat free buffer requested 67576
sysstat immediate (CR) block cleanout applications 1
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 93908
sysstat physical reads 67536

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                           VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 1
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 86566
sesstat db block changes 44
sesstat deferred (CURRENT) block cleanout applications 3
sesstat free buffer requested 66778
sesstat immediate (CR) block cleanout applications 1
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 82891
sesstat physical reads 66770




まとめ

IASで direct path write してINSERTされた場合、ブロッククリーンアウトは発生しない!(コミット時でも遅延でも)
そもそもクリーンアウトが必要な状態になっていないというのが正しいのでしょうね。統計値としては全く動いてないに等しいので。

これも試験にでますよ(嘘w

次回は、同じIASもdirect path write ではないケースではどうなるでしょう。。。(ここまでのエントリーを読んでいる方は、結果は想像できそうではありますが)

次回へつづく。


遅めの夏休みですが、まあ、普段とちがうのは、いろいろなタスクの締め切りに追われずに、マイペースな時間の過ごし方になるぐらいだな。この状況下ではw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9



| | | コメント (0)

2021年9月21日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #9

Previously on Mac De Oracle
バッファキャッシュから溢れ出る程度のデータを登録しコミットした場合は、コミットクリーンアウトがどうなるか、遅延ブロッククリーンアウトされるブロック数はどの程度になるかという、寄り道でしたw

今回は、寄り道し過ぎて忘れるところだった、前回まで、scattered readを伴うtable full scanで発生していた遅延ブロッククリーンアウトが direct path read だったらどうなるか、というシリーズ :) 
(まだ続くのかーーーっ! はいw いろんなケースがありますから。シンプルなケースであっても。それぞれの基本的な挙動を知っいて損はないとおもいます。 複雑なケースだと脳汁出過ぎるくらい複雑なので考えたくもなくなるのでw)

では、早速再現してみましょう。手順はこれまで行なってきたとおりで、違いは遅延ブロッククリーンアウトを発生させるためのtable full scanでdirect path readさせるという部分のみ。手順はscattered readとの比較も入れるので長くなってしまうので、追加ステップを追記した図を見てもらうと何やっているか、流れは理解しやすいかもしれません。Steps


事前準備
バッファキャッシュのサイズは元のサイズに戻してあります

SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

200,000行登録したデータ(セグメントサイズ農地純粋にデータが乗っているブロック数)が乗っているブロック数はこんなところ。セグメントサイズはこれより多いですよ。行データが載っているブロックだけカウントしているので。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ということで、セグメントサイズも確認。

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS
------------------------------ ----------
HOGE 67584

バッファキャッシュの10-15%程度はコミットクリーンアウトされるので、間をとってこれぐらいはコミットクリーンアウトされる。。。

SCOTT@orcl> select 3472883712 / 8192 * 0.13 from dual;

3472883712/8192*0.13
--------------------
55111.68

残りは遅延ブロッククリーンアウトする。だいだいこんなもん。

SCOTT@orcl> select 66667 - 55112 from dual;

66667-55112
-----------
11555

direct path read狙いの全表走査(シリアル実行で発動させることを意図していますが、言うこときいてくれるかあなぁ)

$ cat table_full_scan_with_dpr.sql
alter session set "_serial_direct_read" = always
.
l
/
alter session set "_very_large_object_threshold" = 512
.
l
/

!echo set autot trace exp stat
set autot trace exp stat

select * from hoge
.
l
/

!echo set autot off
set autot off

Scattered Read狙いの全表走査のスクリプト

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 20400
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




さて、うまく再現できるかどうか。。(今回もやったことをほぼすべて載せているので長いです)


0) 対象表をdrop/create
オブジェクトを作り直し前提合せ

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアためOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


4) PDBのSCOTTユーザーでINSERT(データ量2倍、コミットなし)
データサイズはバッファキャッシュに載るサイズ、コミットクリーンアウトではクリーンアウト仕切れないサイズで、ある程度の遅延ブロッククリーンアウトが発生するサイズになっているのは以前と同じ。

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)
insertしただけなので、insertしたデータ量に応じたブロック数がバッファキャッシュに確保されたという程度の情報( free buffer requested = 68766 なので事前に確認していた 66667 以上になっています)は確認できます。
(バッファキャッシュに収まるデータ量ですし)ただ、checkpointの発生でいくらか書き出されているのも見えますよね( DBWR checkpoint buffers written )

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 22909
sysstat DBWR checkpoints 3
sysstat DBWR thread checkpoint buffers written 22902
sysstat DBWR transaction table writes 6
sysstat DBWR undo block writes 573
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 49682
sysstat db block changes 744727
sysstat deferred (CURRENT) block cleanout applications 4
sysstat free buffer requested 68766
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 82
sysstat physical reads 9
sysstat physical writes 22909
sysstat physical writes from cache 22909
sysstat physical writes non checkpoint 22892

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49561
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat free buffer requested 68766
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 40
sesstat physical reads 9


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
コミットクリーンアウトされているブロック数を見ると、事前に計算していた バッファキャッシュの13%( 55112 blocks )に近い 55700 ブロックがコミットのタイミングでクリーンアウトされていることがわかります。ここまでは想定通りの動きです。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanout failures: callback failure 15
sysstat commit cleanouts 56077
sysstat commit cleanouts successfully completed 56062
sysstat consistent gets 50494
sysstat db block changes 7894
sysstat deferred (CURRENT) block cleanout applications 158
sysstat free buffer requested 3217
sysstat immediate (CURRENT) block cleanout applications 154
sysstat no work - consistent read gets 32764
sysstat physical reads 2962

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)
全表走査させてコミットクリーンアウトされなかったブロックがクリーンアウトを確認します。ただし、全表走査ではありますが、direct path read で読み込ませるように工夫しています。

さて狙い通りになるかどうか。。。パラレルクエリーでない場合の強制はちょいとむずいのですが、見る限り、REDOログは生成されています。

ただ、以前のscattered read ( db file sequential read )で発生させた遅延ブロッククリーンアウトの検証結果に比べると明らかに少ない。。
なにかが違いますね。。。。むむむ。なんだろう?
scattered readでほぼ同じバッファキャッシュサイズで、遅延ブロッククリーンアウトさせた際、967432 redo size というサイズが生成されていたのを思い出してみてください!!! 明らかに少ないです。。。。

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:10.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
23 recursive calls
13 db block gets
113559 consistent gets
66712 physical reads
2996 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)
direct path read、遅延ブロッククリーンアウトの有無をシステム統計およびセッション統計から読み取ってみます!

物理読み込み( physical writes )とダイレクトパスリードを示す( physical writes from cache )が同じであることから、間違いなく direct path readが発生しています。ブロック数も 66667 ブロックを超えていることは確認できます。
ただ、immediate (CR) block cleanout applications が想定している量の3倍ぐらいあります:)
遅延ブロッククリーンアウトは行われているのは間違いないですが、前述の通りREDOサイズが異常に少ない。どういうことだろう。。(想定通りの結果に、ニヤニヤなわけですがw)

真相を探るため、われわれはアマゾンの奥深くへ入っていくのであった。。。W

少々本題からそれますが、DBWR parallel query checkpoint buffers written で 44793 ブロックほど書き出されています。これが発生するのは direct path readの影響です。direct path read バッファキャッシュを介ず、常にストレージからデータを読み込む必要があります。この検証では、INSERTでバッファキャッシュに載っているデータであるため一旦書き出す必要があります。書き出されたデータを direct path read で読み込むのでこんな動きになっているというわけですね。。。。。

これ、よくよく考えると、コミットクリーンアウトされていないブロックもそのままの状態で書き出されてますよね。。。ここ試験にでますよ(嘘ですw

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 44793
sysstat DBWR checkpoints 2
sysstat DBWR object drop buffers written 2
sysstat DBWR parallel query checkpoint buffers written 44793
sysstat cleanouts only - consistent read gets 33057
sysstat commit cleanouts 18
sysstat commit cleanouts successfully completed 18
sysstat consistent gets 120519
sysstat db block changes 97
sysstat deferred (CURRENT) block cleanout applications 8
sysstat free buffer requested 127
sysstat immediate (CR) block cleanout applications 33057
sysstat immediate (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 50145
sysstat physical reads 66827
sysstat physical reads direct 66710
sysstat physical writes 44795
sysstat physical writes from cache 44795
sysstat physical writes non checkpoint 44795

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 33057
sesstat commit cleanouts 5
sesstat commit cleanouts successfully completed 5
sesstat consistent gets 120131
sesstat db block changes 48
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 120
sesstat immediate (CR) block cleanout applications 33057
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 49952
sesstat physical reads 66823
sesstat physical reads direct 66710


10) Oracle再起動
Oracleを再起動して、諸々綺麗にした状態で、今一度、direct path readで全表走査させてみましょう。

$ sudo service oracle restart


11) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


12) CDBのSYSで統計取得(再起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


13) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をdirect path readで全表走査)2回目
お!!! REDOが生成されていないですね。Scattered Readの場合でも、コミット時でも一度クリーンアウトされたブロックはクリーンアウト済みなので、クリーンアウトされるような挙動は発生しませんでしたが、 direct path read でもおなじかなーー。

と。。。。とりあえず、システム統計とセッション統計も確認しておきましょう!

SCOTT@orcl> @table_full_scan_with_dpr.sql
1* alter session set "_serial_direct_read" = always

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 512

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:03.90

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91159 consistent gets
66938 physical reads
0 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


14) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をdirect path readで全表走査)2回目
セッション統計のphysical readsとhysical reads direct は同一であることから direct path readになっていることは間違いありません。また、ブロック数も 66667 ブロック以上にはなっているので全ブロック読み込まれているようですね。
ただ、妙な値を示している統計があります。

immediate (CR) block cleanout applications  10959 

遅延ブロッククリーンアウトが行われている時に上がる統計です。しかも、コミットクリーンアウトされたブロック数を差し引いたブロック数にほぼ一致します。(1回目の実行ではこの3倍ぐらいに跳ね上がっていましたが。。一度クリーンアウトされたのでは??)

さらに不思議なことに、REDO生成されないんですね。。。。

ん? ちょっと待ってください。一度、クリーンアウトされたブロックがなぜ、再度クリーンアウトされているのでしょう? scattered readで遅延ブロッククリーンアウトされたケースと動きが違います!!!!!!

Oracleを再起動する前のステップでREDOログが異常に少ないにも関わらず、遅延クリーンアウトされていた統計値が高くなった。Oracleを再起動した後でも、同様に、direct path read で読み込み、遅延ブロッククリーンアウト発生。しかもREDOログはありません。。。これって、クリーンアウト行われているようですが、実際にはメモリー上だけで実祭のブロックはクリーンアウトされずに残っているということですよね。なんども発生しているわけですから。
(ブロックダンプしなくても統計値から状況は見えてきましたよね!!)

direct path readはその名の通り、バッファキャッシュを介さず、常にストレージからデータブロックを読み込み、PGAへ。このケースだとSELECT文なので単純にPGAへ直接読み込み、メモリ上ではクリーンアップは行なっているようですが、クエリーが終了すれば、単に捨てられるのみ。。。なので、クリーンアウトの結果は永続化されない。。。ということになりますよね!

ということは、SELECT文の場合は、scattered read等でバッファキャッシュを経由させないと、遅延ブロッククリーンアウトは、ずーっと先延ばしされる。。。direct path readのSELECT文を2回実行してクリーンアウトさせたわけだが、この後、scattered readで全表走査させれば、遅延ブロッククリーンアウトが発生して、大量のREDOログが生成されるて、完全にクリーンアウトされる。。。。。。はず。。。ですよね。

試してみよう!!!!

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat DBWR object drop buffers written 2
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 36
sysstat commit cleanouts successfully completed 36
sysstat consistent gets 106766
sysstat db block changes 166
sysstat deferred (CURRENT) block cleanout applications 18
sysstat free buffer requested 1130
sysstat immediate (CR) block cleanout applications 10959
sysstat immediate (CURRENT) block cleanout applications 10
sysstat no work - consistent read gets 77392
sysstat physical reads 67827
sysstat physical reads direct 66710
sysstat physical writes 2
sysstat physical writes from cache 2
sysstat physical writes non checkpoint 2

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98223
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 587
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67289
sesstat physical reads direct 66710


15) Oracle再起動
諸々情報を綺麗にするので再起動!!

$ sudo service oracle restart


16) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


17) CDBのSYSで統計取得(再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


18) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)3回目

キターーーーーーーーーーーーーーっ!。 予想的中!!。(競馬ならいいのにw) 

大量のREDOログが生成され、物理読み込みも初生しています。direct path readで全表走査させた時とは明らかに違う!!!(以前、見た光景!!w

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.84

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
91170 consistent gets
66720 physical reads
964436 redo size
4539159 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off


19) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)3回目

physical reads が想定ブロック数以上あるため、物理読み込みされ全ブロックが読み込まれていると読み取れます。また、physical reads direct は変化していません。(変化のない統計は記載していません)
つまり direct path read ではなく scattered read で全表走査が行われたことを示しています。

immediate (CR) block cleanout applications                     10959

という統計から、遅延ブロッククリーンアウトが発生し、ほぼ想定していたブロック数であることも確認できます。

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat cleanouts only - consistent read gets 10959
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 98846
sysstat db block changes 10992
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67078
sysstat immediate (CR) block cleanout applications 10959
sysstat no work - consistent read gets 72660
sysstat physical reads 67069

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat cleanouts only - consistent read gets 10959
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 98234
sesstat db block changes 10988
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67071
sesstat immediate (CR) block cleanout applications 10959
sesstat no work - consistent read gets 72324
sesstat physical reads 67063


20) Oracle再起動
以前の検証で、scattered readでブロッククリーンアウトされた場合のSELECT文であっても結果は永続化されるので、再度読み込ませた場合はクリーンアウト済みなので再度遅延クリーンアウトが発生しないことは確認確認済みですが、今一度確認しておきましょうw

$ sudo service oracle restart


21) PDBのscottでログインしてclient_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションを特定するため。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


22) CDBのSYSで統計取得(再再々起動後初回)
内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat scott
...略...


23) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表をscattered readで全表走査)4回目

想定通り、遅延ブロッククリーンアウトは発生せず、REDOログも生成されていません! めでたしめでたしw

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 20400

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:04.32

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
4539159 bytes sent via SQL*Net to clientyoutub
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

24) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)(対象表をscattered readで全表走査)4回目
physical reads はありますが、 physical reads direct は変化していません。これは scattered read で全データを読み込んだと見て良いでしょうね。読み込んだブロックサイズも該当表の想定データブロック数程度です。
また、遅延ブロッククリーンアウトが発生したことを示す統計は変化していないことから、遅延ブロッククリーンアウトは発生していないことも読み取れます。(^^)

統計値が変動したもののみ記載
(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat commit cleanouts 3
sysstat commit cleanouts successfully completed 3
sysstat consistent gets 87659
sysstat db block changes 34
sysstat deferred (CURRENT) block cleanout applications 2
sysstat free buffer requested 67058
sysstat no work - consistent read gets 83510
sysstat physical reads 67048

(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 87049
sesstat db block changes 30
sesstat deferred (CURRENT) block cleanout applications 1
sesstat free buffer requested 67051
sesstat no work - consistent read gets 83175
sesstat physical reads 67042



まとめ

SELECT文であっても、遅延ブロッククリーンアウトが発生すると該当ブロックは更新され、REDOログが生成される。ただし、direct path read で読み込まれた場合を除く。
ということのようですね。

ふむふむという興味深い動きですよね。これ。:)

では、次回は direct path に関わる別の動きも確認してみましょうか。。。
このシリーズ、まだまだ引っ張れそうw

ということで次回へつづく。


台風の影響を心配したけどなんとか良い天気の遅い夏休みで。暑くも寒くもないく散歩の気持ちいい秋空 :)



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8



| | | コメント (0)

2021年9月14日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #8

Previously on Mac De Oracle
前回からのつづき(ちょいと寄り道中)
です。

では、とっとと試してみましょうw

バッファキャッシュから溢れ出る程度のデータ量だったら、どうなるのかなーーーー、という検証です。やりたいことは図の通りです。
Photo_20210911234101

検証方法を考えていたのですが、自動共有メモリー管理になっているのと、sga_max_size/sga_targetを小さくしすぎるとOracle Databaseが起動しないなど諸々引きそうなので、shared_pool_sizeを大きく設定して、バッファキャッシュに回せるメモリーを減らすことで、バッファキャッシュを小さく、バッファから溢れる程度のデータ量も少なくて済むようにして試してみることにします。

準備段階から書いてます。再現させる環境をどうセットアップしたかっていうことも重要だと思うのですよね。少々長くなっちゃいますが。



検証準備

CDBに接続して初期化パラメータを調整!!

SGAコンポーネントの状況
Database Buffersが、3G以上になってます。検証データ量も多くなってしまうので、これを1GB程度まで下げたいですね。検証時間も節約できますし、最小の手数で検証できるほうが良いですから:)

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 3472883712 / 1024 / 1024 AS "MB" from dual;

MB
----------
3312

Elapsed: 00:00:00.00


sga_max_size,sga_min_sizeが4GBですが、ここはそのままにします。あまり小さくしすぎると起動しなくなったり。(^^;;;;

SYS@orclcdb> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 4G
sga_min_size big integer 0
sga_target big integer 4G
unified_audit_sga_queue_size integer 1048576


自動SGA管理なので、Shared Pool Sizeに大きめの値を設定。
自動SGA管理下で自動管理対象メモリーコンポーネントパラメータに値を設定した場合、その値が下限値となり最低でもその値は確保されるという仕組みを利用します!

SYS@orclcdb> show parameter shared_pool

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
shared_pool_reserved_size big integer 39426457
shared_pool_size big integer 0


起動しなくなっても戻しやすいようにspfileをpfileに書き出して退避後に、shared_pool_sizeを3GBへ増やします。これで4Gのうちの3G程度がshared poolに割り当てられ、バッファキャッシュは1GBぐらいになるはず。(VirtualBoxなのでスナップショット取得しておいて戻すのもありですけどw)

SYS@orclcdb> create pfile='pfile20210912.ora' from spfile;

File created.

Elapsed: 00:00:00.00
SYS@orclcdb> alter system set shared_pool_size = 3g scope=both;

System altered.

Elapsed: 00:00:00.77


Database Buffersがいい感じにシュリンクしました。1GBぐらいになりました。これで進めますよー。

SYS@orclcdb> show sga
Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 3238002688 bytes
Database Buffers 1040187392 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb> select 1040187392 / 1024 / 1024 AS "MB" from dual;

MB
----------
992

Elapsed: 00:00:00.01


ざっくりとブロック数を計算

SYS@orclcdb> select 1040187392 / 8192 AS "blocks" from dual;

blocks
---------------
126976

Elapsed: 00:00:00.01


前回のHOGE表に200,000 rowsで、66,667 blocks のデータを生成したので、126,976 blocks を満たすデータ量にしようとすると 400,000 rowsほど必要になりそうですね。。。。少々多めで、バッファキャッシュから溢れる程度の量で、 500,000 rowsのデータを登録することにしましょう!!!!

SYS@orclcdb> select ceil(126976 / 66667) * 200000 AS "rows" from dual;

rows
----------
400000

Elapsed: 00:00:00.00


これまでの検証から 10%-15%程度がCOMMITクリーンアウトされ、残りが遅延されるのは確認できたので、126976 blocks のバッファキャッシュだと、 17,777 blocks ぐらいがコミットクリーンアウトされそうですね。(今回のテストケースではコミットクリーンアウトされないけど。。された場合は最大でこの程度。。。というメモです。はい)

SYS@orclcdb> select ceil(126976 * 0.14) AS "blocks for commit cleanout" from dual;

blocks for commit cleanout
--------------------------
17777

Elapsed: 00:00:00.00

前回作成したデータは、200,000rowsで、66,667 blocksだったので、500,000 rows だと、ざっくり 166,668 blocks ほど。

SCOTT@orcl> select ceil(66667 / 2 * 5) AS "blocks" from dual;

blocks
----------
166668

Elapsed: 00:00:00.00


なので、遅延ブロッククリーンアウトされると想定される(コミットクリーンアウト分を覗くと)ブロック数は、148,891 blocks 程度にはなりそう。

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 17777) AS "blocks" from dual;
blocks
----------
148891

Elapsed: 00:00:00.01


それに加えて、バッファキャッシュに収まらず、コミットする前にバッファキャッシュから落とされ、ストレージへかきだされてしまうブロック数は、これまた、ざっくり計算すると 39,692 blocks ほどですかね。バッファキャッシュのサイズから全てのブロックは乗り切らないので、最初に読み込まれていたブロックから落とされていくことにはなりますね。。
とはいえ、この検証ではキャッシュ落とされるブロック数は特に気にしてなくて、バッファキャッシュ以上のブロック数が生成されていればいいので、落とされそうなのが確認できればOK.

SYS@orclcdb>  select ceil((66667 / 2 * 5) - 126976) AS "blocks" from dual;

blocks
----------
39692

Elapsed: 00:00:00.00


とりあえず、生成するデータ量(行数)の算出とバッファキャッシュサイズの調整はおわり。


次に、実際にデータを生成してブロック数とセグメントサイズを確認して、実行用スクリプトの調整を行なっておきます。



PDBでテストデータの実サイズの確認
SCOTT@orcl> @droppurge_create_hoge.sql
1* drop table hoge purge

Table dropped.

Elapsed: 00:00:00.25
1* create table hoge (id number, data varchar2(2000))

Table created.

Elapsed: 00:00:00.04
1* select segment_name,blocks from user_segments where segment_name like '%HOGE%'

no rows selected

Elapsed: 00:00:00.11


500,000行登録!!

SCOTT@orcl> @insert_each_rows_5.sql
1* begin for i in 1..500000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:02:37.68


データが登録されているブロック数は、166,667 blocks で、事前に計算していた 166,668 blocks にほぼおなじ。(狙い通り)
セグメントサイズは、約 1344 MB ですね。

SCOTT@orcl> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
166667

Elapsed: 00:00:05.03
SCOTT@orcl> select segment_name,blocks,bytes/1024/1024 AS "MB" from user_segments where segment_name = 'HOGE';

SEGMENT_NAME BLOCKS MB
------------------------------ ---------- ----------
HOGE 172032 1344

Elapsed: 00:00:00.17


Scattered read でTable Full Scanできるように少々隠しパラメータを調整しておきますね。念の為。(セッションレベルで調整してます)
セグメントサイズが、1344 MBなので、_very_large_object_threshold は、2048 MBぐらい設定しておけば、Scattered readのまま行けそうですね。

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 2040
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off


準備完了!!!!!





準備長かったけどw やっと本題です!!!w 実行している内容はいままでと同じなのでかなり端折ってポイントだけ記載。

バッファキャッシュから溢れるほどのデータ量で。コミットクリーンアウトはどうなるのだろうか。。。。想定では、ほぼコミットクリーンアウトできないはずではあるのだが。。。。

CDBのSYSで統計取得(コミット後)

commit cleanouts successfully completedはどれぐらいだったのか。。。。。ありません。commit cleanouts successfully completedに差分がなかったので、コミットクリーンアウトしようして失敗、commit cleanout failures: block lostと同数なので、1ブロックもコミットクリーンアウトできない!  commit cleanout failures: block lostがバッファキャッシュに対象ブロックがなかったことを示しています。

つまり、バッファキャッシュに乗り切らなため、最初にINSERTされたブロックはそのままストレージへ物理書き込みされて追い出された結果。。ということになりますね。

登録したブロック全てが遅延クリーンアウト対象になってしまった、ということになります。コミット時にクリーンアウトできてないわけだから!!!!!!!! (イメージ図でざっくり書いたとおりの感じに。。。

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanout failures: block lost 3028
sysstat commit cleanouts 3028
sysstat consistent gets 274
sysstat db block changes 1
sysstat no work - consistent read gets 149
sysstat physical reads 60

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanout failures: block lost 3028
sesstat commit cleanouts 3028
sesstat db block changes 1


次に、Scattered Readが実行される全表走査を行わせ、遅延ブロッククリーンアウトどれだけ発生するか結果確認!

PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)
想定通り、物理読み込み(この時点ではscattered readなのか、direct path readなのかわかりませんが)になっています。また、大量のREDOログが生成されているので遅延ブロッククリーンアウトが発生しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:01:03.08

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
367852 consistent gets
160269 physical reads
14670268 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off


物理読み込みを伴う全表走査でどの程度の遅延ブロッククリーンアウトが発生したか統計を確認!!!!

CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

想定どおり、INSERTした全ブロックが、immediate (CR) block cleanout applications = 166667 で遅延ブロッククリーンアウトされたことがわかります。(冒頭に記載していますが、データが格納されているブロック数は、 166667 blocks でしたよね)
physical readsは意図通り発生していますが、physical reads directが変化していないので、狙い通りScattered Readになったようですね

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 241
sysstat DBWR thread checkpoint buffers written 241
sysstat DBWR undo block writes 1109
sysstat cleanouts only - consistent read gets 166667
sysstat commit cleanouts 55
sysstat commit cleanouts successfully completed 55
sysstat consistent gets 377331
sysstat db block changes 166904
sysstat deferred (CURRENT) block cleanout applications 29
sysstat immediate (CR) block cleanout applications 166667
sysstat immediate (CURRENT) block cleanout applications 15
sysstat no work - consistent read gets 38449
sysstat physical reads 160805
sysstat physical writes 137551
sysstat physical writes from cache 137551
sysstat physical writes non checkpoint 137466

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 166667
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 374538
sesstat db block changes 166710
sesstat deferred (CURRENT) block cleanout applications 3
sesstat immediate (CR) block cleanout applications 166667
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 36779
sesstat physical reads 160513


念の為、今一度、物理読み込みを伴う全表走査を行なって、クリーンアウトされたのか確認してみましょうw(疑い深いw)
もう一度、PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

redo size0 なのでクリーンアウトは発生してない。想定通り

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 2040

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

500000 rows selected.

Elapsed: 00:00:33.17

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 669K| 647M| 46462 (1)| 00:00:02 |
| 1 | TABLE ACCESS FULL| HOGE | 669K| 647M| 46462 (1)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
200399 consistent gets
148198 physical reads
0 redo size
1016952118 bytes sent via SQL*Net to client
367706 bytes received via SQL*Net from client
33335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500000 rows processed

set autot off

同様に、統計でも確認してみます!
CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

クリーンアウトを示す統計値は上昇していません!!!(うんうんw)

差分のあった統計のみ記載

CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 732
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 200522
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 200403
sysstat physical reads 148200
sysstat physical writes 68570
sysstat physical writes from cache 68570
sysstat physical writes non checkpoint 68331

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 200414
sesstat db block changes 9
sesstat no work - consistent read gets 200366
sesstat physical reads 148198



OK. Done. ということで、まとめ!

バッファキャッシュには収まりきれないデータ量の場合、コミットクリーンアウトしようとしていたブロックも追い出されてしまうので、結果的に、全ブロックが遅延ブロッククリーンアウトになった。というイメージしていた結果の通りでした。
(今回のケースもシンプルケースなので比較的予想しやすい結果ですが、クリーンアウトに関わる統計は以外に多く、複雑な動きになるものもあります。再現するののめんどくさいのでしませんがw)
Photo_20210911234101

寄り道はここまで、次回は、こんどこそ、direct path readと遅延ブロッククリーンアウトの関係をみていきたいと思います。


来週天気いいかなー。遅い夏休みなのに。微妙な気がしてきた。。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7


| | | コメント (0)

2021年9月12日 (日)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #7

Previously on Mac De Oracle
前回は、コミットクリーンアウトと遅延クリーンアウト、そして、そこにTable Full ScanでScattered Read (待機イベントだと db file scattered read) を絡めてストレージへ永続化されたクリーンアウトが遅延されてしまったブロックを物理読み込みませつつ遅延ブロッククリーンアウトを再現させてみました。
また、次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。なんてことを言っていましたが、またまた、ちょいと寄り道ですしますw

バッファキャッシュから溢れるぐらいのデータをぐるぐる系INSERTで、しかも1回のコミットにしたら、コミット前にあふれたデータはストレージへ書き出され、かつ、クリーンアウトも遅延されるよなー。という予想を元に、ちょいと遊んでから次に進みたいw と思います。

これまでの流れから、基本的なクリーンアウトおよび遅延ブロッククリーンアウトとしては以下ようなパターンを確認してきました。

バッファキャッシュの上でコミットクリーンアウトおよび、遅延ブロッククリーンアウト(単純なタイプ)が行われているケース
Photo_20210911234001

ここからが想像というか、私が理解している範囲から想像した動き。バッファキャッシュから溢れはしないけど、いっぱいいっぱいな場合は、クリーンアウトされるブロックがキャッシュ上に多くあるだろうな。と.
とは言っても、バッファキャッシュ上ではあるわけです。
Photo_20210911234002


そこで、ちょいと意地悪をして、バッファキャッシュから溢れ出る程度のデータ量だったどうなるのかなーーーーと。冒頭ですでコメントしているわけですけどもw 多分、以下のような動きだよねー、と。
そういえば、以前、DBTSで行なったセッションの「バッファキャッシュ欠乏症」の部分で、似たようなバッファキャッシュから溢れ出したブロックの挙動をなんとかするみたいな資料も今回の動きを想像するにはよいかもしれないですね。
Photo_20210911234101


と、思い、頭の中のイメージを Pagesでざざっと作ったところで、本日はここまで。次回へつづく。


Beat SaberとWalkingの合わせ技で、効果的な減量継続中w



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6



| | | コメント (0)

2021年9月10日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #6

Previously on Mac De Oracle
前々回と前回はバッファキャッシュの10%を超えるデータ量のINSERT文の実行とCOMMITの実行で、バッファキャッシュの10%-15%程度は、COMMIT時にクリーンアウトされ、残ったブロックのクリーンアウトは先送りされる。という検証を2つのパターンで確認してみました。

どのような流れで発生するかを各ステップ毎にシステム統計(CDB)とクエリーを実行するセッションのセッション統計(PDB)を取得し、どのように統計値が変化すれば、コミットクリーンアウトや遅延ブロッククリーンアウトが起きているのかを見ながらすすめました。以下2つのエントリーで確認した動きの違いはイメージできたのか少々不安ではありますがw (そこそこ長いエントリーなのでw)

こちら前々回は、クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
で、

前回は、クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

なんです :) 


まだ、イメージつかめない方もいるかもしれないので

超ざっくりした絵が頭の中に浮かばない方向けに、上記検証を行う前に、私の頭の中にうかんだ、ラフイメージをほぼそのまま

(こまけーとこは気にしないでくださいね。ラフイメージですから、こうだろうなーというのを想像している状態そのままのイメージですのでw)

クリーンアウトが遅延されたブロックが永続化される前に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
No4


クリーンアウトが遅延されたブロックが永続化された後に、遅延ブロッククリーンアウトさせてみたケース
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5
No5

再現させてるケースはシンプルなものなので処理時間云々を比較してはいないですが、複雑なケースになると、本来スマートスキャンさせたいのにシングルブロックリードが多くなったりするケースなど、以前紹介したURLを見ていただければ参考になるかもしれないですね。
クエリーやDMLの処理時間が伸びてビビるぐらいに仕事量が増えてたり、先送りされたことで、もろもろ後処理が複雑化する場合もあるわけで)

ということで、こんな図をイメージしながら、ネタ作ってます。はいw


次回は、図中のscattered read 部分を direct path read にしつつ、最後の最後で、scattered read にしてみる、とか、そんなイメージをぼやーーーーんと浮かべながら、発生させる方法をどうするか考えてますw。つづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5



| | | コメント (0)

2021年9月 9日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #5

前回はバッファキャッシュの10%を超えるデータブロックへのINSERT文の実行とCOMMITの実行で、バッファキャッシュの13%-15%程度はCOMMIT時にクリーンアウトされ、残りは遅延ブロッククリーンアウト(先送り)される。
direct path readではないSELECT文による(前回のケースでは scattered read))遅延ブロッククリーンアウトは、1度のみ発生するという状況を確認しました。

ところで、
前回のエントリで、2度、全表走査(前回の検証ではscattered read)を実行しているのですが、物理読み込みは発生させていません。(INSERT→COMMIT→SELECT→SELECTという流れで、十分なバッファキャッシュがあるので、当然ではあるのですがw)
バッファキャッシュに乗ったままのブロックが遅延クリーンアウトされていたわけです。

前回のエントリ:古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

前述の1.2.それぞれの実行で物理読み込みだったなにか変化はあるのだろうか。。。予想では、上記に加えてscattered readに伴う物理読み込みが増えるだけのはずです。その動きを見てみることにします。(こういう動きを見ていると楽しいですよねw)
手順は前回と同じですが、各全表走査の前にインスタンスを再起動してバッファキャッシュを空にしておきます。
(buffer cacheをflushすればいいじゃん。という声も聞こえてきそうですが、今回は再起動でクリアしました。はいw)


前回から多少変更したスクリプトも載せておきます(本文中にもありますが)、細かい解説は後述

$ cat table_full_scan.sql
alter session set "_serial_direct_read" = never
.
l
/
alter session set "_very_large_object_threshold" = 1056
.
l
/

!echo set autot trace exp stat
set autot trace exp stat


select * from hoge
.
l
/

!echo set autot off
set autot off




0) 対象表をdrop/create
オブジェクト作り直し

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにインスタンス再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別するため

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(統計差分取得のためのベースラインを取得しているだけ)

SYS$orclcdb> @show_stat scott


4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

未コミットの状態なので特に、気にせず、ふーーーん。ぐらいの感じで眺めていただければいいですね。前回同様に、commit cleanouts, commit cleanouts successfully completed, deferred (CURRENT) block cleanout applications, immediate (CURRENT) block cleanout applicationsといったクリーンアウト関連統計が極わずかありますが、この時点で発生しているのは本題ではないので気にしなくてOK

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 23589
sysstat DBWR checkpoints 2
sysstat DBWR thread checkpoint buffers written 22643
sysstat DBWR transaction table writes 50
sysstat DBWR undo block writes 848
sysstat cleanouts and rollbacks - consistent read gets 5
sysstat commit cleanout failures: callback failure 20
sysstat commit cleanouts 1320
sysstat commit cleanouts successfully completed 1300
sysstat consistent gets 124041
sysstat db block changes 757165
sysstat deferred (CURRENT) block cleanout applications 715
sysstat immediate (CR) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 196
sysstat no work - consistent read gets 46398
sysstat physical reads 4063
sysstat physical writes 23589
sysstat physical writes from cache 23589
sysstat physical writes non checkpoint 23494

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 49596
sesstat db block changes 744727
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 15


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)
前回同様ノイズもなく、綺麗にコミット時のクリーンアウトが発生しています。バッファキャッシュの約14-5%程度なのは前回と変わらずですね。

差分のある統計のみ記載

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) Oracle Databaseを再起動してバッファキャッシュをクリア

ここが前回と違う手順で、クリーンアウトされないブロックはずーーーーーっと残るよね。ということの確認でもあります。(alter system flush buffer_cacheでも同じことができるわけですが、ここでは再起動しています)

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


9) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


10) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したので統計値の差分取得用ベースライン統計の取得)

SYS$orclcdb> @show_stat scott


11) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の1回目

ここでは待機イベントまでは確認できませんが、Table full scanと実行統計よりRedoログがたっぷり生成されていることは確認できます。SELECT文ですが。。。つまり、遅延ブロッククリーンアウトが発生しているということですね。確認は後述の統計で。
狙い通り、物理読み込みも発生しています!

Note)
"_very_large_object_threshold" = 1056 としているのは、direct path readとなる上限セグメントサイズをhoge表が超えているためdirect path readを抑止するためにこの隠しパラメータで上限値を引き上げ、scattered readになるように強制しています。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.01
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.10

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


12) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の1回目

遅延ブロッククリーンアウト関連統計値が上昇しているので、遅延ブロッククリーンアウトの発生が確認できます。ここまでは前回と同じ。(同じじゃないと困りますがw)

違う点は、事前にインスタンスを再起動しているため、physical reads が上昇しています。これは hoge表を scattered readで全表走査しているからです。(phsical read directは発生していない)バッファキャッシュを経由するのでconsistent gets,no work - consistent read gets も上昇しています。狙い通りです。

そして、重量な遅延ブロッククリーンアウトですが、バッファキャッシュでヒットしていた時と同数のブロックで発生しています。(ニッコリ

インスタンスの停止や起動があったとしても、クリーンアウトが先送りされたブロックはアクセスされない限りクリーンアウトされず残っているということを示しています!!!!(ここ試験にでますよー。嘘)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 7
sysstat commit cleanouts successfully completed 7
sysstat consistent gets 98967
sysstat db block changes 11016
sysstat deferred (CURRENT) block cleanout applications 3
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 72543
sysstat physical reads 67403

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98357
sesstat db block changes 11012
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72208
sesstat physical reads 67397


13) Oracle Databaseを再起動してバッファキャッシュをクリア

再度、インスタンスを再起動して、バッファキャッシュをクリアします。後続の全表走査では、遅延ブロッククリーンアウトは発生せず、物理読み込み(この検証では scattered readさせています)を伴うTable full scanが行われるだけのはずです。

$ sudo service oracle restart
[sudo] password for oracle:
Restarting oracle (via systemctl): [ OK ]
$


14) PDBのscottでログインし、client_infoをセット

disconnectしたので再度、client infoをセットし直し!

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


15) CDBのSYSで統計取得(再起動後初回)

内容は省略!(再起動したのでベースラインとなる統計を取得)

SYS$orclcdb> @show_stat scott


16) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)- scattered read / table full scan の2回目

実行統計から、physical readsが、発生しています。Redoは生成されていないことも読み取れるので、遅延ブロッククリーンアウトは発生していないことも確認できます。:) 想定通りですね。

SCOTT@orcl> @table_full_scan.sql
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
1* alter session set "_very_large_object_threshold" = 1056

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:06.44

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off
SCOTT@orcl>


17) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)/ scattered read / table full scan の2回目

physical reads は発生していますが、physical reads directではないことが確認できます。Table full scanをscattered readで読み込んでいるという想定通りの結果。 
遅延ブロッククリーンアウトを示deferred (CURRENT) block cleanout applications 、immediate (CURRENT) block cleanout applicationsや、コミットクリーンアウトを示すcommit cleanouts 、commit cleanouts successfully completed という統計が極わずかに変動していますが、今回の検証ではノイズなので気にしたくてOK。

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 11
sysstat commit cleanouts 15
sysstat commit cleanouts successfully completed 15
sysstat consistent gets 87375
sysstat db block changes 79
sysstat deferred (CURRENT) block cleanout applications 12
sysstat immediate (CURRENT) block cleanout applications 3
sysstat no work - consistent read gets 83322
sysstat physical reads 67028
sysstat physical writes 11
sysstat physical writes from cache 11
sysstat physical writes non checkpoint 11

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 2
sesstat commit cleanouts successfully completed 2
sesstat consistent gets 86936
sesstat db block changes 29
sesstat deferred (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 83098
sesstat physical reads 67023




まとめ

遅延ブロッククリーンアウトは、インスタンスを停止して残ったままということが確認できました。(クリアされるまで残るのですよねー)
クリーンアウトが遅延されているブロックが物理読み込みされた(アクセスされた)タイミングで遅延ブロッククリーンアウトが発生することも確認できました。(物理読み込みの有無には関係しない)
バッファキャッシュ上の遅延ブロッククリーンアウト同様、クリーンアウトされたブロックでは、再度、クリーンアウト対象になるような更新が発生しなければ、遅延ブロッククリーンアウトは発生しない

久しぶりにシステム統計やセッション統計を見ててワクワクしてきましたよーーーーっw

ところで、
冒頭でも記載しましたが、バッファキャッシュ上の遅延ブロッククリーンアウト(再掲)に加え、物理読み込みを伴う遅延ブロッククリーンアウトのauto traceの結果をまとめて記載しておきます。SELECT文ですが、REDOログが生成されている場合は遅延ブロッククリーンアウトが発生しているということになります。

実行計画

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

1. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータがバッファキャッシュ上ににあるためクリーンアウトに伴う物理読み込みはない。

Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed

2. 直後に再度全表走査した場合も、キャッシュヒットしているので、物理読み込みはない

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed


3. 遅延ブロッククリーンアウトを発生させた場合。遅延ブロッククリーンアウト対象のデータを物理読み込みし、バッファキャッシュに載せている動きが見えます。

Statistics
----------------------------------------------------------
38 recursive calls
13 db block gets
91519 consistent gets
67073 physical reads
967432 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

4. 3.の直後に再度全表走査した場合。事前にキャッシュをクリアしているため、物理読み込みがありますが、遅延ブロッククリーンアウトは発生していません。(Redoが生成されていないことで確認できます)

Statistics
----------------------------------------------------------
30 recursive calls
0 db block gets
80211 consistent gets
66719 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
200000 rows processed

ではでは。
次回は、scattered readのtable full scanではなく、direct path readだったらどうなるのか調べてみましょうか。。(いろいろな再現方法があるわけですが、手間のかからないお手軽な再現方法で確認してみようと思いますw)


洗濯機の修理が終わって一安心w ということで、次回へつづく。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4


| | | コメント (0)

2021年9月 3日 (金)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #4

Previously on Mac De Oracle
前回はバッファキャッシュの10%未満のデータブロックへのINSERT文の実行とCOMMITの実行で、遅延ブロッククリーンアウトは発生せず、COMMIT時にすべての対象ブロックがクリーンアウトされるということを確認しました。

今回は、そのデータ量を倍にして、バッファキャッシュの10%程度を超えるデータブロックが遅延ブロッククリーンアウトされるのかを見ていくことにします。手順は前回と同じですが、遅延ブロッククリーンアウトさせた後で、もう一度全表走査してクリーンアウトが繰り返されないことも確認しておきます(次回以降に予定している確認への伏線なのですがw)


0) 対象表をdrop/create
オブジェクトを作り直して前提条件を合わせておきます

SCOTT@orcl> @droppurge_create_hoge

Table dropped.

Table created.

SCOTT@orcl> select segment_name,blocks from user_segments where segment_name like '%HOGE%';

no rows selected


1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('Target Session');
3* end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SCOTT@orcl>


3) CDBのSYSで統計取得(初回)

内容は省略!(ベースラインを取得しているだけなので)

SYS$orclcdb> @show_stat

4) PDBのSCOTTユーザーでデータINSERT(データ量2倍、コミットなし)

SCOTT@orcl> @insert_each_rows_2
1* begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.

Elapsed: 00:00:29.48
SCOTT@orcl>


5) CDBのSYSで統計取得(INSERT後、未コミット)

INSERTしただけです。未コミットなので特に気になる情報は現れていません。この値からコミット後にどのように変化するのか? という部分に注目する必要があるんですよー。
deferred (CURRENT) block cleanout applications と immediate (CURRENT) block cleanout applications が僅かにありますが、この時点では気にする部分ではないです

(値の変化が1以上ある統計のみ表示)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 22756
sysstat DBWR checkpoints 33
sysstat DBWR thread checkpoint buffers written 22756
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 606
sysstat consistent gets 49761
sysstat db block changes 744980
sysstat deferred (CURRENT) block cleanout applications 4
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 192
sysstat physical reads 18
sysstat physical writes 22756
sysstat physical writes from cache 22756
sysstat physical writes non checkpoint 22756

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 49501
sesstat db block changes 744980
sesstat deferred (CURRENT) block cleanout applications 4
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 65
sesstat physical reads 17


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

この結果、ノイズも少なく、綺麗に取れてます!!! w

前々回の事前確認の通り、2倍のデータブロック数は、 66667ブロック で、バッファキャッシュの10%は、ざっくり計算で、42394ブロック。つまり、想定では 42394ブロック ほどが、commit時のブロッククリーンアウトの対象と想定していました。

覚えてますか? みなさん!

実際にcommit時にクリーンアウトされたのはどれぐらいでしょう? 
結果は、55700ブロックとなりました。想定より多いですねw ほぼ合ってはいますが。
実際にはバッファキャッシュの13%〜15%程度が閾値になっているように見えます。とはいえ、commit時にcleanoutされたブロック数は 55700ブロック ですから、残る 10967ブロック のcleanoutは遅延されたということは確実です。commit対象のデータブロック全てをcleanoutするわけではない、ということは確認できたのではないでしょうか?

(差分が1以上ある統計のみ記載)
(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 55700
sysstat commit cleanouts successfully completed 55700
sysstat db block changes 1

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat commit cleanouts 55700
sesstat commit cleanouts successfully completed 55700
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

SCOTT@orcl> sset autot trace exp stat
SCOTT@orcl> salter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

200000 rows selected.

Elapsed: 00:00:05.14

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 194K| 188M| 18189 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 194K| 188M| 18189 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
46 recursive calls
13 db block gets
91636 consistent gets
7 physical reads
967348 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
200000 rows processed


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

遅延ブロッククリーンアウトは、事前の計算通り、 10967ブロック 発生しています。SELECT文では、immediate (CR) block cleanout applications として現れることも確認できます。
また、cleanouts only - consistent read gets として も同数計上されているところが見てます。綺麗に現れています。

commit cleanouts, ommit cleanouts successfully completed がでていますが、ここでは気にしなくてよいですね、極わずかで。SELECT文なので。
immediate (CURRENT) block cleanout applications、deferred (CURRENT) block cleanout applications もでていますが、同じく極わずかで、対象表のものではないと考えられるためここでは気にしなくて良いですね。

しかし、計算通りに発生してくれると確認が楽w (想定外の動きじゃなくてよかったw)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat DBWR checkpoint buffers written 272
sysstat DBWR thread checkpoint buffers written 272
sysstat cleanouts only - consistent read gets 10967
sysstat commit cleanouts 16
sysstat commit cleanouts successfully completed 16
sysstat consistent gets 117000
sysstat db block changes 11207
sysstat deferred (CURRENT) block cleanout applications 10
sysstat immediate (CR) block cleanout applications 10967
sysstat immediate (CURRENT) block cleanout applications 2
sysstat no work - consistent read gets 83351
sysstat physical reads 939
sysstat physical writes 272
sysstat physical writes from cache 272
sysstat physical writes non checkpoint 260

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat cleanouts only - consistent read gets 10967
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 98162
sesstat db block changes 11011
sesstat deferred (CURRENT) block cleanout applications 2
sesstat immediate (CR) block cleanout applications 10967
sesstat immediate (CURRENT) block cleanout applications 2
sesstat no work - consistent read gets 72113
sesstat physical reads 122


10) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

そして、ここからがおまけの確認ステップです

もう一度、同じ表を全表走査してみます。どうなると思いますか? 遅延されていたブロッククリーンアウトも行われたのですから、当然、該当オブジェクトで遅延ブロッククリーンアウトは発生しない。はず! ですよね。

確認してみましょう。(発生してたらどうしようw、もうしそうなったらバグレポートでも上げようかなw)

.......

Redoは生成されてない! (よかった! 想定どおりだ!w)

SCOTT@orcl> @table_full_scan
1* alter session set "_serial_direct_read" = never

Session altered.

Elapsed: 00:00:00.00
set autot trace exp stat

1* select * from hoge

200000 rows selected.

Elapsed: 00:00:05.43

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 214K| 207M| 18223 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 214K| 207M| 18223 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
80061 consistent gets
0 physical reads
0 redo size
406775148 bytes sent via SQL*Net to client
147264 bytes received via SQL*Net from client
13335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
200000 rows processed

set autot off

SCOTT@orcl>

11) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)2回目

これもノイズが少なく綺麗に取れました。該当セッションでは物理読み込みも発生していないので、キャッシュから全データを読み込んだようです。

そして、想定どおり、該当セッションでは遅延ブロッククリーンアウトは発生していません!

commit cleanouts、commit cleanouts successfully completed、deferred (CURRENT) block cleanout applicationsが1ブロックありますがCDB側の管理情報関連でしょうね。気にする部分ではないですね。

(差分のあった統計のみ記載)

(CDB)システム統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sysstat commit cleanouts 1
sysstat commit cleanouts successfully completed 1
sysstat consistent gets 80150
sysstat db block changes 13
sysstat deferred (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 80075

(PDB) SCOTTのセッション統計

SOURCE  NAME                                                             VALUE
------- ------------------------------------------------- --------------------
sesstat consistent gets 80076
sesstat db block changes 9
sesstat no work - consistent read gets 80043




まとめ

おおよそ、バッファキャッシュの10%程度が commit時にcleanout されるという点については、約15%程度と見ておいたほうが良さそうですが、まあ大きな違いはないので、その辺りに閾値があると考えて問題はなさそうです。
また、それを超えるブロックについては、cleanoutが先送りされ、最初に該当ブロックにアクセスしたSQLがその影響を受ける。

この検証ではSELECT文では、immediate (CR) block cleanout applications という形で統計に現れました。UPDATE文やDELETE文の場合は他の統計として現れそうですね。(CURRENT)関連のcleanoutの統計は今回動いていないのでSQL文を変えて同じような検証をしてみると面白い結果をえられそうです。

そして、SELECT文で、遅延ブロッククリーンアウトされてしまえば、その間に更新が発生しなければ、クリーンアウトは発生しない(おまけで検証した部分ですが、別検証では興味深い動きを紹介する予定です。その伏線でもあります)

次回へつづく


5年目を迎えた、パナソニックのドラム洗濯機がH故障した。慌てて近所のコインランドリーを検索w 近所にあってよかったw



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3



| | | コメント (0)

2021年9月 2日 (木)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #3

Previously on Mac De Oracle
前回は準備を終えたところまででした。

今日は、簡単なところから確認していきましょう。

もしも、「遅延ブロッククリーンアウトが起きない程度のブロック更新量だったなら。。。」。結果は遅延ブロッククリーンアウトは起きないはず。 ですよね。

ざっと手順を紹介しておきましょう。下図の 1)〜9)の順で行います
20210901-221016

前述の手順で、各操作後の統計の差分(変化量)を見る。マニュアルの統計の説明ってざっくり過ぎてよくわからないのが多いわけですがw、操作と値の変化を合わせて観察すると、それなりには理解できる程度に値が変化していることに気づきますw :)

1) 統計をクリアするのにOracle再起動

$ sudo service oracle restart


2) PDBのscottでログインし、client_infoをセット
v$sessionのclient_info列の'TargetSession'文字列で他のSCOTTユーザーのセッションと区別できるようにしています。

SCOTT@orcl> @set_client_info
1 begin
2 DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
3* end;

PL/SQL procedure successfully completed.


3) CDBのSYSで統計取得(初回)
初回なのですべてリストしていますが、CON_ID=0のCDBのシステム統計([g]v$sysstat)とCON_ID=3のPDBの2)のCLIENT_INFOを設定されたセッションのセッション統計([g]v$sesstat)を取得します。
今回のケースではcleanoutが含まれている統計の差異だけに着目すれば良いのですが。準備運動程度のテストケースなので一応すべて載せておきます :)
(マルチテナントだと、DBRWの動きを見るにはCDBのDBWR関連の統計を見る必要があるため、CDBのシステム統計とPDBの当該セッションのセッション統計を対象にしています)

SYS$orclcdb> @show_stat
SOURCE NAME VALUE CON_ID
------- ------------------------------------------------------------ -------------------- ----------
sysstat DBWR checkpoint buffers written 0 0
sysstat DBWR checkpoints 0 0
sysstat DBWR fusion writes 0 0
sysstat DBWR lru scans 0 0
sysstat DBWR object drop buffers written 0 0
sysstat DBWR parallel query checkpoint buffers written 0 0
sysstat DBWR revisited being-written buffer 0 0
sysstat DBWR tablespace checkpoint buffers written 0 0
sysstat DBWR thread checkpoint buffers written 0 0
sysstat DBWR transaction table writes 0 0
sysstat DBWR undo block writes 0 0
sysstat cleanouts and rollbacks - consistent read gets 6 0
sysstat cleanouts only - consistent read gets 0 0
sysstat commit cleanout failures: block lost 0 0
sysstat commit cleanout failures: buffer being written 0 0
sysstat commit cleanout failures: callback failure 0 0
sysstat commit cleanout failures: cannot pin 0 0
sysstat commit cleanout failures: hot backup in progress 0 0
sysstat commit cleanout failures: write disabled 0 0
sysstat commit cleanouts 570 0
sysstat commit cleanouts successfully completed 570 0
sysstat consistent gets 158,083 0
sysstat db block changes 3,247 0
sysstat deferred (CURRENT) block cleanout applications 316 0
sysstat immediate (CR) block cleanout applications 6 0
sysstat immediate (CURRENT) block cleanout applications 15 0
sysstat no work - consistent read gets 108,516 0
sysstat physical reads 12,551 0
sysstat physical reads direct 0 0
sysstat physical writes 0 0
sysstat physical writes direct 0 0
sysstat physical writes from cache 0 0
sysstat physical writes non checkpoint 0 0
sysstat transaction tables consistent read rollbacks 0 0
sysstat transaction tables consistent reads - undo records applied 0 0
sesstat DBWR checkpoint buffers written 0 3
sesstat DBWR checkpoints 0 3
sesstat DBWR fusion writes 0 3
sesstat DBWR lru scans 0 3
sesstat DBWR object drop buffers written 0 3
sesstat DBWR parallel query checkpoint buffers written 0 3
sesstat DBWR revisited being-written buffer 0 3
sesstat DBWR tablespace checkpoint buffers written 0 3
sesstat DBWR thread checkpoint buffers written 0 3
sesstat DBWR transaction table writes 0 3
sesstat DBWR undo block writes 0 3
sesstat cleanouts and rollbacks - consistent read gets 0 3
sesstat cleanouts only - consistent read gets 0 3
sesstat commit cleanout failures: block lost 0 3
sesstat commit cleanout failures: buffer being written 0 3
sesstat commit cleanout failures: callback failure 0 3
sesstat commit cleanout failures: cannot pin 0 3
sesstat commit cleanout failures: hot backup in progress 0 3
sesstat commit cleanout failures: write disabled 0 3
sesstat commit cleanouts 1 3
sesstat commit cleanouts successfully completed 1 3
sesstat consistent gets 374 3
sesstat db block changes 4 3
sesstat deferred (CURRENT) block cleanout applications 1 3
sesstat immediate (CR) block cleanout applications 0 3
sesstat immediate (CURRENT) block cleanout applications 0 3
sesstat no work - consistent read gets 220 3
sesstat physical reads 28 3
sesstat physical reads direct 0 3
sesstat physical writes 0 3
sesstat physical writes direct 0 3
sesstat physical writes from cache 0 3
sesstat physical writes non checkpoint 0 3
sesstat transaction tables consistent read rollbacks 0 3
sesstat transaction tables consistent reads - undo records applied 0 3


4) PDBのSCOTTユーザーでデータINSERT(コミットなし)
データを1行単位でインサートしています。バルクインサートも使ってないです。綺麗なぐるぐる系ですねw。コミット時の効果を確認しやすいようにコミットは後で実行します!
このインサートで、前回の事前準備の時に確認しておいた、33334ブロックが更新されることになります

SCOTT@orcl> @insert_each_rows
1* begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;

PL/SQL procedure successfully completed.


5) CDBのSYSで統計取得(INSERT後、未コミット)

注) 3)の統計との差分のみ記載

未コミットであるこの時点で、commitクリーンアウトが発生(該当セッション統計でも同数発生。commit cleanoutsとcommit cleanouts successfully completed)してますが、これは気にしなくてもよいですね。
実行したトランザクションは、未コミットなので、この実行による直接的な影響ではないので。
また、deferred (CURRENT) block cleanout applicationsやimmediate (CURRENT) block cleanout applications の遅延ブロッククリーンアウトを示す統計も微量ですがこれも同様とみて良いでしょう。

この時点で統計取得理由は、操作毎に変化する統計を追うためなので、ふーーん。ぐらいでの雰囲気でOKです :)
コミット後とその後に該当オブジェクトをアクセスさせた時の遅延ブロッククリーンアウトの有無部分の部分が主役ですので。

参考)
INSERTした行の含まれる全ブロックはバッファキャッシュに載り切るブロック数なので、バッファキャッシュから書き出されているような動きも観測されていないのは確認できると思います。
(physical writes from cache,physical writes non checkpoint,physical writesの統計に変化がないので未記載ですが、それらの統計が動いていないということがその理由です)


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoints 1
sysstat commit cleanouts 6
sysstat commit cleanouts successfully completed 6
sysstat consistent gets 22884
sysstat db block changes 373785
sysstat deferred (CURRENT) block cleanout applications 5
sysstat immediate (CURRENT) block cleanout applications 1
sysstat no work - consistent read gets 59
sysstat physical reads 12


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 6
sesstat commit cleanouts successfully completed 6
sesstat consistent gets 22816
sesstat db block changes 373785
sesstat deferred (CURRENT) block cleanout applications 5
sesstat immediate (CURRENT) block cleanout applications 1
sesstat no work - consistent read gets 49
sesstat physical reads 12


6) PDBのSCOTTユーザーでコミットの実行

SCOTT@orcl> commit;

Commit complete.


7) CDBのSYSで統計取得(コミット後)

注)5)と7)で取得した統計の差分のみ記載

ここが主役ですよー。このケースではバッファキャッシュの10%に満たないブロック数になるようにしたINSERT文(繰り返し実行)で 33334ブロックになるようにしました。これらを1度でcommitした場合、すべてのブロックがcommit時にblock cleanoutされるはずです。

では見てみましょう。

SCOTTのセッション統計より、commit cleanouts および commit cleanouts successfully completed から想定どおり全ブロックがcommit時にcleanoutされていることがわかります!
システム統計はインスタンス全体なのでPDBのそれら統計より大きめにでているのも確認できます。CDB側ではほんの少し deferred (CURRENT) block cleanout applications がありますが、管理情報系の遅延ブロッククリーンアウトでしょうね。 
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1で紹介したとおり 10% 未満では commit 時点で該当ブロックすべてcleanoutされることが確認できました。


(CDB)システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 139
sysstat DBWR transaction table writes 22
sysstat DBWR undo block writes 55
sysstat commit cleanouts 33343
sysstat commit cleanouts successfully completed 33343
sysstat consistent gets 18111
sysstat db block changes 138
sysstat deferred (CURRENT) block cleanout applications 5
sysstat no work - consistent read gets 10853
sysstat physical reads 827
sysstat physical writes 139
sysstat physical writes from cache 139
sysstat physical writes non checkpoint 139


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 33334
sesstat commit cleanouts successfully completed 33334
sesstat db block changes 1


8) PDBのSCOTTユーザーで、遅延ブロッククリーンアウト影響有無確認(対象表を全表走査)

_serial_direct_read = never はdirect path readさせないためのおまじないです。direct path readで読み込まれたケースの動きは別エントリーで見ていく予定なので、direct path readを発生させずtable full scan (ようするにバッファキャッシュに載せる動き)で読み込むよう強制しています。
また、行数が多いのでその時間の短縮のためにautot trace exp statを有効にしてSELECT文を実行させつつ、termout offと同じ効果とauto explainの機能でSELECT文の実行統計からredoの生成有無を確認しています。(redoがあるということはなんらかの更新が行われているわけで、SELECT文の場合は比較的容易に遅延ブロッククリーンアウトの発生を推測できる統計にもなります)
以下のケースでは多少redoが生成されていますが、おそらく recursive callによるもので、HOGE表のオブジェクトそのものに対するもではなさそうです。(このケースではhoge表のオブジェクトに対する遅延ブロッククリーンアウトや
コミット時のブロッククリーンアウトの観察が主題なので、周りのノイズはあまり気にしなくてもOK(追いかけたい場合は別ですがw)


SCOTT@orcl> set autot trace exp stat
SCOTT@orcl> alter session set "_serial_direct_read" = never;

Session altered.

Elapsed: 00:00:00.00
SCOTT@orcl> select * from hoge;

100000 rows selected.

Elapsed: 00:00:02.59

Execution Plan
----------------------------------------------------------
Plan hash value: 2339479017

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 57526 | 55M| 9098 (1)| 00:00:01 |
| 1 | TABLE ACCESS FULL| HOGE | 57526 | 55M| 9098 (1)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

Statistics
----------------------------------------------------------
21 recursive calls
13 db block gets
40465 consistent gets
3 physical reads
2212 redo size
203382760 bytes sent via SQL*Net to client
73706 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed

SCOTT@orcl> set autot off


9) CDBのSYSで統計取得(遅延ブロッククリーンアウト有無確認)

注)7)のコミット時点からとの差異のみ記載。

多少ですが、SELECT文でredoが生成されてはいます。これは、commit cleanouts/commit cleanouts successfully completed (commit時に実施されるblock cleanout)と遅延ブロッククリーンアウトの統計の一つ、deferred (CURRENT) block cleanout applications が現れている影響ですね。コミット時点でHOGE表の全ブロックはcleanout済みなので recursive callによる内部管理情報関連で定常的に現れるものと考えられ、この検証では気にするところではないのでスルーしてください。

また、physical reads は、ほぼないため、この全表走査では、物理読み込み(direct path read含む)は、発生していないことも確認できます。
HOGE表はバッファキャッシュに載ったままという意図通りの状態にはなっているようです。
これ、今後のテストケースでも利用するので、キャッシュからエージアウトされないという点が確認できてると、以降の検証やりやすいんです:)


(CDB) システム統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sysstat DBWR checkpoint buffers written 34849
sysstat DBWR transaction table writes 32
sysstat DBWR undo block writes 897
sysstat commit cleanouts 558
sysstat commit cleanouts successfully completed 558
sysstat consistent gets 69973
sysstat db block changes 2862
sysstat deferred (CURRENT) block cleanout applications 311
sysstat immediate (CURRENT) block cleanout applications 28
sysstat no work - consistent read gets 57908
sysstat physical reads 812
sysstat physical writes 34849
sysstat physical writes from cache 34849
sysstat physical writes non checkpoint 34738


(PDB) SCOTTのセッション統計

SOURCE  NAME                                            VALUE
------- ---------------------------------------------------- ---------------
sesstat commit cleanouts 5
sesstat consistent gets 47037
sesstat db block changes 38
sesstat deferred (CURRENT) block cleanout applications 4
sesstat no work - consistent read gets 43103
sesstat physical reads 117


まとめ

DMLにより更新されたブロック数が、バッファキャッシュの10%未満のブロック数である場合、commit時にすべてcleanoutされ、対象表では遅延ブロッククリーンアウトは発生しない。
(想定通りなのですが、ちょいと安心w 19cでどう動くか確認してなかってしので少々ドキドキしてたw のはナイショ)

というわけで、今回はここまで。

いきなり涼しくなって、なんだこりゃ。。。。



古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1
古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2



| | | コメント (0)

2021年8月31日 (火)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #2

Previously on Mac De Oracle

前回は、遅延ブロッククリーンアウトに限らず、大きく変わったわけでも、最近実装された機能でもないのに意外に知られてないのか、良いところ悪いところ含め、現場でロストしてしまっているような知識って意外と多いのかもねー。なんて感じたので遅延ブロッククリーンアウトネタのURLリンクをまとめてみた。

続編書くにしても、同じようなことやっても面白くないので、ブロックダンプのような方法は使わず、[g]v$sysstatや[g]v$sesstatなどの統計から、ちょいと血糖値や尿酸値高めだよね的な角度からどのような変化が起きるか見ていくことにした :)

 

環境はVirtualBox上の19cでこれからの主流になるマルチテナントで試してみます。(非マルチテナントでの変化見ててもこれからはあまり役に立たないので)

今日は準備編

VirtualBox
https://www.virtualbox.org/

Pre-Built Developer VMs (for Oracle VM VirtualBox)のDatabase App Development VMとか
https://www.oracle.com/downloads/developer-vm/community-downloads.html

 

 

事前に準備しておくスクリプトは以下のとおり。繰り返し実行するので作っておくと便利ですよ。:)

まず最初に、遅延ブロッククリーンアウトはバッファキャッシュの10%ほどのブロックをコミット時にクリーンアウトして、残りを先送りするという基本的なお約束があるので、上記環境のOracle 19cがどの程度のバッファキャッシュなのかとブロックサイズを確認。これ大切ですよ。スクリプト準備する上でも :)

メモリサイズは大きめですが。。。w うちのは。(^^;;;


$ VBoxManage -v
6.1.26r145957
$
$ VBoxManage showvminfo 'Oracle DB Developer VM 19.3' | grep -E 'Memory|CPUs'
Memory size: 16384MB
Number of CPUs: 4
$

コミット時にブロッククリーンアウトされそうなブロック数をざっくり算出すると 42394 ブロックぐらいになりそう。


SYS@orclcdb> show sga

Total System Global Area 4294963960 bytes
Fixed Size 9143032 bytes
Variable Size 805306368 bytes
Database Buffers 3472883712 bytes
Redo Buffers 7630848 bytes

SYS@orclcdb>
SYS@orclcdb> show parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192
SYS@orclcdb>
SYS@orclcdb> select ceil( 3472883712 / 8192 * 0.1 ) from dual;

CEIL(3472883712/8192*0.1)
-------------------------
42394

Elapsed: 00:00:00.00

42394ブロックを超える程度のサイズのデータを生成するINSERT文と、その範囲に収まるデータ量生成INSERT文スクリプトを作れば良さそうですね。

想定されるブロックに収まる程度の量。1ブロック 8KB でデフォルトのPCTFREEは10%なのでざっくり6000bytes/rec超えるぐらい。
で、1ブロックに3行ぐらい入るようにすれば面白いかな。。

ということで、

表はなんどもdrop/createするので以下のDDLで。初回は索引を作らず、表のみので影響をみることにする。


$ cat droppurge_create_hoge.sql

drop table hoge purge;
create table hoge (id number, data varchar2(2000));

データ作成(バッファキャッシュの10%未満のデータ登録)
なお、確実に遅延ブロッククリーンアウトの影響を見たいので1行ごとのINSERTを繰り返し、コミット前後の状態の変化も見たいのでcommitも含めていない。(commitは別途実行する)


$ cat insert_rows.sql

begin for i in 1..100000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
33334

データ作成(バッファキャッシュの10%を超えるのデータ登録)
単純にループ回数を倍にして増量。これで事前に算出したバッファキャッシュの10%以上のブロック数は更新される。。。はず。


$ cat insert_rows_2.sql

begin for i in 1..200000 loop insert into hoge values(i, lpad('*', 2000, '*')); end loop; end;
.
l
/

ブロック数の事前確認(20,000ブロックぐらい?は遅延される想定)


SCOTT@ORCL> select count(distinct dbms_rowid.rowid_block_number(rowid)) as "blocks" from hoge;

blocks
----------
66667

ここまでが遅延ブロッククリーンアウトを意図的に起こすためのデータ作成SQLスクリプト

以降は、遅延ブロッククリーンアウトの発生等を見るための[g]v$sysstatと[g]v$sestatを取得するスクリプトと、[g]v$sesstatから特定のセッションを取得するためのクライアント情報をセットするスクリプト。


$ cat set_client_info.sql
BEGIN
DBMS_APPLICATION_INFO.SET_CLIENT_INFO('TargetSession');
END;
.
l
/

システム統計とセッション統計を取得して差分を見ていく必要があるので各統計のスナップショット取得用スクリプトが必要なわけですが、今回はマルチテナント環境。なので、システム統計はCDB全体から、セッション統計は該当するPDBかつ、前述のスクリプトでClient Infoが設定されているセッションに限定するスクリプトを作る必要があるんですよね。DBWRの動きも含めてみたいときって。少し多めに統計名を取得していますが、実際に重要なのはcleanout系の統計ですね。いくつかのテストケースを実施する上で合わせてみておきたい統計も事前に入れてあります:)


$ cat show_stat.sql
set linesize 400
set tab off
set pagesize 1000
col name for a60
col value for 999,999,999,999,999
SELECT
'sysstat' AS "SOURCE"
, name
, value
, con_id
FROM
gv$sysstat
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
UNION ALL
SELECT
'sesstat' AS "SOURCE"
, name
, value
, vsesstat.con_id
FROM
gv$sesstat vsesstat
inner join gv$statname vstatnam
on
vsesstat.statistic# = vstatnam.statistic#
WHERE
name IN (
'physical writes direct'
, 'physical writes from cache'
, 'physical writes non checkpoint'
, 'consistent gets'
, 'no work - consistent read gets'
, 'cleanouts and rollbacks - consistent read gets'
, 'cleanouts only - consistent read gets'
, 'deferred (CURRENT) block cleanout applications'
, 'immediate (CR) block cleanout applications'
, 'immediate (CURRENT) block cleanout applications'
, 'commit cleanout failures: block lost'
, 'commit cleanout failures: buffer being written'
, 'commit cleanout failures: callback failure'
, 'commit cleanout failures: cannot pin'
, 'commit cleanout failures: hot backup in progress'
, 'commit cleanout failures: write disabled'
, 'commit cleanouts'
, 'commit cleanouts successfully completed'
, 'db block changes'
, 'physical read requests'
, 'physical reads'
, 'physical reads direct'
, 'physical write requests'
, 'physical writes'
, 'physical writes direct'
, 'DBWR checkpoint buffers written'
, 'DBWR thread checkpoint buffers written'
, 'DBWR tablespace checkpoint buffers written'
, 'DBWR parallel query checkpoint buffers written'
, 'DBWR object drop buffers written'
, 'DBWR transaction table writes'
, 'DBWR undo block writes'
, 'DBWR revisited being-written buffer'
, 'DBWR lru scans'
, 'DBWR checkpoints'
, 'DBWR fusion writes'
, 'transaction tables consistent reads - undo records applied'
, 'transaction tables consistent read rollbacks'
)
and sid = (
select
sid
from
gv$session
where
username = upper('&1')
and client_info = 'TargetSession'
)
order by
4, 1, 2
;

undefine 1

ちなみに、統計を使って状況を確認する方法って意外に利用されているんですよね。日本だとあまり活用されてないようにも感じることは多いのですが、日々の統計を追っかけてると、どのメトリックが高く跳ね上がるのか把握できるので知ってて損することはないと思います:)

そういえば、Tanel PoderのSnapperもその手のツールではありますね。
Session Snapper
http://tech.e2sn.com/oracle-scripts-and-tools/session-snapper

私の過去のセッションでもElappsed Timeを見せないでチューニング効果を見てもらうネタとしてシステム統計を使ってたりします。
db tech showcase Tokyo 2013 - A35 特濃JPOUG:潮溜まりでジャブジャブ、SQLチューニング
https://www.slideshare.net/discus_hamburg/db-tech-showcase-tokyo-2013-a35-sql

少々脱線しますが、
最近、VirtualBox、なつかしー。なんて言う方もいますが、古いバージョンのOracleを残しておけるので、リリース毎の動きの差などを見たい場合は便利なのですよーw(クラウドだと強制アップグレードされちゃうので旧バージョンとの動作比較をネタにしたいときなどには向いてないw)

次回へつづく


東京では救急車のサイレンがまだまだ通常より多く聞こえます。。。。

Stay home, Stay Safe and Stay Hydrated.


古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1


 

| | | コメント (0)

2021年8月23日 (月)

古くて新しい? 遅延ブロッククリーンアウト (deferred block cleanout) #1

昔からOracle Database触ってると結構当たり前だったことではあるけど、最近、主にExadata以降にOraclerになったエンジニアへ伝承というかノウハウの一つとして継承されていないのかなー。
と感じることが何度かあり、属人化とは少々違う、失われた知識みたいなのもあるのかではないかと。。。そんなアトモスフィアを感じつつ、遅延ブロッククリーンアウトについて軽く書こうかなと。

と、今思ってるだけで、めんどくさくなって続かないかもしれませんw 

 

昔からある有名な遅延ブロッククリーンアウトの情報はMOSにもありますが、読んだことある方はどれぐらいいるのだろう。かなーーーり昔は日本独自?のMetalinkとかKROWNで細かく書かれていた(ような)記憶はあるが、最近はそこまで書かれてないような。。。まあいいかw

遅延ロギング・ブロック・クリーンアウトについて(KROWN:48106) (Doc ID 1716869.1)

こまけーことは置いといて、MOSとか、他のブログを読むと動きはざっくり理解できると思うのですが、

ポイントは、Buffer Cache サイズのおおよそ10%ぐらいまでがcommit時にクリーンアウトされる最大サイズ、それ以外は先送りされるというところ。
commit時にクリーンアウトされないので、後続のSQL文で該当するブロックがアクセスされたタイミングでタイミングでクリーンアウトされる。通常は更新を伴わないSELECT文でも発生する。そこで気づく方は多いわけです。はい。

SELECT文なのになんでREDOが生成されているんだろう???? と。

例えば、Buffer Cacheが 10ブロック分あったとして、UPDATE文で5ブロック更新してcommitした場合、10ブロックの10%の1ブロックだけがcommit時にブロッククリーンアウトされ、残りの 4ブロックのクリーンアウトは遅延される。ざっくり言えばそんな感じ。
DMLでINSERT/DELETE/UPDATEでどのどの程度のブロックが更新されるかで、遅延されるブロック数はざっくり判断できちゃいます。
ちなみに、どのタイミングでどの程度、クリーンアウトされたかという統計情報はあるが、トランザクションがcommitされたタイミングでどれぐらいクリーンアウトが遅延されたブロックが存在するかを示す統計はないので、どの程度の更新ブロックがあるかと、commit時にクリーンアウトできるブロック数の差分からざっくり判断するのがリーズナブル(他の流派の方もいるかもしれないがw)

OLTPのようなショートトランザクションでは、あまりクリーンアウトが先送りされることはないわけですが、ロングトランザクションでコミット1回というタイプだと、commit時にはブロックをクリーンアウトしきれない程度のブロックが更新され、クリーンアウトが遅延されるという状況は仕様どおりなわけです。。。。ロングトランザクションで発生しやすいということにはなりますね。(だったら、ぐるぐる系のバッチ更新処理って最高じゃね?、。。。それはちょっと。。。w 向かう方向が違うかなw)

話は少し代わりますが、前述のようにcommtのタイミングではBuffer Cacheの10%程度までなので、Buffer Cache を大きくすれば、commit時にクリーンアウトされる最大サイズも大きくなる。。。というのは簡単にイメージできると思います。。。

そして、遅延クリーンアウトブロックが発生しないケースがあるのご存知ですか?(知ってて損はないですよ。。)

 



以下のサイトで有益な情報を提供しているので一度は見ておくと良いと思います。

最近のOraclerは知らないかもしれないけど、AskTomでもTomが、遅延グロッククリーンアウトについて答えてます。 


このQAでのポイントは回答にある以下の部分、ここでも10 percent of our buffer cacheと言及されていますよね。これ2005のQAです。この頃まだ小学生だった方達が今Oraclerとしてデビューしてたりするわけですよねw 

”If we were to re-run the above example with the buffer cache set to hold at least 5,000 blocks, we'll find that we generate little to no redo on any of the SELECTs - we will not have to clean dirty blocks during either of our SELECT statements. This is because the 500 blocks we modified fit comfortably into 10 percent of our buffer cache, and we are the only user. There is no one else is mucking around with the data, and no one else is causing our data to be flushed to disk or accessing those blocks. In a live system, it will be normal for at least some of the blocks to not be cleaned out sometimes.”

Does select generate undo ? / AskTom / 2005

これは比較的新しいQAですね
delayed block cleanout / AskTom / 2017

レジェンド、ジョナサンルイスのブログでも
Clean it up / Jonathan Lewis / 2009

MaxGuage for Oracleで有名なエクセムさんのブログ

この記事で引用されているMetalinkは、現在のMOSには存在しないのでMOSのアカウントがあるかたが頑張って検索してもヒットしないのでご注意を。(日付が記載されていないので何年の記事かわからないですが、記事自体かなり古いはずなのでネタ元も古いと思ってください)
遅延ブロッククリーンアウト / エクセムさんのブログ

このブログでも10%ルールの話は出てきています。
Delayed Block Cleanout in Oracle / databasejournal By David Fitzjarrell / 2015


比喩がわかりにくいかもしれないけど、有名な Shit the Oracle さんのブログ (このエントリーも日付がないけど、適当なタイミングでUpdateされているような、気がしないでもない)
遅延ブロッククリーンアウト / SHIFT-the-Oracle


以下、システムサポートさんのブログ、わかりやすいですよね。冒頭で少し紹介した遅延クリーンアウトブロックが発生しないケースにも軽くふれてますね。direct path writeしているケースに注目ですよ!
フルスキャンで何故シングルブロックリードが発生するのか?(2/2)


NTTDのOracle ACE 高橋さんもExadataでの遅延ブロッククリーンアウトの洗礼を受けた模様、比較的最近なのですね。 :)
exadataと遅延ブロッククリーンアウトとシングルブロックリード / オラクルデータベースの技術メモ

ブロックダンプしながら確認するなんて、マニアックですよね。w このかた。前述のシステムサポートさんの記事で紹介されているdirect path writeのケースも検証されています。素敵です。
遅延ブロッククリーンアウトを観測する① / SQL*Plusの使いにくさは異常


ということで、私が書かなくても、これだけの情報があるので、書こうか書かないか、迷うーーーーっw。
紹介したエントリーの内容を確認するための方法こんな方法でも確認できるよー的な内容にしましょうかね。。。

では、Stay Home and Stay Safe.

次回へつづく。

| | | コメント (0)

2021年3月31日 (水)

Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0) - including hidden params

Previously on Mac De Oracle
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた


 

ということで、今回はめでたくサポート終了となった11gR2と19cのパラメータ差分を確認してみた(何年振りだろうこれw)。21cがでたらまたやる予定。

 


11gR2 : 11.2.0.4.0
19c : 19.3.0.0.0

 

で比較した結果は以下

 

Difference of Initialization Parameters between 11gR2 (11.2.0.4.0) and 19c (19.3.0.0.0)

 


どうやればこの差分が作れるかというと、DBリンクでもできるけどそんなことしてません。というかやりたくないw ので、旧リリースでパラメータをcsv出力して(comma区切りではないですがw)、19c側で外部表として参照して差分を出力.

 

パラメータの取り出し。linesizeは適当に調整する必要はあるかも


ORACLE-ORCLCDB@SYS> !cat paramout.sql
set linesize 300
set trimspool on
set head off
set feedback off
set timi off
set termout off
set time off
set timi off
set pagesize 0
spo &1..csv

SELECT
TRIM(a.ksppinm)
||'|'||TRIM(a.ksppdesc)||'|'
FROM
x$ksppi a
/
spo off
undefine 1

 

 

上記スクリプトの実行、結果の例


ORACLE-ORCLCDB@SYS> @paramout 11.2.0.4.0_parameters
ORACLE-ORCLCDB@SYS> !cat 11.2.0.4.0_parameters.csv
_appqos_qt|System Queue time retrieval interval|
_ior_serialize_fault|inject fault in the ior serialize code|
_shutdown_completion_timeout_mins|minutes for shutdown operation to wait for sessions to complete|
_inject_startup_fault|inject fault in the startup code|
_latch_recovery_alignment|align latch recovery structures|
_spin_count|Amount to spin waiting for a latch|
_latch_miss_stat_sid|Sid of process for which to collect latch stats|
_max_sleep_holding_latch|max time to sleep while holding a latch|
_max_exponential_sleep|max sleep during exponential backoff|
_other_wait_threshold|threshold wait percentage for event wait class Other|
_other_wait_event_exclusion|exclude event names from _other_wait_threshold calculations|
・・・略・・・

 

外部表の元ネタであるファイル置き場用ディレクト作製とディレクトリオブジェクトの作製、そして、元ネタの配置は以下の通り


ORACLE-ORCLCDB@SYS> !pwd
/home/oracle
ORACLE-ORCLCDB@SYS> !ls -l exttab
合計 488
-rw-rw-r--. 1 oracle oracle 194319 3月 29 21:26 11.2.0.4_parameters.csv

ORACLE-ORCLCDB@SYS> CREATE DIRECTORY ext_tab AS '/home/oracle/exttab';

ディレクトリが作成されました。

ORACLE-ORCLCDB@SYS> l
1 CREATE TABLE ksppi_11_2_0_4_0 (
2 ksppinm VARCHAR2(80)
3 ,ksppdesc VARCHAR2(255)
4 )
5 ORGANIZATION EXTERNAL (
6 TYPE ORACLE_LOADER
7 DEFAULT DIRECTORY ext_tab
8 ACCESS PARAMETERS (
9 RECORDS DELIMITED BY NEWLINE
10 FIELDS TERMINATED BY '|'
11 (
12 ksppinm
13 ,ksppdesc
14 )
15 )
16 LOCATION (
17 '11.2.0.4_parameters.csv'
18 )
19* )
ORACLE-ORCLCDB@SYS> /

表が作成されました。

ORACLE-ORCLCDB@SYS> SELECT COUNT(1) FROM ksppi_11_2_0_4_0;

COUNT(1)
----------
2915

 

 

以下のスクリプトでcdb$rootのx$ksppiと前述の外部表をfull outer joinして差分をhtml形式で出力すればできあがり


col "11g R2 11.2.0.4.0" for a17
col "19c 19.3.0.0.0" for a17
set pagesize 10000
set timi off
set feed off
set markup html on spool on
spo param-diff.html
SELECT
CASE
WHEN prev.ksppinm = curr.ksppinm
OR (
prev.ksppinm IS NULL
AND curr.ksppinm IS NOT NULL
)
THEN curr.ksppinm
ELSE prev.ksppinm
END AS ksppinm
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL THEN '○'
ELSE 'n/a'
END AS "11g R2 11.2.0.4.0"
,CASE
WHEN prev.ksppinm = curr.ksppinm THEN '○'
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN '○'
ELSE 'n/a'
END AS "19c 19.3.0.0.0"
,CASE
WHEN prev.ksppdesc = curr.ksppdesc THEN curr.ksppdesc
WHEN prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL THEN curr.ksppdesc
WHEN prev.ksppinm iS NOT NULL AND curr.ksppinm IS NULL
THEN prev.ksppdesc
ELSE prev.ksppdesc
END AS kspdesc
,CASE
WHEN prev.ksppdesc = curr.ksppdesc
OR (prev.ksppinm IS NULL AND curr.ksppinm IS NOT NULL)
OR (prev.ksppinm IS NOT NULL AND curr.ksppinm IS NULL)
THEN NULL
ELSE curr.ksppdesc
END AS "New description"
FROM
(
SELECT * FROM x$ksppi WHERE con_id = 0
) curr
FULL OUTER JOIN ksppi_11_2_0_4_0 prev
ON
curr.ksppinm = prev.ksppinm
ORDER BY
1
;
spool off
set feed on
set set markup html off

 

 


露天風呂♨️行きたい...

 

 


Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params
Difference of Initialization Parameters between 11g and 12c #2
19cの初期化パラメータ数や隠しパラメータ数などどう変化したのか、久々に確認してみた

 

| | | コメント (0)

2021年2月18日 (木)

MicrosoftのEdgeがChromium版Edgeでたので久々にブラウザベンチマーク

MicrosoftのEdgeがChromium版Edgeになって、なんとMac版もでたので久々に、古い Mac Pro mid2012 Mojaveでベンチマークとってみた

20210218-210959

対処対象ブラウザは、Safari / Firefox / Google Chrome / Microsoft Edge (Chromium版Edge) / Vivaldi / Opera を JetStream2の総合値で比較

昔のBrowser Benchmarkのエントリー(古い順)
IEって遅いんだよー。ってことをあまり気にしてない方へ。 / 2010年

まだ、レガシーブラウザと戦うのですか... / 2013年

あれから1年.....あの恐怖が再びw / 2014年

Edgeも出たし、JetStreamも出たので久々にブラウザのベンチマーク / 2015年

JetStream2の結果(2021版は以下のとおり) Safariが少々良い結果を出していますが、Firefoxを除き、あまり差はなくなってますね。Firefox頑張れ?(WindowsでIE使いたいって方も少数派だと思うので今回はWindowsでの比較は略。気が向いたらやるかもしれませんがWindows BootCampで起動するMacBookしかないんだよなーw)

Safari Safari_20210218205801
Safariresults

 

Firefox
20210218-133230
20210218-133224

 

Google Chrome

Chrome


Chromeresults

 

Microsoft Edge (Chromium版Edge)

Chromemsedge


Chromemsedgeresults

 

Vivaldi

Vivaldi


Vivaldiresults

 

Opera

Operaresults


Opera

 

気軽に食べ歩きにいける世の中になってほしい。。。

| | | コメント (0)

2021年2月13日 (土)

実行計画は、SQL文のレントゲン写真だ! No.30

OracleのResource Managerネタを書こう書こうと思いつつ類似ネタが他のブログでも扱われていることに気づきw
ネタを被らないようにしたいなぁとw 考えているうちにすでに2月の半ばw もうすこし考えまするw

ということで、今回はこないだ、実行計画という名のレントゲンネタに絡んだtwitterのやりとりがあったついでなので、INSERTの実行計画をいくつか追加しておきます。

Oracleもバージョンが上がるごとに実行計画の改善やオペレーション名を変えたりするので古いバージョンだと、そんなオペレーション出なーーーい。ということもありますが、そんなオペレーションがでたら、こんな意味なんだぁ。

と理解しておけば裏でどう動いているかイメージしやすいのではないかと思います。それがイメージできていれば、もし、性能問題に絡んでいた時にはどう対処するのが良いか、助けになるとはずです (^^)

DIRECT PATH LOADING登場前のOracle Databaseでは気にする必要はなかったのですが、DIRECT PATHが行われるようになってから実行計画のOperationでDIRECT PATHとの区別がつきやすいように追加されたという微かな記憶があります。
(まちがっていたらコメントいただけますと m(_ _)m

ということで、実行計画の見てみましょう。INSERT文なので単純ですw INSERTのoperationが(裏で)どう行われているかの違いです。
(以下の実行計画ではIASを利用して500MBほどの元表から同一定義の別表へ全データをINSERTしています)

LOAD TABLE CONVENTIONALというoperationが該当部分。 覚えておくと何かの役にはたつと思います!

20210213-150833

この実行計画になるように以下のようにNOAPPENDヒントでdirect path writeを抑止しています。ヒントの使い方も覚えておくとなにかのときにや役立ちますよ:)
NO_GATHER_OPTIMIZER_STATISTICSヒントは今回の実行計画には直接関係ないリアルタイム統計の取得を抑止するヒントです。
19cのおそらく後半のリリースから従来型のDMLでもリアルタイム統計が取得されるようになったようです。この例ではじゃまなので抑止。

従来型のDMLでもリアルタイム統計が取得される https://docs.oracle.com/cd/F19136_01/tgsql/optimizer-statistics-concepts.html#GUID-769E609D-0312-43A7-9581-3F3EACF10BA9

Real-Time Statistics https://docs.oracle.com/en/database/oracle/oracle-database/19/dblic/Licensing-Information.html#GUID-0F9EB85D-4610-4EDF-89C2-4916A0E7AC87

INSERT
/*+
MONITOR
NOAPPEND
NO_GATHER_OPTIMIZER_STATISTICS
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

では、つづいて、direct path writeが動作した場合は該当部分のoperationはどうなるでしょうか? :)

LOAD AS SELECT に変わったoperationが該当部分です! operation は LOAD AS SELECT ですが、待機イベントは direct path write です。
どのようなoperationがどのような待機イベントに繋がるのかっていうのもおぼえておくと何かの役に立ちますよ ;)

20210213-150416

実行したINSERT文のヒントがNOAPPPENDからAPPENDに変えてあることに注目。この例ではdirect path writeをヒントで強制しています。

INSERT
/*+
MONITOR
APPEND
NO_GATHER_OPTIMIZER_STATISTICS
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

最後に、NO_GATHER_OPTIMIZER_STATISTICS ヒントを外してリアルタイム統計取得が動いた場合にはどのようなoperationになるかみてみましょう。

20210213-153502

OPTIMIZER STATISTICS GATHERING というoperationが現れました。これは19cの後半で追加された従来型DMLでのリアルタイム統計取得が動作した場合も現れるとマニュアルに記載されているoperationとも同一です。
direct path writeなのかによらず、リアルタイム統計取得の動作有無を確認するためにはこのoperationの有無をチェックする必要があります!

INSERT
/*+
MONITOR
APPEND
*/
INTO
hoge2
SELECT
*
FROM
hoge
;

なかなかよい、レントゲンコレクションが撮れたな :)


Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan

 

| | | コメント (0)

2020年12月26日 (土)

標準はあるにはあるが癖の多いSQL 全部俺 おまけ SQL de 湯婆婆やるにも癖がでるw

恒例の標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020のおまけですw

忙しすぎて、この手の遊びが疎かになっておりました。完全に乗り遅れておりましたが、湯婆婆やりましたw

スタジオジブリさん、「画像は常識の範囲でご自由にお使いください。」とのことでありがたく使わせていただきます!
https://www.ghibli.jp/works/chihiro/#frame

Chihiro016

では、Oracle (19c)を使って、Oracle 湯婆婆 から
Advent Calendar 標準はあるにはあるが癖の多いSQL 全部俺でも登場したSUBSTR/DBMS_RANDOM.VALUEや文字列連結の違い。
それに、SQLスクリプトでインタラクティブにパラメータを渡せるかという点にも違いがあります。

SQL*Plus/psqlではインタラクティブにパラメータを渡せますが、mysqlにはなさそう(あったらコメントください)

インタラクティブにパラメータを渡せるSQL*Plus/psqlでは、それぞれ、ACCEPTや\promptなどで名前を入力しています。
mysqlでは仕方ないのでSETコマンドで設定する方法をとりました。


ORACLE> @ora_yubaba
契約書だよ。そこに名前を書きな。山田千尋

湯婆婆
--------------------------------------------------------------------------------
フン 山田千尋 というのかい。贅沢な名だねぇ
今からお前の名前は 田 だ。いいかい、田 だよ。
わかったら返事をするんだ、
!!

$ cat ora_yubaba.sql
SET LINESIZE 80
SET TAB OFF
SET VERIFY OFF
ACCEPT fullname CHAR PROMPT '契約書だよ。そこに名前を書きな。'
WITH yourname
AS
(
SELECT
SUBSTR(
'&&fullname'
, DBMS_RANDOM.VALUE(1,LENGTH('&&fullname')), 1
) AS newname
FROM
dual
)
SELECT
'フン '
||'&&fullname'
||' というのかい。贅沢な名だねぇ'
||CHR(13)||CHR(10)
||'今からお前の名前は '
||newname
||' だ。いいかい、'
||newname
||' だよ。'
||CHR(13)||CHR(10)||'わかったら返事をするんだ、'
||CHR(13)||CHR(10)||'!!' AS "湯婆婆"
FROM
yourname;

undefine fullname
SET VERIFY ON

PostgreSQL (12)

次は、PostgreSQL 湯婆婆

postgres=> \i postgresql_yubaba.sql
契約書だよ。そこに名前を書きな : 山田千尋
湯婆婆
----------------------------------------------
フン 山田千尋 というのかい。贅沢な名だねぇ +
今からお前の名は 田 だ。いいかい、田 だよ。+
わかったら返事をするんだ、 +
!!
(1 row)
$ cat postgresql_yubaba.sql
\prompt '契約書だよ。そこに名前を書きな : ' fullname

WITH yourname
AS
(
SELECT
SUBSTR(
:'fullname'::TEXT
, CEIL(RANDOM() * LENGTH(:'fullname'::TEXT))::INTEGER, 1::INTEGER
) AS newname
)
SELECT
'フン '
||:'fullname'
||' というのかい。贅沢な名だねぇ'
||E'\n'
||'今からお前の名は '
||newname
||' だ。いいかい、'
||newname
||' だよ。'
||E'\n'||'わかったら返事をするんだ、'
||E'\n'||'!!' AS "湯婆婆"
FROM
yourname;


MySQL 8.0

最後に、MySQL 湯婆婆

mysql> SET @契約書だよ。そこに名前を書きな = '山田千尋';
Query OK, 0 rows affected (0.13 sec)

mysql> \! vi mysql_yubaba.sql
mysql> \. mysql_yubaba.sql
Query OK, 0 rows affected (0.02 sec)

*************************** 1. row ***************************
湯婆婆: フン 山田千尋 というのかい。贅沢な名だねぇ
今からお前の名は 尋 だ。いいかい、尋 だよ。
わかったら返事をするんだ、
!!
1 row in set (0.02 sec)
$ cat mysql_yubaba.sql
SET sql_mode = 'ANSI';
WITH yourname
AS
(
SELECT
SUBSTRING(
@契約書だよ。そこに名前を書きな
, CEIL(RAND() * CHAR_LENGTH(@契約書だよ。そこに名前を書きな)), 1
) AS newname
)
SELECT
'フン '
||@契約書だよ。そこに名前を書きな
||' というのかい。贅沢な名だねぇ'
||'\r\n'
||'今からお前の名は '
||newname
||' だ。いいかい、'
||newname
||' だよ。'
||'\r\n'||'わかったら返事をするんだ、'
||'\r\n'||'!!' AS "湯婆婆"
FROM
yourname\G



こういう遊びはみなさん好きですよね? 
では、また。



似たようなネタのエントリー
Oracle de Fizzbuzz #1 - いまごろ・・・ですが・・
Oracle de Fizzbuzz #2
Mac de Caché というか MUMPS というか Objectscript か - fizzbuzz
PL/SQL de ケンブリッジ関数
こんなのでいいのかなぁ。ズンドコキヨシ  ObjectScript / MUMPS






標準はあるにはあるが癖の多い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にも癖がある:)

| | | コメント (0)

2020年12月25日 (金)

標準はあるにはあるが癖の多いSQL 全部俺 #25 SQL de Fractalsにも癖がある:)

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の25日目です。

5年前にクリスマスのお遊び - SQL de Fractals :)というネタを書いてました。

EXPLAIN EXTENDED - Happy New Year!
元ネタは、ARRAY_TO_STRINGとARRAY_AGGの組み合わせ、とgenerate_seriesを利用した再帰問合せを利用したPostgreSQLバージョンのSQL

今であれば、以下のように書き換え、STRING_AGGでけにした方が良いのではないだろうか。
また、generate_series部分の方言の影響を最小にするのであれば、この部分も再帰問合せを利用した一連番号のセット生成にするなどの変更は可能ですね。

では、オリジナルのPostgreSQLの構文でARRAY_TO_STRINGとARRAY_AGGをSTRING_AGGに変更した例から(generate_seriesを階層再帰問合せにすることも可能)

PostgreSQL

WITH RECURSIVE
q (r, i, rx, ix, g) AS
(
SELECT
r::DOUBLE PRECISION * 0.02
, i::DOUBLE PRECISION * 0.02
, .0::DOUBLE PRECISION
, .0::DOUBLE PRECISION
, 0
FROM
generate_series(-60, 20) r
, generate_series(-50, 50) i
UNION ALL
SELECT
r
, i
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN rx * rx - ix * ix
END + r
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN 2 * rx * ix
END + i
, g + 1
FROM
q
WHERE
rx IS NOT NULL AND g < 99
)
SELECT
STRING_AGG(s, '' ORDER BY r) AS Mandelbrot
FROM
(
SELECT
i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
FROM
q
GROUP BY i, r
) q
GROUP BY i
ORDER BY i;

Fractacle_postgresql


Oracle

では、上記、非互換の多いSQLをOracle向けに書き換えてみましょう。一連番号生成はOracleの方言である階層問合せにしてあります。あえてw
また、PostgreSQLのSTRING_AGG部分は、OracleのLISTAGGで代替しています。

WITH
q (r, i, rx, ix, g) AS
(
SELECT
CAST(r.r AS DOUBLE PRECISION) * 0.02 AS r
, CAST(i.i AS DOUBLE PRECISION) * 0.02 AS i
, CAST(.0 AS DOUBLE PRECISION) AS rx
, CAST(.0 AS DOUBLE PRECISION) AS ix
, 0 AS g
FROM
(
SELECT
LEVEL - 61 AS r
FROM
DUAL
CONNECT BY
LEVEL <= 80
) r,
(
SELECT
LEVEL - 51 AS i
FROM
DUAL
CONNECT BY
LEVEL <= 100
) i
UNION ALL
SELECT
r
, i
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN
rx * rx - ix * ix
END + r AS rx
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN
2 * rx * ix
END + i AS ix
, g + 1 AS g
FROM
q
WHERE
rx IS NOT NULL
AND g < 99
)
SELECT
LISTAGG(s,'') WITHIN GROUP ( ORDER BY r ) AS Mandelbrot
FROM
(
SELECT
i, r, SUBSTR(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
FROM
q
GROUP BY i, r
) q
GROUP BY i
ORDER BY i;

Fractacle_oracle

MySQL 8.0

さて、最後は、これまで一連番号生成が辛かったMySQLです。
MySQL 8.0から再帰問合せが利用できるようになったおかげてMySQLのSQLでもこんな遊びができるようになりました!!!!

すげーーーーーーーっ!


再帰問合せを駆使し、PostgreSQLのSTRING_AGG、OracleのLISTAGGの代替としてGROUP_CONCAT関数を利用しています。
部分文字列はSUBSTRINGですね。
そしてもう一つの非互換対応が TRUNCATE(MAX(g) / 10 + 1, 0) 部分です。
OracleとPostgreSQLは MAX(g) / 10 + 1 だけでも問題ないですが、MySQLでは MAX(g) / 10 + 1 の結果は整数にはなりません。
その対策としてTRUNCATEを追加しています。

なかなか痺れますね。これまで紹介してきた非互換対応の総まとめは大げさですが、それらを有効に組み合わせて書き換えてみました。

WITH RECURSIVE
q (r, i, rx, ix, g)
AS
(
SELECT
CAST(r.v AS DOUBLE PRECISION) * 0.02 AS r
, CAST(i.v AS DOUBLE PRECISION) * 0.02 AS i
, CAST(0.0 AS DOUBLE PRECISION) AS rx
, CAST(0.0 AS DOUBLE PRECISION) AS ix
, 0 AS g
FROM
(
WITH RECURSIVE gen_nums(v)
AS
(
SELECT -60
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 < 20
)
SELECT v from gen_nums
) r
,(
WITH RECURSIVE gen_nums(v)
AS
(
SELECT -50
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 < 50
)
SELECT v from gen_nums
) i
UNION ALL
SELECT
CAST(r AS DOUBLE PRECISION) AS r
, CAST(i AS DOUBLE PRECISION) AS i
, CAST(
CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN rx * rx - ix * ix
END + r
AS DOUBLE PRECISION
) AS rx
, CAST(
CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN 2 * rx * ix
END + i
AS DOUBLE PRECISION
) AS ix
, g + 1 AS g
FROM
q
WHERE
rx IS NOT NULL
AND g < 99
)
SELECT
GROUP_CONCAT(s,'' ORDER BY r SEPARATOR '') AS Mandelbrot
FROM
(
SELECT
i, r, SUBSTRING(' .:-=+*#%@', TRUNCATE(MAX(g) / 10 + 1, 0), 1) s
FROM
q
GROUP BY
i, r
) q
GROUP BY q.i
ORDER BY q.i;

Fractacle_mysql



ちなみに、Redshiftは再帰的なCTEは現時点では未サポートなので再帰問合せが必要なお遊びは今のところできません。

Amazon Redshift - サポートされていないPostgreSQL機能
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/c_unsupported-postgresql-features.html




さてさて、なんとか25個の窓を開けることができました。

今年は、コロナ禍の中、大変厳しい一年になりましたが、みなさま、お身体を大事に、そして、ご家族と過ごす時間を大切に。

メリークリスマス。(何年か前に作った Pipeline function で christmas treeのムービーで)




標準はあるにはあるが癖の多い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 乱数作るにも癖がある

| | | コメント (0)

2020年12月24日 (木)

標準はあるにはあるが癖の多いSQL 全部俺 #24 乱数作るにも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の24日目です。

たまに、乱数が必要になることがあるのですが、その乱数生成にも癖があるんですよね。

簡単ですが今日はこれぐらいでw (ラストスパートで息切れ中w


いつものようにOracleから

131.4 DBMS_RANDOMサブプログラムの要約
131.4.7 VALUEファンクション
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_RANDOM.html#GUID-AAD9E936-D74F-440D-9E16-24F3F0DE8D31

Oracleの場合は組み込み関数ではなく、パッケージ関数として提供されています。この点が大きな違いですよね。

ORACLE> SELECT (DBMS_RANDOM.VALUE(1,10)) FROM dual;

(DBMS_RANDOM.VALUE(1,10))
-------------------------
3.37937063

ORACLE> SELECT DBMS_RANDOM.VALUE FROM dual;

VALUE
----------
.853380477


PostgreSQL

表9.6 乱数関数
https://www.postgresql.jp/document/12/html/functions-math.html

postgres=> select random();
random
-------------------
0.774311667308211
(1 row)

MySQL

三者三様で皆関数名も違ったり、提供されている機能も差がありますね。

RAND([N])
https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_rand

mysql> select rand();
+--------------------+
| rand() |
+--------------------+
| 0.6516789492700984 |
+--------------------+
1 row in set (0.06 sec)



クリスマス、何食べようか考えているところで、年越しそば予約してないことに気づくw (なんとかなるさw






標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある

| | | コメント (0)

2020年12月23日 (水)

標準はあるにはあるが癖の多いSQL 全部俺 #23 複数行INSERTにも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の23日目です。

クリスマスイブイブですね。なんとかここまできたw

ネタ尽きた感がなくもないですが、ほぼ使っている方はいないのではないかという、複数行INSERTにも癖があるというお話

では、Oracleからみてください

Oracle

INESRT
https://docs.oracle.com/cd/F19136_01/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423


この構文実は、マルチテーブルインサートの変形パターンで同一表へ複数インサートするようにしたもの。。。
そもそもこの構文が方言ではあるのですが、。。。。

ORACLE> desc a
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
STR VARCHAR2(100)

ORACLE> INSERT ALL
2 INTO a VALUES(2,'yama')
3 INTO a VALUES(3,'kawa')
4 INTO a VALUES(4,'umi')
5 SELECT * FROM dual;

PostgreSQL

PostgreSQLにはやはりない。そりゃ、Oracleの方言だからねw ですが、一応同一表なら可能です。

INSERT
https://www.postgresql.jp/document/12/html/sql-insert.html


同じく、MySQLも同一構文。。。。

postgres=> \d a
Table "bill.a"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | integer | | |
str | character varying(100) | | |

postgres=>
postgres=> INSERT
postgres-> INTO a VALUES
postgres-> (2,'yama')
postgres-> ,(3,'kawa')
postgres-> ,(4,'umi');
INSERT 0 3

MySQL(8.0)

INSERT
https://dev.mysql.com/doc/refman/8.0/en/insert.html

mysql> desc a;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| str | varchar(100) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.02 sec)
mysql> INSERT
-> INTO a VALUES
-> (2,'yama')
-> ,(3,'kawa')
-> ,(4,'umi');
Query OK, 3 rows affected (0.07 sec)
Records: 3 Duplicates: 0 Warnings: 0



クリスマスイブのネタ何にしよう。本気で浮かばないw (最終日のネタはほぼできているのにw)






標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client
標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法
標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?
標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言
標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w
標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある

| | | コメント (0)

2020年12月22日 (火)

標準はあるにはあるが癖の多いSQL 全部俺 #22 集合演算にも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の22日目です。

さて、残すところ、今日をいれてあと4回

なんとなくネタが1つ足りてない気がするがw なんとかするw

ということで、集合演算の癖を見てみましょう。Oracle 20cがリリースされていたら試せた機能もありますが、使える環境がないので、マニュアルだけ貼っておきます。

Oracle 20c - SQL set演算子の拡張 - union [all] / minus [all] / intersect [all] / except[all]
https://docs.oracle.com/cd/F32587_01/ftnew/enhanced-sql-set-operators1.html

19cまではこんな感じ

Oracle 19c - UNION [ALL]、INTERSECTおよびMINUS演算子
https://docs.oracle.com/cd/F19136_01/sqlrf/The-UNION-ALL-INTERSECT-MINUS-Operators.html#GUID-B64FE747-586E-4513-945F-80CB197125EE

MINUSはOracleの方言です。そのうちシノニム扱いされて、EXCEPTが一般的に利用されることになっていくのでしょうね。

Oracle

ORACLE> r
1 SELECT *
2 FROM
3 (
4 WITH gen_nums(v)
5 AS
6 (
7 SELECT 1
8 FROM
9 dual
10 UNION ALL
11 SELECT v + 1
12 FROM
13 gen_nums
14 WHERE v + 1 <= 10
15 )
16 SELECT v FROM gen_nums
17 )
18 UNION
19 SELECT *
20 FROM
21 (
22 WITH gen_nums(v)
23 AS
24 (
25 SELECT 5
26 FROM
27 dual
28 UNION ALL
29 SELECT v + 1
30 FROM
31 gen_nums
32 WHERE v + 1 <= 15
33 )
34 SELECT v FROM gen_nums
35 )
36* ORDER BY 1

V
----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

15 rows selected.

ORACLE> r
1 SELECT *
2 FROM
3 (
4 WITH gen_nums(v)
5 AS
6 (
7 SELECT 1
8 FROM
9 dual
10 UNION ALL
11 SELECT v + 1
12 FROM
13 gen_nums
14 WHERE v + 1 <= 10
15 )
16 SELECT v FROM gen_nums
17 )
18 UNION ALL
19 SELECT *
20 FROM
21 (
22 WITH gen_nums(v)
23 AS
24 (
25 SELECT 5
26 FROM
27 dual
28 UNION ALL
29 ELECT v + 1
30 FROM
31 gen_nums
32 WHERE v + 1 <= 15
33 )
34 SELECT v FROM gen_nums
35 )
36* ORDER BY 1

V
----------
1
2
3
4
5
5
6
6
7
7
8
8
9
9
10
10
11
12
13
14
15

21 rows selected.

ORACLE> r
1 SELECT *
2 FROM
3 (
4 WITH gen_nums(v)
5 AS
6 (
7 SELECT 1
8 FROM
9 dual
10 UNION ALL
11 SELECT v + 1
12 FROM
13 gen_nums
14 WHERE v + 1 <= 10
15 )
16 SELECT v FROM gen_nums
17 )
18 INTERSECT
19 SELECT *
20 FROM
21 (
22 WITH gen_nums(v)
23 AS
24 (
25 SELECT 5
26 FROM
27 dual
28 UNION ALL
29 SELECT v + 1
30 FROM
31 gen_nums
32 WHERE v + 1 <= 15
33 )
34 SELECT v FROM gen_nums
35 )
36* ORDER BY 1

V
----------
5
6
7
8
9
10

6 rows selected.

ORACLE> r
1 SELECT *
2 FROM
3 (
4 WITH gen_nums(v)
5 AS
6 (
7 SELECT 1
8 FROM
9 dual
10 UNION ALL
11 SELECT v + 1
12 FROM
13 gen_nums
14 WHERE v + 1 <= 10
15 )
16 SELECT v FROM gen_nums
17 )
18 MINUS
19 SELECT *
20 FROM
21 (
22 WITH gen_nums(v)
23 AS
24 (
25 SELECT 5
26 FROM
27 dual
28 UNION ALL
29 SELECT v + 1
30 FROM
31 gen_nums
32 WHERE v + 1 <= 15
33 )
34 SELECT v FROM gen_nums
35 )
36* ORDER BY 1

V
----------
1
2
3
4

PostgreSQL

union [all] / intersect[all] / except[all]
https://www.postgresql.jp/document/12/html/queries-union.html

これらの構文に関しては、PostgreSQLの方が一歩先でしたね:)

Oracle 20c以降ではこんなことができるようになるよ、というイメージをPostgreSQLで掴んでおくと良いかもしれないですねー

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> UNION
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
----
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(15 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> UNION ALL
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
----
1
2
3
4
5
5
6
6
7
7
8
8
9
9
10
10
11
12
13
14
15
(21 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> INTERSECT
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
----
5
6
7
8
9
10
(6 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> INTERSECT ALL
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
----
5
6
7
8
9
10
(6 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> EXCEPT
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
---
1
2
3
4
(4 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 1
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 10
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) a
postgres-> EXCEPT ALL
postgres-> SELECT *
postgres-> FROM
postgres-> (
postgres(> WITH RECURSIVE gen_nums(v)
postgres(> AS
postgres(> (
postgres(> SELECT 5
postgres(> UNION ALL
postgres(> SELECT v + 1
postgres(> FROM
postgres(> gen_nums
postgres(> WHERE v + 1 <= 15
postgres(> )
postgres(> SELECT v FROM gen_nums
postgres(> ) b
postgres-> ORDER BY 1;
v
---
1
2
3
4
(4 rows)


MySQL (8.0)

union [all]

13.2.10.3 UNION Clause
https://dev.mysql.com/doc/refman/8.0/en/union.html

MySQLはこれらの部分では少々遅れてますね。実際同じ結果を得ようとすると工夫しないといけないです。
これまで実装されてこなかったということは、恐そのような用途で利用されることが少なかったということなのでしょうか。(想像でしかないけど。。なんとなく今後は実装されそうな方向に向かっているような雰囲気も感じつつ。)

mysql> SELECT *
-> FROM
-> (
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 10
-> )
-> SELECT v FROM gen_nums
-> ) a
-> UNION
-> SELECT *
-> FROM
-> (
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 5
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 15
-> )
-> SELECT v FROM gen_nums
-> ) b
-> ORDER BY 1;
+------+
| v |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
| 14 |
| 15 |
+------+
15 rows in set (0.02 sec)

mysql> SELECT *
-> FROM
-> (
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 10
-> )
-> SELECT v FROM gen_nums
-> ) a
-> UNION ALL
-> SELECT *
-> FROM
-> (
-> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 5
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 15
-> )
-> SELECT v FROM gen_nums
-> ) b

-> ORDER BY 1;
+------+
| v |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 5 |
| 6 |
| 6 |
| 7 |
| 7 |
| 8 |
| 8 |
| 9 |
| 9 |
| 10 |
| 10 |
| 11 |
| 12 |
| 13 |
| 14 |
| 15 |
+------+
21 rows in set (0.02 sec)



来年は全部俺、どうするかな。ネタの一気放出みたいな時間取りやすければいいんだけど。先週までは本気で辛かった。時間ギリギリで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 演算結果にも癖がある

| | | コメント (0)

2020年12月21日 (月)

標準はあるにはあるが癖の多いSQL 全部俺 #21 演算結果にも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の21日目です。

以前取り上げた内容にも近いですが、演算だけに着目した方がわかりやすいだろうということでネタにしてみました。

SQLを移植する場合は、このようなところでも注意しないと。。。ね。デフォルトの挙動に任せたままでは危険ですね

Oracle

ORACLE> SELECT 1 / 3 FROM dual;

1/3
----------
.333333333

ORACLE> SELECT 1 / 3 * 3 FROM dual;

1/3*3
----------
1

ORACLE> SELECT (1 / 3) * 3 FROM dual;

(1/3)*3
----------
1

ORACLE> SELECT CAST(1 AS DOUBLE PRECISION) / CAST(3 AS DOUBLE PRECISION) * CAST(3 AS DOUBLE PRECISION) FROM dual;

CAST(1ASDOUBLEPRECISION)/CAST(3ASDOUBLEPRECISION)*CAST(3ASDOUBLEPRECISION)
--------------------------------------------------------------------------
1

ORACLE> SELECT DUMP(CAST(1 AS DOUBLE PRECISION) / CAST(3 AS DOUBLE PRECISION)) FROM dual;

DUMP(CAST(1ASDOUBLEPRECISION)/CAST(3ASDOUBLEPRECISION))
--------------------------------------------------------------------------------
Typ=2 Len=21: 192,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34

ORACLE> SELECT DUMP(CAST(1 AS DOUBLE PRECISION) / CAST(3 AS DOUBLE PRECISION) * CAST(3 AS DOUBLE PRECISION)) FROM dual;

DUMP(CAST(1ASDOUBLEPRECISION)/CAST(3ASDOUBLEPRECISION)*CAST(3ASDOUBLEPRECISION))
--------------------------------------------------------------------------------
Typ=2 Len=21: 192,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,10
0,100,100,100,100


MySQL

Oracleに近いですが、若干差異がありますね。

mysql> SELECT 1 / 3;
+--------+
| 1 / 3 |
+--------+
| 0.3333 |
+--------+
1 row in set (0.02 sec)

mysql> SELECT 1 / 3 * 3;
+-----------+
| 1 / 3 * 3 |
+-----------+
| 1.0000 |
+-----------+
1 row in set (0.02 sec)

mysql> SELECT CAST(1 AS DOUBLE PRECISION) / 3 * 3;
+---------------------------+
| cast(1 as double) / 3 * 3 |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.26 sec)

mysql> SELECT CAST(1 AS DOUBLE) / CAST(3 AS DOUBLE) * 3;
+-------------------------------------------+
| cast(1 as double) / cast(3 as double) * 3 |
+-------------------------------------------+
| 1 |
+-------------------------------------------+
1 row in set (0.02 sec)

mysql> SELECT CAST(1 AS DOUBLE) / CAST(3 AS DOUBLE) * CAST(3 AS DOUBLE);
+-----------------------------------------------------------+
| cast(1 as double) / cast(3 as double) * cast(3 as double) |
+-----------------------------------------------------------+
| 1 |
+-----------------------------------------------------------+
1 row in set (0.01 sec)

PostgreSQL

PostgreSQLはデフォルトの挙動が明らかに異なります。

postgres=> SELECT 1 / 3;
?column?
----------
0
(1 row)

postgres=> SELECT 1 / 3 * 3;
?column?
----------
0
(1 row)

postgres=> SELECT 1::DOUBLE PRECISION / 3::DOUBLE PRECISION * 3::DOUBLE PRECISION;
?column?
----------
1
(1 row)

postgres=> SELECT PG_TYPEOF(1::DOUBLE PRECISION / 3::DOUBLE PRECISION * 3::DOUBLE PRECISION);
pg_typeof
------------------
double precision
(1 row)

postgres=> SELECT PG_TYPEOF(1 / 3 * 3);
pg_typeof
-----------
integer
(1 row)




あと少し :)





標準はあるにはあるが癖の多い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 結果セットを単一列に連結するにも癖がある

| | | コメント (0)

2020年12月20日 (日)

標準はあるにはあるが癖の多いSQL 全部俺 #20 結果セットを単一列に連結するにも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の20日目です。

また、本記事はJPOUG Advent Calendar 2020の20日目の窓へクロスポストしています。
JPOUG Advent Calendar 2020の19日目はNaotaka ShinogiさんのNutanix Eraで描くDatabase管理とOracleSEのデータ同期でした。

さて、今日は、最終日のネタ作りの途中でどうしても、非互換なところと格闘しないといけないので、その部分の対応をかねてw

今回は、結果セット(複数行)を単一列に連結する集約関数 Oracleでは LISTAGG()という関数の非互換です。
同様の関数はあるものの関数名は違うし多少引数も違うのでぱっと見、どう書き換えるかってなると迷うわけです。知ってれば別ですけども。

では、早速みてみましょう。

Oracle

LISTAGG

LISTAGG( [ ALL ] [ DISTINCT ] measure_expr [, 'delimiter'] [listagg_overflow_clause] ) [ WITHIN GROUP ] (order_by_clause) [OVER query_partition_clause]
https://docs.oracle.com/cd/F19136_01/sqlrf/LISTAGG.html#GUID-B6E50D8E-F467-425B-9436-F7F8BF38D466

使ったことがある方なら、ああ、アレかと思い出せると思います。あまり使う機会はないので私もマニュアル見て思出すことが多いですね。この関数w

ORACLE>
*1 SELECT * FROM list;

ID STR
---------- -------------
1 foo
1 bar
1 tiger
1 scott
2 bill
2 steve

ORACLE> r
1 SELECT
2 id
3 , LISTAGG(str, ',')
4 WITHIN GROUP
5 (
6 ORDER BY str
7 )
8 AS lists
9 FROM
10 list
11 GROUP BY id
12* ORDER BY id

ID LISTS
---------- ----------------------------------------
1 bar,foo,scott,tiger
2 bill,steve

次は、MySQL(8.0)

MySQLでは GROUP_CONCAT()関数が該当します。使い方は似ていますが、セパレータの指定方法に特徴がありますね。

GROUP_CONCAT(expr)
https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_group-concat

GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | expr}
[ASC | DESC] [,col_name ...]]
[SEPARATOR str_val])

mysql> SELECT
-> id
-> , GROUP_CONCAT(
-> str
-> ORDER BY str
-> SEPARATOR ','
-> )
-> AS lists
-> FROM
-> list
-> GROUP BY id
-> ORDER BY id;
+------+---------------------+
| id | lists |
+------+---------------------+
| 1 | bar,foo,scott,tiger |
| 2 | bill,steve |
+------+---------------------+
2 rows in set (0.03 sec)

PostgreSQL

PostgreSQLには類似する機能を持つ関数が複数ありますが、LISTAGG()と同じ結果を得る場合には、array_agg+array_to_string関数の組み合わせか、LISTAGGに近いSTRING_AGG()関数を単独で利用します。

array_agg(expression)
string_agg(expression, delimiter)
https://www.postgresql.jp/document/12/html/functions-aggregate.html

array_to_string(anyarray, text [, text])
https://www.postgresql.jp/document/12/html/functions-array.html

postgres=> SELECT
postgres-> id
postgres-> , STRING_AGG(
postgres(> str, ','
postgres(> ORDER BY str
postgres(> )
postgres-> AS lists
postgres-> FROM
postgres-> list
postgres-> GROUP BY id
postgres-> ORDER BY id;
id | lists
----+---------------------
1 | bar,foo,scott,tiger
2 | bill,steve
postgres=> SELECT
postgres-> id
postgres-> , ARRAY_TO_STRING(
postgres(> ARRAY_AGG(str ORDER BY str)
postgres(> , ','
postgres(> )
postgres-> AS lists
postgres-> FROM
postgres-> list
postgres-> GROUP BY id
postgres-> ORDER BY id;
id | lists
----+---------------------
1 | bar,foo,scott,tiger
2 | bill,steve




最終日のエントリーはこの集約関数やSUBSTR()など、標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020で紹介した複数のSQL構文や関数を利用したネタを予定しています。(ネタが厳しくなったら早めに公開するかもしれませんが)


明日の12月21日のJPOUG Advent Calendar 2020Yohei Azekatsu さんです。何か、やらかして。。。いや、何か、やってくれることでしょう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

| | | コメント (0)

2020年12月19日 (土)

標準はあるにはあるが癖の多いSQL 全部俺 #19 帰ってきた、部分文字列の扱いでも癖w

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の19日目です。

標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
で、すっかり忘れてた。非互換ではその手のが多いw

部分文字列の扱いの癖、盲点というかなんというか、小数の扱いの違いを忘れてましたw

Oracle

Positionが大きい場合の挙動では Oracle以外は空文字を返します。これも非互換は非互換ですが。

注目してもらいたいのは、整数じゃないとき。Oracleは、小数点以下切り捨てで動きます。

ORACLE> set tab off
ORACLE> set null [NULL]
ORACLE> col str for a30

ORACLE> SELECT SUBSTR('1234567890', 10, 1) AS str FROM dual;

STR
------------------------------
0

ORACLE> SELECT SUBSTR('1234567890', 11, 1) AS str FROM dual;

STR
------------------------------
[NULL]

ORACLE> SELECT SUBSTR('1234567890',10.4, 1) AS str FROM dual;

STR
------------------------------
0

ORACLE> SELECT SUBSTR('1234567890',10.5, 1) AS str FROM dual;

STR
------------------------------
0

ORACLE> SELECT SUBSTR('1234567890',10.9, 1) AS str FROM dual;

STR
------------------------------
0


MySQL (8.0)

Positionが範囲外であれば空文字を返すのは冒頭で説明した通りですが、Oracleと明らかに違うのは、小数点以下は四捨五入
整数以外も受け付けてくれますが、デフォルトの挙動で、切り捨てか、四捨五入という違いにより取り出される結果に差異が出ます。ハマりますよね。これw



mysql> SELECT SUBSTRING('1234567890', 10, 1) AS str;
+-----+
| str |
+-----+
| 0 |
+-----+
1 row in set (0.01 sec)

mysql> SELECT SUBSTRING('1234567890', 11, 1) AS str;
+-----+
| str |
+-----+
| |
+-----+
1 row in set (0.02 sec)

mysql> SELECT SUBSTRING('1234567890', 10.4, 1) AS str;
+-----+
| str |
+-----+
| 0 |
+-----+
1 row in set (0.02 sec)

mysql> SELECT SUBSTRING('1234567890', 10.5, 1) AS str;
+-----+
| str |
+-----+
| |
+-----+
1 row in set (0.06 sec)

mysql> SELECT SUBSTRING('1234567890', 10.9, 1) AS str;
+-----+
| str |
+-----+
| |
+-----+
1 row in set (0.02 sec)

mysql> SELECT SUBSTRING('1234567890', 1.4, 1) AS str;
+-----+
| str |
+-----+
| 1 |
+-----+
1 row in set (0.01 sec)

mysql> SELECT SUBSTRING('1234567890', 1.5, 1) AS str;
+-----+
| str |
+-----+
| 2 |
+-----+
1 row in set (0.02 sec)

mysql> SELECT CONCAT(CONCAT('''',SUBSTRING('1234567890', 10.9, 1)),'''') AS str;
+-----+
| str |
+-----+
| '' |
+-----+
1 row in set (0.02 sec)


PostgreSQL

こちらPostgreSQLは単純、Positionに指定できるのは整数のみです。ある意味わかりやすいですw 文字列の位置が 1.9とかなかなかですからね。

postgres=> SELECT SUBSTRING('1234567890', 10, 1) AS str;
str
-----
0
(1 row)

postgres=> SELECT SUBSTRING('1234567890', 11, 1) AS str;
str
-----

(1 row)

postgres=> SELECT CONCAT(CONCAT('''',SUBSTRING('1234567890', 11, 1)),'''') AS str;
str
-----
''
(1 row)

postgres=> SELECT SUBSTRING('1234567890', 10.4, 1) AS str;
ERROR: function pg_catalog.substring(unknown, numeric, integer) does not exist
LINE 1: SELECT SUBSTRING('1234567890', 10.4, 1) AS str;


おまけ

Redshift

PostgreSQLの血筋のはずですが、少数はエラーにもならず、そんなのねーよ。的な空文字が帰ってきます。なかなか男前です。素直に考えれば、1.9のところって文字の途中な訳で。。。

これもなかなか気づかいないです。エラーにはならないタイプなので、結果をみて???? としばらく悩むタイプですね。非互換としては事前に判断が難しいタイプ。リテラルで少数指定されていれば気づきやすいですが、バインド変数だったりすると気づくのは、かなり辛いです。

redshift=# SELECT SUBSTRING('1234567890', 10, 1) AS str;
str
-----
0
(1 row)

redshift=# SELECT SUBSTRING('1234567890', 11, 1) AS str;
str
-----

(1 row)

redshift=# SELECT SUBSTRING('1234567890', 10.4, 1) AS str;
str
-----

(1 row)

redshift=# SELECT SUBSTRING('1234567890', 10.5, 1) AS str;
str
-----

(1 row)

redshift=# SELECT SUBSTRING('1234567890', 10.9, 1) AS str;
str
-----

(1 row)

redshift=# SELECT SUBSTRING('1234567890', 1.4, 1) AS str;
str
-----

(1 row)

redshift=# SELECT SUBSTRING('1234567890', 1.5, 1) AS str;
str
-----

(1 row)

redshift=# SELECT CONCAT(CONCAT('''',SUBSTRING('1234567890', 10.9, 1)),'''') AS str;
str
-----
''
(1 row)


hr>
さあ、カウントダウンだ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 (+)の外部結合は方言

| | | コメント (0)

2020年12月18日 (金)

標準はあるにはあるが癖の多いSQL 全部俺 #18 (+)の外部結合は方言

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の18日目です。

今日はこまけーことに気づいてしまい、ブログ忘れそうだったw

ということで、2時間を切ったところで書いてますw

今日は、(+)を使った外部結合はOracle以外で通るのか(まあ、通らないですけどねー、Oracleの方言なのでw

と言った結合関連ネタを軽めで m(_ _)m

Oracle

(+)を使ったOracleの外部結合は、方言として有名ですよね。私は随分前から使わなくなってしまったので、今日は久々なにタイプした気がしますw
ANSI構文より(+)をオススメされる時があることはあるのですが、大抵の場合、実行計画がイケてるない時の対策としてだったりします。最近のは調べてないですが、。。。時間があったらネタにしてみたいと思います。。。w

ORACLE> SELECT *
2 FROM
3 m, d
4 WHERE
5 m.id = d.id(+);

ID ID SUBID
---------- ---------- ----------
1 1 1
2 2 1
3


ORACLE> SELECT *
2 FROM
3 m
4 LEFT OUTER JOIN d
5 ON m.id = d.id;

ID ID SUBID
---------- ---------- ----------
1 1 1
2 2 1
3

ORACLE> SELECT *
2 FROM
3 m, d;

ID ID SUBID
---------- ---------- ----------
1 1 1
2 1 1
3 1 1
1 2 1
2 2 1
3 2 1

ORACLE> SELECT *
2 FROM
3 m
4 CROSS JOIN d;

ID ID SUBID
---------- ---------- ----------
1 1 1
2 1 1
3 1 1
1 2 1
2 2 1
3 2 1

ORACLE> SELECT *
2 FROM
3 m, d
4 WHERE
5 m.id = d.id;

ID ID SUBID
---------- ---------- ----------
1 1 1
2 2 1

ORACLE> SELECT *
2 FROM
3 m
4 INNER JOIN d
5 ON m.id = d.id;

ID ID SUBID
---------- ---------- ----------
1 1 1
2 2 1


PostgreSQL

お次はPostgreSQL、通りませんよね!

postgres=> SELECT *
postgres-> FROM
postgres-> m
postgres-> LEFT OUTER JOIN d
postgres-> ON m.id = d.id;
id | id | subid
----+----+-------
1 | 1 | 1
2 | 2 | 1
3 | |
(3 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> m, d
postgres-> WHERE
postgres-> m.id = d.id(+);
ERROR: syntax error at or near ")"
LINE 5: m.id = d.id(+);
^
postgres=> SELECT *
postgres-> FROM
postgres-> m, d;
id | id | subid
----+----+-------
1 | 1 | 1
1 | 2 | 1
2 | 1 | 1
2 | 2 | 1
3 | 1 | 1
3 | 2 | 1
(6 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> m
postgres-> CROSS JOIN d;
id | id | subid
----+----+-------
1 | 1 | 1
1 | 2 | 1
2 | 1 | 1
2 | 2 | 1
3 | 1 | 1
3 | 2 | 1
(6 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> m, d
postgres-> WHERE
postgres-> m.id = d.id;
id | id | subid
----+----+-------
1 | 1 | 1
2 | 2 | 1
(2 rows)

postgres=> SELECT *
postgres-> FROM
postgres-> m
postgres-> INNER JOIN d
postgres-> ON m.id = d.id;
id | id | subid
----+----+-------
1 | 1 | 1
2 | 2 | 1
(2 rows)


MySQL (8.0)

それは、MySQLでも同じ。。。で(+)はエラーですよねー

mysql> SELECT *
-> FROM
-> m
-> LEFT OUTER JOIN d
-> ON m.id = d.id;
+------+------+-------+
| id | id | subid |
+------+------+-------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | NULL | NULL |
+------+------+-------+
3 rows in set (0.07 sec)

mysql> SELECT *
-> FROM
-> m, d
-> WHERE
-> m.id = d.id(+);
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 ')' at line 5


mysql> SELECT *
-> FROM
-> m, d;
+------+------+-------+
| id | id | subid |
+------+------+-------+
| 1 | 2 | 1 |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 3 | 1 | 1 |
+------+------+-------+
6 rows in set (0.06 sec)

mysql> SELECT *
-> FROM
-> m
-> CROSS JOIN d;
+------+------+-------+
| id | id | subid |
+------+------+-------+
| 1 | 2 | 1 |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 3 | 1 | 1 |
+------+------+-------+
6 rows in set (0.04 sec)

mysql> SELECT *
-> FROM
-> m, d
-> WHERE
-> m.id = d.id;
+------+------+-------+
| id | id | subid |
+------+------+-------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
+------+------+-------+
2 rows in set (0.05 sec)

mysql>
mysql> SELECT *
-> FROM
-> m
-> INNER JOIN d
-> ON m.id = d.id;
+------+------+-------+
| id | id | subid |
+------+------+-------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
+------+------+-------+
2 rows in set (0.03 sec)


Redshift

Redshiftでは〜、通じるんですよね。 (+) の外部結合

WHERE 句の Oracle スタイルの外部結合
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_WHERE_oracle_outer.html



ギリギリ 18個目の窓を開けた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 その空白は許されないのか?

| | | コメント (0)

2020年12月17日 (木)

標準はあるにはあるが癖の多いSQL 全部俺 #17 その空白は許されないのか?

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の17日目です。

もう少しだ頑張れ、自分w

ということで、今日は、そうなの? みたいな違いをみてみます。
関数と()の間に空白が入るとどうなるか。。。

では、いつもの通り Oracle から初めて、PostgreSQL , MySQLの順に見てみます。

Oracle

まあ、普通ですよね

ORACLE> SELECT COUNT(1) FROM dual;

COUNT(1)
----------
1

ORACLE> SELECT COUNT( 1 ) FROM dual;

COUNT(1)
----------
1

ORACLE> SELECT COUNT (1) FROM dual;

COUNT(1)
----------
1
¥
ORACLE> SELECT COUNT ( 1 ) FROM dual;

COUNT(1)
----------
1

PostgreSQL (Redshiftも同じ)

これもなんとことはない。。。

postgres=> SELECT COUNT(1);
count
-------
1
(1 row)

postgres=> SELECT COUNT( 1 );
count
-------
1
(1 row)

postgres=> SELECT COUNT (1);
count
-------
1
(1 row)

postgres=> SELECT COUNT ( 1 );
count
-------
1
(1 row)


MySQL (8.0)

おおおおおー。これは!

mysql> SELECT COUNT(1);
+----------+
| count(1) |
+----------+
| 1 |
+----------+
1 row in set (0.02 sec)
¥
mysql> SELECT COUNT( 1 );
+------------+
| count( 1 ) |
+------------+
| 1 |
+------------+
1 row in set (0.03 sec)

mysql> SELECT COUNT (1);
ERROR 1046 (3D000): No database selected

mysql> SELECT COUNT ( 1 );
ERROR 1046 (3D000): No database selected

ところが、sql_modeをANSIにすると。。。。。

ここ知らないと??ってなりますよね。関数と()の間にスペース入れるかどうかって、私はスペースなし派ですが、流派によってはありそうな。。。知らんけど。

5.1.11 Server SQL Modes
https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html

MySQLに絵文字を保存しようとしたら文字列が消える問題
http://soudai1025.blogspot.com/2016/03/"

mysql> set sql_mode = 'ANSI';
Query OK, 0 rows affected (0.06 sec)

mysql> SELECT COUNT(1);
+----------+
| count(1) |
+----------+
| 1 |
+----------+
1 row in set (0.05 sec)

mysql> SELECT COUNT( 1 );
+------------+
| count( 1 ) |
+------------+
| 1 |
+------------+
1 row in set (0.01 sec)

mysql> SELECT COUNT (1);
+-----------+
| count (1) |
+-----------+
| 1 |
+-----------+
1 row in set (0.06 sec)

mysql> SELECT COUNT ( 1 );
+-------------+
| count ( 1 ) |
+-------------+
| 1 |
+-------------+
1 row in set (0.04 sec)



眠い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のレントゲンを撮る方法

| | | コメント (0)

2020年12月16日 (水)

標準はあるにはあるが癖の多いSQL 全部俺 #16 SQLのレントゲンを撮る方法

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の16日目です。

さて、別の関数ネタをやろうと思うと予定外の打ち合わせが多く、今日も癖の多いSQLネタのはずが、その癖の多いSQLのレントゲン(実行計画)の撮り方の違いをネタにしてみました。m(_ _)m

大きな進化を遂げたのはいうまでも無く、MySQLですね。8.0になって大幅に機能格納した感があります。チューニングもしやすくなることでしょうね:)

こうやって、CTEの再帰問合せの実行計画見ながらハードリカー飲むのもいいものですw

まず、Oracleの実行計画確認方法はEEオプション含め3つ。SQL*Plusのauto trace以外はActual Planが確認できます。一手間かかりますが。

8 SQL*Plusのチューニング
https://docs.oracle.com/cd/F19136_01/sqpug/tuning-SQL-Plus.html#GUID-233D9103-017C-4832-B5E1-E38D32F9B00D

Oracle その1 / auto trace : SQL*Plusの機能で、オプションなしで利用できますが Actual Plan は見ることができません>< 実行統計は見れるのですけども

ORACLE> set tab off
ORACLE> set linesize 300
ORACLE> set autot trace exp
ORACLE> r
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10
12 )
13 SELECT v from gen_nums
14*

Execution Plan
----------------------------------------------------------
Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 26 | 4 (0)| 00:00:01 |
| 1 | VIEW | | 2 | 26 | 4 (0)| 00:00:01 |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | |
| 3 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
|* 4 | RECURSIVE WITH PUMP | | | | | |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - filter("V"+1<=10)


Oracleその2 / DBMS_XPLAN.DISPLAY_CURSOR()を利用したActual Plan取得(これはSEでも使えるオプション不要の機能

DBMS_XPLAN.DISPLAY_CURSOR
https://docs.oracle.com/cd/F19136_01/tgsql/generating-and-displaying-execution-plans.html#GUID-83F88700-3902-4D19-8182-AF2B92AEA7EB

ORACLE> r
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ GATHER_PLAN_STATISTICS */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10
12 )
13* SELECT v from gen_nums
1
2
3
4
5
6
7
8
9
10

10 rows selected.

ORACLE> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format=>'ALLSTATS LAST'));
SQL_ID a8yzv4a008jvr, child number 0
-------------------------------------
WITH gen_nums(v) AS ( SELECT /*+ GATHER_PLAN_STATISTICS */ 1 FROM
dual UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <= 10 )
SELECT v from gen_nums

Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 |
| 1 | VIEW | | 1 | 2 | 10 |00:00:00.01 |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | 1 | | 10 |00:00:00.01 |
| 3 | FAST DUAL | | 1 | 1 | 1 |00:00:00.01 |
| 4 | RECURSIVE WITH PUMP | | 10 | | 9 |00:00:00.01 |
--------------------------------------------------------------------------------------------------


Oracle その3 / Real Time SQL Monitor (EEオプション)

21 データベース操作の監視
https://docs.oracle.com/cd/F19136_01/tgsql/monitoring-database-operations.html#GUID-C941CE9D-97E1-42F8-91ED-4949B2B710BF

ORACLE> set pages 0
ORACLE> set linesize 1000
ORACLE> set long 1000000
ORACLE> set longchunksize 1000000
r
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT /*+ MONITOR */ 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10
12 )
13* SELECT v from gen_nums
1
2
3
4
5
6
7
8
9
10

10 rows selected.

ORACLE> select dbms_sqltune.report_sql_monitor(type=>'text') from dual;
SQL Monitoring Report

SQL Text
------------------------------
WITH gen_nums(v) AS ( SELECT /*+ MONITOR */ 1 FROM dual
UNION ALL SELECT v + 1 FROM gen_nums WHERE v + 1 <= 10 ) SELECT v from gen_nums

Global Information
------------------------------
Status : DONE (ALL ROWS)
Instance ID : 1
Session : SCOTT (25:48803)
SQL ID : 9g75y7v030mbt
SQL Execution ID : 16777216
Execution Started : 12/15/2020 15:58:45
First Refresh Time : 12/15/2020 15:58:45
Last Refresh Time : 12/15/2020 15:58:45
Duration : .000232s
Module/Action : SQL*Plus/-
Service : orcl
Program : sqlplus@localhost.localdomain (TNS V1-V3)
Fetch Calls : 2

Global Stats
=============================
| Elapsed | Cpu | Fetch |
| Time(s) | Time(s) | Calls |
=============================
| 0.00 | 0.00 | 2 |
=============================

SQL Plan Monitoring Details (Plan Hash Value=1492144221)
=================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
=================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 10 | | |
| 1 | VIEW | | 2 | 4 | 1 | +0 | 1 | 10 | | |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 1 | +0 | 1 | 10 | | |
| 3 | FAST DUAL | | 1 | 2 | 1 | +0 | 1 | 1 | | |
| 4 | RECURSIVE WITH PUMP | | | | 1 | +0 | 10 | 9 | | |
=================================================================================================================================================

MySQL

なんと、MySQL8.0から実行計画みやすいし、Actualまで出るじゃないですかーーーーーーー〜。


8.8.2 EXPLAIN Output Format
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

mysql> explain analyze WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 10
-> )
-> SELECT v from gen_nums;
+----------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (actual time=0.000..0.001 rows=10 loops=1)
-> Materialize recursive CTE gen_nums (actual time=0.019..0.020 rows=10 loops=1)
-> Rows fetched before execution (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 10) (cost=2.73 rows=2) (actual time=0.002..0.003 rows=4 loops=2)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.001..0.001 rows=5 loops=2)
|
+----------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)

PostgreSQL

そういえば、なんと無くMySQLの実行計画の見え方とPostgreSQLのは似てる気がします:)

14.1. EXPLAINの利用
https://www.postgresql.jp/document/12/html/using-explain.html

postgresql=> explain (analyze, buffers, verbose) WITH RECURSIVE gen_nums(v)
AS
(
SELECT 1
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 <= 10
)
SELECT v from gen_nums;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
CTE Scan on gen_nums (cost=3.21..3.83 rows=31 width=4) (actual time=0.004..0.016 rows=10 loops=1)
Output: gen_nums.v
CTE gen_nums
-> Recursive Union (cost=0.00..3.21 rows=31 width=4) (actual time=0.003..0.013 rows=10 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.001..0.002 rows=1 loops=1)
Output: 1
-> WorkTable Scan on gen_nums gen_nums_1 (cost=0.00..0.26 rows=3 width=4) (actual time=0.001..0.001 rows=1 loops=10)
Output: (gen_nums_1.v + 1)
Filter: ((gen_nums_1.v + 1) <= 10)
Rows Removed by Filter: 0
Planning Time: 0.055 ms
Execution Time: 0.039 ms
(12 rows)



さて、rebootしますよ。何かを。という話はもう少しあとで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

| | | コメント (0)

2020年12月15日 (火)

標準はあるにはあるが癖の多いSQL 全部俺 #15 SQL command line client

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の15日目です。

最初に、これSQLじゃないじゃん! はい。その通りです m(_ _)m

如何にもこうにも、時間取れなくて、安易な差異の紹介に走りました。 
とはいえ、SQL command line clentの使い勝手の違いって意外に無視できなかったりします
全ては紹介できないですが、個人的にどのエンジンのSQL command line clientでも使う機能だけですが:)


SQL command line clientでSQL叩いて、一旦、exitしてなんて面倒なことしたくないのでホストコマンドを実行したくなった時は ! とか \! です。

Oracle

OracleのSQL*Plusでは ! でホストコマンドを実行できます(Windowsは host or $)

Use the following command to execute operating system commands. - ! [ command ]
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqpqr/index.html#SQPQR108

ORACLE> ! date
2020年 12月15日 火曜日 0時34分38秒 JST


PostgreSQL

psql - \! [ command ]
https://www.postgresql.jp/document/12/html/app-psql.html

postgres=> \! date
2020年 12月15日 火曜日 0時35分34秒 JST

Mysql

mysql - \! [ command ]
https://dev.mysql.com/doc/refman/8.0/en/mysql-commands.html

mysql> \! date
2020年 12月15日 火曜日 0時39分00秒 JST

そして、無くてはならない編集コマンド, edit や \e と言ったショートカットなどがありますね。一通り覚えておくと便利です。

ORACLE

ED[IT] [file_name[.ext]]
https://docs.oracle.com/cd/F19136_01/sqpug/EDIT.html#GUID-25BC5CA1-4B03-4186-8ED3-715B5C6A6C42


ORACLE> select 1 from dual;

1
----------
1

ORACLE> edit

PostgreSQL

\e, \edit [ filename ] [ line_number ]
https://www.postgresql.jp/document/12/html/app-psql.html

postgres=> select 1;
?column?
----------
1
(1 row)

postgres=> \e


MySQL

edit, \e
https://dev.mysql.com/doc/refman/8.0/en/mysql-commands.html

mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.02 sec)

mysql> edit



これ、書いてて思い出した、explainの違いも書いておいた方がいいか。。。。これもSQLそのものではないけど、重要なはず。






標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル
標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある
標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり
標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある

| | | コメント (0)

2020年12月14日 (月)

標準はあるにはあるが癖の多いSQL 全部俺 #14 連番の集合を返すにも癖がある


標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の14日目です。


今回は大作(軽めにしたかったけど、少々難しのでそのまま載せることにしました)

業務上あまり多くないですが、一連番号の集合を作りたくなることがあります。シーケンスを使わずに。。

Oracleには昔から比較的簡単なクエリーで一連番号の集合を作り出せる(本来その目的のた目のクエリーではないですが)クエリーがいくつかあります。
PostgreSQLには、8.0(7.xぐらいから存在していたのか調べきれず。間違っていたらコメントいただけると助かります)ぐらいから 集合を返す関数として、generate_series()が組み込まれています。
MySQLは調べた限りですが、その手の便利関数やクエリーはあまりなさそうでした。

でそれを救う救世主w と言うのは大げさですが、WITH句を使った再帰問い合わせを使うと比較的互換の高い利用ができるようになってきました。
完全に同一構文ということではないのですけども。。。書き換える部分は少ない方だと思います:)

では、

Oracleから

まず、方言から古い順に紹介していきます。

Oracle/その1:階層問合せとlevel擬似列を利用する方法

他の方法に比べると処理時間なども有利ではあるのですが、Oracleだけでしか利用できない方法です。昔から利用している方は手グセでこちらをタイプすることも多いはずw
実行計画もシンプルで、Oracleで利用可能な方法の中では負荷は軽め(私が検証した範囲では)

階層問合せか、再帰問合せか、それが問題だ #2
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2011/01/2-8488.html


階層問合せ
https://docs.oracle.com/cd/F19136_01/sqlrf/Hierarchical-Queries.html#GUID-0118DF1D-B9A9-41EB-8556-C6E7D6A5A84E

ORACLE> set tab off
ORACLE> set linesize 300
ORACLE> r
1 SELECT
2 LEVEL AS r
3 FROM
4 dual
5 CONNECT BY
6 LEVEL <= 10
7*

R
----------
1
2
3
4
5
6
7
8
9
10

10 rows selected.
Elapsed: 00:00:00.10
ORACLE>ORACLE> set autot trace exp
ORACLE> r
...略...

Execution Plan
----------------------------------------------------------
Plan hash value: 1236776825

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
|* 1 | CONNECT BY WITHOUT FILTERING| | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(LEVEL<=10)


Oracle/その2:CUBEとrownum擬似列を利用する方法

これOracle 8iぐらいから拡張されたという記憶(間違ってたらごめんなさい)が、CUBEというクロス集計が簡単に書ける構文で長いクエリー書かなくて済むようになったーーーと
リリースされた当時は嬉しかった拡張の一つです。こんな使い方もできるのなーというところですが、実行計画を見るとかなり重めなんですよね。実際階層問合せより重いので、一連番号生成目的で利用することはほぼないですが、できるということで紹介しておきます。


20.3 CUBE(GROUP BYの拡張)
https://docs.oracle.com/cd/F19136_01/dwhsg/sql-aggregation-data-warehouses.html#GUID-C5FDD050-DCE0-4FE1-9741-420E2F970A36

ROWNUM疑似列
https://docs.oracle.com/cd/F19136_01/sqlrf/ROWNUM-Pseudocolumn.html#GUID-2E40EC12-3FCF-4A4F-B5F2-6BC669021726

ORACLE> r
1 SELECT rownum
2 FROM
3 (
4 SELECT 1
5 FROM
6 dual
7 GROUP BY
8 CUBE(1,1,1,1,1)
9 )
10 WHERE
11* rownum <= 10

ROWNUM
----------
1
2
3
4
5
6
7
8
9
10

10 rows selected.
Elapsed: 00:00:00.16

Execution Plan
----------------------------------------------------------
Plan hash value: 2264780677

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 66 (0)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 1 | | 66 (0)| 00:00:01 |
| 3 | TEMP TABLE TRANSFORMATION | | | | | |
| 4 | MULTI-TABLE INSERT | | | | | |
| 5 | SORT GROUP BY NOSORT ROLLUP | | 1 | | 2 (0)| 00:00:01 |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | DIRECT LOAD INTO (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D698E_276CE9B | | | | |
| 8 | DIRECT LOAD INTO (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D698F_276CE9B | | | | |
| 9 | VIEW | | 32 | 416 | 64 (0)| 00:00:01 |
| 10 | VIEW | | 32 | 416 | 64 (0)| 00:00:01 |
| 11 | UNION-ALL | | | | | |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 16 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 18 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 19 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 20 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 24 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 25 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 26 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 28 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 30 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 32 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 33 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 34 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 35 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 36 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 37 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 38 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 39 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 40 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 41 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 42 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698E_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D698F_276CE9B | 1 | 13 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=10)


Oracle/その3:再帰問合せを利用する方法

冒頭で紹介した階層問合せはOracleの方言ばりばりですが、階層問合せをサポートしているエンジンも多くなってきたこともあり、同一構文とまでは行きませんがかなり互換性は高い方法です。
汎用性のある方法にしたい場合は階層問合せを利用しておくと良いかもしれませんね。

Oracleの再帰問合せ構文では、recursive がないのが大きな違いです。また、dual表の利用も必要なのでこの点が他のエンジンと違うところと思っておけば大丈夫だと思います。
意外と構文はシンプルです。階層問合せに比べるとタイプする文字列は多いですけどw

再帰的副問合せのファクタリング
https://docs.oracle.com/cd/F19136_01/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6

ORACLE> r
1 WITH gen_nums(v)
2 AS
3 (
4 SELECT 1
5 FROM
6 dual
7 UNION ALL
8 SELECT v + 1
9 FROM
10 gen_nums
11 WHERE v + 1 <= 10
12 )
13 SELECT v from gen_nums
14*

V
----------
1
2
3
4
5
6
7
8
9
10

10 rows selected.
Elapsed: 00:00:00.16

Execution Plan
----------------------------------------------------------
Plan hash value: 1492144221

--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 26 | 4 (0)| 00:00:01 |
| 1 | VIEW | | 2 | 26 | 4 (0)| 00:00:01 |
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST| | | | | |
| 3 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
|* 4 | RECURSIVE WITH PUMP | | | | | |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - filter("V"+1<=10)


MySQL

調べた限りですが、MySQLにはPostgreSQLのような集合を返す関数として、generate_series()関数やOracleのような多くの方言はなさそうで
MySQL8.0からサポートされた再帰問合せが利用できます。Oracleで紹介した3つめの方法です。

多少構文は異なりますが、違うのは dual表がないのとrecursiveが必要なところ(MySQLの場合dual表を利用することも可能なので差異はrecursiveだけにすることもできます)

Recursive Common Table Expressions
https://dev.mysql.com/doc/refman/8.0/en/with.html#common-table-expressions-recursive

mysql> WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 10
-> )
-> SELECT v from gen_nums;
+------+
| v |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.06 sec)

mysql> explain analyze WITH RECURSIVE gen_nums(v)
-> AS
-> (
-> SELECT 1
-> UNION ALL
-> SELECT v + 1
-> FROM
-> gen_nums
-> WHERE v + 1 <= 10
-> )
-> SELECT v from gen_nums;
+----------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------+
| -> Table scan on gen_nums (actual time=0.000..0.001 rows=10 loops=1)
-> Materialize recursive CTE gen_nums (actual time=0.019..0.020 rows=10 loops=1)
-> Rows fetched before execution (actual time=0.000..0.000 rows=1 loops=1)
-> Repeat until convergence
-> Filter: ((gen_nums.v + 1) <= 10) (cost=2.73 rows=2) (actual time=0.002..0.003 rows=4 loops=2)
-> Scan new records on gen_nums (cost=2.73 rows=2) (actual time=0.001..0.001 rows=5 loops=2)
|
+----------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)


さて、最後は、
PostgreSQL

PostgreSQL/その1:generate_series()関数を利用する方法

PostgreSQL方言の関数ですが、使いやすいですねタイプする文字数は一番少ない:)

9.24. 集合を返す関数 - 表9.61 連続値生成関数 - generate_series
https://www.postgresql.jp/document/12/html/functions-srf.html

postgresql=> SELECT r FROM generate_series(1, 10) r;
r
----
1
2
3
4
5
6
7
8
9
10
(10 rows)

test=> explain (analyze,buffers,verbose) SELECT r FROM generate_series(1, 10) r;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Function Scan on pg_catalog.generate_series r (cost=0.00..10.00 rows=1000 width=4) (actual time=0.007..0.008 rows=10 loops=1)
Output: r
Function Call: generate_series(1, 10)
Planning Time: 0.025 ms
Execution Time: 0.022 ms
(5 rows)


PostgreSQL/その2:再帰問合せを利用する方法

見ての通り、MySQLで利用した構文がそのまま利用できます。全体で見る再帰問合せを利用する方法がもっとも差異の少ないことがわかります。微妙なさなんですけどね。 無くせないものか。。そこw

7.8. WITH問い合わせ(共通テーブル式)
https://www.postgresql.jp/document/12/html/queries-with.html


postgresql=> WITH RECURSIVE gen_nums(v)
postgresql-> AS
postgresql-> (
postgresql(> SELECT 1
postgresql(> UNION ALL
postgresql(> SELECT v + 1
postgresql(> FROM
postgresql(> gen_nums
postgresql(> WHERE v + 1 <= 10
postgresql(> )
postgresql-> SELECT v from gen_nums;
v
----
1
2
3
4
5
6
7
8
9
10
(10 rows)
postgresql=> explain (analyze, buffers, verbose) WITH RECURSIVE gen_nums(v)
AS
(
SELECT 1
UNION ALL
SELECT v + 1
FROM
gen_nums
WHERE v + 1 <= 10
)
SELECT v from gen_nums;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
CTE Scan on gen_nums (cost=3.21..3.83 rows=31 width=4) (actual time=0.004..0.016 rows=10 loops=1)
Output: gen_nums.v
CTE gen_nums
-> Recursive Union (cost=0.00..3.21 rows=31 width=4) (actual time=0.003..0.013 rows=10 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.001..0.002 rows=1 loops=1)
Output: 1
-> WorkTable Scan on gen_nums gen_nums_1 (cost=0.00..0.26 rows=3 width=4) (actual time=0.001..0.001 rows=1 loops=10)
Output: (gen_nums_1.v + 1)
Filter: ((gen_nums_1.v + 1) <= 10)
Rows Removed by Filter: 0
Planning Time: 0.055 ms
Execution Time: 0.039 ms
(12 rows)



ということで、今日は、役に立つような立たないような、でも何かの役に立ちそうなネタにしてみました。:)





標準はあるにはあるが癖の多い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 あると便利ですが意外となかったり

| | | コメント (0)

2020年12月13日 (日)

標準はあるにはあるが癖の多いSQL 全部俺 #13 あると便利ですが意外となかったり

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の13日目です。

ゼェゼェ。半ばに差し掛かりネタはあるものの書くことに疲れつつあるw (頑張れ自分w

と言うことで、今日は、あれば便利なので使ったりしますが、意外とありそうでなかったりする INITCAP()

この関数単語の先頭を大文字にしてくれる関数ですが、 MySQLの組み込み関数にはなかったりします。PostgreSQLにはあったり。

最初は
Oracleから。

INITCAP(char)
https://docs.oracle.com/cd/F19136_01/sqlrf/INITCAP.html#GUID-9FE9E0EE-D6B6-4C2C-BDEF-4FF4E1314560

ORACLE> SELECT INITCAP('oracle') FROM dual;

INITCA
------
Oracle

ORACLE> SELECT INITCAP('oracle-elison') FROM dual;

INITCAP('ORAC
-------------
Oracle-Elison

ORACLE> SELECT INITCAP('oracle,elison') FROM dual;

INITCAP('ORAC
-------------
Oracle,Elison

ORACLE> SELECT INITCAP('oracle|elison') FROM dual;

INITCAP('ORAC
-------------
Oracle|Elison

PostgreSQL

PostgreSQLにはOracleと同じ関数がサポートされています。

initcap(string)
https://www.postgresql.jp/document/12/html/functions-string.html

postgres=> SELECT INITCAP('oracle');
initcap
---------
Oracle
(1 row)

postgres=> SELECT INITCAP('oracle-elison');
initcap
---------------
Oracle-Elison
(1 row)

postgres=> SELECT INITCAP('oracle,elison');
initcap
---------------
Oracle,Elison
(1 row)

postgres=> SELECT INITCAP('oracle|elison');
initcap
---------------
Oracle|Elison
(1 row)


MySQL

個人的に意外だったのはMySQL. INITCAP()サポートされてません。UDFで作り込むしかないですね。

N/A
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html


おまけ、
PostgreSQLを祖先にもつRedshiftにはPostgreSQL同様の関数がありました。
20201213-30538
Redshift

INITCAP(string)
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_INITCAP.html





実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多い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 文字[列]探すにも癖がある

| | | コメント (0)

2020年12月12日 (土)

標準はあるにはあるが癖の多いSQL 全部俺 #12 文字[列]探すにも癖がある

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の12日目です。

私も癖多めですw

とうことで、アドベントカレンダーも約半分の折り返し地点です。ふー。

今日は、INSTR()

Oracleでは、SUBSTR()同様のバリエーションと挙動が見られます。ある意味分かり易ですね。
と言うことは、方言になりやすいはず。とも言えるわけです。はい。

では、見ていきましょう。


Oracle

positionは、SUBSTR()同様に負の値が使えます。0は、0しか返しません。
occurrenceは、正の値のみを取ります。

{ INSTR| INSTRB| INSTRC| INSTR2| INSTR4}(string , substring [, position [, occurrence ] ])
https://docs.oracle.com/cd/F19136_01/sqlrf/INSTR.html#GUID-47E3A7C4-ED72-458D-A1FA-25A9AD3BE113

ORACLE> SELECT INSTR('1234a56789a', 'a') FROM dual;

INSTR('1234A56789A','A')
------------------------
5

ORACLE> SELECT INSTR('1234a56789a', 'a', 1) FROM dual;

INSTR('1234A56789A','A',1)
--------------------------
5

ORACLE> SELECT INSTR('1234a56789a', 'a', -1) FROM dual;

INSTR('1234A56789A','A',-1)
---------------------------
11

ORACLE> SELECT INSTR('1234a56789a', 'a', 1, 1) FROM dual;

INSTR('1234A56789A','A',1,1)
----------------------------
           5

ORACLE> SELECT INSTR('1234a56789a', 'a', 1, 2) FROM dual;

INSTR('1234A56789A','A',1,2)
----------------------------
           11

ORACLE> SELECT INSTR('1234a56789a', 'a', -1, 2) FROM dual;

INSTR('1234A56789A','A',-1,2)
-----------------------------
           5

ORACLE> SELECT INSTR('1234a56789a', 'a', -1, 1) FROM dual;

INSTR('1234A56789A','A',-1,1)
-----------------------------
           11

ORACLE> SELECT INSTR('1234a56789a', 'a', 0) FROM dual;

INSTR('1234A56789A','A',0)
--------------------------
           0

ORACLE> set null [NULL]
ORACLE> SELECT INSTR('1234a56789a', 'a', null) FROM dual;

INSTR('1234A56789A','A',NULL)
-----------------------------
[NULL]

ORACLE> SELECT INSTR('1234a56789a', 'a', '') FROM dual;

INSTR('1234A56789A','A','')
---------------------------
[NULL]

ORACLE> SELECT INSTR('1234a56789a', '') FROM dual;

INSTR('1234A56789A','')
-----------------------
[NULL]

ORACLE> SELECT INSTR('1234a56789a', NULL) FROM dual;

INSTR('1234A56789A',NULL)
-------------------------
[NULL]

ORACLE> SELECT INSTR('1234a56789a', 'a', 1, 0) FROM dual;
SELECT INSTR('1234a56789a', 'a', 1, 0) FROM dual
*
ERROR at line 1:
ORA-01428: argument '0' is out of range

ORACLE> SELECT INSTR('1234a56789a', 'a', 1, -1) FROM dual;
SELECT INSTR('1234a56789a', 'a', 1, -1) FROM dual
*
ERROR at line 1:
ORA-01428: argument '-1' is out of range

MySQL

SUBSTR()はOracleに類似した挙動を持つ部分が多かったMySQLもINSTR()についてはそうでもないですね。positionやoccurrenceなどの引数がありません。
ただ、LOCATE()と言う類似した関数があります。LOCATE()と言う関数ではposition引数がありますが、0以上の整数でのみOracleと同じ挙動で負の値は、常に0ゼロを返すようです。

positionやoccurrence を利用している場合の移行は要注意と言うところですね。

INSTR(str,substr)
https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_instr

mysql> SELECT INSTR('1234a56789a', 'a');
+---------------------------+
| instr('1234a56789a', 'a') |
+---------------------------+
| 5 |
+---------------------------+
1 row in set (0.04 sec)

mysql> SELECT INSTR('1234a56789a', '');
+--------------------------+
| instr('1234a56789a', '') |
+--------------------------+
| 1 |
+--------------------------+
1 row in set (0.04 sec)

mysql> SELECT INSTR('1234a56789a', null);
+----------------------------+
| instr('1234a56789a', null) |
+----------------------------+
| NULL |
+----------------------------+
1 row in set (0.03 sec)

mysql> SELECT INSTR('1234a56789a', 'a', 1);
ERROR 1582 (42000): Incorrect parameter count in the call to native function 'instr'
mysql> SELECT INSTR('1234a56789a', 'a', 1, 1);
ERROR 1582 (42000): Incorrect parameter count in the call to native function 'instr'


LOCATE(substr,str), LOCATE(substr,str,pos)
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_locate

mysql> SELECT LOCATE('a', '1234a56789a');
+----------------------------+
| locate('a', '1234a56789a') |
+----------------------------+
| 5 |
+----------------------------+
1 row in set (0.08 sec)

mysql> SELECT LOCATE('a', '1234a56789a', 2);
+-------------------------------+
| locate('a', '1234a56789a', 2) |
+-------------------------------+
| 5 |
+-------------------------------+
1 row in set (0.05 sec)

mysql> SELECT LOCATE('a', '1234a56789a', 5);
+-------------------------------+
| locate('a', '1234a56789a', 5) |
+-------------------------------+
| 5 |
+-------------------------------+
1 row in set (0.08 sec)

mysql> SELECT LOCATE('a', '1234a56789a', 6);
+-------------------------------+
| locate('a', '1234a56789a', 6) |
+-------------------------------+
| 11 |
+-------------------------------+
1 row in set (0.09 sec)

mysql> SELECT LOCATE('a', '1234a56789a', -1);
+--------------------------------+
| locate('a', '1234a56789a', -1) |
+--------------------------------+
| 0 |
+--------------------------------+
1 row in set (0.35 sec)

mysql> SELECT LOCATE('a', '1234a56789a', 1);
+-------------------------------+
| locate('a', '1234a56789a', 1) |
+-------------------------------+
| 5 |
+-------------------------------+
1 row in set (0.13 sec)

mysql> SELECT LOCATE('a', '1234a56789a', 0);
+-------------------------------+
| locate('a', '1234a56789a', 0) |
+-------------------------------+
| 0 |
+-------------------------------+
1 row in set (0.35 sec)


PostgreSQL

42.13.3. 付録 本節には、移植作業を簡略化するために使用できる、Oracle互換のinstr関数のコードがあります。
https://www.postgresql.jp/document/12/html/plpgsql-porting.html#PLPGSQL-PORTING-APPENDIX

INSTR()はないのですが、類似関数として以下があるようです。また、position()と言う関数もあります。ですが、どちらもpositionやoccurrenceといった引数はない。マニュアルにOracleからの移植作業向けUDFの解説がある点は興味深いところ。

strpos(string, substring)
https://www.postgresql.jp/document/7.4/html/functions-string.html

postgres=> SELECT STRPOS('1234a56789a', 'a');
strpos
--------
5
(1 row)

postgres=> SELECT STRPOS('1234a56789a', '');
strpos
--------
1
(1 row)

postgres=> \pset null [NULL]
Null display is "[NULL]".
postgres=> SELECT STRPOS('1234a56789a', NULL);
strpos
--------
[NULL]
(1 row)

position(substring in string)
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_POSITION.html

postgres=> SELECT POSITION('a' in '1234a56789a');
position
----------
5
(1 row)

postgres=> SELECT POSITION('' in '1234a56789a');
position
----------
1
(1 row)

postgres=> SELECT POSITION(NULL in '1234a56789a');
position
----------
[NULL]
(1 row)

postgres=> SELECT POSITION(0 in '1234a56789a');
ERROR: function pg_catalog.position(unknown, integer) does not exist
LINE 1: SELECT POSITION(0 in '1234a56789a');
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
postgres=>


Redshift

PostgreSQL系の流れをくむRedshiftに、CHARINDEX()なる関数がある。同名の関数名が見つかるのはSQL Serverですね。それはそれで興味深い。

STRPOS(string, substring )
CHARINDEX( substring, string )
POSITION(substring IN string )
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_POSITION.html






実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)
標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル

| | | コメント (0)

2020年12月11日 (金)

標準はあるにはあるが癖の多いSQL 全部俺 #11 デュエル、じゃなくて、デュアル

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の10日目です。

 

ネタがなくてw そこ狙って来たか! 

と思われるかもしれませんが、これを語らずして、非互換語るなかれw

とうことで、Oracleでは、当たり前に利用している dual について

定数式をSELECT文で計算する場合などに指定する表です。dual表に、おイタしたりしたネタも過去あったような気がしますw

Oracleの方言なので、単純ですが、非互換では有名ですね。

 

では、本家から

Oracle

DUAL表からの選択
https://docs.oracle.com/cd/F19136_01/sqlrf/Selecting-from-the-DUAL-Table.html#GUID-0AB153FC-5238-4E79-8522-C9E2A04AB5E4

ORACLE> select 1 from dual;

1
----------
1

ORACLE> select 1;
select 1
*
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

 

MySQL

昨日のエントリで使ってしまったw ので気づいた方もいると思いますが、MySQLは dual 付けられるんですよね。

13.2.10 SELECT Statement
https://dev.mysql.com/doc/refman/8.0/en/select.html

mysql> select 1 from dual;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.06 sec)

mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.04 sec)

 

 

PostgreSQL

PostgreSQLには dual を使うような習慣もないですし、文法的に用意されていません。無理やり dual 表を定義すれば別でしょうけど、無駄なだけなので移行するなら、素直に dualを削除が潔いと思いますw

SELECT
https://www.postgresql.jp/document/12/html/sql-select.html

postgres=> select 1 from dual;
ERROR: relation "dual" does not exist
LINE 1: select 1 from dual;
^
postgres=> select 1;
?column?
----------
1
(1 row)

 

おまけ

Redshift

Redshiftにも dual はありません。

SELECT
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_SELECT_synopsis.html

 

redshift=# select 1 from dual;
ERROR: relation "dual" does not exist


redshift=# select 1;
?column?
----------
1
(1 row)

 

また、Athenaも同様です

 

SELECT
https://docs.aws.amazon.com/ja_jp/athena/latest/ug/select.html


 

新しい年には、何か変化が欲しいと感じる今日この頃w 

 

(2024.6更新)

喜ばしいことに、Oracle Database 23ai以降、from dualが必須ではなくなりました!
SELECT Withhout FROM Clause
https://docs.oracle.com/en/database/oracle/oracle-database/23/nfcoa/application_development.html#GUID-86198-1




実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019


 

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><
標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)

 

| | | コメント (1)

2020年12月10日 (木)

標準はあるにはあるが癖の多いSQL 全部俺 #10 文字列連結の罠(有名なやつ)

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の10日目です。


なんとか10日目の窓をあけましたw

今回は、有名な非互換なので、まさか、これに引っかかる方はいないと思いますが、定番のお約束みたいな非互換ネタなので書かないといけないですよね!!

では、いつも通り Oracle から。

NULLと空文字(マニュアルでは長さゼロの文字列値と記載されています。有名な非互換)
https://docs.oracle.com/cd/E82638_01/sqlrf/Nulls.html#GUID-B0BA4751-9D88-426A-84AD-BCDBD5584071

CONCAT(char1, char2)
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CONCAT.html#GUID-D8723EA5-C93A-45C3-83FB-1F3D2A4CEAF2

連結演算子
https://docs.oracle.com/cd/F19136_01/sqlrf/Concatenation-Operator.html#GUID-08C10738-706B-4290-B7CD-C279EBC90F7E

空文字をNULLとして扱う点と、||による文字列連結(CONCATと同意)の挙動がOracle以外の世界と違うんですよね。これも有名です。

ORACLE> SELECT 'foo' || 'bar' FROM dual;

'FOO'|
------
foobar

ORACLE> SELECT 'foo' || '' FROM dual;

'FO
---
foo

ORACLE> SELECT 'foo' || NULL FROM dual;

'FO
---
foo

ORACLE> SELECT CONCAT('foo','bar') FROM dual;

CONCAT
------
foobar

ORACLE> SELECT CONCAT('foo','') FROM dual;

CON
---
foo

ORACLE> SELECT CONCAT('foo',null) FROM dual;

CON
---
foo

PostgreSQL

PostgreSQLでは、Oracleと異なり 文字列連結子でNULLを結合すると結果は、NULLになります。ここがOracleと異なる挙動ですね。
これを回避するにはconcat() or concat_ws()のいずれかを利用できます。

string || string
concat(str "any" [, str "any" [, ...] ])
concat_ws(sep text, str "any" [, str "any" [, ...] ])
https://www.postgresql.jp/document/12/html/functions-string.html

そして、これまた、悩ましいのは、 ||でNULLを結合した場合と、CONCAT_WS()でNULLを結合した挙動が異なるんですね。
||でNULLの場合はNULLですが、CONCATでNULLを結合するとOracleと同じ挙動になるんですよ。

postgres=> SELECT 'foo' || 'bar';
?column?
----------
foobar
(1 row)

postgres=> SELECT 'foo' || '';
?column?
----------
foo
(1 row)

postgres=> SELECT 'foo' || null;
?column?
----------
[NULL]
(1 row)

postgres=> SELECT CONCAT('foo', '');
concat
--------
foo
(1 row)

postgres=> SELECT CONCAT('foo', NULL);
concat
--------
foo
(1 row)

postgres=>
postgres=> SELECT CONCAT_WS('','foo','bar' );
concat_ws
-----------
foobar
(1 row)

postgres=> SELECT CONCAT_WS('','foo','' );
concat_ws
-----------
foo
(1 row)

postgres=> SELECT CONCAT_WS('','foo',NULL );
concat_ws
-----------
foo
(1 row)

postgres=>


MySQL

MySQLでは、|| は文字列連結子ではなく、なんと、論理演算子!!!!!! 

え〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜~っ。

12.4.3 Logical Operators - OR, ||
https://dev.mysql.com/doc/refman/8.0/en/logical-operators.html

mysql> SELECT 'foo' || 'bar' FROM dual;
+----------------+
| 'foo' || 'bar' |
+----------------+
| 0 |
+----------------+
1 row in set, 3 warnings (0.03 sec)

mysql> SELECT 'foo' || '' FROM dual;
+-------------+
| 'foo' || '' |
+-------------+
| 0 |
+-------------+
1 row in set, 2 warnings (0.04 sec)

mysql> SELECT 'foo' || NULL FROM dual;
+---------------+
| 'foo' || null |
+---------------+
| NULL |
+---------------+
1 row in set, 2 warnings (0.06 sec)


実は逃げ道があるようで、sql_mode='PIPES_AS_CONCAT' にすると文字列連結子に早変わり!w

ですが、挙動はPostgreSQL同様に、NULLと連結したり演算子すると結果はNULLになると言う一般的な動きをします。
Oracleは演算に関してはNULLが絡むとNULLになりますが、文字列連結だけはNULLが空文字のような扱いを受けると言う挙動を示します。理解しちゃえばあれですが、エラーにならないだけに混乱するタイプの非互換ですね。

PostgreSQLとは異なり、CONCAT_WS()だけがOracleと同じ挙動を示します。


CONCAT(str1,str2,...)
CONCAT_WS(separator,str1,str2,...)
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html


mysql> set sql_mode='PIPES_AS_CONCAT';
Query OK, 0 rows affected (0.03 sec)

mysql> SELECT 'foo' || 'bar' FROM dual;
+----------------+
| 'foo' || 'bar' |
+----------------+
| foobar |
+----------------+
1 row in set (0.02 sec)

mysql> SELECT 'foo' || '' FROM dual;
+-------------+
| 'foo' || '' |
+-------------+
| foo |
+-------------+
1 row in set (0.04 sec)

mysql> SELECT 'foo' || NULL FROM dual;
+---------------+
| 'foo' || null |
+---------------+
| NULL |
+---------------+
1 row in set (0.01 sec)

mysql> SELECT CONCAT('foo','bar');
+---------------------+
| concat('foo','bar') |
+---------------------+
| foobar |
+---------------------+
1 row in set (0.01 sec)

mysql> SELECT CONCAT('foo','');
+------------------+
| concat('foo','') |
+------------------+
| foo |
+------------------+
1 row in set (0.02 sec)

mysql> SELECT CONCAT('foo',NULL);
+--------------------+
| concat('foo',null) |
+--------------------+
| NULL |
+--------------------+
1 row in set (0.03 sec)

mysql> SELECT CONCAT_WS('foo',NULL);
+-----------------------+
| concat_ws('foo',null) |
+-----------------------+
| |
+-----------------------+
1 row in set (0.02 sec)

mysql> SELECT CONCAT_WS('','foo',NULL);
+--------------------------+
| concat_ws('','foo',null) |
+--------------------------+
| foo |
+--------------------------+
1 row in set (0.01 sec)

mysql> SELECT CONCAT_WS('','foo','bar');
+---------------------------+
| concat_ws('','foo','bar') |
+---------------------------+
| foobar |
+---------------------------+
1 row in set (0.02 sec)

mysql> SELECT CONCAT_WS('','foo','');
+------------------------+
| concat_ws('','foo','') |
+------------------------+
| foo |
+------------------------+
1 row in set (0.02 sec)

mysql> SELECT CONCAT_WS('','foo',NULL);
+--------------------------+
| concat_ws('','foo',null) |
+--------------------------+
| foo |
+--------------------------+
1 row in set (0.01 sec)

mysql>

ややこしやー、ややこしやー。

みなさん、ついてこれてますか? この手の内容が25日まで続きますからね。(私が続けられるか次第だが。。。。頑張りマッス!





実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!
標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?
標準はあるにはあるが癖の多いSQL 全部俺 #9 部分文字列の扱いでも癖が出る><

| | | コメント (0)

2020年12月 8日 (火)

標準はあるにはあるが癖の多いSQL 全部俺 #8 翌月末日って何日?

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の8日目です。

日付関連の非互換はネタが沢山w 

翌月末日って何日? を求める場合にも違いがあります。ほんとにもうww

Oracle

Oracleには、last_day()関数ががあります。date型の引数をとるので期間リテラルと演算可能。
では、Oracleで翌月末日を求めてみる。シンプルですね。

LAST_DAY(date)
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/LAST_DAY.html#GUID-296C7C02-7FB9-4AAC-8927-6A79320CE0C6

ORACLE> alter session set nls_date_format = 'yyyy-mm-dd';

Session altered.

ORACLE> select last_day(sysdate + interval '1' month) from dual;

LAST_DAY(S
----------
2021-01-31

Mysql

MySQLにもOracle同様にlast_day()関数を使って、簡単に「翌月末日って何日?」を求めることができますよね。

LAST_DAY(date)
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html

mysql> select last_day(curdate() + interval 1 month);
+----------------------------------------+
| last_day(curdate() + interval 1 month) |
+----------------------------------------+
| 2021-01-31 |
+----------------------------------------+
1 row in set (0.03 sec)


Postgresql

なぜか、昔から頑なに last_day()関数がありません。UDFで頑張るか、ちょいと頑張って「翌月末日って何日?」を求めなければいけません。
翌々月1日の前日が「翌月末日って何日?」なので、その方法で求めてみます。やれやれ、last_day()ってUDF作った方が楽そうですねw


N/A
https://www.postgresql.jp/document/12/html/functions-datetime.html


postgres=> select cast(date_trunc('month', current_date + interval '2 month') - interval '1 day' as date);
date
------------
2021-01-31
(1 row)


Redshift

PostgreSQLの流れを汲むRedshiftですが、last_day()関数ありました! PostgreSQLに、もしあればこんな構文だろうと想像します。

LAST_DAY ( { date | timestamp } )
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_LAST_DAY.html

redshift=# select last_day(current_date + interval '1 month');
last_day
------------
2021-01-31
(1 row)




ブログ書きながら寝落ちしてたw
20201208-02932






実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!
標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!

| | | コメント (0)

2020年12月 7日 (月)

標準はあるにはあるが癖の多いSQL 全部俺 #7 期間リテラル!

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の7日目です。

さて、日付関連は非互換の宝庫ではありますが、ほんとなんで違うの。。。と言う微妙な違いだったり、キーーーーってなりますよね。なぜ合わせられないw

今日は期間リテラル。


Oracle

期間リテラル
https://docs.oracle.com/cd/F19136_01/sqlrf/Literals.html#GUID-49FADC66-794D-4763-88C7-B81BB4F26D9E

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'yyyy-mm-dd';

セッションが変更されました。

SQL> SELECT SYSDATE + INTERVAL '10' DAY FROM DUAL;

SYSDATE+IN
----------
2020-12-16


MySQL

Temporal Intervals
https://dev.mysql.com/doc/refman/8.0/en/expressions.html#temporal-intervals

mysql> SELECT CURDATE() + INTERVAL 10 DAY;
+-----------------------------+
| curdate() + interval 10 day |
+-----------------------------+
| 2020-12-16 |
+-----------------------------+
1 row in set (0.00 sec)

PostgreSQL

8.5. 日付/時刻データ型
https://www.postgresql.jp/document/12/html/datatype-datetime.html

sql=> SELECT CAST(CURRENT_DATE + INTERVAL '10 DAY' AS DATE);

date
------------
2020-12-16
(1 row)



実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2020

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!/a>
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派
標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!

| | | コメント (0)

2020年12月 6日 (日)

標準はあるにはあるが癖の多いSQL 全部俺 #6 時間厳守!

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の6日目です。

日付、日時関連も非互換の多い部分ですね。

該当関数の挙動を見てみると違いと、以外と違いがあるので面白いですよ。日付、日時関数って多くて全てを取り上げてると大変なので、今回は、OracleのSYSDATEを取り上げて、PostgreSQLではどれ使うのがいいのかなぁ

と言うところを見ていきたいと思います。

まず、このエントリー内で利用している、関数の特性を示す単語の意味を定義しておきたいと思います。

・同一SQL文の上で何度callされても同じ日時を返す特性:文内同一
・同一SQL文の上でcallされる毎に異なる日時を返す特性:文内非同一
・同一トランザクション内で何度callしても同一日時を返す特性:トランザクション内同一
・同一トランザクション内でcallされる毎に異なる日時を返す特性:トランザクション内非同一

関数としては沢山ありすぎて個別に調査した内容を個別に書こうと思うと、アドベントカレンダーの一エントリーとして終わる気がししないのでw

軽めに書いてもこんな感じw


SYSDATEはタイムゾーンを持たないので、PostgreSQLのCURRENT_DATEかなー、と思ってしまうと大きな間違いで、もっとも挙動として近いのは、Oracleで利用していたタイムゾーンに合わせる形で利用するPostgreSQLのstatement_timestamp()と言うことになる。
有名なOrafceで提供されている oracle.sysdate() も中を覗いてみると、statement_timestamp() が利用されている。


このエントリーの後半で挙動の確認方法と oracle.sysdate() が statement_timestamp() を利用しているソースを記載しています。


Oracle Database

SYSDATE
https://docs.oracle.com/cd/E82638_01/sqlrf/SYSDATE.html#GUID-807F8FC5-D72D-4F4D-B66D-B0FE1A8FA7D2

年月日時分秒 タイムゾーンなし

特性
文内同一、トランザクション内非同一


SYSTIMESTAMP
https://docs.oracle.com/cd/E82638_01/sqlrf/SYSTIMESTAMP.html#GUID-FCED18CE-A875-4D5D-9178-3DE4FA956516

年月日時分秒.秒未満精度(9) タイムゾーンあり

特性
文内同一、ランザクション内非同一


CURRENT_DATE
https://docs.oracle.com/cd/E82638_01/sqlrf/CURRENT_DATE.html#GUID-96795097-D6F0-4288-90E7-9D7C49B4F6E5

年月日時分秒 タイムゾーンなし

特性
文内同一、トランザクション内非同一


CURRENT_TIMESTAMP
https://docs.oracle.com/cd/E82638_01/sqlrf/CURRENT_TIMESTAMP.html#GUID-CBD42B84-869D-45C7-9FFC-001DD7712097

年月日時分秒.秒未満精度(9) タイムゾーンあり

特性
文内同一、トランザクション内非同一


LOCALTIMESTAMP
https://docs.oracle.com/cd/E82638_01/sqlrf/LOCALTIMESTAMP.html#GUID-3C3D1F29-5F53-41F2-B2D6-A3767DFB22CA

年月日時分秒.秒未満精度(9) タイムゾーンなし

特性
文内同一、トランザクション内非同一




PostgreSQL
PostgreSQLのtimestamp型の秒未満精度は最大6桁であるため、Oracleのtimestamp型の最大精度より低いことに注意
https://www.postgresql.jp/document/12/html/functions-datetime.html


CURRENT_DATE
年月日(PostgreSQLのDATE型で返す)タイムゾーンなし

特性
文内同一、トランザクション内同一


CURRENT_TIMESTAMP
年月日時分秒.秒未満精度(6) タイムゾーンあり

特性
文内同一、トランザクション内同一


LOCALTIMESTAMP
年月日時分秒.秒未満精度(6) タイムゾーンなし

特性
文内同一、トランザクション内同一


clock_timestamp()
年月日時分秒.秒未満精度(6) タイムゾーンあり

特性
文内非同一、トランザクション内非同一


transaction_timestamp()
CURRENT_TIMESTAMPに同じ
関数名が具体的に何を返すか明確になっている点の違いのみ

特性
文内同一、トランザクション内同一


statement_timestamp()
年月日時分秒.秒未満精度(6) タイムゾーンあり

特性
文内同一、トランザクション内非同一
statement_timestamp()が、もっともOracleのSYSDATE/SYSTIMESTAMPに近い挙動を示す

now()
transaction_timestamp()の別名
利用することは推奨されていない(何を返すかわかりにくい関数名である影響と思われる)

特性
文内同一、トランザクション内同一





Oracle Database

トランザクション内同一性の確認(1秒間隔で3回実行)
Oracle Databaseの日付時刻関数は、トランザクション内非同一

SYSDATE              SYSTIMESTAMP
-------------------- ----------------------------------------
2020/11/08 16:12:03 2020/11/08 16:12:03.345275 +00:00
2020/11/08 16:12:04 2020/11/08 16:12:04.397601 +00:00
2020/11/08 16:12:05 2020/11/08 16:12:05.475719 +00:00

LOCALTIMESTAMP CURRENT_DATE CURRENT_TIMESTAMP
------------------------------ -------------------- ----------------------------------------
2020/11/09 01:12:03.345277 2020/11/09 01:12:03 2020/11/09 01:12:03.345277 +09:00
2020/11/09 01:12:04.397603 2020/11/09 01:12:04 2020/11/09 01:12:04.397603 +09:00
2020/11/09 01:12:05.475721 2020/11/09 01:12:05 2020/11/09 01:12:05.475721 +09:00


文内同一性の確認(最後のカラムは、1秒待機後に返すようにして実行)
(ore_sleep()と言うUDFを作成し、内部で(Oracle 18cまで)dbms_lock.sleep(1) or (Oracle 19c以降)dbms_session.sleep(1) を実行)

以下の結果から、Oracle Databaseの日付時刻関数は、すべて文内同一

SQL> SELECT
sysdate, sysdate, ORE_SLEEP(1), sysdate
FROM
dual;

SYSDATE SYSDATE ORE_SLEEP(1) SYSDATE
------------------- ------------------- -------------- -------------------
2020/11/21 05:49:46 2020/11/21 05:49:46 0 2020/11/21 05:49:46

SQL> r
1 SELECT
2 systimestamp, systimestamp, ORE_SLEEP(1), systimestamp
3 FROM
4* dual

SYSTIMESTAMP SYSTIMESTAMP OREO_SLEEP(1) SYSTIMESTAMP
---------------------------------------- ---------------------------------------- -------------- ----------------------------------------
2020/11/21/05:48:19.459180 +00:00 2020/11/21/05:48:19.459180 +00:00 0 2020/11/21/05:48:19.459180 +00:00

SQL> r
1 SELECT
2 localtimestamp,localtimestamp,ORE_SLEEP(1),localtimestamp
3 FROM
4* dual

LOCALTIMESTAMP LOCALTIMESTAMP ORE_SLEEP(1) LOCALTIMESTAMP
---------------------------------------- ---------------------------------------- -------------- ----------------------------------------
2020/11/21 14:58:13.350655 2020/11/21 14:58:13.350655 0 2020/11/21 14:58:13.350655

SQL> r
1 SELECT
2 current_date,current_date,ORE_SLEEP(1),current_date
3 FROM
4* dual

CURRENT_DATE CURRENT_DATE ORE_SLEEP(1) CURRENT_DATE
------------------- ------------------- -------------- -------------------
2020/11/21 14:59:47 2020/11/21 14:59:47 0 2020/11/21 14:59:47

SQL> r
1 SELECT
2 current_timestamp,current_timestamp,ORE_SLEEP(1),current_timestamp
3 FROM
4* dual

CURRENT_TIMESTAMP CURRENT_TIMESTAMP ORE_SLEEP(1) CURRENT_TIMESTAMP
---------------------------------------- ---------------------------------------- -------------- ----------------------------------------
2020/11/21/15:00:41.661495 +09:00 2020/11/21/15:00:41.661495 +09:00 0 2020/11/21/15:00:41.661495 +09:00





PostgreSQL


トランザクション内同一性の確認(1秒間隔で3回実行)
PostgreSQLの日付時刻関数は、トランザクション内同一と非同一が混在

CURRENT_DATEは時刻を持たないためこの方法では検証不能だが、
マニュアルではトランザクション内で同一と記載されている。
clock_timestamp(),statement_timestamp()はトランザクション内非同一、それ以外は、トランザクション内同一

 current_date |       current_timestamp       |
--------------+-------------------------------+
2020-11-08 | 2020-11-08 16:12:05.897343+00 |
2020-11-08 | 2020-11-08 16:12:05.897343+00 |
2020-11-08 | 2020-11-08 16:12:05.897343+00 |

clock_timestamp | localtimestamp |
------------------------------+----------------------------+
2020-11-08 16:12:05.936468+00 | 2020-11-08 16:12:05.897343 |
2020-11-08 16:12:07.304506+00 | 2020-11-08 16:12:05.897343 |
2020-11-08 16:12:08.532788+00 | 2020-11-08 16:12:05.897343 |

now | statement_timestamp | transaction_timestamp
------------------------------+-------------------------------+-------------------------------
2020-11-08 16:12:05.897343+00 | 2020-11-08 16:12:05.936423+00 | 2020-11-08 16:12:05.897343+00
2020-11-08 16:12:05.897343+00 | 2020-11-08 16:12:07.304408+00 | 2020-11-08 16:12:05.897343+00
2020-11-08 16:12:05.897343+00 | 2020-11-08 16:12:08.532704+00 | 2020-11-08 16:12:05.897343+00


文内同一性の確認(最後のカラムは、1秒待機後に返すようにして実行)
以下の結果から、PostgreSQL日付時刻関数は、文内同一/非同一混在。

current_dateについてはこの方法では検証できないが、ドキュメントより文内同一であると判断。

sql=> SELECT
sql-> current_date, current_date, pg_sleep(1), current_date;

current_date | current_date | pg_sleep | current_date
--------------+--------------+----------+--------------
2020-11-21 | 2020-11-21 | | 2020-11-21


文内同一

sql=> SELECT
sql-> current_timestamp,current_timestamp,pg_sleep(1),current_timestamp;

current_timestamp | current_timestamp | pg_sleep | current_timestamp
-------------------------------+-------------------------------+----------+-------------------------------
2020-11-21 06:05:48.930432+00 | 2020-11-21 06:05:48.930432+00 | | 2020-11-21 06:05:48.930432+00

文内非同一

sql=> SELECT
sql-> clock_timestamp(),clock_timestamp(),pg_sleep(1),clock_timestamp();

clock_timestamp | clock_timestamp | pg_sleep | clock_timestamp
-------------------------------+-------------------------------+----------+-------------------------------
2020-11-21 06:08:48.920466+00 | 2020-11-21 06:08:48.920466+00 | | 2020-11-21 06:08:49.925492+00

文内同一

sql=> SELECT
sql-> localtimestamp,localtimestamp,pg_sleep(1),localtimestamp;

localtimestamp | localtimestamp | pg_sleep | localtimestamp
----------------------------+----------------------------+----------+----------------------------
2020-11-21 06:19:19.547055 | 2020-11-21 06:19:19.547055 | | 2020-11-21 06:19:19.547055


文内同一

sql=> SELECT
sql-> now(),now(),pg_sleep(1),now();

now | now | pg_sleep | now
-------------------------------+-------------------------------+----------+-------------------------------
2020-11-21 06:20:07.457373+00 | 2020-11-21 06:20:07.457373+00 | | 2020-11-21 06:20:07.457373+00

文内同一

sql=> SELECT
sql-> statement_timestamp(),statement_timestamp(),pg_sleep(1),statement_timestamp();

statement_timestamp | statement_timestamp | pg_sleep | statement_timestamp
-------------------------------+-------------------------------+----------+-------------------------------
2020-11-21 06:20:52.502137+00 | 2020-11-21 06:20:52.502137+00 | | 2020-11-21 06:20:52.502137+00


文内同一

sql=> SELECT
sql-> transaction_timestamp(),transaction_timestamp(),pg_sleep(1),transaction_timestamp();

transaction_timestamp | transaction_timestamp | pg_sleep | transaction_timestamp
-------------------------------+-------------------------------+----------+-------------------------------
2020-11-21 06:21:24.562833+00 | 2020-11-21 06:21:24.562833+00 | | 2020-11-21 06:21:24.562833+00







orafce によるエミュレーション関数(参考)
https://github.com/orafce/orafce/blob/master/README.asciidoc


oracle.sysdate()
PostgreSQLのstatement_timestamp()をラップしているため属性はstatement_timestamp()と同じ
sysdate関数のエミュレーションとしては最も近い属性を持っていると見られる。
OracleのSYSDATE代替関数とされている。
特性としては、問題ないと考えられ、文内同一 (OracleのSYSDATEと同じ挙動),トランザクション内非同一(OracleのSYSDATEと同じ挙動)

実装を見てみると、

https://github.com/orafce/orafce/blob/master/orafce--3.14.sql
の関数定義を見るとC言語の関数であり、statement_timestampをと言うコメントがある. 
statement_timestamp()がOracleのSYSDATEの挙動に近いと言う理由からなのだろうと想像する。納得感あり!!!!

CREATE FUNCTION oracle.sysdate()
RETURNS oracle.date
AS 'MODULE_PATHNAME','orafce_sysdate'
LANGUAGE C STABLE STRICT;
COMMENT ON FUNCTION oracle.sysdate() IS 'Ruturns statement timestamp at server time zone';

さらに、orafceのoracle.sysdate()のC言語のソースを追ってみる。。。
datefce.cのorafce_sysdateが本体であることがわかる
https://github.com/orafce/orafce/blob/master/builtins.h

extern PGDLLEXPORT Datum orafce_sysdate(PG_FUNCTION_ARGS);


https://github.com/orafce/orafce/blob/master/datefce.c

/********************************************************************
*
* ora_sysdate - sysdate
*
* Syntax:
*
* timestamp sysdate()
*
* Purpose:
*
* Returns statement_timestamp in server time zone
* Note - server time zone doesn't exists on PostgreSQL - emulated
* by orafce_timezone
*
********************************************************************/

Datum
orafce_sysdate(PG_FUNCTION_ARGS)
{
Datum sysdate;
Datum sysdate_scaled;

sysdate = DirectFunctionCall2(timestamptz_zone,
CStringGetTextDatum(orafce_timezone),
TimestampTzGetDatum(GetCurrentStatementStartTimestamp()));

/* necessary to cast to timestamp(0) to emulate Oracle's date */
sysdate_scaled = DirectFunctionCall2(timestamp_scale,
sysdate,
Int32GetDatum(0));

PG_RETURN_DATUM(sysdate_scaled);
}

および、GetCurrentStatementStartTimestamp()よりstatement_timestamp()であることがわかる
https://docs.huihoo.com/doxygen/postgresql/backend_2utils_2adt_2timestamp_8c_source.html#l01239

01239 statement_timestamp(PG_FUNCTION_ARGS)
01240 {
01241 PG_RETURN_TIMESTAMPTZ(GetCurrentStatementStartTimestamp());
01242 }








Oracleの検証に利用したコード、昔は、DBMS_LOCK.SLEEP()ってなんでDBMS_LOCKパッケージにあるの? と言う感じだったが、最近はわかりやすいDBMS_SESSIONパッケージが推奨で、DBMS_LOCK.SLEEP()は非推奨なのでご注意を
ore_seep(seonds) - UDF sample
CREATE OR REPLACE FUNCTION ore_sleep
(
seconds NUMBER
)
RETURN NUMBER
AS
BEGIN
$IF DBMS_DB_VERSION.VER_LE_12 $THEN
DBMS_LOCK.SLEEP(seconds);
$ELSE
DBMS_SESSION.SLEEP(seconds);
$END
RETURN 0;
END;
/




実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2020

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!
標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです
標準はあるにはあるが癖の多いSQL 全部俺 #4 リテラル値での除算の内部精度も違うのよ!/a>
標準はあるにはあるが癖の多いSQL 全部俺 #5 和暦変換機能ある方が少数派

| | | コメント (0)

2020年12月 3日 (木)

標準はあるにはあるが癖の多いSQL 全部俺 #3 データ型確認したい時あるんです

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の3日目です。

忙しい、今年も勢いで始めた割には、事前にネタの構想があったわけでもないというギリギリの状態で書いてますがw
なんとか3つ目の窓をあけました。:)

今回は、データ型確認したいことありませんか? という話。
Oracleを長年使い、PostgreSQL(互換含む)への移行という時に、調査していた時のこと、。。。

データ型見たいよね、これ。Oracleだとdump()で間接的にデータ型見れたなと。。。で他のエンジンではどうなのよ。。。と調べ始めたら結構大変でした。。
という2年半ぐらい前の苦労を思い出しつつ書いてみますw

調べていくと、わかりやすい関数名だったり、ありそうでないものもあるんですよね。。。。まさに、非互換の多い部分だった。。。

まず、Oracle
おなじみのdump()関数ですね。typ=nn の数字でデータ型を判断します. typ=2は、NUMBER型、typ=96は、CHAR型ですね。

SQL> select dump(123) from dual;

DUMP(123)
---------------------
Typ=2 Len=3: 194,2,24

SQL> select dump(123,8) from dual;

DUMP(123,8)
---------------------
Typ=2 Len=3: 302,2,30

SQL> select dump(123,10) from dual;

DUMP(123,10)
---------------------
Typ=2 Len=3: 194,2,24

SQL> select dump(123,16) from dual;

DUMP(123,16)
--------------------
Typ=2 Len=3: c2,2,18

SQL> select dump(123,17) from dual;

DUMP(123,17)
---------------------
Typ=2 Len=3: c2,^B,^X

SQL>
SQL> select dump('123',1016) from dual;

DUMP('123',1016)
--------------------------------------------
Typ=96 Len=3 CharacterSet=AL32UTF8: 31,32,33


次は、Postgresql

pg_typeof()って関数が使えます。結果がデータ型名で返されるのでわかりやすい!

sql=> select pg_typeof(123);
pg_typeof
-----------
integer
(1 row)

sql=> select pg_typeof('123'::text);
pg_typeof
-----------
text
(1 row)

sql=> select pg_typeof(now());
pg_typeof
--------------------------
timestamp with time zone
(1 row)

Athena

Prestoで使える関数なので、そのまま typeof()って関数でこれもデータ型名で返されます。
なぜ、Athenaかって? 勢いですかねぇ。

% aws athena start-query-execution --query-string "select typeof(123);" --result-configuration OutputLocation=s3://xxxx-athena-results
--------------------------------------------------------------
| StartQueryExecution |
+-------------------+----------------------------------------+
| QueryExecutionId | f658de1e-b711-433d-b603-15835b6e5de5 |
+-------------------+----------------------------------------+
%
% aws athena get-query-results --query-execution-id 92f46e33-0b2e-4e94-90b7-8acb3d6fce3b --output text | grep DATA
DATA _col0
DATA integer


Redshift

N/A


MySQL
もし、あったらツッコミよろしくお願いします。 m(_ _)m

N/A
 


参考

Oracle - DUMP
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DUMP.html#GUID-A05793C9-B35D-4BA7-B68C-E3693BCF47A5

Oracle Built-in Data Types
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-7B72E154-677A-4342-A1EA-C74C1EA928E6

PostgreSQL - 表9.63 システムカタログ情報関数 - pg_typeof
https://www.postgresql.jp/document/11/html/functions-info.html#FUNCTIONS-INFO-CATALOG-TABLE

Athena - 6.4. Conversion Functions - typeof
https://prestodb.io/docs/0.172/functions/conversion.html




実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination
標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!

| | | コメント (0)

2020年12月 2日 (水)

標準はあるにはあるが癖の多いSQL 全部俺 #2 関数名は同じでも引数が逆の罠!

標準はあるにはあるが癖の多いSQL 全部俺w Advent Calendar 2020の2日目です。

今日は、心と時間の余裕がないので、軽めですw

いきなりですがタイトルの通り、関数名が同じなら引数の並びや数も一緒だ!

と決めつけちゃいけないw 案件です。 数時間ハマった挙句、マニュアルを読み直すという王道で解決した事案ですはい。マニュアル読みましょうね。読みづらいのもあるけど。。

現在の私、何をやってるかピンボケ感満載なロール名ではあるのですが、その名の通り、OracleのSQLで質問もうけるわ、たまには、SparkSQLでも質問受けたりしますw

そのSparkSQLでハマったのがrtrim(),ltrim()。

長年Oracleを使ってきたので、手癖でタイプしちゃうわけですよ! 思い込み、ダメ絶対!w

では、その大切なマニュアルの解説でRDBMSの有名どころのltrim/rtrimとSparkSQLのltrim/rtrimのシンタックスを確認してみましょう。

まず、Oracleは以下の通り。見たなれ安心感w

LTRIM( str [, trimStr] )str : トリムされるソースの文字列型または、リテラル文字列trimStr : トリムしたい文字列。デフォルトは、半角空白1文字
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA

次、PostgreSQL

RTRIM( str [, trimStr] )str : トリムされるソースの文字列型または、リテラル文字列trimStr : トリムしたい文字列。デフォルトは、半角空白1文字
https://www.postgresql.jp/document/11/html/functions-string.html

ついでなので、Redshift
PostgreSQLと同じですね。

RTRIM( string, trim_chars ) string : 切り捨てる文字列の列または式。 trim_chars : 末尾から切り捨てる文字を表す、文字列の列または式。
https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_RTRIM.html


MySQL
MySQLのRTRIMには第二引数は無い! 単体では同じことができないので、他の関数と組み合わせるんでしょうね。(試してないですが)

RTRIM( str )str : トリムされるソースの文字列型または、リテラル文字列
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rtrim

そして、最後に、今回ハマった
SparkSQL

RTRIM( [trimStr ,] str )str : トリムされるソースの文字列型または、リテラル文字列trimStr : トリムしたい文字列。デフォルトは、半角空白1文字
トリムしたい文字列の引数位置が、Oracle/PostgreSQL/Redshiftとは逆なので引数が少ないMySQLと異なりシンタックスエラーにならない><
https://spark.apache.org/docs/2.3.1/api/sql/index.html#rtrim

rtrim()も同じです。

Oracle
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA

PostgreSQL
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA

Redshift
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA

MySQL
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA

SparkSQL
https://docs.oracle.com/cd/F19136_01/sqlrf/RTRIM.html#GUID-95A7DAFB-F7AB-48F4-BE24-64B3C7A840AA


簡単な例を

SparkSQL

>>> SQL="select rtrim(' ', 'SparkSQL ') as d"
>>> spark.sql(SQL).show()
+--------+
| d |
+--------+
|SparkSQL|
+--------+

Oracle

SQL> select '|' || rtrim('SparkSQL ', ' ') || '|' as d from dual;
D
----------
|SparkSQL|

PostgreSQL / Redshift

test=> select '|' || rtrim('SparkSQL ', ' ') || '|' as d;
d
------------
|SparkSQL|



実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺) Advent Calendar 2019

標準はあるにはあるが癖の多いSQL 全部俺 #1 Pagination

| | | コメント (0)

2020年7月26日 (日)

RDS Oracle 雑多なメモ#21 / DBMS_DATAPUMPパッケージ Schema mode de expdp/impdp - metadata onlyとデータのインポート

Previously, Mac De Oracle...

RDS Oracle 雑多なメモ#20 / DBMS_DATAPUMPパッケージ Schema mode de expdp/impdpの準備 SQL_FILEモードでDDL抜き出し

でした。

今回は、RDS Oracleへスキーマ単位のインポートでメタデータだけをインポートするにはどうするかというおはなし。

メタデータだけをインポートするにはこれまでに何度か話題にしたエクスポートされたオブジェクトとその階層を表すオブジェクトパスが重要な意味を持っています。

まずはフルエクスポートログにリストされるオブジェクトパスの例をご覧ください。

Processing object type DATABASE_EXPORT/EARLY_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/PACKAGE_BODIES/PACKAGE/PACKAGE_BODY
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/FUNCTIONAL_INDEX/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type DATABASE_EXPORT/STATISTICS/MARKER
Processing object type DATABASE_EXPORT/PRE_SYSTEM_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/PRE_INSTANCE_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/TABLESPACE
Processing object type DATABASE_EXPORT/PROFILE
Processing object type DATABASE_EXPORT/SCHEMA/USER
Processing object type DATABASE_EXPORT/ROLE
Processing object type DATABASE_EXPORT/RADM_FPTM
Processing object type DATABASE_EXPORT/GRANT/SYSTEM_GRANT/PROC_SYSTEM_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/GRANT/SYSTEM_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/ROLE_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/DEFAULT_ROLE
Processing object type DATABASE_EXPORT/SCHEMA/ON_USER_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/TABLESPACE_QUOTA
Processing object type DATABASE_EXPORT/RESOURCE_COST
Processing object type DATABASE_EXPORT/TRUSTED_DB_LINK
Processing object type DATABASE_EXPORT/SCHEMA/SEQUENCE/SEQUENCE
Processing object type DATABASE_EXPORT/DIRECTORY/DIRECTORY
Processing object type DATABASE_EXPORT/DIRECTORY/GRANT/OWNER_GRANT/OBJECT_GRANT
Processing object type DATABASE_EXPORT/DIRECTORY/GRANT/WITH_GRANT_OPTION/OBJECT_GRANT
Processing object type DATABASE_EXPORT/CONTEXT
Processing object type DATABASE_EXPORT/SCHEMA/PUBLIC_SYNONYM/SYNONYM
Processing object type DATABASE_EXPORT/SCHEMA/TYPE/TYPE_SPEC
Processing object type DATABASE_EXPORT/SYSTEM_PROCOBJACT/PRE_SYSTEM_ACTIONS/PROCACT_SYSTEM
Processing object type DATABASE_EXPORT/SYSTEM_PROCOBJACT/PROCOBJ
Processing object type DATABASE_EXPORT/SYSTEM_PROCOBJACT/POST_SYSTEM_ACTIONS/PROCACT_SYSTEM
Processing object type DATABASE_EXPORT/SCHEMA/PROCACT_SCHEMA
Processing object type DATABASE_EXPORT/EARLY_OPTIONS/VIEWS_AS_TABLES/TABLE
Processing object type DATABASE_EXPORT/EARLY_POST_INSTANCE_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/TABLE

上記オブジェクトパスツリーを一部省略しつつ見やすくすると以下のようなツリー構造になっているのが理解しやすいはず。

20200726-10936

図にも書いていますが、メタデータのみをインポートするにはデータを除外(Exclude)してしまえばよいわけです。

DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA

また、同じツリーの階層にあるオブジェクトもスキーマレベルのインポートではインポートされないということも理解しやすいはずです。:)

なお、DATAPUMPが扱うオブジェクトパスの詳細は 
データベース・リファレンス - 6.238 SCHEMA_EXPORT_OBJECTS
を参照してくださいね。知らない方は多いですが、DATAPUMPと仲良くなるためには必要なビューです。



では、スキーマモードのメタデータオンリーインポートの仕込みから
同じデータベースへスキーマをインポートするので事前に削除しておきます。

BILL> drop user hoge cascade;

User dropped.

BILL> drop profile fizzbuzz;

Profile dropped.

BILL> drop role fizzbuzz;

Role dropped.

BILL> drop tablespace fizzbuzz including contents and datafiles;

Tablespace dropped.

たとえばこの状態でスキーマレベルのインポートを行ってしまうと、もう、想像はできていると思いますが、依存オブジェクト不足によりインポートは失敗します。
ユーザーのデフォルト表領域や依存するプロファイルが存在しない状態ではインポート時に実行されるCREATE USER文がエラーとなりインポートはエラーで終了することになります。

BILL> @imp_schema_metadataonly data_pump_dir fullexp hoge
Master table "BILL"."SYS_IMPORT_SCHEMA_03" successfully loaded/unloaded
Starting "BILL"."SYS_IMPORT_SCHEMA_03":
Processing object type DATABASE_EXPORT/SCHEMA/USER
ORA-39083: Object type USER:"HOGE" failed to create with error:
ORA-02380: profile FIZZBUZZ does not exist

Failing sql is:
CREATE USER "HOGE" IDENTIFIED BY VALUES

...中略...

TABLESPACE "FIZZBUZZ" TEMPORARY TABLESPACE "TEMP" PROFILE "FIZZBUZZ"

Processing object type DATABASE_EXPORT/SCHEMA/ROLE_GRANT
ORA-39083: Object type ROLE_GRANT failed to create with error:
ORA-01917: user or role 'HOGE' does not exist

Failing sql is:
GRANT "CONNECT" TO "HOGE"

ORA-39083: Object type ROLE_GRANT failed to create with error:
ORA-01924: role 'FIZZBUZZ' not granted or does not exist

Failing sql is:
GRANT "FIZZBUZZ" TO "HOGE"

Processing object type DATABASE_EXPORT/SCHEMA/DEFAULT_ROLE
ORA-39083: Object type DEFAULT_ROLE:"HOGE" failed to create with error:
ORA-01918: user 'HOGE' does not exist

...中略...

Job "BILL"."SYS_IMPORT_SCHEMA_03" completed with 7 error(s) at Fri Jul 24 17:51:45 2020 elapsed 0 00:00:03

PL/SQL procedure successfully completed.


では、上記のようなエラーを回避するため、前回フルエクスポートから取り出したDDL を実行して作成します。

BILL> CREATE BIGFILE TABLESPACE "FIZZBUZZ" DATAFILE
2 SIZE 104857600
3 AUTOEXTEND ON NEXT 104857600 MAXSIZE 1073741824
4 LOGGING ONLINE PERMANENT BLOCKSIZE 8192
5 EXTENT MANAGEMENT LOCAL AUTOALLOCATE DEFAULT
6 NOCOMPRESS SEGMENT SPACE MANAGEMENT AUTO;

Tablespace created.

BILL> CREATE ROLE "FIZZBUZZ";

Role created.

BILL> CREATE PROFILE "FIZZBUZZ"
2 LIMIT
3 COMPOSITE_LIMIT DEFAULT
4 SESSIONS_PER_USER DEFAULT
5 CPU_PER_SESSION DEFAULT
6 CPU_PER_CALL DEFAULT
7 LOGICAL_READS_PER_SESSION DEFAULT
8 LOGICAL_READS_PER_CALL DEFAULT
9 IDLE_TIME UNLIMITED
10 CONNECT_TIME DEFAULT
11 PRIVATE_SGA DEFAULT
12 FAILED_LOGIN_ATTEMPTS DEFAULT
13 PASSWORD_LIFE_TIME DEFAULT
14 PASSWORD_REUSE_TIME DEFAULT
15 PASSWORD_REUSE_MAX DEFAULT
16 PASSWORD_VERIFY_FUNCTION DEFAULT
17 PASSWORD_LOCK_TIME DEFAULT
18 PASSWORD_GRACE_TIME DEFAULT
19 INACTIVE_ACCOUNT_TIME DEFAULT ;

Profile created.


準備ができたので、メタデータオンリーのスキーマモードインポートを試してみます。
スキーマ以下のオブジェクトはインポート(統計情報をフィルタするのを忘れましたがw)できました。hoge表には1行だけデータがありますがデータはインポートされていないことが確認できるはずです。

BILL> @imp_schema_metadataonly data_pump_dir fullexp hoge
Master table "BILL"."SYS_IMPORT_SCHEMA_04" successfully loaded/unloaded
Starting "BILL"."SYS_IMPORT_SCHEMA_04":
Processing object type DATABASE_EXPORT/SCHEMA/USER
Processing object type DATABASE_EXPORT/SCHEMA/ROLE_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/DEFAULT_ROLE
Processing object type DATABASE_EXPORT/SCHEMA/TABLESPACE_QUOTA
Processing object type DATABASE_EXPORT/SCHEMA/PROCACT_SCHEMA
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type DATABASE_EXPORT/STATISTICS/MARKER
Job "BILL"."SYS_IMPORT_SCHEMA_04" successfully completed at Fri Jul 24 17:56:34 2020 elapsed 0 00:00:13

PL/SQL procedure successfully completed.

BILL> select username from dba_users where username='HOGE';

USERNAME
------------------------------
HOGE

BILL> select count(1) from hoge.hoge;

COUNT(1)
----------
0


では次にデータだけをインポートしてみます。
TABLE_DATAのみをインポートする方法と、スキーマインポートで表が存在する場合に、データをTRUNCATE後、データインポートする方法があります。(表が存在している場合のデフォルトの動作はデータインポートのスキップです)
以下の例では 通常 のスキーマインポートで表が存在する場合 、既存データをTRUNCATEして データをインポートする方法で行ってます。ユーザーが存在する場合にはCREATE USERは失敗し表が存在した場合にtruncate後にデータがインポートされます

BILL> @imp_schema data_pump_dir fullexp hoge
Master table "BILL"."SYS_IMPORT_SCHEMA_01" successfully loaded/unloaded
Starting "BILL"."SYS_IMPORT_SCHEMA_01":
Processing object type DATABASE_EXPORT/SCHEMA/USER
ORA-31684: Object type USER:"HOGE" already exists

Processing object type DATABASE_EXPORT/SCHEMA/ROLE_GRANT
Processing object type DATABASE_EXPORT/SCHEMA/DEFAULT_ROLE
Processing object type DATABASE_EXPORT/SCHEMA/TABLESPACE_QUOTA
Processing object type DATABASE_EXPORT/SCHEMA/PROCACT_SCHEMA
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE
Table "HOGE"."HOGE" exists and has been truncated.
Data will be loaded but all dependent metadata will be skipped due to table_exists_action of truncate
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA
. . imported "HOGE"."HOGE" 5.046 KB 1 rows
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type DATABASE_EXPORT/STATISTICS/MARKER
Job "BILL"."SYS_IMPORT_SCHEMA_01" completed with 1 error(s) at Fri Jul 24 18:01:30 2020 elapsed 0 00:00:05

PL/SQL procedure successfully completed.

BILL> select count(1) from hoge.hoge;

COUNT(1)
----------
1

スクリプトの例は以下のとおり
DBMS_DATAPUMP.METADATA_FILTER()プロシージャでデータを示すオブジェクトパス(DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA)を除外しているところがポイント

BILL> !cat imp_schema_metadataonly.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(200);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
cSchemaName CONSTANT VARCHAR2(30) := UPPER('&3');
cExcludePath CONSTANT VARCHAR2(128) := 'DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA';
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'IMPORT'
,job_mode => 'SCHEMA'
,job_name => NULL
,remote_link => NULL
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'METADATA_FILTER - schema name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'SCHEMA_LIST'
,value => '''' || cSchemaName || ''''
);

v4Debug := 'EXCLUDE_PATH='||cExcludePath;
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'EXCLUDE_PATH_LIST'
,value => '''' || cExcludePath || ''''
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
SET SERVEROUTPUT OFF
SET VERIFY ON

DBMS_DATAPUMP.SET_PARAMETER()でTABLE_EXISTS_ACTION (表が存在する場合)でTRUNCATE(既存データのトランケート後データをインポート)する支持をしています。

BILL> !cat imp_schema.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(200);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
cSchemaName CONSTANT VARCHAR2(30) := UPPER('&3');
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'IMPORT'
,job_mode => 'SCHEMA'
,remote_link => NULL
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'METADATA_FILTER - schema name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'SCHEMA_LIST'
,value => '''' || cSchemaName || ''''
);

v4Debug := 'TABLE_EXISTS_ACTION=TRUNCATE';
DBMS_DATAPUMP.SET_PARAMETER (
handle => vDataPumpJobHandle
,name => 'TABLE_EXISTS_ACTION'
,value => 'TRUNCATE'
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
SET SERVEROUTPUT OFF
SET VERIFY ON


今回は、上記2つのスクリプトに分けましたが、1つにまとめるように作り変えればより便利できますよ! それはみなさんへの宿題w 

ということで今回のネタはおしまい :)




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...


RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ
RDS Oracle 雑多なメモ#16 - 再び:) / FAQ
RDS Oracle 雑多なメモ#17/ FAQ
RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp
RDS Oracle 雑多なメモ#19 FAQ / DBMS_DATAPUMPパッケージ de ジョブの停止
RDS Oracle 雑多なメモ#20 / DBMS_DATAPUMPパッケージ Schema mode de expdp/impdpの準備 SQL_FILEモードでDDL抜き出し

| | | コメント (0)

2020年7月25日 (土)

RDS Oracle 雑多なメモ#20 / DBMS_DATAPUMPパッケージ Schema mode de expdp/impdpの準備 SQL_FILEモードでDDL抜き出し

RDS Oracle 雑多なメモ#20 / DBMS_DATAPUMPパッケージ Schema mode de expdp/impdpの準備


Previously, Mac De Oracle...

RDS Oracle 雑多なメモ#19 FAQ / DBMS_DATAPUMPパッケージ de ジョブの停止

でした。

今回は、RDS Oracleへスキーマ単位でインポートを行う準備に必要などを。

実は、RDS Oracleでは、フルインポートはしないようにと記載されています。ということは、つまりスキーマモードでのインポートと、それ以下の単位でのインポートのみを行うこと、となっています。ここがポイント

詳細は以下マニュアルを参照ください。
Amazon RDS での Oracle へのデータのインポート


フルモードでインポートできない場合の注意点として、スキーマレベルでインポートする際に必要となる可能性のあるオブジェクトは事前に何がしかの方法で作成しておく必要があるということを意味します。
代表的なオブジェクトは以下、

  • ユーザーのデフォルトTABLESPACE(USERS表をそのまま利用しているケースは希だと思います)
  • ユーザーのPROFILE(パスワード有効期限管理などで利用しているケースは多いかも)
  • ROLE(直接付与権限以外はロールを付与して権限を管理することは多いはずです)
  • DIRECTORY(Oracle側で事前に作成しているディレクトリ以外のディレクトリへのアクセスが必要な場合にはディレクトリオブジェクトも事前に作成しておく必要があります)

さらに、スキーマ間で依存している場合、特に、他のスキーマのオブジェクト権限を付与する必要がある場合は、スキーマをインポートする順番にも気を遣う必要も出てきます。
インポート時にエラーが出ないものもありますが。。それは今回のテーマではないのでこの辺でw
(依存しているオブジェクトは事前にALL/DBA_DEPENDENCIESビューを利用して調査しておくとよいでしょうね)



では、DDLを取得する前に、ネタを仕込んでおきましょう。

表領域 fizzbuzz をデフォルト表領域とし、プロファイル fizzbuzz が設定されたユーザー hoge を作成します。
また、connectロールとresourceロールを付与された  fizzbuzz ロールが hoge に付与されているとします。


表領域の追加

BILL> create tablespace fizzbuzz datafile size 100m autoextend on maxsize 1g;

Tablespace created.

プロファイルの追加

BILL> create profile fizzbuzz limit idle_time unlimited;

Profile created.

ロールの追加

BILL> create role fizzbuzz;

Role created.

BILL> grant connect to fizzbuzz;

Grant succeeded.

BILL> grant resource to fizzbuzz;

Grant succeeded.


ユーザーの作成(デフォルト表領域とプロファイルの指定)

BILL> create user hoge identified by xxxxxxxxxxxxxxxx
2 default tablespace fizzbuzz
3 temporary tablespace temp
4 quota unlimited on fizzbuzz
5 profile fizzbuzz;

User created.


ロールの付与

BILL> grant fizzbuzz to hoge;

Grant succeeded.


上記のようなユーザーだとするとスキーマ単位のインポート前に表領域の作成、プロファイルの作成、ロールの作成が必要になりますよね。
(スキーマレベルのインポートでは依存オブジェクトが自動的にインポートされるわけでは無いので)

また、元のデータベースでOracle Managed Fileを利用した表領域では無い場合にはOracle Managed Fileを利用した表領域として再作成してあげる必要あります。(ここも重要。元のデータベースもOracle Managed fileを利用していればそのまま利用できます)

ということで、DBMS_METADATA.GET_DDL()または、フルエクスポートしたData Pumpのダンプファイルファイルから上記に該当するDDLを抜き出し、事前にオブジェクトを作成しておく必要がありますよね。
(DDLソースコード管理されているのであれば、それらを再利用することも可能だとは思いますが、世の中広いのでそんな管理されていないところも多いでしょうし。DDLを取り出す複数の方法は覚えてて損は無いですよ :)


今回は、フルエクスポートダンプファイルがあるので、そこからData Pump APIを利用して取り出してみようと思います。(DBMS_METADATA.GET_DDL()の方が楽だとは思います。個人的にはw)

フルダンプからDDLを取り出す際でも、重要なのがオブジェクトパス(このパスの階層を意識していないと思わぬ失敗をするのは昔のエントリの通り)ですが、今回は比較的シンプルなので確認してみましょう。


事前にエクスポートのログから以下のパスを探し出しておきます。
(今回の例では、表領域とプロファイル、それにロールへのオブジェクトパスです。以下がそのオブジェクトパス)

Processing object type DATABASE_EXPORT/TABLESPACE
Processing object type DATABASE_EXPORT/PROFILE
Processing object type DATABASE_EXPORT/ROLE

余談ですが、同じ階層オブジェクトパスにスキーマオブジェクトのパス(DATABASE_EXPORT/SCHEMA)が存在しています。同じ階層なので、スキーマレベルのインポートではTABLESPACE/ROLE/PROFILEはインポートされることはありません。(この階層構造を理解できれば楽)
DATABASE_EXPORT/SCHEMA以下がインポートの対象になるので、メタデータフィルターでEXCLUDE または INCLUDEするか制御できることになります。TABLESPACE,PROFILEやROLEはSCHEMAと同レベルなのでそもそも対象外となりフィルタすることはできません。

Processing object type DATABASE_EXPORT/SCHEMA/

理屈がわかれば癖の強いDATAPUMPなんてw 

では、DDLを取り出してみましょう。

前提のオブジェクトを含むフルエクスポートダンプファイル fullexp.dmp は事前に取得済みであるとします。

BILL> @extract_ddl data_pump_dir fullexp DATABASE_EXPORT/TABLESPACE'',''DATABASE_EXPORT/PROFILE'',''DATABASE_EXPORT/ROLE
Master table "BILL"."SYS_SQL_FILE_FULL_17" successfully loaded/unloaded
Starting "BILL"."SYS_SQL_FILE_FULL_17":
Processing object type DATABASE_EXPORT/TABLESPACE
Processing object type DATABASE_EXPORT/PROFILE
Processing object type DATABASE_EXPORT/ROLE
Job "BILL"."SYS_SQL_FILE_FULL_17" successfully completed at Sun Jul 19 19:12:00 2020 elapsed 0 00:00:02

PL/SQL procedure successfully completed.

取り出したDDLを確認します

BILL> @cat_file data_pump_dir fullexp.txt

TEXT
-----------------------------------------------------------------------------

...中略...

-- new object type path: DATABASE_EXPORT/TABLESPACE
...中略...
CREATE BIGFILE TABLESPACE "FIZZBUZZ" DATAFILE
SIZE 104857600
AUTOEXTEND ON NEXT 104857600 MAXSIZE 1073741824
LOGGING ONLINE PERMANENT BLOCKSIZE 8192
EXTENT MANAGEMENT LOCAL AUTOALLOCATE DEFAULT
NOCOMPRESS SEGMENT SPACE MANAGEMENT AUTO;
-- new object type path: DATABASE_EXPORT/PROFILE
...中略...
CREATE PROFILE "FIZZBUZZ"
LIMIT
COMPOSITE_LIMIT DEFAULT
SESSIONS_PER_USER DEFAULT
CPU_PER_SESSION DEFAULT
CPU_PER_CALL DEFAULT
LOGICAL_READS_PER_SESSION DEFAULT
LOGICAL_READS_PER_CALL DEFAULT
IDLE_TIME UNLIMITED
CONNECT_TIME DEFAULT
PRIVATE_SGA DEFAULT
FAILED_LOGIN_ATTEMPTS DEFAULT
PASSWORD_LIFE_TIME DEFAULT
PASSWORD_REUSE_TIME DEFAULT
PASSWORD_REUSE_MAX DEFAULT
PASSWORD_VERIFY_FUNCTION DEFAULT
PASSWORD_LOCK_TIME DEFAULT
PASSWORD_GRACE_TIME DEFAULT
INACTIVE_ACCOUNT_TIME DEFAULT ;
-- new object type path: DATABASE_EXPORT/ROLE
...中略...
CREATE ROLE "FIZZBUZZ";
...中略...


スキーマ hogeインポート前に作成が必要なオブジェクトのDDLを取得できました。


これでスキーマレベルのインポート準備完了。
つづく。

参考)
DDLをダンプファイルから取り出すスクリプト例は以下の通り。DBMS_DATAPUMP.OPEN()関数でoperationに’SQL_FILE'を渡しているところとDBMS_DATAPUMP.METADATA_FILTER()プロシージャで取り出すDDLのオブジェクトパスリストを渡している箇所がポイントです
PL/SQLパッケージおよびタイプ・リファレンス - 47 DBMS_DATAPUMP

BILL> !cat extract_ddl.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(50);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
cDdlFileName CONSTANT VARCHAR2(64) := '&2'||'.txt';
cPathList CONSTANT VARCHAR2(4000) := UPPER('&3');
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'SQL_FILE'
,job_mode => 'FULL'
,remote_link => NULL
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'ADD_FILE - Ddlfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDdlFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_SQL_FILE
);

v4Debug := 'METADATA_FILTER - schema name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'INCLUDE_PATH_LIST'
,value => '''' || cPathList || ''''
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
SET SERVEROUTPUT OFF
SET VERIFY ON




やっと、いろいろ落ち着いたかと思ったら次から次に似たような事案が落ちてくる今日この頃w。
ということで、しばらくは機嫌悪いかも、ガルーーーーーっw



Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...


RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ
RDS Oracle 雑多なメモ#16 - 再び:) / FAQ
RDS Oracle 雑多なメモ#17/ FAQ
RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp
RDS Oracle 雑多なメモ#19 FAQ / DBMS_DATAPUMPパッケージ de ジョブの停止

| | | コメント (0)

2020年7月20日 (月)

RDS Oracle 雑多なメモ#19 FAQ / DBMS_DATAPUMPパッケージ de ジョブの停止

Previously, Mac De Oracle...

RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp

DBMS_DATAPUMPパッケージを利用した Full Export スクリプトと、ディレクトリ名は大文字で渡すことというマニュアルに記載されていない、発生するとしばらく道に迷ってしまいそうなエラーが返るというお話でした。

今日は、その続き、Data PumpジョブはコマンドラインであってもCTRL+Cでは停止することはできないことはみなさんご存知の通りだとは思います。

正しい停止手順は以下の通り

DBA_DATAPUMP_JOBSで停止したいDATAPUMPジョブ名とジョブオーナー名を確認する

4.199 DBA_DATAPUMP_JOBS
https://docs.oracle.com/cd/F19136_01/refrn/DBA_DATAPUMP_JOBS.html#GUID-141B1FA2-9DE4-4EAF-8270-630E68431DDA


expdp/impdpのattachコマンド、または、DBMS_DATAPUMP.ATTACH()ファンクションで該当ジョブへattachする

3.4.4 ATTACH (expdp/impdpとも同じ対話コマンド)
https://docs.oracle.com/cd/F19136_01/sutil/datapump-import-utility.html#GUID-ADF15D47-FD19-4794-A5C4-685740AA04F9

47.5.2 ATTACHファンクション
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_DATAPUMP.html#GUID-E073EA12-363D-4A6B-9596-1E1D40EA747C


expdp/impdpのkill_jobコマンド、または、DBMS_DATAPUMP.STOP_JOB()プロシージャで該当ジョブを停止する

2.5.7 KILL_JOB (expdp/impdpとも同じ対話コマンド)
https://docs.oracle.com/cd/F19136_01/sutil/oracle-data-pump-export-utility.html#GUID-9DA12603-B6FA-4631-8DFA-B75466CAF178

47.5.16 STOP_JOBプロシージャ
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_DATAPUMP.html#GUID-9CE265FA-F9C6-4816-8AE0-AD0BEF3DC3CA


コマンドラインでもAPI経由でも手順は全く同じ。利用するツールが異なるだけでなので、まずは手順を覚えることが大切なんですよね。これ。
あとは、コマンドラインでは以下のマニュアルを参照し、具体的なコマンドオプションを知る
DBMS_DATAPUMPプロシージャを利用する場合は以下のマニュアルを参照し、どのようなスクリプトになるかを知る

あとは、実践のみですよー、みなさん! :)


では、DBMS_DATAPUMPパッケージでの例を(すでにエクスポートは実行中という状況からはじめます)

BILL> @show_datapump_jobs

OWNER_NAME JOB_NAME STATE JOB_MODE OPERATION
----------------- ------------------------- ------------- ------------- -------------
BILL SYS_EXPORT_FULL_01 EXECUTING FULL EXPORT



BILL> @cancel_datapump_job SYS_EXPORT_FULL_01 BILL

PL/SQL procedure successfully completed.


BILL> @show_datapump_jobs

no rows selected

BILL> @ls_dir data_pump_dir

FILENAME TYPE FILESIZE MTIME
--------------------------- ---------- ---------- ---------
fullexp.log file 2761 18-JUL-20
datapump/ directory 4096 18-JUL-20


BILL> @cat_file data_pump_dir fullexp.log

TEXT
----------------------------------------------------------------------------------------
FLASHBACK automatically enabled to preserve database integrity.
Starting "BILL"."SYS_EXPORT_FULL_01":
Processing object type DATABASE_EXPORT/EARLY_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/PACKAGE_BODIES/PACKAGE/PACKAGE_BODY
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/FUNCTIONAL_INDEX/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type DATABASE_EXPORT/STATISTICS/MARKER
Processing object type DATABASE_EXPORT/PRE_SYSTEM_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/PRE_INSTANCE_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/TABLESPACE
Processing object type DATABASE_EXPORT/PROFILE
Processing object type DATABASE_EXPORT/SCHEMA/USER
Processing object type DATABASE_EXPORT/ROLE
Processing object type DATABASE_EXPORT/RADM_FPTM
Processing object type DATABASE_EXPORT/GRANT/SYSTEM_GRANT/PROC_SYSTEM_GRANT

...中略...

Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/TABLE
Job "BILL"."SYS_EXPORT_FULL_01" stopped due to fatal error at Sat Jul 18 10:02:49 2020 elapsed 0 00:00:50

41 rows selected.


BILL> @rm_file data_pump_dir fullexp.log
DATA_PUMP_DIR/fullexp.log removed.

PL/SQL procedure successfully completed.

BILL> @ls_dir data_pump_dir

FILENAME TYPE FILESIZE MTIME
--------------------------- ---------- ---------- ---------
datapump/ directory 4096 18-JUL-20

停止できましたー


今回利用したDATAPUMPジョブ停止スクリプトの例は以下のとおり。

BILL> !cat show_datapump_jobs.sql

col owner_name for a30
col job_name for a30
col state for a30
col job_mode for a30
col operation for a30
SELECT
owner_name
, job_name
, state
, job_mode
, operation
FROM
DBA_DATAPUMP_JOBS
ORDER BY
owner_name
, job_name
, state
;


BILL> !cat cancel_datapump_job.sql

DECLARE
hdl NUMBER;
BEGIN
hdl := DBMS_DATAPUMP.ATTACH(
job_name => UPPER('&1')
,job_owner => UPPER('&2')
);

DBMS_DATAPUMP.STOP_JOB(
handle => hdl
, immediate => 1
, keep_master => 0
);
END;
/


ls_dir.sql, rm_file.sql そして、cat_file.sqlの例は過去のエントリを参照してみてください。

ls_dir.sql
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-3-fa.html

rm_file.sql
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-7-fa.html

cat_file.sql
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-4-fa.html

では、次回へつづく。




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...


RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ
RDS Oracle 雑多なメモ#16 - 再び:) / FAQ
RDS Oracle 雑多なメモ#17/ FAQ
RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp

| | | コメント (0)

2020年7月19日 (日)

RDS Oracle 雑多なメモ#18 / DBMS_DATAPUMPパッケージ de expdp/impdp

さて、COVID-19の感染者も再び増加傾向にある今日この頃ですが、みなさま、お変わりありませんか?

半年ぶり以上ぶりのエントリです。(2020年のOracle ACEのKPIカウントもスタートしたのでやっと再始動)
再始動第一段目は、癖者の極み、Data Pumpネタで。

何年か前にも癖者だというエントリは書いたことがありますが、今回はコマンドラインではなく、DBMS_DATAPUMPパッケージ。そう、APIが主人公です

Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...) https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2017/04/data-pumpw6---s.html

そして最近では、

DS Oracle 雑多なメモ#1 / FAQ 〜 https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-1-fa.html

から

RDS Oracle 雑多なメモ#17/ FAQ https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2019/02/rds-oracle-17-f.html


何度かData Pumpネタを取り上げてきました、今回は RDS Oracleでは需要が多そうな
DBMS_DATAPUMPパッケージからAPIを利用してexpdp/impdp相当の操作に特化していくつご紹介しておきます。

普段はコマンドラインだけでした操作したことないData Pumpですが、いざDBMS_DATAPUMPパッケージしか使えないという状況になって、涙目状態は辛いですよね。
そんなことにならないように普段から慣れておくとかスクリプト集を用意しておくと良いと思います。

コマンドラインでも癖もとだよねぇ〜というネタのとおりDBMS_DATAPUMPパッケージでも一癖あるのはおなじですw
とはいえ、コマンドラインである程度ポイントを抑えていればある程度は感でいけますよ。

では、さっそく、DBMS_DATAPUMPパッケージの準備運動として、Data Pump Full exportを試してみましょう。
以前も同類のスクリプトは公開済みなので、過去のエントリを読んだことがあるなら簡単なはず:)

環境 Oracle Client 19c (SQL*Plusパッケージが含まれているパッケージ) https://www.oracle.com/jp/database/technologies/instant-client.html

AWS RDS Oracle Database 19c https://aws.amazon.com/jp/console/

 

実行例)


BILL> @expdp_full data_pump_dir fullexp
FLASHBACK automatically enabled to preserve database integrity.
Starting "BILL"."SYS_EXPORT_FULL_01":
Processing object type DATABASE_EXPORT/EARLY_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/TABLE_DATA
Processing object type DATABASE_EXPORT/NORMAL_OPTIONS/VIEWS_AS_TABLES/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TABLE_DATA
Processing object type DATABASE_EXPORT/SCHEMA/PACKAGE_BODIES/PACKAGE/PACKAGE_BODY
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/STATISTICS/FUNCTIONAL_INDEX/INDEX_STATISTICS
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type DATABASE_EXPORT/STATISTICS/MARKER
Processing object type DATABASE_EXPORT/PRE_SYSTEM_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/PRE_INSTANCE_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/TABLESPACE
Processing object type DATABASE_EXPORT/PROFILE
Processing object type DATABASE_EXPORT/SCHEMA/USER
Processing object type DATABASE_EXPORT/ROLE
Processing object type DATABASE_EXPORT/RADM_FPTM
Processing object type DATABASE_EXPORT/GRANT/SYSTEM_GRANT/PROC_SYSTEM_GRANT

...中略...

Processing object type DATABASE_EXPORT/SCHEMA/VIEW/VIEW
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/INDEX
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/INDEX/FUNCTIONAL_INDEX/INDEX
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/CONSTRAINT/CONSTRAINT
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/CONSTRAINT/REF_CONSTRAINT
Processing object type DATABASE_EXPORT/SCHEMA/TABLE/TRIGGER
Processing object type DATABASE_EXPORT/SCHEMA/EVENT/TRIGGER
Processing object type DATABASE_EXPORT/FINAL_POST_INSTANCE_IMPCALLOUT/MARKER
Processing object type DATABASE_EXPORT/SCHEMA/POST_SCHEMA/PROCACT_SCHEMA
Processing object type DATABASE_EXPORT/AUDIT_UNIFIED/AUDIT_POLICY_ENABLE
Processing object type DATABASE_EXPORT/POST_SYSTEM_IMPCALLOUT/MARKER
. . exported "SYS"."KU$_USER_MAPPING_VIEW" 5.921 KB 28 rows

...中略...

. . exported "SCOTT"."TAB31" 541.6 MB 2000000 rows
. . exported "SCOTT"."TAB311" 521.6 MB 2000000 rows
. . exported "SCOTT"."TAB3" 267.9 MB 1000000 rows

...中略...

. . exported "HOGE"."HOGE" 5.093 KB 1 rows

...中略...

Master table "BILL"."SYS_EXPORT_FULL_01" successfully loaded/unloaded
******************************************************************************
Dump file set for BILL.SYS_EXPORT_FULL_01 is:
/rdsdbdata/datapump/fullexp.dmp
Job "BILL"."SYS_EXPORT_FULL_01" successfully completed at Sat Jul 18 10:25:02 2020 elapsed 0 00:04:25

PL/SQL procedure successfully completed.

Elapsed: 00:04:27.32
BILL>
BILL> @ls_dir data_pump_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- ---------
datapump/ directory 4096 18-JUL-20
fullexp.log file 9071 18-JUL-20
fullexp.dmp file 1461907456 18-JUL-20

Elapsed: 00:00:00.15

なお、上記で利用している ls_dir.sql は以下エントリを参照のこと。 
RDS Oracle 雑多なメモ#3 / FAQ https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-3-fa.html

RDS Oracle 雑多なメモ#9 / FAQでも紹介しているスクリプトを多少変更してあります。
Full ExportなのでDBMS_DATAPUMP.OPEN()のjob_modeパラメータが'FULL'になっている点と、DBMS_DATAPUMP.METADATA_FILTER()でEXCLUDEやINCLUDEしていない。つまり、Full Exportになっています。
https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2018/09/rds-oracle-9-fa.html

参考)PL/SQLパッケージおよびタイプ・リファレンス
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_DATAPUMP.html#GUID-AEA7ED80-DB4A-4A70-B199-592287206348

なお、マニュアルには書かれてないのですが、ディレクトリ名は、"大文字" にするのがポイントです。Case Sensitiveで大文字しか認識しません。スクリプトを注意深く見ていただけると、UPPER()を使っている部分(赤字部分)があることに気づくとおもいます。もし、小文字で渡してしまうとDBMS_DATAPUMPパッケージの癖の強いエラーメッセージを受け取りことになり、意味わからなーーーーい。としばらくは迷子になってしまうことでしょう。後半で、UPPER()なしで小文字で渡してしまった場合にはどのようなエラーが返されるのかお見せしたいと思います。

BILL> !cat expdp_full.sql

SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(50);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(128) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(128) := '&2'||'.log';
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'EXPORT'
,job_mode => 'FULL'
,remote_link => NULL
,job_name => NULL
,version => 'LATEST'
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory(
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory(
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
SET SERVEROUTPUT OFF
SET VERIFY ON

 

では、少しだけ前振りしていた、もしも、ディレクトリ名を小文字で渡してしまったら....以下のようなエラーが返されます。

なかなかの癖者なエラーメッセージですよね。このエラーですぐに問題点に気づけたら達人に域ですw (癖者な人よりは扱いやすいとは思いますがw ただのAPIなので)

BILL> @expdp_full data_pump_dir fullexp2
old 3: cDirectory CONSTANT VARCHAR2(30) := '&1';
new 3: cDirectory CONSTANT VARCHAR2(30) := 'data_pump_dir';
old 4: cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
new 4: cDumpFileName CONSTANT VARCHAR2(64) := 'fullexp2'||'.dmp';
old 5: cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
new 5: cLogFileName CONSTANT VARCHAR2(64) := 'fullexp2'||'.log';
ORA-39001: invalid argument value
ADD_FILE - dumpfile
DECLARE
*
ERROR at line 1:
ORA-39001: invalid argument value
ORA-06512: at line 86
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 79
ORA-06512: at "SYS.DBMS_DATAPUMP", line 4929
ORA-06512: at "SYS.DBMS_DATAPUMP", line 5180
ORA-06512: at line 26

では、次回へつづく。


Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミングData Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行するData Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...

 

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ
RDS Oracle 雑多なメモ#16 - 再び:) / FAQ
RDS Oracle 雑多なメモ#17/ FAQ

 

| | | コメント (0)

2020年2月23日 (日)

FAQ / SCOTTスキーマにあるEMP表とかどうすれば作れるの?

SCOTTスキーマをアンロックしたはいいけど、サンプル表がないーーーいなんてときは、utlsampl.sql を実行すればいいよ

SCOTT@orcl> select table_name from user_tables;

no rows selected

SCOTT@orcl> !
SCOTT@orcl> @?/rdbms/admin/utlsampl
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version0.0.0
[oracle@localhost ~]$ sqlplus scott@orcl

Enter password:
Last Successful login time: Sun Feb 23 2020 06:24:56 -05:00

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Feb 23 06:24:56 2020
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle. All rights reserved.

Last Successful login time: Sun Feb 23 2020 06:24:46 -05:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SCOTT@orcl> select table_name from user_tables;

TABLE_NAME
------------------------------
DEPT
EMP
BONUS
SALGRADE

SCOTT@orcl> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10

14 rows selected.

--
最近、英会話のレベルチェックうけて、1年半振りにレベルアップ:)

| | | コメント (0)

2020年1月20日 (月)

W3C Translations of Current W3C Technical Reports

ブックマーク代わりのメモ

Translations of Current W3C Technical Reports


not already a completed or ongoing translation for the technical report in the language.

| | | コメント (0)

2020年1月 6日 (月)

How to fix when can't boot VM - VirtualBox error : Trying to open a VM config ".... .vbox" which has the same UUID as an existing virtual machine / FAQ

なにがどうしてなのか、まったくわかりませんが、VirtualBoXのVMが突然アクセスできなくなり以下のような状況になった。
理由は不明ですが、似たような症状の方は意外に多いようですが、解決方法がわかりやすくまとまってるエントリーもなさそうだったので、対処した手順を備忘録として書いておきます。

先日まで問題なく使えてなのですが、なにかのはずみでこうなっちゃうこともあるらしい。まさにその状態だったのがこれ。
Vbox000

どのようなエラーなのかも不明だったのですが、.vboxファイルと.vmdkそして、Snapshotsの場所を確認...

$ cd /Volumes/Macintosh\ HD/VirtualBoxDisks/test 
$ ll
-rw------- 1 hoge foobar 21147287552 1 4 18:26 test-disk1.vmdk
-rw-------@ 1 hoge foobar 16163 11 14 2017 test.vbox
-rw------- 1 hoge foobar 16163 11 14 2017 test.vbox-prev
drwx------ 6 hoge foobar 192 11 14 2017 Logs
drwx------ 4 hoge foobar 128 11 11 2017 Snapshots

.vboxを特定、直接起動してやっとエラーの詳細までたどり着いた:)
上記の例では、test.vboxをダブルクリックして起動します。(Oracle VM VirtualBoxマネージャだと冒頭の画面のままうんともすんとも言わないので、直接起動してエラー詳細ダイアログまでたどり着けました。logからはそれっぽいのは見つからず。)

以下エラーダイアログの".vbox' which has the same UUID as an existing virtual machine."から判断すると、test.vbox で起動するVMに割り当てられているUUIDが重複してるということらしい。ん〜、たしかに、VMイメージインポートはしたがそれぐらいなんだよなー以前と違うのは。
ではあるのだが、それ以上深追いするより、起動できなくなったVMを起動できるように回復させる方が重要なので、解決策を探ると。。。。
Vbox001

VBoxManage internalcommands sethduuidで、重複しているとされるVMのUUIDを新たに取得。
ただ、前述のコマンドは vdi やvmdkなどの仮想ディスクイメージファイルのUUIDを設定するもの。
VMのUUIDを再設定するためのコマンドは現時点では提供されていない(参考 VBoxManage internalcommands uage参照のこと)
では、どうやって、VMのUUIDを取得するかというと、実は苦肉の策っぽいのですが、

VBoxManage internalcommands sethduuidで仮想ディスクイメージファイルのUUIDを2度設定する VBoxManage internalcommands sethduuidで仮想ディスクイメージファイルのUUIDを2度設定する

大切なので二度書きました。

一度目の実行で得たUUIDをVM向けに利用し、二度目の実行で、仮想ディスクイメージファイルのHD向けUUIDを設定するのがコツとのこと。

問題のVMは仮想ディスクが1つ、スナップショットが2つあります。VBoxManage internalcommands sethduuid でUUIDを二回変更する対象は、 test-disk1.vmdkです。

test-disk1.vmdkのUUIDをVBoxManage internalcommands sethduuidでセットします。ただ、ここで設定したUUIDはVMのUUID重複の解消に利用します。

$ VBoxManage internalcommands sethduuid test-disk1.vmdk
UUID changed to: 5c46b404-42e2-4389-a0cc-a451d614b039

再度、test-disk1.vmdkのUUIDをVBoxManage internalcommands sethduuidでセットします。ここで設定したUUIDはtest-disk1.vmdkのUUIDを変更するために行います。
(なお、複数の仮想ディスクイメージがあってもどれか一つを利用して行えばOKです。VMのUUIDを変更したいだけの操作なので)

$ VBoxManage internalcommands sethduuid test-disk1.vmdk
UUID changed to: 3f99c052-ab4f-4c59-ada7-50553b46a35e

2度の操作で得られた2つのUUID
以下は、VM用
5c46b404-42e2-4389-a0cc-a451d614b039

以下は、仮想ディスク test-disk1.vmdk のUUIDです。
3f99c052-ab4f-4c59-ada7-50553b46a35e

これらを元に、test.vboxをテキストエディタで編集して以下の部分を変更します。
Vbox002
Vbox003

さて、次がポイントです。このVMには既存のスナップショットが2世代存在します。(test.vboxをテキストエディタで開いた状態)
2世代のスナップショットは以下のように階層化されて管理されており、ぞれぞれのスナップショット(仮想ディスクイメージ)がその親のUUIDを Parent UUIDとして保持しています。保持しているParent UUIDを確認する方法は、参考 VBoxManage internalcommands dumphdinfo を参照のこと。
Vbox004

今回ルートとなるtest-disk1.vmdのUUIDを3f99c052-ab4f-4c59-ada7-50553b46a35eへ変更したため、直下のスナップショットのParent UUIDを3f99c052-ab4f-4c59-ada7-50553b46a35eに設定する必要があります。

$ VBoxManage internalcommands sethdparentuuid Snapshots/{d7c94038-b98d-46d2-9241-4bac71bf14b7}.vmdk 3f99c052-ab4f-4c59-ada7-50553b46a35e
UUID changed to: 3f99c052-ab4f-4c59-ada7-50553b46a35e

UUID=d7c94038-b98d-46d2-9241-4bac71bf14b7をParent UUIDとして持つのはUUID=02415676-9386-4bf2-931d-df2891ecfdffのスナップショットです。今回影響ありませんでしたが、念のためd7c94038-b98d-46d2-9241-4bac71bf14b7で設定し直しています。

$ VBoxManage internalcommands sethdparentuuid Snapshots/{02415676-9386-4bf2-931d-df2891ecfdff}.vmdk d7c94038-b98d-46d2-9241-4bac71bf14b7
UUID changed to: d7c94038-b98d-46d2-9241-4bac71bf14b7

仮想ディスクイメージ上のUUIDを設定し直した後で、text.vboxの該当箇所も合わせて変更します。それぞれのUUIDは複数の箇所で参照されているので一括して変更すると変更もれなく変更することができます。
Vbox005
Vbox006

変更した test.vboxを保存後して、test.vboxをダブルクリックするとVMが起動できるようになります。

ただし、以下のようにアクセスできない状態となった Oracle VM VirtualBoxマネージャの状態は前述の手順では改善できませんでした。
Vbox000

少々面倒なのですが、該当VMを一旦除去(物理削除はしない)Oracle VM VirtualBoxマネージャから除去して、追加し直すだけで復活させることができました。ʅ(◞‿◟)ʃ
20200104-230253
20200104-223203
20200104-223539
20200104-230054



参考 VBoxManage internalcommands dumphdinfo
$ VBoxManage internalcommands dumphdinfo test-disk1.vmdk
--- Dumping VD Disk, Images=1
Dumping VD image "test-disk1.vmdk" (Backend=VMDK)
Header: Geometry PCHS=16383/16/63 LCHS=1024/255/63 cbSector=209715200
Header: uuidCreation={3f99c052-ab4f-4c59-ada7-50553b46a35e}
Header: uuidModification={bb2f68b6-c9b0-480d-9eab-3b2eb81b9b3f}
Header: uuidParent={00000000-0000-0000-0000-000000000000}
Header: uuidParentModification={00000000-0000-0000-0000-000000000000}

参考 VBoxManage internalcommands usage

$ VBoxManage internalcommands
Oracle VM VirtualBox Command Line Management Interface Version 6.0.14
(C) 2005-2019 Oracle Corporation
All rights reserved.

Usage: VBoxManage internalcommands [command arguments]

Commands:

loadmap <vmname|uuid> <symfile> <address> [module] [subtrahend] [segment]
This will instruct DBGF to load the given map file
during initialization. (See also loadmap in the debugger.)

loadsyms <vmname|uuid> <symfile> [delta] [module] [module address]
This will instruct DBGF to load the given symbol file
during initialization.

sethduuid <filepath> [<uuid>]
Assigns a new UUID to the given image file. This way, multiple copies
of a container can be registered.

sethdparentuuid <filepath> <uuid>
Assigns a new parent UUID to the given image file.

dumphdinfo <filepath>
Prints information about the image at the given location.

listpartitions -rawdisk <diskname>
Lists all partitions on <diskname>.

createrawvmdk -filename <filename> -rawdisk <diskname>
[-partitions <list of partition numbers> [-mbr <filename>] ]
[-relative]
Creates a new VMDK image which gives access to an entire host disk (if
the parameter -partitions is not specified) or some partitions of a
host disk. If access to individual partitions is granted, then the
parameter -mbr can be used to specify an alternative MBR to be used
(the partitioning information in the MBR file is ignored).
The diskname is on Linux e.g. /dev/sda, and on Windows e.g.
\\.\PhysicalDrive0).
On Linux or FreeBSD host the parameter -relative causes a VMDK file to
be created which refers to individual partitions instead to the entire
disk.
The necessary partition numbers can be queried with
VBoxManage internalcommands listpartitions

renamevmdk -from <filename> -to <filename>
Renames an existing VMDK image, including the base file and all its extents.

converttoraw [-format <fileformat>] <filename> <outputfile>
Convert image to raw, writing to file.

converthd [-srcformat VDI|VMDK|VHD|RAW]
[-dstformat VDI|VMDK|VHD|RAW]
<inputfile> <outputfile>
converts hard disk images between formats

repairhd [-dry-run]
[-format VDI|VMDK|VHD|...]
<filename>
Tries to repair corrupted disk images

debuglog <vmname|uuid> [--enable|--disable] [--flags todo]
[--groups todo] [--destinations todo]
Controls debug logging.

passwordhash <passsword>
Generates a password hash.

gueststats <vmname|uuid> [--interval <seconds>]
Obtains and prints internal guest statistics.
Sets the update interval if specified.

WARNING: This is a development tool and shall only be used to analyse
problems. It is completely unsupported and will change in
incompatible ways without warning.

Syntax error: Command missing

| | | コメント (0)

2019年9月30日 (月)

なぜ、そこに、LONG型があるんだ / FAQ

all/dba/user_tab_columns

https://docs.oracle.com/cd/E82638_01/refrn/ALL_TAB_COLUMNS.html#GUID-F218205C-7D76-4A83-8691-BFD2AD372B63

これらのビューは、列の属性関連の情報を持つビューです。
たまに、便利なびゅーではあるのですが、これらのビューをアクセスする使うスクリプトというかPL/SQLでコード書くこともあるのですが、一箇所だけ、使いにくいところがあります。

 

どこかわかります?

下位互換のためだろうと思われるのですが、一般には推奨されていない LONG型の列 が残っています。

ご存知だとは思いますが、一般的なガイドだと、CLOBの利用が推奨されています。
下位互換のためだから仕方ないのだとは思うのですが。

LONG型といえば、とにかく制約が多くて、文字列操作を行うにもめんどくさいわけで、実際に利用したい状況になると、うううううっとなることしばしば。

LONG型

で、普段どうやって、その面倒くさいところを回避しているかといえば、CLOBに変換してしまうことがが多いです。
CLOBにしてしまえば、沢山の制約から解放されますしね :)

以下のような感じで。


SCOTT> l
1 CREATE TABLE my_dba_tab_columns
2 AS
3 SELECT
4 owner
5 ,table_name
6 ,column_name
7 ,data_type
8 ,data_type_mod
9 ,data_type_owner
10 ,data_length
11 ,data_precision
12 ,data_scale
13 ,nullable
14 ,column_id
15 ,TO_CLOB(default_length) AS default_length
16 ,num_distinct
17 ,low_value
18 ,high_value
19 ,density
20 ,num_nulls
21 ,num_buckets
22 ,last_analyzed
23 ,sample_size
24 ,character_set_name
25 ,char_col_decl_length
26 ,global_stats
27 ,user_stats
28 ,avg_col_len
29 ,char_length
30 ,char_used
31 ,v80_fmt_image
32 ,data_upgraded
33 ,histogram
34 ,default_on_null
35 ,identity_column
36 ,sensitive_column
37 ,evaluation_edition
38 ,unusable_before
39 ,unusable_beginning
40 ,collation
41 FROM
42* dba_tab_columns
SCOTT> /

Table created.

SCOTT> desc dba_tab_columns
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
TABLE_NAME NOT NULL VARCHAR2(128)
COLUMN_NAME NOT NULL VARCHAR2(128)
DATA_TYPE VARCHAR2(128)
DATA_TYPE_MOD VARCHAR2(3)
DATA_TYPE_OWNER VARCHAR2(128)
DATA_LENGTH NOT NULL NUMBER
DATA_PRECISION NUMBER
DATA_SCALE NUMBER
NULLABLE VARCHAR2(1)
COLUMN_ID NUMBER
DEFAULT_LENGTH NUMBER
DATA_DEFAULT LONG
NUM_DISTINCT NUMBER
LOW_VALUE RAW(2000)
HIGH_VALUE RAW(2000)
DENSITY NUMBER
NUM_NULLS NUMBER
NUM_BUCKETS NUMBER
LAST_ANALYZED DATE
SAMPLE_SIZE NUMBER
CHARACTER_SET_NAME VARCHAR2(44)
CHAR_COL_DECL_LENGTH NUMBER
GLOBAL_STATS VARCHAR2(3)
USER_STATS VARCHAR2(3)
AVG_COL_LEN NUMBER
CHAR_LENGTH NUMBER
CHAR_USED VARCHAR2(1)
V80_FMT_IMAGE VARCHAR2(3)
DATA_UPGRADED VARCHAR2(3)
HISTOGRAM VARCHAR2(15)
DEFAULT_ON_NULL VARCHAR2(3)
IDENTITY_COLUMN VARCHAR2(3)
SENSITIVE_COLUMN VARCHAR2(3)
EVALUATION_EDITION VARCHAR2(128)
UNUSABLE_BEFORE VARCHAR2(128)
UNUSABLE_BEGINNING VARCHAR2(128)
COLLATION VARCHAR2(100)

SCOTT> desc my_dba_tab_columns
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
TABLE_NAME NOT NULL VARCHAR2(128)
COLUMN_NAME NOT NULL VARCHAR2(128)
DATA_TYPE VARCHAR2(128)
DATA_TYPE_MOD VARCHAR2(3)
DATA_TYPE_OWNER VARCHAR2(128)
DATA_LENGTH NOT NULL NUMBER
DATA_PRECISION NUMBER
DATA_SCALE NUMBER
NULLABLE VARCHAR2(1)
COLUMN_ID NUMBER
DEFAULT_LENGTH CLOB
NUM_DISTINCT NUMBER
LOW_VALUE RAW(2000)
HIGH_VALUE RAW(2000)
DENSITY NUMBER
NUM_NULLS NUMBER
NUM_BUCKETS NUMBER
LAST_ANALYZED DATE
SAMPLE_SIZE NUMBER
CHARACTER_SET_NAME VARCHAR2(44)
CHAR_COL_DECL_LENGTH NUMBER
GLOBAL_STATS VARCHAR2(3)
USER_STATS VARCHAR2(3)
AVG_COL_LEN NUMBER
CHAR_LENGTH NUMBER
CHAR_USED VARCHAR2(1)
V80_FMT_IMAGE VARCHAR2(3)
DATA_UPGRADED VARCHAR2(3)
HISTOGRAM VARCHAR2(15)
DEFAULT_ON_NULL VARCHAR2(3)
IDENTITY_COLUMN VARCHAR2(3)
SENSITIVE_COLUMN VARCHAR2(3)
EVALUATION_EDITION VARCHAR2(128)
UNUSABLE_BEFORE VARCHAR2(128)
UNUSABLE_BEGINNING VARCHAR2(128)
COLLATION VARCHAR2(100)

 


db tech showcase 2019もおわり、今年も残すところ 3ヶ月あまり。一年早い. そして。
来週は、開催時期を秋に変更してから2回目の多摩川花火大会。天気がよいといいのですが:)

ではまた。

| | | コメント (0)

2019年8月25日 (日)

FAQ / PL/SQL PACKAGEでパプリックスコープを持つ定数をSQL文中で利用するには...

かなーり、ご無沙汰しておりました。(本業でいっぱいいっぱいで、という言い訳はこれぐらいにしてw) 偶に聞かれることがあるので、FAQネタから。 パッケージでパブリックなスコープを持つ定数は無名PL/SQLブロックやパッケージ、プロシージャ、ファンクションでしか参照できないんですよねー 例えば、DBMS_CRYPTOパッケージでHASHファンクションを利用してSH-256を作成したいなーと思って、

39.4 DBMS_CRYPTOのアルゴリズム
https://docs.oracle.com/cd/F19136_01/arpls/DBMS_CRYPTO.html#GUID-CE3CF17D-E781-47CB-AEE7-19A9B2BCD3EC
DBMS_CRYPTO.HASH()は関数なのでSQL文から呼びたーい、と以下のような使い方をすると...

SQL> SELECT DBMS_CRYPTO.HASH(TO_CLOB('hoge'), DBMS_CRYPTO.HASH_SH2569) AS "SH-256" FROM dual;
SELECT DBMS_CRYPTO.HASH(TO_CLOB('hoge'), DBMS_CRYPTO.HASH_SH2569) AS "SH-256" FROM dual
*
ERROR at line 1:
ORA-00904: "DBMS_CRYPTO"."HASH_SH2569": invalid identifier


SQL>
SQL> select DBMS_CRYPTO.HASH_SH256 from dual;
select DBMS_CRYPTO.HASH_SH256 from dual
*
ERROR at line 1:
ORA-06553: PLS-221: 'HASH_SH256' is not a procedure or is undefined
見事にエラーとなるわけです。 DBMS_CRYPTO.HASH_SH256は、パッケージファンクションではないので...利用可能なのはPL/SQLでのみ。
SQL> set serveroutput on
SQL>
¥SQL>
SQL> begin
2 dbms_output.put_line('DBMS_CRYPTO.HASH_SH256 : ' || DBMS_CRYPTO.HASH_SH256);
3 end;
4 /
DBMS_CRYPTO.HASH_SH256 : 4

PL/SQL procedure successfully completed.
SQL文で活用する為には、ファンクションでラップする必要があります。 以下のように。
SQL> l
1 CREATE OR REPLACE FUNCTION get_hash_sh256_type
2 RETURN NUMBER
3 AS
4 BEGIN
5 RETURN DBMS_CRYPTO.HASH_SH256;
6* END;
SQL> /

Function created.

SQL>
冒頭でエラーとなっていたSQL文をDBMS_CRYPTO.HASH_SH256を返すファンクションを使うように書き換えると、 はい、できました。
SQL> l
1 SELECT
2 DBMS_CRYPTO.HASH(
3 TO_CLOB('hoge')
4 , get_hash_sh256_type()
5 ) AS "SH-256"
6 FROM
7* dual
SQL> /

SH-256
--------------------------------------------------------------------------------
ECB666D778725EC97307044D642BF4D160AABB76F56C0069C71EA25B1E926825

SQL>


露店の焼きそばと焼き鳥を食べつつ、晩夏の夏祭りと、涼しい朝晩の気温で熟睡可能な山形より。 では、では。

| | | コメント (0)

2019年3月24日 (日)

Oracle VM VirtualBox 6.0 でも治ってなかった、ドラッグするとDesktopが取り残される問題 / FAQ

Oracle VM VirtualBox 5.x台からあった問題、6.0でも修正されていないですね。
ただ、解決方法はあって、一旦、Dockにしまって再度取り出すと症状は治まるんですよね。これw

以下、タイトルバーをクリックしてドラッグするとなぜかデスクトップだけが取り残されてのっぺらぼう状態になってしまったスクリーンショット

20190324-152727

| | | コメント (0)

2019年3月22日 (金)

ORA_HASH()を使ってリストパーティションにハッシュパーティションのような均一配分を

ハッシュパーティションってリストパーティションみたいにパーティション狙い撃ちできて、かつ、ハッシュパーティションみたいに、データをパーティション間で均一化できないのかなぁ
というずいぶん昔の話を思い出して、そう言えば書いてないかもしれない。いつの話だよってぐらい昔の話だけどw
どうやったかというと、
ハッシュキーにふさわしい値をもつ列を決める(一意キーとか主キー列が理想、カーディナリティの低い、分布に偏りのあるデータを持つ列は使わない)

で、ハッシュキーが決まったら、話は早くて、ORA_HASH()関数で取得できるハッシュ値を利用したリストパーティションを作成するだけ。
ゴニョゴニュ言わなくても、SQLとPL/SQLのコードを見ていただければ、理解していただけるかと。

ORCL@SCOTT> l
1 CREATE TABLE list_p_tab
2 (
3 id_code VARCHAR2(10) NOT NULL
4 , foo VARCHAR2(30)
5 , id_code_hash_value NUMBER(2) NOT NULL
6 )
7 PARTITION BY LIST (id_code_hash_value)
8 (
9 PARTITION list_p_tab_p1 VALUES(0)
10 ,PARTITION list_p_tab_p2 VALUES(1)
11 ,PARTITION list_p_tab_p3 VALUES(2)
12 ,PARTITION list_p_tab_p4 VALUES(3)
13* )
ORCL@SCOTT> /

Table created.

ORCL@SCOTT> ALTER TABLE list_p_tab ADD CONSTRAINT gpk_list_p_tab PRIMARY KEY(id_code) USING INDEX GLOBAL;

Table altered.

ORCL@SCOTT> l
1 DECLARE
2 TYPE id_code_t IS TABLE OF list_p_tab.id_code%TYPE INDEX BY PLS_INTEGER;
3 TYPE foo_t IS TABLE OF list_p_tab.foo%TYPE INDEX BY PLS_INTEGER;
4 id_codes id_code_t;
5 foos foo_t;
6 k PLS_INTEGER := 1;
7 BEGIN
8 FOR i IN 1..400000 LOOP
9 id_codes(k) := TO_CHAR(i,'fm0000000009');
10 foos(k) := i;
11 k := k + 1;
12 IF k > 1000 THEN
13 FORALL j in 1..k-1
14 INSERT INTO list_p_tab VALUES(id_codes(j), foos(j), ORA_HASH(id_codes(j),3));
15 COMMIT;
16 k := 1;
17 END IF;
18 END LOOP;
19* END;
ORCL@SCOTT> /

PL/SQL procedure successfully completed.

Elapsed: 00:00:12.28

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'LIST_P_TAB',granularity=>'ALL',cascade=>true,no_invalidate=>false,degree=>4);

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.51

ORCL@SCOTT> r
1 select
2 table_name
3 ,partition_name
4 ,num_rows
5 from
6 user_tab_partitions
7 where
8 table_name = 'LIST_P_TAB'
9 order by
10* 1,2

TABLE_NAME PARTITION_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
LIST_P_TAB LIST_P_TAB_P1 99901
LIST_P_TAB LIST_P_TAB_P2 100194
LIST_P_TAB LIST_P_TAB_P3 100056
LIST_P_TAB LIST_P_TAB_P4 99849

ORCL@SCOTT> select id_code_hash_value,count(1) from list_p_tab group by id_code_hash_value order by 1;

ID_CODE_HASH_VALUE COUNT(1)
------------------ ----------
0 99901
1 100194
2 100056
3 99849

Elapsed: 00:00:00.06

ORCL@SCOTT> explain plan for
2 select
3 *
4 from
5 list_p_tab
6 where
7 id_code_hash_value = 1;

Explained.

Elapsed: 00:00:00.10
ORCL@SCOTT> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2143708561

----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 2054K| 275 (1)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE| | 100K| 2054K| 275 (1)| 00:00:01 | 2 | 2 |
| 2 | TABLE ACCESS FULL | LIST_P_TAB | 100K| 2054K| 275 (1)| 00:00:01 | 2 | 2 |
----------------------------------------------------------------------------------------------------

グローバル索引を作成してあるので、パーティション関係ない検索は主キー索引経由でも可。

ORCL@SCOTT> explain plan for select * from list_p_tab where id_code = '00004000000';

Explained.

Elapsed: 00:00:00.02
ORCL@SCOTT> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------
Plan hash value: 4132161764

---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 21 | 3 (0)| 00:00:01 | | |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| LIST_P_TAB | 1 | 21 | 3 (0)| 00:00:01 | ROWID | ROWID |
|* 2 | INDEX UNIQUE SCAN | GPK_LIST_P_TAB | 1 | | 2 (0)| 00:00:01 | | |
---------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID_CODE"='00004000000')

| | | コメント (0)

2019年3月21日 (木)

ハッシュパーティションでのデータの偏り / FAQ

ぼやき漫才みたいな感じですが、Oracleに限らず、ハッシュパーティションでパーティション間のデータを均一にしたいなら、ユニークな値かそれに準ずる列を選ぶべきなわけですが、なにを間違ってしまったのか、稀ではありますが、少々残念なことになっていすることもあります。
とは言え、早めに気づけば影響も小さくて済むわけで:)


というわけで、今回はそんなおはなし。

id_code列の値はユニーク
ORCL@SCOTT> select count(*),count(distinct id_code) from org;

COUNT(*) COUNT(DISTINCTID_CODE)
---------- ----------------------
400000 400000
type列の値は、一意性がなくカーディナリティーも低い、かつ、大きな偏りがある。。
ORCL@SCOTT> select type,count(1) from org group by type;

type COUNT(1)
---------- ----------
1 60000
2 60000
9 60000
0 220000
ハッシュパーティションを選択する主な理由は、パーティションへのデータの均一分散なので、列の値がユニークな列をパーティションキーとしてパーティション化することが多いわけですが、。。
以下は、ハッシュキーに一意な値を持つ列を選択した場合の例
ORCL@SCOTT> r
1 create table hash_p_tab
2 partition by hash(id_code)
3 (
4 partition hash_p_tab_p1
5 ,partition hash_p_tab_p2
6 ,partition hash_p_tab_p3
7 ,partition hash_p_tab_p4
8 )
9 as select
10 id_code
11 ,foo
12 ,type
13 from
14* org

Table created.

ORCL@SCOTT> alter table hash_p_tab add constraint gpk_hash_p_tab primary key(id_code) using index global;

Table altered.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'hash_p_tab',cascade=>true,no_invalidate=>false,granularity=>'ALL');


ORCL@SCOTT> select table_name,partition_name,num_rows from user_tab_partitions order by 1,2;

TABLE_NAME PARTITION_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HASH_P_TAB HASH_P_TAB_P1 99901
HASH_P_TAB HASH_P_TAB_P2 100194
HASH_P_TAB HASH_P_TAB_P3 100056
HASH_P_TAB HASH_P_TAB_P4 99849


しかし、値の分布に偏りのあるカーディナリティの低い列を選んで残念なことになっているケースも稀にあったりします。

なぜ、ハッシュキーにこの列を選んだんだ! みたいな。。。

そんな時は、設計した人に聞くしかないです。何がやりたかったのかを。。。私に聞かれてもハッシュキーの選択をミスったんですよねーたぶん、としか言えないので。
ORCL@SCOTT> r
1 create table hash_p_tab_skew
2 partition by hash(type)
3 (
4 partition hash_p_tab_skew_p1
5 ,partition hash_p_tab_skew_p2
6 ,partition hash_p_tab_skew_p3
7 ,partition hash_p_tab_skew_p4
8 )
9 as select
10 id_code
11 ,foo
12 ,type
13 from
14* org

Table created.

ORCL@SCOTT> alter table hash_p_tab_skew add constraint gpk_hash_p_tab_skew primary key(id_code) using index global;

Table altered.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'hash_p_tab_skew',cascade=>true,no_invalidate=>false,granularity=>'ALL');

PL/SQL procedure successfully completed.

ORCL@SCOTT> select table_name,partition_name,num_rows from user_tab_partitions where table_name = upper('hash_p_tab_skew') order by 1,2

TABLE_NAME PARTITION_NAME NUM_ROWS
------------------------------ ------------------------------ ----------
HASH_P_TAB_SKEW HASH_P_TAB_SKEW_P1 0
HASH_P_TAB_SKEW HASH_P_TAB_SKEW_P2 280000
HASH_P_TAB_SKEW HASH_P_TAB_SKEW_P3 60000
HASH_P_TAB_SKEW HASH_P_TAB_SKEW_P4 60000
で、データを均一にパーティションに分散させることをなんとなーく、イメージしつつ、上記のようなミスをしてしまうと、パラレルクエリーなどで最もデータの多いパーティションの処理時間に引っ張られて想定より処理時間が長くなるなんてことも。。。あるわけで
例がイケてないけど(気にしないでw)、パラレルサーバーのスレーブ間でbuffer gets部分を見ていただくと偏りはわかりやすいはず。(赤字部分):)
偏りあり
orcl2@SCOTT> @sql_skew
1 select
2 /*+
3 monitor
4 parallel(4)
5 */
6 count(1)
7 from
8 hash_p_tab_skew t01
9 inner join hash_p_tab_skew2 t02
10 on
11 t01.id_code = t02.id_code
12* and t01.type = t02.type


Parallel Execution Details (DOP=4 , Servers Allocated=4)
==========================================================================================
| Name | Type | Server# | Elapsed | Cpu | Other | Buffer | Wait Events |
| | | | Time(s) | Time(s) | Waits(s) | Gets | (sample #) |
==========================================================================================
| PX Coordinator | QC | | 0.02 | 0.00 | 0.02 | 72 | |
| p000 | Set 1 | 1 | 0.18 | 0.17 | 0.01 | 1940 | |
| p001 | Set 1 | 2 | 0.05 | 0.05 | 0.00 | 448 | |
| p002 | Set 1 | 3 | 0.05 | 0.05 | 0.00 | 448 | |
| p003 | Set 1 | 4 | 0.00 | 0.00 | 0.00 | | |
==========================================================================================

偏りの悪影響のイメージはこんな感じ
20190321-165450
20190321-165622

偏りなし
orcl2@SCOTT> @sql_noskew
1 select
2 /*+
3 monitor
4 parallel(4)
5 */
6 count(1)
7 from
8 hash_p_tab t01
9 inner join hash_p_tab2 t02
10 on
11 t01.id_code = t02.id_code
12* and t01.type = t02.type


Parallel Execution Details (DOP=4 , Servers Allocated=4)
==========================================================================================
| Name | Type | Server# | Elapsed | Cpu | Other | Buffer | Wait Events |
| | | | Time(s) | Time(s) | Waits(s) | Gets | (sample #) |
==========================================================================================
| PX Coordinator | QC | | 0.01 | 0.00 | 0.00 | 96 | |
| p000 | Set 1 | 1 | 0.08 | 0.08 | | 714 | |
| p001 | Set 1 | 2 | 0.08 | 0.08 | 0.00 | 714 | |
| p002 | Set 1 | 3 | 0.08 | 0.07 | 0.01 | 714 | |
| p003 | Set 1 | 4 | 0.08 | 0.08 | | 712 | |
==========================================================================================
パーティション毎にぞれぞれ得手不得手があります。そこんとこを把握したうえで、有効活用したいものですよね。(パーティションもいろいろ進化してきて便利になってきたわけですが、その分わかりにくいところも増えてきて理解するのに大変だったり 18cのも差分は把握しといたほうがいいかw...:)

| | | コメント (0)

Join Elimination(結合の排除)と 参照整合性制約 / FAQ

偶に聞かれることがあるので、再び、Join Elimination(結合の排除)について
まずは、以下のSQL文を。
order表とcustomers表をinner joinしている単純な文ですが、重要なのは、実行計画の方!


order表とcustomers表をinner joinしているのに、order表だけ(この場合、order表の主キー索引だけのIndex Only Scanになっていますが)で、customes表を結合していせん。

理由は単純で、以下のSQL文では、customsers表の結合が不要なだけなんです。なぜかわかりますか?
以前、浅瀬でジャブジャブしていたセッション資料にヒントがあります。
order表に定義されている参照整合性制約によりcustomer_idがcustomsers表に存在していることを確認するための結合は不要と、オプティマイザーが判断した結果なんですよね。これ。
上記以外のケースでも無駄な結合を排除しようとする最適化を行うことがあります。内部的にはSQL文を書き換えてくれているわけですね。無駄に結合を行わないために。。。10053トレースをとって、 Join Elimination で grep をかけてみるとオプティマイザの気持ちが見えてきます:)
ORCL@OE> explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 , customers c
8 where
9 o.customer_id = c.customer_id
10 and order_id < 2400;

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ORDER_ID"<2400)
上記、SQL文は、参照整合性制約により、orders表に存在するcustomer_idがcustomers表に存在することが保証されているため、結合により存在確認が不要となり、Optimizerは内部的にSQL文を以下のように書き換えたということになります。賢いですよね。
ORCL@OE> r
1 explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 where
8* order_id < 2400

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 1653993310

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 184 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| ORDER_PK | 46 | 184 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ORDER_ID"<2400)

13 rows selected.


order表の参照整合性制約を確認しておきます。

ORCL@OE> r
1 select
2 table_name
3 ,owner
4 ,constraint_name
5 ,constraint_type
6 ,r_owner
7 ,r_constraint_name
8 ,status
9 ,rely
10 from
11 user_constraints
12 where
13* constraint_type='R'

TABLE_NAME OWNER CONSTRAINT_NAME C R_OWNER R_CONSTRAINT_NAME STATUS RELY
------------------------------ ------------------------------ ------------------------------ - ------------------------------ ------------------------------ -------- ----
ORDERS OE ORDERS_CUSTOMER_ID_FK R OE CUSTOMERS_PK ENABLED
INVENTORIES OE INVENTORIES_WAREHOUSES_FK R OE WAREHOUSES_PK ENABLED
INVENTORIES OE INVENTORIES_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_ORDER_ID_FK R OE ORDER_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
PRODUCT_DESCRIPTIONS OE PD_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED

ORCL@OE> r
1 select
2 table_name
3 ,column_name
4 ,constraint_name
5 from
6 user_cons_columns
7 where
8 table_name in ('ORDERS','CUSTOMERS')
9 order by
10* table_name

TABLE_NAME COLUMN_NAME CONSTRAINT_NAME
------------------------------ ------------------------------ ------------------------------
CUSTOMERS CUSTOMER_ID CUSTOMERS_PK
CUSTOMERS CUST_FIRST_NAME CUST_FNAME_NN
CUSTOMERS CUSTOMER_ID CUSTOMER_ID_MIN
CUSTOMERS CREDIT_LIMIT CUSTOMER_CREDIT_LIMIT_MAX
CUSTOMERS CUST_LAST_NAME CUST_LNAME_NN
ORDERS ORDER_ID ORDER_PK
ORDERS ORDER_TOTAL ORDER_TOTAL_MIN
ORDERS ORDER_MODE ORDER_MODE_LOV
ORDERS CUSTOMER_ID ORDER_CUSTOMER_ID_NN
ORDERS CUSTOMER_ID ORDERS_CUSTOMER_ID_FK
ORDERS ORDER_DATE ORDER_DATE_NN




Oracle SQL DeveloperでリバースエンジニアリングしたERDは以下のとおり

20190321-144842

では、最後に、参照整合性制約を無効化した場合、実行計画はどうなるか見ておきましょう。
ORCL@OE> alter table orders disable constraint orders_customer_id_fk;

Table altered.

ORCL@OE> r
1 select
2 table_name
3 ,owner
4 ,constraint_name
5 ,constraint_type
6 ,r_owner
7 ,r_constraint_name
8 ,status
9 ,rely
10 from
11 user_constraints
12 where
13* constraint_type='R'

TABLE_NAME OWNER CONSTRAINT_NAME C R_OWNER R_CONSTRAINT_NAME STATUS RELY
------------------------------ ------------------------------ ------------------------------ - ------------------------------ ------------------------------ -------- ----
ORDERS OE ORDERS_CUSTOMER_ID_FK R OE CUSTOMERS_PK DISABLED
INVENTORIES OE INVENTORIES_WAREHOUSES_FK R OE WAREHOUSES_PK ENABLED
INVENTORIES OE INVENTORIES_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_ORDER_ID_FK R OE ORDER_PK ENABLED
ORDER_ITEMS OE ORDER_ITEMS_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED
PRODUCT_DESCRIPTIONS OE PD_PRODUCT_ID_FK R OE PRODUCT_INFORMATION_PK ENABLED

あらまあ、不思議w わざとらしいw
customers表げ結合されちゃってネステッドループ結合に!
ORCL@OE> r
1 explain plan for
2 select
3 distinct
4 order_id
5 from
6 orders o
7 , customers c
8 where
9 o.customer_id = c.customer_id
10* and order_id < 2400

Explained.

ORCL@OE> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------
Plan hash value: 2552081916

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 46 | 552 | 3 (34)| 00:00:01 |
| 1 | SORT UNIQUE NOSORT | | 46 | 552 | 3 (34)| 00:00:01 |
| 2 | NESTED LOOPS SEMI | | 46 | 552 | 2 (0)| 00:00:01 |
|* 3 | TABLE ACCESS BY INDEX ROWID| ORDERS | 46 | 368 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | ORDER_PK | 46 | | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | CUSTOMERS_PK | 319 | 1276 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - filter("O"."CUSTOMER_ID">0)
4 - access("ORDER_ID"<2400)
5 - access("O"."CUSTOMER_ID"="C"."CUSTOMER_ID")

目黒方面の密林で、美登利の寿司弁当を食べるのが最近のマイブームw
ではまた。

| | | コメント (0)

2019年2月21日 (木)

Wait Events

データベース関連で待機イベントと言えば、これまでは、Oracle Database しか浮かばなかったわけですが、今は、PostgreSQL、そして、MySQL にも実装された。

待機イベントを知らずして、どうするの? でも大丈夫。 今までOracleの待機イベントに親しんできたデータベースエンジニアの活躍の場が広がるんじゃないかなぁ。。。と遠くをみている。。。

Oracle Database Wait Events

PostgreSQL Wait Events

MySQL : 25.12.15.1 Wait Event Summary Tables

| | | コメント (0) | トラックバック (0)

2019年2月12日 (火)

RDS Oracle 雑多なメモ#17/ FAQ

Oracle DB インスタンスの一般的な DBA タスクに記載されているAmazon RDS OracleとオンプレのOracleの操作方法の違は意外に多く、長年かけて体に染み付いていて、脊髄反応でタイプしてしまうとエラー、あ”〜っなんてこともしばしばw

仕方ないので、慣れるしかないわけですが、脊髄反応でオンプレのコマンドをタイプして、あ”〜っ! となったことのある個人的な Top5 を備忘録として書いておきます:)
脊髄反応でそんな権限ないよーというショックなエラーうけとる回数を少しでも減らせるようAmazon RDSパッケージのタイプ練習中の日々w (いずれ、うまく切り替えられるようになれるだろうと信じてw

Oracle DB インスタンスの一般的な DBA システムタスク
Oracle DB インスタンスの一般的な DBA データベースタスク
Oracle DB インスタンスの一般的な DBA ログタスク
Oracle DB インスタンスの一般的な DBA のその他のタスク

個人的に、つい、オンプレと同じ操作をして、エラーになってしまった Top5 w

1位. sysオブジェクトへ権限付与で grant文をタイプしてしまう。
2位. つい、alter system kill session をタイプしてしまう。
3位. オンラインログファイルを切り替えたり、追加、削除で、alter database add logfile..をタイプしたり、alter system switch logfileをタイプしてしまう。
4位. ディレクトリオブジェクトを作成しようとして、create directory...をタイプしてしまう。
5位. rmanの検証コマンドを使おうとして、生のrmanは使えなかった、と気づくw

私がつい、脊髄反応でオリジナルのコマンドをタイプして、エラー? なぬ? あ、RDSではAmazon RDS向けのパッケージ使うんだった!!と 気づく典型的な操作の数々(^^;;;;; 長年しみついた手癖で脊髄反応しちゃうのでどうしようもないのですw
みなさんはどのコマンドで、あ”! となることが多いのでしょうか?(おそらく Top.1は、私と同じ、grant関連ではないでしょうか?w 一番使う機会が多いですからね)

話は少々脱線しますが、上記操作を行うAmazon RDSパッケージでデータベースサイズが大きくなると処理時間を要するものもそこそこあります。
Amazon RDSパッケージで提供されていても内部ではOracleの対応する機能を実行しているわけで、処理時間を要するタイプの操作を行った場合、処理の進行状況を確認確認したくなることもあります。そんな時は、v$session_longops を参照するとよいのではないかと思います。全ての機能が詳細な情報をv$session_longopsに載せてくれるわけではないですが。。(オンプレのOracle Databaseを利用していたという方でも、v$session_longops を使ったことはないなんてことも少なくないような気がします)


RDS Oracleでも v$session_longops ビューは効果的に利用できる例として、datapumpやOracle DB インスタンスの一般的な DBA データベースタスクでも解説されているrmanの検証コマンドの実行時など、操作にそれなりの時間を要するタイプのものです。(datapumpについては、詳細なステータスを記録していないようなので、datapumpのlogを覗くほうが状況確認としては便利ではありますが、一応、datapumpもv$session_longopsには記録されます。この点はOracle由来なのでオンプレでも同じです。)

一例として、それなりに時間を要する処理の代表格、Amazon RDS プロシージャ rdsadmin.rdsadmin_rman_util.validate_database(Oracle DB インスタンスの一般的な DBA データベースタスク参照)を利用した関連ファイル検証の進行状況をv$session_longopsを利用してモニターリング:)

今回利用したRDS Oracleのデータファイルはぞれぞれ以下のようなサイズです。

SQL> r
1 select
2 tablespace_name
3 ,file_name
4 ,sum(bytes)/1024/1024 "MB"
5 from
6 dba_data_files
7 group by
8 tablespace_name
9 ,file_name
10 union all
11 select
12 tablespace_name
13 ,file_name
14 ,sum(bytes)/1024/1024 "MB"
15 from
16 dba_temp_files
17 group by
18 tablespace_name
19* ,file_name

TABLESPACE_NAME FILE_NAME MB
------------------------------ ------------------------------------------------------------ ----------
SYSAUX /rdsdbdata/db/ORCL_A/datafile/o1_mf_sysaux_fxpjf1nv_.dbf 498.9375
USERS /rdsdbdata/db/ORCL_A/datafile/o1_mf_users_fxpjf3d2_.dbf 100
UNDO_T1 /rdsdbdata/db/ORCL_A/datafile/o1_mf_undo_t1_fxpjf2lx_.dbf 290
RDSADMIN /rdsdbdata/db/ORCL_A/datafile/o1_mf_rdsadmin_fxpkkz9k_.dbf 7
SYSTEM /rdsdbdata/db/ORCL_A/datafile/o1_mf_system_fxpjdxws_.dbf 500
TEMP /rdsdbdata/db/ORCL_A/datafile/o1_mf_temp_fxpjf34b_.tmp 100
----------
sum 1495.9375


事前に作成しておいたスクリプトでAmazon RDS プロシージャ rdsadmin.rdsadmin_rman_util.validate_databaseの処理状況をモニタリングしています(スクリプトの例は後半参照のこと)

SQL> @show_validate_status

レコードが選択されませんでした。

SQL> /

NOW SID SERIAL# OPNAME CONTEXT SOFAR TOTALWORK % done
-------------------------------- ---------- ---------- ------------------------------ ---------- ---------- ---------- ----------
19-02-11 09:42:18.217701 +00:00 665 51783 RMAN: full datafile backup 1 123322 178680 69.02

SQL> /

NOW SID SERIAL# OPNAME CONTEXT SOFAR TOTALWORK % done
-------------------------------- ---------- ---------- ------------------------------ ---------- ---------- ---------- ----------
19-02-11 09:42:19.298741 +00:00 665 51783 RMAN: full datafile backup 1 139452 178680 78.05

SQL> /

NOW SID SERIAL# OPNAME CONTEXT SOFAR TOTALWORK % done
-------------------------------- ---------- ---------- ------------------------------ ---------- ---------- ---------- ----------
19-02-11 09:42:20.367723 +00:00 665 51783 RMAN: full datafile backup 1 156028 178680 87.32

SQL> /

NOW SID SERIAL# OPNAME CONTEXT SOFAR TOTALWORK % done
-------------------------------- ---------- ---------- ------------------------------ ---------- ---------- ---------- ----------
19-02-11 09:42:21.289310 +00:00 665 51783 RMAN: full datafile backup 1 170428 178680 95.38

SQL> /

レコードが選択されませんでした。

SQL>
SQL> !cat show_validate_status.sql
col "NOW" for a32
col opname for a30
SELECT
systimestamp AS "NOW"
,sid
,serial#
,opname
,context
,sofar
,totalwork
,round(sofar / totalwork * 100, 2) "% done"
FROM
v$session_longops
WHERE
opname LIKE 'RMAN%'
AND opname NOT LIKE '%aggregate%'
AND sofar != totalwork
AND totalwork != 0;


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ
RDS Oracle 雑多なメモ#16 - 再び:) / FAQ

| | | コメント (0) | トラックバック (0)

2019年2月11日 (月)

SQL*Plusでcsv出力できるんですよ #2 null はどうなる? / FAQ

前回は、SQL*Plusでcsvファイルをお手軽にできることを確認したので、今回はもう少し細かいところを確認しておきます。

csvファイルを作成するOracle Databaseのバージョン等は以下のとおり。

SQL> select
2 banner_full
3 from
4 v$version;

BANNER_FULL
--------------------------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production
Version 18.3.0.0.0

データベースキャラクタセットは最近では一般的なAL32UTF8

SQL> r
1 select
2 parameter
3 , value
4 from
5 nls_database_parameters
6 where
7* parameter in ('NLS_CHARACTERSET')

PARAMETER VALUE
---------------------------------------- ------------------------------
NLS_CHARACTERSET AL32UTF8

SQL>
SQL> !echo $NLS_LANG
Japanese_Japan.AL32UTF8

SQL> !echo $LANG
ja_JP.UTF-8

適当に作成した表は以下のとおり。NULLの取り込みを見ておきたかったのでnullも含めてあります。

SQL> desc test
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
DATA VARCHAR2(10)
FOO NOT NULL VARCHAR2(10)

SQL> select * from test order by id;

ID DATA FOO
---------- ---------- ----------
1 テスト note
2 平成 note
3 abcdbef note
4 あ note
5 A note
6 note

6行が選択されました。


id=6のdata列は null なのですが空白区別しにくいので可視化して確認しておきます。
注意)set null コマンドで設定した文字列は csv作成時のにも反映されるため空にリセットすることをお忘れなく。

SQL> set null [null]
SQL> select * from test order by id;

ID DATA FOO
---------- ---------- ----------
1 テスト note
2 平成 note
3 abcdbef note
4 あ note
5 A note
6 [null] note

6行が選択されました。

SQL> set null ""

csvファイルの作成。スクリプトの例は前回の記事(SQL*Plusでcsv出力できるんですよ / FAQ)参照のこと。

SQL> @makecsv test
SQL> !cat loaddata_test.csv
1,"テスト","note"
2,"平成","note"
3,"abcdbef","note"
4,"あ","note"
5,"A","note"
6,,"note"

SQL> exit
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Productionとの接続が切断されました。
discus-mother:˜ oracle$


ということで、 nullは、,, としてcsvファイルに書き出されることを確認しました。
だたし、set nullでnullを他の文字列に置き換えている場合には、置換した文字列がそのままcsvファイルへかきだされてしまうので注意が必要です。




previously on Mac De Oracle
SQL*Plusでcsv出力できるんですよ / FAQ


数日前の朝、仕事先に向かおうとしら、ちょいと熱っぽい?、頭痛もあるな! と体温を測ったら37度、インフル?
と思い仕事を休んで夕方まで様子見。。。熱は夜更けすぎに、平熱と変わっていましたw めでたしめでたし:) インフルじゃなくてよかった。
家庭内隔離解除されてほっとしているところ。。
では、また。

| | | コメント (0) | トラックバック (0)

SQL*Plusでcsv出力できるんですよ / FAQ

SQL*Plusでcsv出力する簡単な方法って、意外に知られてないようなのでメモ程度に書いておきます。
自分でもコピペネタとするためにw

SQL> select * from q order by id;

ID DATA
---------- ----------
1 テスト
2 平成
3 abcdbef
4 あ
5 A

SQL> set markup csv on
SQL> select * from q order by id;

"ID","DATA"
1,"テスト"
2,"平成"
3,"abcdbef"
4,"あ"
5,"A"

SQL> set markup csv off

set markup csv on でcsv出力を簡単に取得できます。
これがなかったころはパッケージ作ったりしてましたけど、これなら手間いらず:)

スプールしてファイルに書き出すスクリプトを作っておくと便利です。
以下のスクリプトは &1 パラメータでcsv化する表名称を渡すだけ。

SQL> !cat makecsv.sql
--
-- parameter 1 : table name
--
set feed off
set timi off
set head off
set termout off
set veri off
set markup csv on
spool loaddata_&1..csv
select * from &1 order by id;
spo off
set markup csv off
set termout on
set head on
set feed on
set veri on
undefine 1

SQL> @makecsv q
SQL>
SQL> !cat loaddata_q.csv
1,"テスト"
2,"平成"
3,"abcdbef"
4,"あ"
5,"A"

| | | コメント (0) | トラックバック (0)

2019年2月10日 (日)

18cのv$versionは列が増えてるのね / FAQ

今気づいたが、Oracle Database 18cのv$versionがいくつかの表示パターンにあわせたのか、3列になったのね。

SQL> select * from v$version;

BANNER BANNER_FULL BANNER_LEGACY CON_ID
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production 0
Version 18.3.0.0.0

| | | コメント (0) | トラックバック (0)

2019年1月 7日 (月)

PostgreSQL向け、俺よう便利メモ

testdb=> select version();
version
-----------------------------------------------------------------------------
PostgreSQL 10.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.9.3, 64-bit


以降のスクリプトの動作確認用の表定義

testdb=> \d+ hoge
Table "public.hoge"
Column | Type | Modifiers | Storage | Stats target | Description
--------+------------------------+-----------+----------+--------------+-------------
id | numeric(10,0) | not null | main | |
status | numeric(2,0) | not null | main | |
note | character varying(100) | | extended | |
Indexes:
"hoge_pkey" PRIMARY KEY, btree (id)

PL/pgSQLでPL/SQLっぽい感じの無名ブロックを書いてみた。commit/rollbackは含めふことができないってところはちょいとハマるね。Oraclerには。(なれれば問題ないって感じはするが)

testdb=> \! cat ins10000000.sql
DO $$BEGIN
FOR i IN 1..10000000 LOOP
INSERT INTO hoge
(id, status, note)
VALUES(
i, 0, LPAD('hoge',100,'x')
);
END LOOP;
END$$;
testdb=> \i ins10000000.sql

まあ、いろいろ勝手が違うので、よく使いそうなSQL文も作り置きしておかないと。
以下のSQLは、Oracle Databaseだと表と索引のセグメントサイズの確認で dba_segmentsを問い合わせるのと同じイメージな。ビューは違うけど。

testdb=> \! cat show_segment_size.sql
SELECT
objectname
, TO_CHAR(pg_relation_size(objectname::regclass), '999,999,999,999') AS bytes
FROM
(
SELECT
tablename AS objectname
FROM
pg_tables
WHERE
schemaname = 'public'
UNION
SELECT
indexname AS objectname
FROM
pg_indexes
WHERE
schemaname = 'public'
) AS objects
ORDER BY
bytes DESC
;

testdb=> \i show_segment_size.sql 
objectname | bytes
------------+------------------
hoge | 1,412,415,488
hoge_pkey | 224,632,832
(2 rows)

Oracle Databaseとは異なるアーキテクチャを採用しているPostgreSQLの特徴がよく見える部分。全体の30%ぐらいの行を削除して、dead tupleを確認してみたところ。
Inside vacuum - 第一回PostgreSQLプレ勉強会

testdb=> \! cat show_dead_tup_ratio.sql
SELECT
relname
, n_live_tup
, n_dead_tup
, CASE
WHEN n_live_tup > 0
THEN ROUND(n_dead_tup * 100 / n_live_tup, 2)
ELSE NULL
END AS ratio
FROM
pg_stat_user_tables;

testdb=> delete from hoge where id between 1 and 3000000;
DELETE 3000000

testdb=> \i show_dead_tup_ratio.sql
relname | n_live_tup | n_dead_tup | ratio
---------+------------+------------+-------
hoge | 6973030 | 3020431 | 43.00
(1 row)

最後は、Lockの状態を確認するSQL文。この部分もOracle Databaseとは異なるとこも多いので、Lockがどうなってるか見えるようなSQL文は作り置きしておくと便利。
宣言的パーティションのロックもOracleのPartitionとは異なる部分も多いので事前に確認しておくと、あとから驚くようなことがなくて安心できる、かも。

testdb=> \! cat show_locked_objects.sql
SELECT
clock_timestamp()
, pc.relname
, pl.locktype
, pl.database
, pl.relation
, pl.page
, pl.tuple
, pl.virtualtransaction
, pl.pid
, pl.mode
, pl.granted
FROM
pg_locks pl
INNER JOIN pg_class pc
ON
pl.relation = pc.oid
WHERE
pc.relname !~ '^pg_'
AND pc.relname <> 'activelocks';

testdb=> BEGIN;
BEGIN
testdb=> SELECT * FROM hoge FOR UPDATE SKIP LOCKED;
id | status | note
----+--------+------------------------------------------------------------------------------------------------------
1 | 0 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhogedkdkhoge
(1 row)


testdb=> \i show_locked_objects.sql
clock_timestamp | relname | locktype | database | relation | page | tuple | virtualtransaction | pid | mode | granted
-------------------------------+-----------+----------+----------+----------+------+-------+--------------------+-------+-----------------+---------
2019-01-07 07:45:33.742858+00 | hoge_pkey | relation | 16401 | 16405 | | | 8/11426 | 18445 | AccessShareLock | t
2019-01-07 07:45:33.742879+00 | hoge | relation | 16401 | 16402 | | | 8/11426 | 18445 | RowShareLock | t
(2 rows)

| | | コメント (0) | トラックバック (0)

2018年11月 4日 (日)

RDS Oracle 雑多なメモ#16 - 再び:) / FAQ

再び忘れがちなので、備忘録。

RDS Oracleでマスターユーザー以外で、SQL*PLusの Auto trace そして、DBMS_XPLAN.DISPLAY や DBMS_XPLAN.DISPLAY_CURSOR を使おうとすると以下のようなエラーに遭遇! 
なにも準備してないと。(explain plan for文だけは準備していなくても可能なのでが)


TEST> set autot trace exp stat
SP2-0618: Cannot find the Session Identifier. Check PLUSTRACE role is enabled
SP2-0611: Error enabling STATISTICS report

とか

...略...
TEST> select * from table(dbms_xplan.display_cursor(format=>'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
User has no SELECT privilege on V$SESSION

なんてことに、

RDS Oracle、マスターユーザーでは可能なのですが、PLUSTRACEロールも作成されていない、かつ、 plustrce.sql がない.

AWSUSER> select role from dba_roles where role = 'PLUSTRACE';

no rows selected


ということで、いちいち調べるのも面倒なFAQとなっているので、備忘録として書いておきました。

マスターユーザー以外のユーザーに alter session システム権限を付与しておきます。
該当システム権限が付与されていないと、セッションレベルで statistics_level パラメータを変更できません。
このパラメータは、dbms_xplan.display_cursor でactual planを取得する際に必要になるのですが、最悪付与されていない場合には、SQL文に以下のヒントを追加することで代替可能ではありますが、いちいちSQL文に以下のヒントを追加しなければならないので面倒。
とはいえ、alter sessionは付与したくないということもなくはなく、そんな時は以下のヒントで頑張っください。

ex.

SELECT
/*+ gather_plan_statistics */
*
FROM
hoge
WHERE
id = 1;


次に必要なのは、 v$sessionなどを含むいくつかのパフォーマンスビューへのSELECTオブジェクト権限、グローバルな一時表として定義されているplan_tableへの全オブジェクト権限です。
管理面を考えてロールを作成し関連権限をロールに付与、作成したロールを対象ユーザーに付与するようにすると便利です。


alter sessionsシステム権限を含む最低限必要なオブジェクト権限とそれを付与するロール作成スクリプトの例は以下の通り。

foobar$ cat create_dev_role.sql

-- create developer role
create role dev_role;

-- for show parameter
grant alter session to dev_role;
-- for dbms_xplan.display_cursor and auto trace
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SESSION', 'DEV_ROLE', 'SELECT');

-- for dbms_xplan.display_cursor
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL_PLAN_STATISTICS_ALL', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$PARAMETER', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SQL_PLAN', 'DEV_ROLE', 'SELECT');

-- for auto trace
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$STATNAME', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$MYSTAT', 'DEV_ROLE', 'SELECT');
exec rdsadmin.rdsadmin_util.grant_sys_object('V_$SESSTAT', 'DEV_ROLE', 'SELECT');

-- for auto trace (plan_table - temporary table)
exec rdsadmin.rdsadmin_util.grant_sys_object('PLAN_TABLE$', 'DEV_ROLE', 'ALL');

参考
Oracle DB インスタンスの一般的な DBA タスク


ということで、動作確認を兼ねたサンプルは以下のとおり。

まずは、RDS Oracleのマスターユーザーで.
マスターユーザー以外のユーザーの作成と権限とロールの付与(チューニングが必要とされる開発者等)を想定

foobar$ sqlplus awsuser/xxxxxxx@xxxx.xxxxxxxx.rds.amazonaws.com:1521/HOGE

SQL*Plus: Release 12.1.0.2.0 Production on Fri Nov 2 22:05:14 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

Last Successful login time: Fri Nov 02 2018 22:01:24 +09:00

...中略...

AWSUSER> l
1 create user test identified by xxxxxx
2 default tablespace users
3 temporary tablespace temp
4* quota unlimited on users
AWSUSER> /

User created.

AWSUSER> grant connect, resource to test;

Grant succeeded.

AWSUSER> @create_dev_role

Role created.

Grant succeeded.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

AWSUSER> grant dev_role to test;

Grant succeeded.

AWSUSER> exit


作成したユーザーで接続して、各方法で実行計画を取得できるか確認!
クエリーを実行するため適当な表を作成してデータを登録しておく。

foobar$ sqlplus test/xxxxxxx@xxxx.xxxxxxxx.rds.amazonaws.com:1521/HOGE

SQL*Plus: Release 12.1.0.2.0 Production on Fri Nov 2 22:15:23 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

...中略...
TEST>
TEST> l
1 create table hoge (
2 id number not null primary key
3 ,foobar varchar2(20)
4* ) nologging
TEST> /

Table created.

TEST>
TEST> l
1 begin
2 for i in 1..10000 loop
3 insert into
4 hoge (
5 id
6 ,foobar
7 )
8 values (
9 i
10 ,to_char(i)
11 );
12 end loop;
13 commit;
14* end;
TEST> /

PL/SQL procedure successfully completed.


SQL*Plusのauto traceが行えるか確認!

TEST> set autot trace exp stat
TEST> select * from hoge where id = 1;

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 2757398040

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 25 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
342 bytes sent via SQL*Net to client
488 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

参考
文のトレースについて


explain plan for文は準備なしで問題ないのですが、念のための確認! DBMS_XPLAN.DISPLAYプロシージャを利用して実行計画を取得

TEST> 
TEST> explain plan for
2 select * from hoge where id = 1;

explained.

TEST> @show_explain

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
Plan hash value: 2757398040

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 25 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

14 rows selected.

Elapsed: 00:00:00.05

スクリプト例は以下の通り。$ORACLE_HOME/rdbms/admin/utlxpls.sql や、utlxplp.sqlが利用できれば楽なんでが、それら中身は、DBMS_XPLAN.DISPLAYなので大差ない内容。

$ cat show_explain.sql

set linesize 200
set long 100000
set longchunk 200
set tab off

SELECT
*
FROM
TABLE(
DBMS_XPLAN.DISPLAY(
format => 'ALL -PROJECTION -ALIAS'
)
)
;


Actual planをDBMS_XPLAN.DISPLAY_CURSORプロシージャで取得〜

TEST> 
TEST> @show_realplan

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
statistics_level string TYPICAL

Session altered.

*** SQL that you want to get an actual plan ***
1* select * from hoge where id = 1
***********************************************

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID 6f67zkz43kr76, child number 1
-------------------------------------
select * from hoge where id = 1

Plan hash value: 2757398040

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX UNIQUE SCAN | SYS_C005687 | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1)

19 rows selected.

Elapsed: 00:00:00.11

Session altered.


NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
statistics_level string TYPICAL

Actual planを取得するスクリプト例
SQL*Plusのコマンドを駆使してはいますが、ポイントはset statistics_level = allにすることと、DBMS_XPLAN.DISPLAY_CURSORをコールする際のformatパラメータです
set termout off/onでSQL文の結果を表示しないようにしています。この設定はSQLファイルからSQL文を実行した場合にだけ有効です。(ちょっとしたTips :)

$ cat show_realplan.sql

show parameter statistics_level
alter session set statistics_level = all;

set linesize 200
set long 100000
set longchunk 200
set tab off

PROMPT *** SQL that you want to get an actual plan ***

select * from hoge where id = 1
.
l

PROMPT ***********************************************

set termout off
r
set termout on


-- get the actual plan
set timi on
SELECT
*
FROM
TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(
format => 'ALLSTATS LAST'
)
)
;
set timi off
alter session set statistics_level = typical;
show parameter statistics_level

参考
DBMS_XPLANサブプログラムの要約


Apple Store Ginzaで、息子が ”iPadでムービーを作ろう” に参加するので、その合間に、パタパタブログを書き、その足で英会話に向かう日曜日の午後w



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ
RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ

| | | コメント (0) | トラックバック (0)

2018年10月15日 (月)

備忘録 - ビット演算 / FAQ

備忘録
ビット演算こんな感じでできるはず。

orcl@SCOTT> r
1 SELECT
2 d1
3 ,d2
4 ,x1
5 ,x2
6 ,UTL_RAW.BIT_OR(x1,x2) AS x1_bitor_x2
7 FROM
8 (
9 SELECT
10 POWER(2, 1) AS d1
11 , HEXTORAW(TO_CHAR(POWER(2, 1), 'FM0000000X')) AS x1
12 , POWER(2, 3) AS d2
13 , HEXTORAW(TO_CHAR(POWER(2, 3), 'FM0000000X')) AS x2
14 FROM
15 dual
16* )

D1 D2 X1 X2 X1_BITOR_X2
---------- ---------- ---------- ---------- -----------
2 8 00000002 00000008 0000000A

orcl@SCOTT>

Oracle Database 12cリリース2 PL/SQL Packages and Types Reference 270.3.3 BIT_ORファンクション
Oracle Database 12c Release 2 SQL Language Reference 7.90 HEXTORAW
Oracle Database 12c Release 2 SQL Language Reference 7.159 POWER
Oracle Database 12c Release 2 SQL Language Reference 7.237 TO_CHAR (number)
Oracle Database 12c Release 2 SQL Language Reference 2.4 Format Models 2.4.1 Number Format Models
Oracle Database 12c Release 2 SQL Language Reference 2.4 Format Models FM

| | | コメント (0) | トラックバック (0)

2018年9月28日 (金)

RDS Oracle 雑多なメモ#15 - おまけのおまけ / FAQ

やっぱり、改造してしまった。おまけのおまけ編w

前回単純にMD5を取得だけのスクリプトをやっつけで作ったので、それを少し改造して、締めくくり。?(たぶん。。)

前回作成したMD5取得スクリプトを元に、ファイルが同じかどうか比較するスクリプトに作り変えました :)

21:35:35 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24
hoge.log.bak file 1202 18-09-24
hoge.log.gz.bak file 512 18-09-24
01/ directory 4096 18-09-24
hoge.dmp file 1083412480 18-09-24
hoge.log file 904 18-09-24

9行が選択されました。

ダンプファイル(一旦圧縮したファイルを解凍したファイル)とオリジナルのダンプファイルのコピーの比較

21:35:43 rdsora121@BILL> @diff_md5 test_dir hoge.dmp hoge.dmp.bak
Src : 1B84C9E09F13A4FDC6EF2F03137C0338
Dest: 1B84C9E09F13A4FDC6EF2F03137C0338
-- No difference found. --

PL/SQLプロシージャが正常に完了しました。


ダンプファイル(オリジナルを圧縮したファイル)とオリジナルダンプファイルを圧縮したファイルのコピーの比較

21:36:28 rdsora121@BILL> @diff_md5 test_dir hoge.dmp.gz hoge.dmp.gz.bak
Src : D2CB26A7E75C9725F00C30A9280C1599
Dest: D2CB26A7E75C9725F00C30A9280C1599
-- No difference found. --

PL/SQLプロシージャが正常に完了しました。

異なるファイルの比較、ログファイルのコピーと、圧縮したログファイルのコピー。

21:37:35 rdsora121@BILL> @diff_md5 test_dir hoge.log.bak hoge.log.gz.bak
Src : DCE17181C95EACE997F45A2537BC1DA8
Dest: 6398E040E157B4A0CEF50FCF45C76FF7
-- Difference found. --

PL/SQLプロシージャが正常に完了しました。

圧縮したログファイルとそのコピーの比較。同じでなにより:)

21:38:15 rdsora121@BILL> @diff_md5 test_dir hoge.log.gz hoge.log.gz.bak
Src : 6398E040E157B4A0CEF50FCF45C76FF7
Dest: 6398E040E157B4A0CEF50FCF45C76FF7
-- No difference found. --

PL/SQLプロシージャが正常に完了しました。


異なるファイルの比較、どちらもログファイルですが、内容がことなります。

21:38:51 rdsora121@BILL> @cat_file test_dir hoge.log

TEXT
------------------------------------------------------------------------------------------
マスター表"BILL"."SYS_IMPORT_TABLE_01"は正常にロード/アンロードされました
"BILL"."SYS_IMPORT_TABLE_01"を起動しています:
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
. . "SCOTT"."HOGE" 1.008 GB 270000行がインポートされました
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
ジョブ"BILL"."SYS_IMPORT_TABLE_01"が月 9月 24 04:05:17 2018 elapsed 0 00:00:52で正常に完了しました

11行が選択されました。

21:39:38 rdsora121@BILL> @cat_file test_dir hoge.log.bak

TEXT
------------------------------------------------------------------------------------------
"BILL"."EXPTABLE_HOGE"を起動しています:
BLOCKSメソッドを使用して見積り中です...
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
BLOCKSメソッドを使用した見積り合計: 2.125 GB
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
. . "SCOTT"."HOGE" 1.008 GB 270000行がエクスポートされました
マスター表"BILL"."EXPTABLE_HOGE"は正常にロード/アンロードされました
******************************************************************************
BILL.EXPTABLE_HOGEに設定されたダンプ・ファイルは次のとおりです:
/rdsdbdata/userdirs/01/hoge.dmp
ジョブ"BILL"."EXPTABLE_HOGE"が日 9月 23 05:36:33 2018 elapsed 0 00:00:35で正常に完了しました

16行が選択されました。

21:39:48 rdsora121@BILL> @diff_md5 test_dir hoge.log hoge.log.bak
Src : 6DA2D7C6150A9C8ACD60D95D6A61C1B5
Dest: DCE17181C95EACE997F45A2537BC1DA8
-- Difference found. --

PL/SQLプロシージャが正常に完了しました。

スクリプトは以下の通り。MD5の比較には、UTL_RAW.COMPARE関数を利用しています。

21:40:05 rdsora121@BILL> !cat diff_md5.sql
SET VERIFY OFF
SET SERVEROUTPUT ON FORMAT WRAPPED
DECLARE
vDirectoryName VARCHAR2(30);
vSrcFileName VARCHAR2(60);
vDestFileName VARCHAR2(60);
vSrcMd5 RAW(16);
vDestMd5 RAW(16);

PROCEDURE close_bfile
(
iBfile IN OUT NOCOPY BFILE
)
IS
BEGIN
IF DBMS_LOB.ISOPEN(iBfile) = 1 THEN
DBMS_LOB.CLOSE(iBfile);
END IF;
END close_bfile;

FUNCTION md5
(
iDirectoryName IN VARCHAR2
, iFileName IN VARCHAR2
) RETURN RAW
IS
vBfile BFILE;
vBlob BLOB;
vMd5 RAW(16);
BEGIN
vBFile := BFILENAME(UPPER(iDirectoryName), iFileName);
DBMS_LOB.OPEN(vBFile);

DBMS_LOB.CREATETEMPORARY(
lob_loc => vBlob
, cache => false
, dur => DBMS_LOB.SESSION
);

DBMS_LOB.LOADFROMFILE(
dest_lob => vBlob
, src_lob => vBFile
, amount => DBMS_LOB.GETLENGTH(vBFile)
, dest_offset => 1
, src_offset => 1
);

vMd5 := DBMS_CRYPTO.HASH(vBlob, DBMS_CRYPTO.HASH_MD5);

DBMS_LOB.FREETEMPORARY(vBlob);
close_bfile(vBFile);
RETURN vMd5;
EXCEPTION
WHEN OTHERS THEN
DBMS_LOB.FREETEMPORARY(vBlob);
close_bfile(vBFile);
RAISE;
END md5;

BEGIN
vDirectoryName := UPPER('&1');
vSrcFileName := '&2';
vDestFileName := '&3';

vSrcMd5 := md5(vDirectoryName, vSrcFileName);
vDestMd5 := md5(vDirectoryName, vDestFileName);

DBMS_OUTPUT.PUT_LINE('Src : '||vSrcMd5);
DBMS_OUTPUT.PUT_LINE('Dest: '||vDestMd5);

IF UTL_RAW.COMPARE(r1 => vSrcMd5, r2 => vDestMd5) = 0
THEN
DBMS_OUTPUT.PUT_LINE('-- No difference found. --');
ELSE
DBMS_OUTPUT.PUT_LINE('-- Difference found. --');
END IF;

EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM()||':'||SQLCODE());
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
SET SERVEROUTPUT OFF
SET VERIFY ON


たぶん、これで終わりなはず。(なにか浮かばなければw)


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ
RDS Oracle 雑多なメモ#14 - おまけ / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月27日 (木)

RDS Oracle 雑多なメモ#14 - おまけ / FAQ

ということで、done ってしておきながら、おまけ(得意技w)です。

RDS Oracle限定というわけではなくなってきましたがw 勢いでさらに追加。


手作りのcpっぽいスクリプトなので、バグってないか少し不安w ということで、オリジナルファイルと同じなのか確認できるようにメッセージダイジェストを取得するスクリプトも作成しましたw

21:14:43 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
HOGE_DIR /rdsdbdata/userdirs/02
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch
TEST_DIR /rdsdbdata/userdirs/01

8行が選択されました。

21:14:48 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24
hoge.log.bak file 1202 18-09-24
hoge.log.gz.bak file 512 18-09-24
01/ directory 4096 18-09-24
hoge.dmp file 1083412480 18-09-24
hoge.log file 904 18-09-24

9行が選択されました。

ダンプファイルを圧縮ファイル、ログファイル、ダンプファイルなどなど、MD5が取得できるようになりました。ファイルが壊れてないか確認しやすい:)

21:14:55 rdsora121@BILL> @check_md5 test_dir hoge.dmp.gz
TEST_DIR/hoge.dmp.gz MD5 : D2CB26A7E75C9725F00C30A9280C1599

PL/SQLプロシージャが正常に完了しました。

21:15:32 rdsora121@BILL> @check_md5 test_dir hoge.dmp.gz.bak
TEST_DIR/hoge.dmp.gz.bak MD5 : D2CB26A7E75C9725F00C30A9280C1599

PL/SQLプロシージャが正常に完了しました。

21:15:50 rdsora121@BILL> @check_md5 test_dir hoge.log
TEST_DIR/hoge.log MD5 : 6DA2D7C6150A9C8ACD60D95D6A61C1B5

PL/SQLプロシージャが正常に完了しました。

21:16:26 rdsora121@BILL> @check_md5 test_dir hoge.dmp
TEST_DIR/hoge.dmp MD5 : 1B84C9E09F13A4FDC6EF2F03137C0338

PL/SQLプロシージャが正常に完了しました。

21:18:02 rdsora121@BILL> @check_md5 test_dir hoge.dmp.bak
TEST_DIR/hoge.dmp.bak MD5 : 1B84C9E09F13A4FDC6EF2F03137C0338

PL/SQLプロシージャが正常に完了しました。


ざっくり作ったスクリプトは以下の通り。BFILEからファイルを読み込み、一時BLOBとしてBFILEからロード、MD5取得というながれです。
(オンプレでもそのまま使える内容にはなっちゃいましたが、勢いで作ったので:)


21:18:50 rdsora121@BILL> !cat check_md5.sql
SET VERIFY OFF
SET SERVEROUTPUT ON FORMAT WRAPPED
DECLARE
vSrcfile BFILE;
vSrcBlob BLOB;
vSrcDirectoryName VARCHAR2(30);
vSrcFileName VARCHAR2(60);
vSrcFileSize PLS_INTEGER;
vSrcFileMd5 RAW(16);

PROCEDURE close_bfile
(
iBfile IN OUT NOCOPY BFILE
)
IS
BEGIN
IF DBMS_LOB.ISOPEN(iBfile) = 1 THEN
DBMS_LOB.CLOSE(iBfile);
END IF;
END close_bfile;

BEGIN
vSrcDirectoryName := UPPER('&1');
vSrcFileName := '&2';

vSrcFile
:= BFILENAME(UPPER(vSrcDirectoryName), vSrcFileName);

DBMS_LOB.OPEN(vSrcFile);
vSrcFileSize := DBMS_LOB.GETLENGTH(vSrcFile);


DBMS_LOB.CREATETEMPORARY (
lob_loc => vSrcBlob
, cache => false
, dur => DBMS_LOB.SESSION
);

DBMS_LOB.LOADFROMFILE (
dest_lob => vSrcBlob
, src_lob => vSrcFile
, amount => vSrcFileSize
, dest_offset => 1
, src_offset => 1
);

vSrcFileMd5 := DBMS_CRYPTO.HASH(vSrcBlob, DBMS_CRYPTO.HASH_MD5);

DBMS_LOB.FREETEMPORARY(vSrcBlob);
close_bfile(vSrcFile);

DBMS_OUTPUT.PUT_LINE(
vSrcDirectoryName
||'/'||vSrcFileName
||' MD5 : '||vSrcFileMd5
);
EXCEPTION
WHEN OTHERS THEN
DBMS_LOB.FREETEMPORARY(vSrcBlob);
close_bfile(vSrcFile);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
SET SERVEROUTPUT OFF
SET VERIFY ON

ということで、done... これを元にまたまにか作るかもしれない、作らないかもしれないw


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ
RDS Oracle 雑多なメモ#13 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月26日 (水)

RDS Oracle 雑多なメモ#13 / FAQ

メモの続きです。
前回は、圧縮したテキストファイルとダンプファイル(バイナリファイル)を解凍するスクリプトを実行したところまででした。

やっとここまできたw

今回は、解凍したファイルが使えるかなど内容確認をしてみたいと思います。


まず、解凍したログファイル(テキストファイル)の内容を確認
問題なさそうですね。

12:35:01 rdsora121@BILL> @cat_file test_dir hoge.log

TEXT
----------------------------------------------------------------------------------------
"BILL"."EXPTABLE_HOGE"を起動しています:
BLOCKSメソッドを使用して見積り中です...
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
BLOCKSメソッドを使用した見積り合計: 2.125 GB
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
. . "SCOTT"."HOGE" 1.008 GB 270000行がエクスポートされました
マスター表"BILL"."EXPTABLE_HOGE"は正常にロード/アンロードされました
******************************************************************************
BILL.EXPTABLE_HOGEに設定されたダンプ・ファイルは次のとおりです:
/rdsdbdata/userdirs/01/hoge.dmp
ジョブ"BILL"."EXPTABLE_HOGE"が日 9月 23 05:36:33 2018 elapsed 0 00:00:35で正常に完了しました

16行が選択されました。

解凍したダンプファイルをインポートして確認する前に、元のオブジェクトの確認後、削除しておきます。

12:37:17 rdsora121@BILL> set linesize 80
12:37:20 rdsora121@BILL> desc scott.hoge
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
FOO VARCHAR2(4000)

12:37:25 rdsora121@BILL> select count(1) from scott.hoge;

COUNT(1)
----------
270000

12:39:07 rdsora121@BILL> r
1 select
2 segment_name
3 ,sum(bytes)/1024/1024/1024 "GB"
4 from
5 dba_segments
6 where
7 owner='SCOTT'
8 and segment_name in ('HOGE','PK_HOGE')
9 group by
10* segment_name

SEGMENT_NAME GB
------------------------------ ----------
PK_HOGE .004882813
HOGE 2.125

12:39:17 rdsora121@BILL> set linesize 400
12:39:21 rdsora121@BILL>
12:40:02 rdsora121@BILL> drop table scott.hoge purge;

表が削除されました。

dbms_datapumpパッケージを利用したスクリプトを作成してインポート可能か確認 :)

13:03:40 rdsora121@BILL> @import_table test_dir hoge scott hoge
マスター表"BILL"."SYS_IMPORT_TABLE_01"は正常にロード/アンロードされました
"BILL"."SYS_IMPORT_TABLE_01"を起動しています:
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
. . "SCOTT"."HOGE" 1.008 GB 270000行がインポートされました
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
ジョブ"BILL"."SYS_IMPORT_TABLE_01"が月 9月 24 04:05:17 2018 elapsed 0 00:00:52で正常に完了しました

PL/SQLプロシージャが正常に完了しました。

インポート成功!!! 表などの確認!

13:05:54 rdsora121@BILL> set linesize 80
13:06:00 rdsora121@BILL> desc scott.hoge
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
FOO VARCHAR2(4000)

13:06:04 rdsora121@BILL> select count(1) from scott.hoge;

COUNT(1)
----------
270000

13:07:27 rdsora121@BILL> r
1 select
2 segment_name
3 ,sum(bytes)/1024/1024/1024 "GB"
4 from
5 dba_segments
6 where
7 owner='SCOTT'
8 and segment_name in ('HOGE','PK_HOGE')
9 group by
10* segment_name

SEGMENT_NAME GB
------------------------------ ----------
PK_HOGE .004882813
HOGE 2.125


スクリプトは以下の通り。 表モードのインポートをハードコードしていますが、そのあたりパラメータ化すればそこそこ使いやすくなりますかね。。

13:08:03 rdsora121@BILL> !cat import_table.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(50);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
cSchemaName CONSTANT VARCHAR2(30) := UPPER('&3');
cTableName CONSTANT VARCHAR2(30) := UPPER('&4');
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'IMPORT'
,job_mode => 'TABLE'
,remote_link => NULL
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'METADATA_FILTER - schema name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'SCHEMA_LIST'
,value => '''' || cSchemaName || ''''
);

v4Debug := 'METADATA_FILTER - table name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'NAME_LIST'
,value => '''' || cTableName || ''''
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
UNDEFINE 4
SET SERVEROUTPUT OFF
SET VERIFY ON


ということで、ひとまず、RDS Oracleの雑多なメモ done :)



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ
RDS Oracle 雑多なメモ#12 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月25日 (火)

RDS Oracle 雑多なメモ#12 / FAQ

メモの続きです。

さて、前回、cpっぽいスクリプトの問題を改善したので今回は、圧縮したファイルを解凍するスクリプトを作ろうと思います。

前回の操作でファイルが増えて見づらくなったので、解凍に必要な最小限のファイルだけに整理します

12:20:19 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.log file 1202 18-09-23
hoge.dmp file 1083412480 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24
hoge.log.bak file 1202 18-09-24
hoge.log.gz.bak file 512 18-09-24
01/ directory 4096 18-09-24

9行が選択されました。

12:20:25 rdsora121@BILL> @rm_file test_dir hoge.log
TEST_DIR/hoge.log removed.

PL/SQLプロシージャが正常に完了しました。

12:21:49 rdsora121@BILL> @rm_file test_dir hoge.dmp
TEST_DIR/hoge.dmp removed.

PL/SQLプロシージャが正常に完了しました。

12:21:57 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24
hoge.log.bak file 1202 18-09-24
hoge.log.gz.bak file 512 18-09-24
01/ directory 4096 18-09-24

7行が選択されました。

では圧縮したテキストファイルとダンプファイル(バイナリファイル)を解凍してみます。

12:22:06 rdsora121@BILL> @gunzip_file test_dir hoge.log.gz
TEST_DIR/hoge.log.gz uncompressed to hoge.log (1202 bytes)

PL/SQLプロシージャが正常に完了しました。

12:22:44 rdsora121@BILL> @gunzip_file test_dir hoge.dmp.gz
TEST_DIR/hoge.dmp.gz uncompressed to hoge.dmp (1083412480 bytes)

PL/SQLプロシージャが正常に完了しました。

12:23:04 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24
hoge.log.bak file 1202 18-09-24
hoge.log.gz.bak file 512 18-09-24
hoge.log file 1202 18-09-24
01/ directory 4096 18-09-24
hoge.dmp file 1083412480 18-09-24

9行が選択されました。


できた!!!


今回の利用した解凍スクリプトも元は12年前のエントリで利用したコードのほぼ再利用です。
Mac De Oracle (PL/SQL De UNCOMPRESS)

12:24:22 rdsora121@BILL> !cat gunzip_file.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
vSrcGzippedFile BFILE;
vUncompressedBlob BLOB;
vDirectoryName VARCHAR2(30);
vSrcGzippedFileName VARCHAR2(60);
vDestFileName VARCHAR2(60);
VUncompressedSize PLS_INTEGER;

PROCEDURE close_bfile
(
iBfile IN OUT NOCOPY BFILE
)
IS
BEGIN
IF DBMS_LOB.ISOPEN(iBfile) = 1 THEN
DBMS_LOB.CLOSE(iBfile);
END IF;
END close_bfile;

PROCEDURE write_blob_to_file
(
iDirectoryName IN VARCHAR2,
iFileName IN VARCHAR2,
iSrcBlob IN OUT NOCOPY BLOB
)
IS
vFile UTL_FILE.FILE_TYPE;
BEGIN
vFile := UTL_FILE.FOPEN(
UPPER(iDirectoryName),
iFileName,
'wb',
32767
);

DECLARE
cChunkSize CONSTANT PLS_INTEGER := 32767;

vBuffer RAW(32767);
vAmount PLS_INTEGER := cChunkSize;
vNumOfChunk PLS_INTEGER;
BEGIN
vNumOfChunk := CEIL(DBMS_LOB.GETLENGTH(iSrcBlob)/cChunkSize);
FOR chunk# IN 1..vNumOfChunk LOOP
DBMS_LOB.READ(
iSrcBlob,
vAmount,
(cChunkSize * (chunk# - 1)) + 1, /*offet*/
vBuffer
);
UTL_FILE.PUT_RAW(vFile, vBuffer, TRUE);
END LOOP;
END;

UTL_FILE.FCLOSE(vFile);
END write_blob_to_file;

BEGIN
vDirectoryName := UPPER('&1');
vSrcGzippedFileName := '&2';
vDestFileName := SUBSTR('&2', 1, INSTR('&2', '.gz') - 1);

vSrcGzippedFile
:= BFILENAME(UPPER(vDirectoryName), vSrcGzippedFileName);

DBMS_LOB.OPEN(vSrcGzippedFile);
vUncompressedBlob := UTL_COMPRESS.LZ_UNCOMPRESS(vSrcGzippedFile);
vUncompressedSize := DBMS_LOB.GETLENGTH(vUncompressedBlob);

write_blob_to_file(
UPPER(vDirectoryName),
vDestFileName,
vUncompressedBlob
);

DBMS_LOB.FREETEMPORARY(vUncompressedBlob);
close_bfile(vSrcGzippedFile);
DBMS_OUTPUT.PUT_LINE(
vDirectoryName||'/'
||vSrcGzippedFileName
||' uncompressed to '
||vDestFileName
||' ('||vUncompressedSize||' bytes)'
);
EXCEPTION
WHEN OTHERS THEN
DBMS_LOB.FREETEMPORARY(vUncompressedBlob);
close_bfile(vSrcGzippedFile);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
SET SERVEROUTPUT OFF
SET VERIFY ON

ということで、次回、解凍したテキストファイルの内容確認と、解凍したダンプファイルからインポートして内容確認へつづく :)



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ
RDS Oracle 雑多なメモ#11 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月24日 (月)

RDS Oracle 雑多なメモ#11 / FAQ

メモの続きです。

はじめに、
ごねんなさい、ごねんなさい

RDS Oracle 雑多なメモ#6 / FAQ
で作成したUTL_FILE.FCOPYを利用したcpっぽいスクリプトはtextファイルにしか対応できません。
理由は、UTL_FILE.FCOPYプロシージャ自体がtextファイル向けでバイナリファイルに対応していないことが理由ではあるのですが、textファイルしかコピーできないのもなんなので、バイナリファイルにも対応したバージョンに変更したいと思います。

RDS Oracle 雑多なメモ#6 / FAQで作成したスクリプトは今回作成したスクリプトで置き換えていただければ、m(_ _)m


前々回作成したダンプファイル(バイナリファイル)やテキストファイルを使ってみようと思います。

utl_file.fcopyプロシージャでコピーできる例(テキストファイル)

10:48:50 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
01/ directory 4096 18-09-24

11:14:42 rdsora121@BILL> @cp_file test_dir hoge.log test_dir hoge.log.bak1

source file : test_dir/hoge.log
dest file : test_dir/hoge.log.bak1 copied.

PL/SQLプロシージャが正常に完了しました。

11:15:10 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
01/ directory 4096 18-09-24
hoge.log.bak1 file 1202 18-09-24

6行が選択されました。

utl_file.fcopyプロシージャでコピーできない例(バイナリファイル)
エラーが発生し、コピー途中のゴミファイルが作成されていまいます。。。。これはこまる。。

11:15:10 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
01/ directory 4096 18-09-24
hoge.log.bak1 file 1202 18-09-24

11:16:51 rdsora121@BILL> @cp_file test_dir hoge.dmp test_dir hoge.dmp.bak1
-29284:ORA-29284: ファイル読取りエラーが発生しました。

PL/SQLプロシージャが正常に完了しました。

11:17:20 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.log.bak1 file 1202 18-09-24
hoge.dmp.bak1 file 45093 18-09-24
01/ directory 4096 18-09-24

7行が選択されました。


RDS Oracle 雑多なメモ#6 / FAQで作成したcp_file.sqlのコードは以下の通り
これでバイナリーファイルもコピーできると嬉しいのにね〜。昔からできないのでしかたない。。

11:25:20 rdsora121@BILL> !cat cp_file.sql
set verify off
set serveroutput on format wrapped
BEGIN
UTL_FILE.FCOPY(
src_location => UPPER('&1')
,src_filename => '&2'
,dest_location => UPPER('&3')
,dest_filename => '&4'
);
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('source file : &1/&2');
DBMS_OUTPUT.PUT_LINE('dest file : &3/&4 copied.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE()||':'||SQLERRM());
END;
/

undefine 1
undefine 2
undefine 3
undefine 4
set verify on
set serveroutput off


他の手はないかな??? あ、dbms_file_transfer.copy_fileプロシージャを使ったらどうか?

ただし、バイナリファイルをうまくコピーできるようになりましたが、このプロシージャにはいくつか制限があります。
Oracle® Database PL/SQLパッケージおよびタイプ・リファレンス 12c リリース1 (12.1) DBMS_FILE_TRANSFER
コピーされるファイルのサイズは512バイトの倍数である必要があります。
コピーされるファイルのサイズは、2TB以下である必要があります。
コピーの進行状況を監視するには、V$SESSION_LONGOPS動的パフォーマンス・ビューを参照します。

コピーできない例
512バイトの倍数でないバイナリファイルをコピーしようとすると。。。

11:17:20 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.log.bak1 file 1202 18-09-24
hoge.dmp.bak1 file 45093 18-09-24
01/ directory 4096 18-09-24

7行が選択されました。

11:18:02 rdsora121@BILL> @cp_transferfile test_dir hoge.dmp.gz test_dir hoge.dmp.gz.bak1
-19505:ORA-19505: ファイル"/rdsdbdata/userdirs/01/hoge.dmp.gz"の識別に失敗しました。
ORA-27046: ファイル・サイズが論理ブロック・サイズの倍数ではありません。
Additional information: 1

PL/SQLプロシージャが正常に完了しました。

うまくコピーできる例
512バイトの倍数サイズのバイナリファイルのコピー

11:23:12 rdsora121@BILL> @cp_transferfile test_dir hoge.dmp test_dir hoge.dmp.bak2

source file : test_dir/hoge.dmp
dest file : test_dir/hoge.dmp.bak2 copied.

PL/SQLプロシージャが正常に完了しました。

11:24:01 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.log.bak1 file 1202 18-09-24
hoge.dmp.bak1 file 45093 18-09-24
01/ directory 4096 18-09-24
hoge.dmp.bak2 file 1083412480 18-09-24

8行が選択されました。

dbms_transfer_fileパッケージを利用したコードは以下の通り
これも制限が多くなければコードも短くてうれしいわけなんですが。。。しかたなし

11:24:10 rdsora121@BILL> !cat cp_transferfile.sql
set verify off
set serveroutput on format wrapped
BEGIN
DBMS_FILE_TRANSFER.COPY_FILE(
source_directory_object => UPPER('&1')
,source_file_name => '&2'
,destination_directory_object => UPPER('&3')
,destination_file_name => '&4'
);
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('source file : &1/&2');
DBMS_OUTPUT.PUT_LINE('dest file : &3/&4 copied.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE()||':'||SQLERRM());
END;
/

undefine 1
undefine 2
undefine 3
undefine 4
set verify on
set serveroutput off


制限もあるし、使いやすいような使いにくいような...
いろいろ消化不良状態。。。なので

スクリプト長くなってめんどくさいけど、この部分楽はできない模様なので、手作り決定!

最終的に、既存プロシージャにないのなら、作っちゃえ。ということで、cp_file.sqlを全面作り変えw ました。

11:32:13 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
01/ directory 4096 18-09-24

11:32:21 rdsora121@BILL> @cp_file test_dir hoge.dmp test_dir hoge.dmp.bak
TEST_DIR/hoge.dmp (1083412480) to TEST_DIR/hoge.dmp.bak (1083412480)

PL/SQLプロシージャが正常に完了しました。

11:33:07 rdsora121@BILL> @cp_file test_dir hoge.log test_dir hoge.log.bak
TEST_DIR/hoge.log (1202) to TEST_DIR/hoge.log.bak (1202)

PL/SQLプロシージャが正常に完了しました。

11:33:26 rdsora121@BILL> @cp_file test_dir hoge.dmp.gz test_dir hoge.dmp.gz.bak
TEST_DIR/hoge.dmp.gz (2603349) to TEST_DIR/hoge.dmp.gz.bak (2603349)

PL/SQLプロシージャが正常に完了しました。

11:33:42 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.log file 1202 18-09-23
hoge.dmp file 1083412480 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
hoge.dmp.bak file 1083412480 18-09-24
hoge.log.bak file 1202 18-09-24
01/ directory 4096 18-09-24
hoge.dmp.gz.bak file 2603349 18-09-24

8行が選択されました。

新、cp_file.sqlのスクリプトは以下の通り。基本的に、gzip_file.sql内部で利用していたバイナリファイルのハンドリング部分を抜き出した感じです。一部違うのは DBMS_LOB.CREATETEMPORARYで一時BLOBを読んでいるところ。BFILEからBLOBをロードして、出力先ファイルへ書き出しているかしょは圧縮で利用しているコードと同じです。最後に一時BLOBの解放をお忘れなく

11:34:21 rdsora121@BILL> !cat cp_file.sql
SET VERIFY OFF
SET SERVEROUTPUT ON FORMAT WRAPPED
DECLARE
vSrcfile BFILE;
vSrcBlob BLOB;
vSrcDirectoryName VARCHAR2(30);
vDestDirectoryName VARCHAR2(30);
vSrcFileName VARCHAR2(60);
vDestFileName VARCHAR2(60);
vSrcFileSize PLS_INTEGER;
vDestFileSize PLS_INTEGER;

PROCEDURE close_bfile
(
iBfile IN OUT NOCOPY BFILE
)
IS
BEGIN
IF DBMS_LOB.ISOPEN(iBfile) = 1 THEN
DBMS_LOB.CLOSE(iBfile);
END IF;
END close_bfile;

PROCEDURE write_blob_to_file
(
iDirectoryName IN VARCHAR2
, iFileName IN VARCHAR2
, iSrcBlob IN OUT NOCOPY BLOB
)
IS
vFile UTL_FILE.FILE_TYPE;
BEGIN
vFile := UTL_FILE.FOPEN(
UPPER(iDirectoryName)
, iFileName
, 'wb'
, 32767
);

DECLARE
cChunkSize CONSTANT PLS_INTEGER := 32767;

vBuffer RAW(32767);
vAmount PLS_INTEGER := cChunkSize;
vNumOfChunk PLS_INTEGER;
BEGIN
vNumOfChunk := CEIL(DBMS_LOB.GETLENGTH(iSrcBlob)/cChunkSize);
FOR chunk# IN 1..vNumOfChunk LOOP
DBMS_LOB.READ(
iSrcBlob
, vAmount
, (cChunkSize * (chunk# - 1)) + 1 /*offset*/
, vBuffer
);
UTL_FILE.PUT_RAW(vFile, vBuffer, TRUE);
END LOOP;
END;
UTL_FILE.FCLOSE(vFile);
END write_blob_to_file;

BEGIN
vSrcDirectoryName := UPPER('&1');
vSrcFileName := '&2';
vDestDirectoryName := UPPER('&3');
vDestFileName := '&4';

vSrcFile
:= BFILENAME(UPPER(vSrcDirectoryName), vSrcFileName);

DBMS_LOB.OPEN(vSrcFile);
vSrcFileSize := DBMS_LOB.GETLENGTH(vSrcFile);


DBMS_LOB.CREATETEMPORARY (
lob_loc => vSrcBlob
, cache => false
, dur => DBMS_LOB.SESSION
);

DBMS_LOB.LOADFROMFILE (
dest_lob => vSrcBlob
, src_lob => vSrcFile
, amount => vSrcFileSize
, dest_offset => 1
, src_offset => 1
);
vDestFileSize := DBMS_LOB.GETLENGTH(vSrcBlob);

write_blob_to_file(
UPPER(vDestDirectoryName)
, vDestFileName
, vSrcBlob
);

DBMS_LOB.FREETEMPORARY(vSrcBlob);
close_bfile(vSrcFile);

DBMS_OUTPUT.PUT_LINE(
vSrcDirectoryName||'/'||vSrcFileName||' ('||vDestFileSize||') to '
||vDestDirectoryName||'/'||vDestFileName||' ('||vSrcFileSize||')'
);
EXCEPTION
WHEN OTHERS THEN
DBMS_LOB.FREETEMPORARY(vSrcBlob);
close_bfile(vSrcFile);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
UNDEFINE 4
SET SERVEROUTPUT OFF
SET VERIFY ON

ということで、次回へづつく。:)


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ
RDS Oracle 雑多なメモ#10 / FAQ

| | | コメント (0) | トラックバック (0)

RDS Oracle 雑多なメモ#10 / FAQ

メモの続きです。
前回は、dbms_datapumpパッケージを利用してエクスポートを行うスクリプトを作成しました。
今回は、作成したダンプファイル(バイナリファイルでサイズは1GB越え)をutl_compressパッケージを利用して圧縮するスクリプトを作成します。

このネタ実は、12年前の古いネタが元になっています。
Mac De Oracle (PL/SQL De COMPRESS)
Mac De Oracle (PL/SQL De UNCOMPRESS)

ということで、RDS Oracleのディレクトリオブジェクトにエクポートしたダンプファイル(バイナリファイル)圧縮してみましょう。。動くのか?。。。

ディレクトリを確認

10:48:42 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
HOGE_DIR /rdsdbdata/userdirs/02
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch
TEST_DIR /rdsdbdata/userdirs/01

8行が選択されました。

エクスポートしたダンプファイル(バイナリファイル)とログファイル(テキストファイル)が対象ディレクトリ以下にあることを確認

10:42:05 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
01/ directory 4096 18-09-24

まず、ダンプファイル(バイナリファイル)を圧縮します。ダンプファイルは圧縮効率がいいんですよね。なので転送する際には圧縮するのがオススメ
圧縮はうまくいった?ようですね。解凍スクリプト編で正常に解凍できるか確認する予定なのでしばしお待ちを。

10:46:00 rdsora121@BILL> @gzip_file test_dir hoge.dmp
TEST_DIR/hoge.dmp compressed to hoge.dmp.gz(99.76%)

PL/SQLプロシージャが正常に完了しました。

圧縮したファイルは、.gzの拡張子をつけて出力するようにしています。

10:46:23 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
01/ directory 4096 18-09-24

次に、テキストファイルも圧縮!

10:46:32 rdsora121@BILL> @gzip_file test_dir hoge.log
TEST_DIR/hoge.log compressed to hoge.log.gz(57.4%)

PL/SQLプロシージャが正常に完了しました。

10:46:51 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
hoge.dmp file 1083412480 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp.gz file 2603349 18-09-24
hoge.log.gz file 512 18-09-24
001/ directory 4096 18-09-24

スクリプトは以下の通り、utl_compressパッケージを利用しています。バイナリファイルはBFILEから読み込み、圧縮、圧縮結果のバイナリは一時BLOBで返されるので、一時BLOBをバイナリファイルとして書き出しています。最後に一時BLOBを解放してあげてゴミ掃除という流れです。

10:46:58 rdsora121@BILL> !cat gzip_file.sql
SET VERIFY OFF
SET SERVEROUTPUT ON FORMAT WRAPPED
DECLARE
vSrcfile BFILE;
vCompressedBlob BLOB;
vDirectoryName VARCHAR2(30);
vSrcFileName VARCHAR2(60);
vDestFileName VARCHAR2(60);
vSrcFileSize PLS_INTEGER;
vDestFileSize PLS_INTEGER;

PROCEDURE close_bfile
(
iBfile IN OUT NOCOPY BFILE
)
IS
BEGIN
IF DBMS_LOB.ISOPEN(iBfile) = 1 THEN
DBMS_LOB.CLOSE(iBfile);
END IF;
END close_bfile;

PROCEDURE write_blob_to_file
(
iDirectoryName IN VARCHAR2,
iFileName IN VARCHAR2,
iSrcBlob IN OUT NOCOPY BLOB
)
IS
vFile UTL_FILE.FILE_TYPE;
BEGIN
vFile := UTL_FILE.FOPEN(
UPPER(iDirectoryName),
iFileName,
'wb',
32767
);

DECLARE
cChunkSize CONSTANT PLS_INTEGER := 32767;

vBuffer RAW(32767);
vAmount PLS_INTEGER := cChunkSize;
vNumOfChunk PLS_INTEGER;
BEGIN
vNumOfChunk := CEIL(DBMS_LOB.GETLENGTH(iSrcBlob)/cChunkSize);
FOR chunk# IN 1..vNumOfChunk LOOP
DBMS_LOB.READ(
iSrcBlob,
vAmount,
(cChunkSize * (chunk# - 1)) + 1, /*offset*/
vBuffer
);
UTL_FILE.PUT_RAW(vFile, vBuffer, TRUE);
END LOOP;
END;

UTL_FILE.FCLOSE(vFile);
END write_blob_to_file;
--
BEGIN
vDirectoryName := UPPER('&1');
vSrcFileName := '&2';
vDestFileName := '&2'||'.gz';

vSrcFile
:= BFILENAME(UPPER(vDirectoryName), vSrcFileName);

DBMS_LOB.OPEN(vSrcFile);
vSrcFileSize := DBMS_LOB.GETLENGTH(vSrcFile);
vCompressedBlob := UTL_COMPRESS.LZ_COMPRESS(vSrcFile);
vDestFileSize := DBMS_LOB.GETLENGTH(vCompressedBlob);

write_blob_to_file(
UPPER(vDirectoryName),
vDestFileName,
vCompressedBlob
);

DBMS_LOB.FREETEMPORARY(vCompressedBlob);
close_bfile(vSrcFile);

DBMS_OUTPUT.PUT_LINE(
vDirectoryName
||'/'||vSrcFileName||' compressed to '
||vDestFileName||'('
||ROUND((1 - (vDestFileSize / vSrcFileSize)) * 100, 2)||'%)'
);
XCEPTION
WHEN OTHERS THEN
DBMS_LOB.FREETEMPORARY(vCompressedBlob);
close_bfile(vSrcFile);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
SET SERVEROUTPUT OFF
SET VERIFY ON

できた〜

ということで、次回へづつく。:)



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ
RDS Oracle 雑多なメモ#9 / FAQ

| | | コメント (0) | トラックバック (0)

RDS Oracle 雑多なメモ#9 / FAQ

メモの続きです。
ということで、 (どういうことだ〜っ。久々に一人、ツッコミ)

圧縮解凍ネタに進む前に、dbms_datapumpパッケージを使って、1GB越えのファイルを作っておこうかと。ここで作成したダンプファイルを圧縮解凍ネタにしようと思います。
事前にscottユーザーを作成して、hoge表を作成し、1GB程度のセグメントサイズになるようにデータを登録、その後、主キー索引を追加してあります。

14:29:48 rdsora121@BILL> desc scott.hoge
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
FOO VARCHAR2(4000)

14:31:24 rdsora121@BILL>select segment_name,sum(bytes)/1024/1024/1024 "GB" from dba_segments where owner='SCOTT' group by segment_name;

SEGMENT_NAME GB
------------------------------ ----------
PK_HOGE .004882813
HOGE 2.125

経過: 00:00:00.09
14:28:59 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
HOGE_DIR /rdsdbdata/userdirs/02
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch
TEST_DIR /rdsdbdata/userdirs/01

8行が選択されました。

経過: 00:00:00.03
14:29:03 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
01/ directory 4096 18-09-22

scottユーザーのhoge表を表モードでエクスポートしてダンプファイルをtest_dirへ出力させます!
以前やっつけで作ったのスクリプトの再利用なので、エクスポートモードなど他のパラメータも変更できるようにすれば、より良く改良できると思います。いまはやりませんがw
(以前の記事も参考にしてみてくさい。SQL Tuning Setのキャプチャから退避までのスクリプト(やっつけ))
出力されたダンプファイルは約1GBってところですね。

14:35:41 rdsora121@BILL> @export_table test_dir hoge scott hoge
"BILL"."EXPTABLE_HOGE"を起動しています:
BLOCKSメソッドを使用して見積り中です...
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
BLOCKSメソッドを使用した見積り合計: 2.125 GB
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/MARKERの処理中です
. . "SCOTT"."HOGE" 1.008 GB 270000行がエクスポートされました
マスター表"BILL"."EXPTABLE_HOGE"は正常にロード/アンロードされました
******************************************************************************

BILL.EXPTABLE_HOGEに設定されたダンプ・ファイルは次のとおりです:
/rdsdbdata/userdirs/01/hoge.dmp
ジョブ"BILL"."EXPTABLE_HOGE"が日 9月 23 05:36:33 2018 elapsed 0 00:00:35で正常に完了しました

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:40.23
14:36:34 rdsora121@BILL>
14:37:04 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
01/ directory 4096 18-09-23
hoge.log file 1202 18-09-23
hoge.dmp file 1083412480 18-09-23

表モードエクスポートのDataPump Exportジョブを実行するスクリプトは以下のとおり。
(表モードに固定してある部分を変更してスキーマモードにしたり、複数の表のエクスポートに対応できるように改造してやれば、より便利なスクリプトにすることもできると思います。)

14:36:34 rdsora121@BILL> !cat export_table.sql
SET VERIFY OFF
SET SERVEROUTPUT ON
DECLARE
v4Debug VARCHAR2(50);
cDirectory CONSTANT VARCHAR2(30) := UPPER('&1');
cDumpFileName CONSTANT VARCHAR2(64) := '&2'||'.dmp';
cLogFileName CONSTANT VARCHAR2(64) := '&2'||'.log';
cSchemaName CONSTANT VARCHAR2(30) := UPPER('&3');
cTableName CONSTANT VARCHAR2(30) := UPPER('&4');
i NUMBER;
vDataPumpJobHandle NUMBER;
vProgress_ratio NUMBER;
vJobState VARCHAR2(30);
oLogEntry ku$_LogEntry;
oStatus ku$_Status;
BEGIN
DBMS_OUTPUT.ENABLE;

v4Debug := 'OPEN';
vDataPumpJobHandle
:= DBMS_DATAPUMP.OPEN (
operation => 'EXPORT'
,job_mode => 'TABLE'
,remote_link => NULL
,job_name => 'EXPTABLE_'||cTableName
,version => 'LATEST'
);

v4Debug := 'ADD_FILE - dumpfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cDumpFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE
);

v4Debug := 'ADD_FILE - logfile';
DBMS_DATAPUMP.ADD_FILE (
handle => vDataPumpJobHandle
,filename => cLogFileName
,directory => cDirectory
,filetype => DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE
);

v4Debug := 'METADATA_FILTER - schema name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'SCHEMA_LIST'
,value => '''' || cSchemaName || ''''
);

v4Debug := 'METADATA_FILTER - table name';
DBMS_DATAPUMP.METADATA_FILTER (
handle => vDataPumpJobHandle
,name => 'NAME_LIST'
,value => '''' || cTableName || ''''
);

v4Debug := 'START_JOB';
DBMS_DATAPUMP.START_JOB (
handle => vDataPumpJobHandle
);

v4Debug := 'JOB_STATE';
vProgress_ratio := 0;
vJobState := 'UNDEFINED';
WHILE (vJobState != 'COMPLETED') AND (vJobState != 'STOPPED') LOOP
DBMS_DATAPUMP.GET_STATUS (
vDataPumpJobHandle
,DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR
+ DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS
+ DBMS_DATAPUMP.KU$_STATUS_WIP
,-1
,vJobState
,oStatus
);

IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_WIP) != 0)
THEN
oLogEntry := oStatus.wip;
ELSE
IF (BITAND(oStatus.mask, DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR) != 0)
THEN
oLogEntry := oStatus.error;
ELSE
oLogEntry := NULL;
END IF;
END IF;
IF oLogEntry IS NOT NULL
THEN
i := oLogEntry.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(oLogEntry(i).LogText);
i := oLogEntry.NEXT(i);
END LOOP;
END IF;
END LOOP;

DBMS_DATAPUMP.DETACH(vDataPumpJobHandle);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(sqlerrm());
DBMS_OUTPUT.PUT_LINE(v4Debug);
RAISE;
END;
/

UNDEFINE 1
UNDEFINE 2
UNDEFINE 3
UNDEFINE 4
SET SERVEROUTPUT OFF
SET VERIFY ON


ということで、次回へづつく。:)


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ
RDS Oracle 雑多なメモ#8 / FAQ

| | | コメント (0) | トラックバック (0)

RDS Oracle 雑多なメモ#8 / FAQ

メモの続きです。
前回は、utl_fileパッケージでrmっぽいことを行うスクリプトを作成しました。
前回まででutl_fileパッケージを利用したディレクトリオブジェクト配下のファイル操作スクリリプトを作ることができました。

さて、今回はなにをしましょう?。。。。としばし考えて浮かんだのが、昔の圧縮解凍ネタとdbms_datapumpネタの組み合わせ。。。

再利用ネタ、その1は以下。
SQL Tuning Setのキャプチャから退避までのスクリプト(やっつけ
上記ネタの何を利用するかというと、DBMS_DATAPUMPパッケージ。このパッケージもディレクトリオブジェクトを利用するのでネタとしてはちょうどよさげ。

もう一つのネタはかなり古い(12年前?w)
Mac De Oracle (PL/SQL De COMPRESS)
Mac De Oracle (PL/SQL De UNCOMPRESS)
この辺りを使って、圧縮、解凍スクリプトも作っておいたら便利なんじゃないかとおもいます。

それから、kempe さんの記事も利用すればいろいろできそうですね。 プライベートサブネットのVPCエンドポイント経由でS3とやりとりすればパブリックネットワークは経由しなくて良さそうですし:)
RDSとS3でファイルのやり取りを行う

なお、11.2では、aclパラメータに.xml拡張子が必要ですが、12.1以降では必要ありません(いつ変わったw)が、12c以降では以下プロシージャは非推奨で下位互換向けにのこされています
11.2のマニュアルには aclパラメータには、.xml 拡張子が必要とは明記されていないのですが、意味不明なエラーが返されます。例のaclには.xml拡張子が付加されているのですが、パラメータの解説では一切触れられていないのでハマることがあるので注意しましょうね。

15:54:54 orcl@SYS> select version from v$instance;

VERSION
-----------------
11.2.0.4.0

15:54:00 orcl@SYS> !cat test.sql
begin
dbms_network_acl_admin.create_acl (
acl => 'test'
, description => 'acl create test'
, principal => 'SCOTT'
, is_grant => true
, privilege => 'connect'
);
end;
/

15:54:07 orcl@SYS> @test
begin
*
ERROR at line 1:
ORA-46059: Invalid ACL identifier specified
ORA-06512: at "SYS.DBMS_NETWORK_ACL_ADMIN", line 70
ORA-06512: at "SYS.DBMS_NETWORK_ACL_ADMIN", line 218
ORA-06512: at line 2

15:54:19 orcl@SYS> !cat test.sql
begin
dbms_network_acl_admin.create_acl (
acl => 'test.xml'
, description => 'acl create test'
, principal => 'SCOTT'
, is_grant => true
, privilege => 'connect'
);
end;
/

15:54:25 orcl@SYS> @test

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.02
15:54:27 orcl@SYS> exec dbms_network_acl_admin.drop_acl('test.xml');

PL/SQL procedure successfully completed.

dbms_network_acl_adminについては11.2と12.1で考慮はいるかもしれません。(非推奨プロシージャはいずれ廃止されるでしょうから)

というあたりも考慮してネタを作るかどうか。。。
その前に、ファイル、圧縮解凍だけのネタを作っておくか。。

ということで、次回へづつく。:)



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ
RDS Oracle 雑多なメモ#7 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月23日 (日)

RDS Oracle 雑多なメモ#7 / FAQ

メモの続きです。
前回は、utl_fileパッケージでcpっぽいことを行うスクリプトを作成しました。
今回は、ディレクトリオブジェクト以下のファイルにrmっぽい操作が行えるようなスクリプトを作ります :)

作成済みのtest_dirディレクトのファイルを再利用しています。

ディレクトリとファイルを指定してファイル削除!


17:11:54 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v1.txt file 2277 18-09-19
top100_tables_v2.txt file 2277 18-09-21
01/ directory 4096 18-09-21


17:12:37 rdsora121@BILL> @rm_file test_dir top100_tables_v2.txt
TEST_DIR/top100_tables_v2.txt removed.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.03

17:13:25 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v1.txt file 2277 18-09-19
01/ directory 4096 18-09-22

スクリプトは以下の通り。UTL_FILE.FREMOVEを呼び出しているだけの単純なスクリプトにしてあります
第一回目にも書きましたが、スクリプトは、EC2など、SQL*Plusを起動しているクライアント側に作成します。

17:14:51 rdsora121@BILL> !cat rm_file.sql
set verify off
set serveroutput on
BEGIN
UTL_FILE.FREMOVE (
location => UPPER('&1')
, filename => '&2'
);
DBMS_OUTPUT.PUT_LINE(UPPER('&1')||'/&2 removed.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE()||':'||SQLERRM());
END;
/

set serveroutput off
undefine 1
undefine 2
set verify on


次回へ続く


Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ
RDS Oracle 雑多なメモ#6 / FAQ




ところで、今年も秋葉原UDXで開催されたdb tech showcase 2018へ参加してきました。
今回、自分のセッションも司会もなしだったので、久々にゆっくりといろいろな方のセッションを聞くことがきたし、毎年このイベント合う多数のデータベースエンジニアとの交流もたのしくてたのしくてw それ以上表現のしようがないw
(ボキャ貧なのでごめんなさい、ごめんなさい)

そして、インサイトテクノロジーの社長を退かれた小幡さん、 ”おもしろった!” ありがとうございました。
おいしいみかんできたら、送ってね :)

| | | コメント (0) | トラックバック (0)

2018年9月22日 (土)

RDS Oracle 雑多なメモ#6 / FAQ

メモの続きです。
前回は、utl_fileパッケージでmvっぽいことを行うスクリプトを作成しました。
今回は、ディレクトリオブジェクト以下のファイルにcpっぽい操作が行えるようなスクリプトを作ります :)



ただ、 utl_file.fcopyってテキストファイルしかコピーできないんだよね。という制限付きです。いまのところ。
(バイナリ版は手作りで作ればできたような、。。。昔のネタ引っ張り出すかw)

ということで、utl_file.fcopyもdbms_transfer_fileパッケージも使わずにLOB系操作でバイナリファイルとテキストファイルをコピーするよう、cp_file.sqlスクリプトを作り変えました。

RDS Oracle 雑多なメモ#11 / FAQも合わせて参照ください。m(_ _)m




作成済みのtest_dirとhoge_dirディレクトリとファイルを再利用しています。
ざっくりめで書いたので複数ファイルの一括コピーや引数の指定方法などなど、改善したいところはあるわけですがw 適宜対応ということで。

同一ディレクトリでファイルコピー

00:44:59 SQL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v1.txt file 2277 18-09-19
01/ directory 4096 18-09-21

経過: 00:00:02.35
00:55:33 SQL> @cp_file test_dir top100_tables_v1.txt test_dir top100_tables_v2.txt

source file : test_dir/top100_tables_v1.txt
dest file : test_dir/top100_tables_v2.txt copied.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.04
00:57:42 SQL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v1.txt file 2277 18-09-19
top100_tables_v2.txt file 2277 18-09-21
01/ directory 4096 18-09-21

異なるディレクトリへ同一名でファイルコピー

01:01:55 SQL> @cp_file test_dir top100_tables_v2.txt hoge_dir top100_tables_v2.txt

source file : test_dir/top100_tables_v2.txt
dest file : hoge_dir/top100_tables_v2.txt copied.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.05
01:05:13 SQL> @ls_dir hoge_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v2.txt file 2277 18-09-21
02/ directory 4096 18-09-21

異なるディレクトリへ異なるファイル名でコピー

01:06:30 SQL> @cp_file test_dir top100_tables_v2.txt hoge_dir top100_tables_v3.txt

source file : test_dir/top100_tables_v2.txt
dest file : hoge_dir/top100_tables_v3.txt copied.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.04
01:06:47 SQL> @ls_dir hoge_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v2.txt file 2277 18-09-21
02/ directory 4096 18-09-21
top100_tables_v3.txt file 2277 18-09-21

スクリプトは以下の通り。


01:09:56 SQL> !cat cp_file.sql
set verify off
set serveroutput on format wrapped
BEGIN
UTL_FILE.FCOPY(
src_location => UPPER('&1')
,src_filename => '&2'
,dest_location => UPPER('&3')
,dest_filename => '&4'
);
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('source file : &1/&2');
DBMS_OUTPUT.PUT_LINE('dest file : &3/&4 copied.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE()||':'||SQLERRM());
END;
/

undefine 1
undefine 2
undefine 3
undefine 4
set verify on
set serveroutput off


次回へ続く



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ
RDS Oracle 雑多なメモ#5 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月21日 (金)

RDS Oracle 雑多なメモ#5 / FAQ

メモの続きです。
前回は、AWS提供のrdsadmin.rds_file_util.read_text_file関数でcatっぽいスクリプトを作っておきました。(改善したほうがよさげなところもありますがw)
今回は、ディレクトリオブジェクト以下のファイルにmvっぽい操作が行えるようなスクリプトを作ります。

これからの操作はファイルそのものに対しておこなうので、 わかりやすくする意味もあり新たにディレクトオブジェクトを作成して、ファイルを一作っておきます。

実験用ディレクトリオブジェクトの作成

00:49:07 rdsora121@BILL> @create_dir test_dir

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:02.04

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
TEST_DIR /rdsdbdata/userdirs/01

経過: 00:00:00.03


作成したディレクトリオブジェクト以下に適当なファイル(今回はテキストファイル)を作成

01:05:30 rdsora121@BILL> @query2file.sql

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.12

ここでは、”utl_file I/O” - この症状はあれの可能性が高いですね。でも紹介したUTL_FILEパッケージでディレクトオブジェクト以下にファイルを出力するサンプルを再利用してファイルを書き出しました。

01:06:02 rdsora121@BILL> !cat query2file.sql
DECLARE
cDIR_NAME CONSTANT VARCHAR2(30) := 'TEST_DIR';
cFILE_NAME CONSTANT VARCHAR2(128) := 'top100_tablenames__'||TO_CHAR(systimestamp, 'rrmmddhh24miss.ff')||'.txt';
cBufferSize CONSTANT BINARY_INTEGER := 32767;
cOpenMode CONSTANT VARCHAR2(2) := 'w';
fileHandle UTL_FILE.FILE_TYPE;
buffer VARCHAR2(32767);

cBulkReadLimit CONSTANT PLS_INTEGER := 324;
TYPE tBulkReadArray IS TABLE OF VARCHAR2(8192) INDEX BY BINARY_INTEGER;
bulkReadArray tBulkReadArray;
CURSOR cur_top100_tablenames IS
SELECT
table_name
FROM
dba_tables
ORDER BY
table_name
FETCH FIRST 100 ROWS ONLY
;
BEGIN
OPEN cur_top100_tablenames;
fileHandle := UTL_FILE.FOPEN(cDIR_NAME, cFILE_NAME, cOpenMode, cBufferSize);
LOOP
FETCH cur_top100_tablenames
BULK COLLECT INTO bulkReadArray
LIMIT cBulkReadLimit;

EXIT WHEN bulkReadArray.COUNT = 0;

buffer := NULL;
FOR i IN bulkReadArray.FIRST..bulkReadArray.LAST LOOP
buffer := buffer || bulkReadArray(i) || UTL_TCP.CRLF;
END LOOP;
UTL_FILE.PUT(fileHandle, buffer);
UTL_FILE.FFLUSH(fileHandle);
END LOOP;
UTL_FILE.FFLUSH(fileHandle);
UTL_FILE.FCLOSE(fileHandle);
CLOSE cur_top100_tablenames;
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fileHandle) THEN
UTL_FILE.FCLOSE(fileHandle);
END IF;

IF cur_top100_tablenames%ISOPEN THEN
CLOSE cur_top100_tablenames;
END IF;
RAISE;
END;
/

テキストファイルがディレクトリオブジェクト以下に作成されましった!

01:06:29 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tablenames__180919160551.113374000.txt file 2277 18-09-19
01/ directory 4096 18-09-19

念のため中身を確認

01:06:38 rdsora121@BILL> @cat_file test_dir top100_tablenames__180919160551.113374000.txt

TEXT
------------------------------------------------------------------------------------------
ACCESS$
ACLMV$
ACLMV$_REFLOG
ACLMVREFSTAT$


・・・中略・・・

AQ$_SUBSCRIBER_TABLE
AQ$_SYS$SERVICE_METRICS_TAB_G
AQ$_SYS$SERVICE_METRICS_TAB_H
AQ$_SYS$SERVICE_METRICS_TAB_I

100行が選択されました。

同一ディレクトリ内でファイル名を変更

01:29:47 rdsora121@BILL> @mv_file test_dir top100_tablenames__180919160551.113374000.txt test_dir top100_tables.txt

source file : test_dir/top100_tablenames__180919160551.113374000.txt
dest file : test_dir/top100_tables.txt moved.

PL/SQLプロシージャが正常に完了しました。

長いファイル名称を短く変更!

01:29:57 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables.txt file 2277 18-09-19
01/ directory 4096 18-09-19

ファイル名は同じままで、別ディレクトリへファイル移動

08:05:17 rdsora121@BILL> @create_dir hoge_dir

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:02.26

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
HOGE_DIR /rdsdbdata/userdirs/02

経過: 00:00:00.04
08:05:29 rdsora121@BILL> @mv_file test_dir top100_tables.txt hoge_dir top100_tables.txt

source file : test_dir/top100_tables.txt
dest file : hoge_dir/top100_tables.txt moved.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.04
08:06:27 rdsora121@BILL> @ls_dir hoge_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables.txt file 2277 18-09-19
02/ directory 4096 18-09-19

経過: 00:00:02.25
08:06:39 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
01/ directory 4096 18-09-19

ファイル名を変更して、ディレクトリ移動

08:08:22 rdsora121@BILL> @mv_file hoge_dir top100_tables.txt test_dir top100_tables_v1.txt

source file : hoge_dir/top100_tables.txt
dest file : test_dir/top100_tables_v1.txt moved.

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.02
08:09:00 rdsora121@BILL> @ls_dir hoge_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
02/ directory 4096 18-09-19

経過: 00:00:02.05
08:09:11 rdsora121@BILL> @ls_dir test_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
top100_tables_v1.txt file 2277 18-09-19
01/ directory 4096 18-09-19

経過: 00:00:02.05


スクリプトは以下の通り

01:30:20 rdsora121@BILL> !cat mv_file.sql
set verify off
set serveroutput on format wrapped
BEGIN
UTL_FILE.FRENAME(
src_location => UPPER('&1')
,src_filename => '&2'
,dest_location => UPPER('&3')
,dest_filename => '&4'
,overwrite => false
);
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('source file : &1/&2');
DBMS_OUTPUT.PUT_LINE('dest file : &3/&4 moved.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE()||':'||SQLERRM());
END;
/

undefine 1
undefine 2
undefine 3
undefine 4
set verify on
set serveroutput off

次回へ続く



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ
RDS Oracle 雑多なメモ#4 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月20日 (木)

RDS Oracle 雑多なメモ#4 / FAQ

メモの続きです。
前回は、AWS提供のrdsadmin.rds_file_util.listdir関数を利用したlsっぽいファイルリストスクリプトを作成しました。
今回は、同じくAWS提供のrdsadmin.rds_file_util.read_text_file関数でcatっぽいスクリプトを。

ディレクト名を確認し。。

05:10:47 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch

6行が選択されました。

経過: 00:00:00.02

ファイル名を確認してから。。

05:10:50 rdsora121@BILL> @ls_dir bdump

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
ORCL_ora_12254.trc file 4393 18-09-15
ORCL_ora_12254.trm file 202 18-09-15

・・・中略・・・

ORCL_ora_12841.trm file 71 18-09-17
trace/ directory 12288 18-09-17
ORCL_ora_12841.trc file 990 18-09-17
alert_ORCL.log file 138516 18-09-17
ORCL_mmon_11216.trm file 118 18-09-17
ORCL_mmon_11216.trc file 1503 18-09-17

279行が選択されました。

経過: 00:00:02.28

alertログファイルを覗いて見ましょう。

05:10:57 rdsora121@BILL> @cat_file bdump alert_ORCL.log

TEXT
-----------------------------------------------------------------------------------------------
Sat Sep 15 19:03:04 2018
Starting ORACLE instance (normal) (OS id: 12254)
Sat Sep 15 19:03:04 2018

・・・中略・・・

pga_aggregate_limit is 4809 MB

3376行が選択されました。

経過: 00:00:03.09

スクリプトは以下のとおり。
ん〜。ざっくり作りすぎたかもw Top Nクエリもできるようにしたほうがいいかもねぇ〜と。思わなくもないけど、とりあえずこれで。

ちなみに、ディレクトリ名はUPPER()関数で強制的に大文字変換、ファイル名は入力されたファイル名をそのまま利用するようにしています。

05:12:04 rdsora121@BILL> !cat cat_file.sql
set verify off
set long 100000
set longchunk 100000
SELECT
text
FROM
TABLE (
rdsadmin.rds_file_util.read_text_file (
p_directory => UPPER('&1')
, p_filename => '&2'
)
)
;

undefine 1
undefine 2
set verify on

次回へ続く



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ
RDS Oracle 雑多なメモ#3 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月19日 (水)

RDS Oracle 雑多なメモ#3 / FAQ

メモの続きです。
これまでに、ディレクトリ作成、削除、ディレクトリリストスクリプトを作成しました。

残る使用頻度の高そうなスクリプトは、ディレクトリオブジェクト以下にあるファイル操作スクリプト。
OSレイヤーに入れないRDS Oracleで、ディレクトリオブジェクト以下にあるファイルの操作に困るのは目に見えてるので。。。w

RDS Oracleでは、rdsadmin.rds_file_utilパッケージが提供されていますが、提供されている関数は以下の通り。
どう見ても全ての操作を行うに不十分なのですが、よーく思い出してみましょう。
ファイルの削除などの操作は、Oracle Databaseに昔からあるビルトインパッケージ、utl_fileパッケージを利用できちゃいそうですよね!!
ということで、これまた、いちいちタイプするのは面倒なので事前に作成しておいたほうが楽なはず。。。

13:40:16 rdsora121@BILL> desc rdsadmin.rds_file_util
FUNCTION LISTDIR RETURNS RDS_FILE_LIST_T
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_DIRECTORY VARCHAR2 IN
FUNCTION READ_TEXT_FILE RETURNS RDS_TEXT_LIST_T
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_DIRECTORY VARCHAR2 IN
P_FILENAME VARCHAR2 IN

まず、上記AWSから提供されているrdsadmin.rds_file_util.listdir関数を利用したスクリプトを作成しておきます。

13:53:44 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch

6行が選択されました。

経過: 00:00:00.03

ファイルの無い空のディレクトリは以下のようにリストされるか〜。
へぇ〜。

13:53:52 rdsora121@BILL> @ls_dir data_pump_dir

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
datapump/ directory 4096 18-09-17

経過: 00:00:02.25

ディレクトオブジェクトへの読み込み権限がないと以下のようなエラーが返ります。

13:54:07 rdsora121@BILL> @ls_dir opatch_log_dir
rdsadmin.rds_file_util.listdir(UPPER('OPATCH_LOG_DIR'))
*
行5でエラーが発生しました。:
ORA-20900: Directory OPATCH_LOG_DIR does not exist or no privileges.
ORA-06512: "SYS.RDS_SYS_UTIL", 行147
ORA-06512: "SYS.RDS_SYS_UTIL", 行161
ORA-06512: "RDSADMIN.RDS_FILE_UTIL", 行38


経過: 00:00:00.05


対象ディレクトリの読み取り権限が付与され、ディレクトリオブジェクト以下になんらかのファイルがあれば、以下のようなリリストが返ります。
こんな感じにリストされるのな。:)

13:54:42 rdsora121@BILL> @ls_dir adump

FILENAME TYPE FILESIZE MTIME
---------------------------------------------------------------------- ---------- ---------- --------
ORCL_ora_12254_20180915190307342858143795.aud file 805 18-09-15

...中略...

ORCL_ora_12687_20180917043000205002143795.aud file 1549 18-09-17
ORCL_ora_14013_20180917044500213934143795.aud file 1549 18-09-17
audit/ directory 16384 18-09-17

146行が選択されました。

経過: 00:00:02.17


スクリプトは以下のとおり。rdsadmin.rds_file_util.listdir関数は表関数なので以下のような使い方:)

13:55:17 rdsora121@BILL> !cat ls_dir.sql
set verify off
col filename for a70
SELECT
*
FROM
TABLE (
rdsadmin.rds_file_util.listdir(UPPER('&1'))
)
ORDER BY
mtime
;

undefine 1
set verify off


次回へ続く



Previously on Mac De Oracle

RDS Oracle 雑多なメモ#1 / FAQ
RDS Oracle 雑多なメモ#2 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月18日 (火)

RDS Oracle 雑多なメモ#2 / FAQ

メモの続き。

前回、ディレクトリリストとディレクトリオブジェクト作成スクリプトを作ったので、作り忘れてたディレクトリオブジェクト削除スクリプトを。

実行例
前回作成しておいたlist_dir.sqlでディレクトリ名を確認しながら "TEST"ディレクトリを削除しています。

13:30:25 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch
TEST /rdsdbdata/userdirs/01

7行が選択されました。

経過: 00:00:00.02
13:30:29 rdsora121@BILL> @drop_dir test

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.02
13:30:34 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch

6行が選択されました。

スクリプトは以下の通り、rdsadmin.rdsadmin_util.drop_directoryを実行しているだけです:)

13:29:45 rdsora121@BILL> !cat drop_dir.sql
set verify off
exec rdsadmin.rdsadmin_util.drop_directory(p_directory_name => UPPER('&1'));

undefine 1
set verify on

次回へ続く



Previously on Mac De Oracle


RDS Oracle 雑多なメモ#1 / FAQ

| | | コメント (0) | トラックバック (0)

2018年9月17日 (月)

RDS Oracle 雑多なメモ#1 / FAQ

さて、さて、ご無沙汰しておりましたw
メモも溜まってきたので、そろそろ RDS Oracle関連の雑多なメモを。。。

新たなスタートをきって1.5ヶ月目にしてRDS Oracleに触れなければいけない状況となり、オンプレのOracleの使い勝手とことなる部分にハマりつつなんとか乗り切ったのでときのメモです。
(みなさん通る道w)

ディレクトリオブジェクト周りはオンプレのOracleと勝手が違いOSレイヤーには入れないので、rdsadmin.rdsadmin_utilパッケージに慣れておく必要があり。
さらに、ファイル操作についても、rdsadmin.rds_file_utilパッケージで提供されている操作は一部なので、utl_fileパッケージを併用しないと削除、コピー、改名などもできません。
また、ダンプファイルの圧縮解答もOSレイヤーでは実行できないので、utl_compressパッケージを利用する必要もありそう。


Oracle DB インスタンスの一般的な DBA システムタスク
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.Oracle.CommonDBATasks.System.html

Oracle DB インスタンスの一般的な DBA ログタスク
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.Oracle.CommonDBATasks.Log.html

Oracle DB インスタンスの一般的な DBA データベース
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.Oracle.CommonDBATasks.Database.html

Oracle DB インスタンスの一般的な DBA のその他のタスク
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.Oracle.CommonDBATasks.Misc.html


ということで、いちいちexecute ほげほげってタイプするのもめんどくさいのでスクリプト化して、楽しましょう。という自分用メモシリーズです。
今後どれくらいRDS Oracleを利用する機会があるのかまったく予想できませんが、備えあれば。。。ということで ;)

まず、RDSADMIN.RDSADMIN_UTILパッケージをdescribeしてみた。
詳細は前述のマニュアルを見てもらうとして、頻繁に利用しそうなのは createdirectoryプロシージャ、drop_directoryプロシージャ、それに rds_versionファンクション
あたりかなぁ。個人的には。
(他のものも頻繁に使いそうなら、スクリプト化しておくと便利かだと思います。)

22:55:55 rdsora121@BILL> desc rdsadmin.rdsadmin_util
PROCEDURE ADD_LOGFILE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
BYTES BINARY_INTEGER IN DEFAULT
PROCEDURE ADD_LOGFILE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_SIZE VARCHAR2 IN
PROCEDURE ALTER_DB_TIME_ZONE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_NEW_TZ VARCHAR2 IN
PROCEDURE ALTER_DEFAULT_EDITION
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
EDITION_NAME VARCHAR2 IN
PROCEDURE ALTER_DEFAULT_TABLESPACE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
TABLESPACE_NAME VARCHAR2 IN
PROCEDURE ALTER_DEFAULT_TEMP_TABLESPACE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
TABLESPACE_NAME VARCHAR2 IN
PROCEDURE ALTER_SUPPLEMENTAL_LOGGING
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_ACTION VARCHAR2 IN
P_TYPE VARCHAR2 IN DEFAULT
PROCEDURE CHECKPOINT
PROCEDURE CREATE_DIRECTORY
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_DIRECTORY_NAME VARCHAR2 IN
PROCEDURE DISABLE_DISTR_RECOVERY
PROCEDURE DISCONNECT
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
SID NUMBER IN
SERIAL NUMBER IN
METHOD VARCHAR2 IN DEFAULT
PROCEDURE DROP_DIRECTORY
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_DIRECTORY_NAME VARCHAR2 IN
PROCEDURE DROP_LOGFILE
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
GRP BINARY_INTEGER IN
PROCEDURE ENABLE_DISTR_RECOVERY
PROCEDURE FLUSH_BUFFER_CACHE
PROCEDURE FLUSH_SHARED_POOL
PROCEDURE FORCE_LOGGING
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_ENABLE BOOLEAN IN DEFAULT
PROCEDURE GRANT_APEX_ADMIN_ROLE
PROCEDURE GRANT_SYS_OBJECT
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_OBJ_NAME VARCHAR2 IN
P_GRANTEE VARCHAR2 IN
P_PRIVILEGE VARCHAR2 IN DEFAULT
P_GRANT_OPTION BOOLEAN IN DEFAULT
PROCEDURE KILL
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
SID NUMBER IN
SERIAL NUMBER IN
METHOD VARCHAR2 IN DEFAULT
FUNCTION RDS_VERSION RETURNS VARCHAR2
PROCEDURE RENAME_GLOBAL_NAME
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_NEW_GLOBAL_NAME VARCHAR2 IN
PROCEDURE RESTRICTED_SESSION
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_ENABLE BOOLEAN IN DEFAULT
PROCEDURE REVOKE_SYS_OBJECT
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
P_OBJ_NAME VARCHAR2 IN
P_REVOKEE VARCHAR2 IN
P_PRIVILEGE VARCHAR2 IN DEFAULT
PROCEDURE SET_CONFIGURATION
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
NAME VARCHAR2 IN
VALUE VARCHAR2 IN
PROCEDURE SHOW_CONFIGURATION
引数名 タイプ In/Out Default?
------------------------------ ----------------------- ------ --------
NAME VARCHAR2 IN DEFAULT
PROCEDURE SWITCH_LOGFILE

22:56:11 rdsora121@BILL>

ということで、とりあえず、DBのバージョンと、RDSのバージョンを一括取得するスクリプトを作った。
実行例

23:24:14 rdsora121@BILL> @show_version

VERSION
------------------------------
12.1.0.2.0


RDS_VERSION
------------------------------
1.2

スクリプトは以下の通り。
rdsadmin.rdsadmin_util.rds_versionファンクションを利用しています。
(ちなみに、スクリプトはクライアントとなるEC2などに作成することになります)

23:24:24 rdsora121@BILL> !cat show_version.sql
col version for a30
col rds_version for a30

SELECT
version
FROM
v$instance
;

SELECT
rdsadmin.rdsadmin_util.rds_version AS rds_version
FROM
dual
;


次は、ディレクトリ操作関連の便利スクリプト群の作成。
まずは、どんなディレクトリオブジェクトが存在するのかパスも含めて確認できると嬉しいですよね。RDS Oracleと言えど。

実行例

23:36:26 rdsora121@BILL> @list_dir

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
ADUMP /rdsdbdata/log/audit
BDUMP /rdsdbdata/log/trace
DATA_PUMP_DIR /rdsdbdata/datapump
OPATCH_INST_DIR /rdsdbbin/oracle/OPatch
OPATCH_LOG_DIR /rdsdbbin/oracle/QOpatch
OPATCH_SCRIPT_DIR /rdsdbbin/oracle/QOpatch

6行が選択されました。

経過: 00:00:00.03

スクリプトは以下のとおり。
dba_directoriesを問い合わせているだけの簡単なお仕事 :)

23:36:31 rdsora121@BILL> !cat list_dir.sql
col directory_name for a40
col directory_path for a70
SELECT
directory_name
,directory_path
FROM
dba_directories
ORDER BY
directory_name
;


お次は、ディレクトリオブジェクトの作成スクリプト。

23:41:24 rdsora121@BILL> @create_dir test

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:02.12

DIRECTORY_NAME DIRECTORY_PATH
---------------------------------------- ----------------------------------------------------------------------
TEST /rdsdbdata/userdirs/01

経過: 00:00:00.06

スクリプトは以下のとおり。
rdsadmin.rdsadmin_util.create_directoryプロシージャを利用してRDS Oracleのディレクトリオブジェクトを作成後、dba_directoriesビューから作成したディレクトリオプジェクトを問い合わせています。

23:41:36 rdsora121@BILL> !cat create_dir.sql
set verify off
col directory_name for a40
col directory_path for a70

exec rdsadmin.rdsadmin_util.create_directory(p_directory_name => UPPER('&1'));

SELECT
directory_name
,directory_path
FROM
dba_directories
WHERE
directory_name = UPPER('&1')
ORDER BY
directory_name
;

undefine 1
set verify on


次回に続く

| | | コメント (0) | トラックバック (0)

2018年8月19日 (日)

FAQ::位置情報による時間帯の自動調整 - iPhone/Apple Watch/iPad/Mac

ひさびさのエントリーでございます。

試用期間終わり、ひとまず生き延びておりますw

そして、早々に海外かつ人生はつの海外出張ということで、
Mac De Oracleの中の人として、ここは声を大きくして言っておきたいw

MacとiPhoneとApple Watchって

”国を跨いでも現地時間に自動調整してくれちゃうのは超便利!”

iPhone/Apple Watchを持ってる方は大抵 時間の自動調整はオンになっている方は多いかなーとは思いますが、意外に忘れてたりするのがMac側だったりします。

ということで、FAQとして書いておきますね。

まず、iPhone/Apple Watchについては、iPhone側で自動調整が有効かされていれば、Apple Watchもそれに同期して調整されます。 
日本から海外のどこかに到着すると自動的にに同期してくれちゃう。

20180819_04458


本題のMac側ですが、「システム環境設定」> 「セキュリティーとプライバシー」 > 「プライバシー」タブをクリックしたら以下の通り。
「日付の時間」の「時間帯」タブを設定して「現在の位置情報に基づいて、時間帯を自動的に設定」チェックボックスをチェックするだけ!

20180819_04954


これだけで、海外出張時にも自動的に現地時間に時間が同期されるので、Mac触ってて時間が日本の時間のままで混乱しちゃうなんてことも避けられまっす!!

:)


ついでにiPhoneで世界時計を設定しておくと、さらに便利だったりします。この設定は、Apple Watchへも連携されるのでApple Watchでも確認しやすく重宝します。
20180819_10442

| | | コメント (0) | トラックバック (0)

2018年4月18日 (水)

Dead Connection Detection (FAQ)

最近あまりお目にかかったことなく忘れかけてたのと、12cでは改善されてたのでメモ

12cだと、11g以前に比べ、Dead Connection Detection (DCD) までの時間が大幅に短縮されているよ。
というホワイトペーパー

Dead Connection Detection
http://www.oracle.com/technetwork/database/enterprise-edition/oraclenetdcd-2179641.pdf

| | | コメント (0) | トラックバック (0)

2018年4月 8日 (日)

textフォーマットのAWRレポートの各セクションをcsvファイル化する際のポイント(メモ)

土日は土日で子供とマイクラのマルチプレイ、平日は平日でなんだか余裕ないぞー。(なんだこれw) ということで、
Temp落ちネタはFBのTLコピペすれば終わりそうなんだが。。。w
再び、別のネタ。気分転換にはなってないけどwww


textフォーマットのAWRレポートをcsvファイル化する際、各セクションの最終行判定が面倒だったのでメモ(12.2版)
(どんな言語で作ってもこの部分、レポートの文字コード、それに、日付書式でハマることが多い印象w、それに、12.2では、 Timeはsecのみではなく、us/msなど時間の単位の扱いが追加されたので変換を忘れるとハマる)

例えば、Top 10 Foreground Events by Total Wait Timeの場合、
"Top 10 Foreground Events by Total Wait Time"というセクションヘッダーから、次のセクションヘッダーの"Wait Classes by Total Wait Time"の直前の行までを、Top 10 Wait Eventとしてcsv化する。

この場合、”Wait Classes by Total Wait Time”の直前の行は、2バイトで<FF><LF>なので、<FF><LF>の行が"Top 10 Foreground Events by Total Wait Time"セクションの終わり。

なのだが、"Top 10 Foreground Events by Total Wait Time"セクションの直前の行は、1バイトで<LF>のみとなり、 <FF>を含まないケースもある.....
各セクションの最終行の判定は、2バイトで<FF><LF>または、1バイトで<LF> で判定する必要がある。と。

TextフォーマットのAWRレポートの例
viでTextフォーマットのAWRレポートを開いて、eol部分を!で表示。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
Buffer Nowait %: 99.99 Redo NoWait %: 100.00!
Buffer Hit %: 97.86 In-memory Sort %: 100.00!
Library Hit %: 90.46 Soft Parse %: 93.77!
Execute to Parse %: 62.59 Latch Hit %: 99.82!
Parse CPU to Parse Elapsd %: 75.51 % Non-Parse CPU: 89.49!
Flash Cache Hit %: 0.00!
!
Top 10 Foreground Events by Total Wait Time!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
Total Wait Avg % DB Wait!
Event Waits Time (sec) Wait time Class!
------------------------------ ----------- ---------- --------- ------ --------!
DB CPU 3.5 167.2!
library cache: mutex X 81 .1 1.15ms 4.4 Concurre!
PX Deq: Slave Session Stats 229 .1 231.53us 2.5 Other!
log file sync 10 0 2.11ms 1.0 Commit!
library cache: bucket mutex X 3 0 4.96ms .7 Concurre!
db file sequential read 24 0 583.13us .7 User I/O!
PGA memory operation 600 0 11.39us .3 Other!
enq: BF - allocation contentio 6 0 390.50us .1 Other!
latch: call allocation 3 0 240.67us .0 Other!
latch: shared pool 3 0 226.33us .0 Concurre!
^L!
Wait Classes by Total Wait Time!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
Avg Avg!
Total Wait Wait % DB Active!
Wait Class Waits Time (sec) Time time Sessions!
---------------- ---------------- ---------------- ---------- ------ --------!
DB CPU 4 167.2 0.0!
System I/O 3,469 3 734.15us 120.9 0.0!
Other 3,482 2 689.81us 114.0 0.0!
User I/O 1,261 1 421.44us 25.2 0.0!
Concurrency 95 0 1.20ms 5.4 0.0!
Commit 14 0 2.40ms 1.6 0.0!
Configuration 1 0 108.00us .0 0.0!
!
Host CPU!
~~~~~~~~ Load Average!
CPUs Cores Sockets Begin End %User %System %WIO %Idle!
----- ----- ------- --------- --------- --------- --------- --------- ---------!
12 12 1 0.14 0.12 0.1 0.0 0.0 99.8!
!
Instance CPU!
~~~~~~~~~~~~!
% of total CPU for Instance: 0.1!
% of busy CPU for Instance: 82.3!
%DB time waiting for CPU - Resource Mgr: 0.0!
:set listchars=eol:!

おまけ。上記、テキスト部分のバイナリダンプ
<LF>部分の確認
20180408_215624


<FF><LF>部分の確認
20180408_215736




Textモードで出力したAWRレポートをCSV化しようなんて、めんどくさい事しないほうがいいよね。
悪いことは言わないから、htmlで出力してCSV化した方が楽だからw 絶対!
ちなみに、わたしは、Top 10 wait eventセクションだけしかやりませんでしたw(時間ないし)

| | | コメント (0) | トラックバック (0)

2018年3月29日 (木)

Oracle JavaScript Extention Toolkit(JET) de ブラウザの性能比較

なんだか余裕ないので、ちょいと、息抜きw

Oracle JavaScript Extention Toolkit(JET)
Oracle JET

さあ、あなたのIE11とそれ以外のモダンブラウザで 10,000 nodesを描画してPerformanceを比較してみましょう!。
Oracle JET > Visualizations > Diagram > Performance

以下、Safari 11.3 (macOS High Sierra/2 x 2.4 GHz 6-Core Intel Xeon/Mac Pro Mid(2012))
20180329_225621


こちらも、どうぞ。
Edgeも出たし、JetStreamも出たので久々にブラウザのベンチマーク

| | | コメント (0) | トラックバック (0)

2018年3月18日 (日)

Temp落ち #9 - 自動PGA管理で_pga_max_sizeと戯れたPGAサイズって本当に使えるのか? 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理下で、_pga_max_size隠しパラメータとpga_aggregate_targetを最大値に設定するした場合、global memory boundは
最大で、約839GBまで増加することがわかりました。

ただ、これは、内部的なパラメータ上の話。


今日は、実際にソートやハッシュ結合を行なわせ、PGAのSQL Work Areaサイズがどこまで利用できるものなのか確認してみることにします。

さて、どういう結末になりますやら(w

随分前から同じようなことを試している方はいますし....:)

なぜ今そこをdiggingしちゃってるのか? って?

メモリーたっぷりあるのに、何故Temp落ち? が話題になったからに決まってるじゃないですかw

 

まず、デフォルト設定では最大サイズだった 1GB を超えられるか? の確認

注意) 事前にpga_aggregate_target および _pga_max_size をそれぞれ 4TB - 1に設定し、パラメータの上では、global memory boundが約839GBにしてあります。

なお、Temp落ちの確認データやスクリプトはTemp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認のエントリーを参照のこと。

ソートやハッシュ結合は2GB程度になるように調整しています。
2GBのソート

ORCL@SCOTT> @auto_sortwk2gb_optimal.sql

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C750000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 34 | +2 | 1 | 1M | | | . | 5.26 | Cpu (1) |
| 1 | SORT ORDER BY | | 1M | 553K | 34 | +2 | 1 | 1M | | | 2GB | 26.32 | Cpu (5) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90829 | 17 | +1 | 1 | 1M | 2637 | 3GB | . | 68.42 | Cpu (3) |
| | | | | | | | | | | | | | direct path read (10) |
========================================================================================================================================================

2GBのハッシュ結合

ORCL@SCOTT> @auto_hashwk2gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C084000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
===========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
===========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 37 | +2 | 1 | 840K | | | . | 4.35 | Cpu (1) |
| 1 | HASH JOIN | | 843K | 351K | 37 | +2 | 1 | 840K | | | 2GB | 26.09 | Cpu (5) |
| | | | | | | | | | | | | | PGA memory operation (1) |
| 2 | TABLE ACCESS FULL | M1 | 843K | 90828 | 15 | +1 | 1 | 840K | 2633 | 3GB | . | 52.17 | Cpu (1) |
| | | | | | | | | | | | | | direct path read (11) |
| 3 | TABLE ACCESS FULL | M2 | 846K | 90581 | 23 | +16 | 1 | 840K | 2619 | 3GB | . | 17.39 | Cpu (2) |
| | | | | | | | | | | | | | direct path read (2) |
===========================================================================================================================================================

ソートやハッシュ結合は4GB程度になるように調整しています。
見ての通り、ソートは4GBのメモリー内ソートですが、ハッシュ結合は、2GBまで使ったところで Temp落ち! (こんなもんなんですよ。実は!)
4GBのソート

ORCL@SCOTT> @auto_sortwk4gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C085000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C085000'
16 ORDER BY
17* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-----------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=2686238998)
============================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
============================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 40 | +2 | 1 | 2M | | | . | 27.78 | Cpu (5) |
| 1 | SORT ORDER BY | | 2M | 182K | 41 | +1 | 1 | 2M | | | 4GB | 38.89 | Cpu (6) |
| | | | | | | | | | | | | | PGA memory operation (1) |
| 2 | UNION-ALL | | | | 12 | +2 | 1 | 2M | | | . | | |
| 3 | TABLE ACCESS FULL | M1 | 855K | 90828 | 6 | +2 | 1 | 850K | 2633 | 3GB | . | 22.22 | Cpu (2) |
| | | | | | | | | | | | | | direct path read (2) |
| 4 | TABLE ACCESS FULL | M1 | 855K | 90828 | 6 | +8 | 1 | 850K | 2633 | 3GB | . | 11.11 | Cpu (2) |
============================================================================================================================================================

4GBのハッシュ結合 最大3GBのPGAが消費されていますが、Temp落ちしないサイズ、つまり、optimalで処理する場合には、最大2GBが最大サイズとなっています。以下を3GBのハッシュ結合にすると、2GBまでPGAを消費し、一時表領域が3GB利用されます。

ORCL@SCOTT> @auto_hashwk4gb_optimal.sql
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=3532417599)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 216 | +2 | 1 | 3M | | | | | . | . | 4.49 | Cpu (7) |
| 1 | HASH JOIN | | 2M | 670K | 217 | +1 | 1 | 3M | 18091 | 4GB | 18091 | 4GB | 3GB | 4GB | 79.49 | Cpu (87) |
| | | | | | | | | | | | | | | | | direct path read temp (19) |
| | | | | | | | | | | | | | | | | direct path write temp (18) |
| 2 | VIEW | | 2M | 182K | 9 | +2 | 1 | 2M | | | | | . | . | | |
| 3 | UNION-ALL | | | | 9 | +2 | 1 | 2M | | | | | . | . | | |
| 4 | TABLE ACCESS FULL | M1 | 752K | 90828 | 5 | +2 | 1 | 750K | 2633 | 3GB | | | . | . | 1.92 | Cpu (3) |
| 5 | TABLE ACCESS FULL | M1 | 752K | 90828 | 4 | +7 | 1 | 750K | 2633 | 3GB | | | . | . | 1.92 | Cpu (3) |
| 6 | VIEW | | 2M | 181K | 78 | +99 | 1 | 2M | | | | | . | . | 0.64 | Cpu (1) |
| 7 | UNION-ALL | | | | 78 | +99 | 1 | 2M | | | | | . | . | | |
| 8 | TABLE ACCESS FULL | M2 | 759K | 90581 | 50 | +92 | 1 | 750K | 2618 | 3GB | | | . | . | 9.62 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (13) |
| 9 | TABLE ACCESS FULL | M2 | 759K | 90581 | 36 | +141 | 1 | 750K | 2618 | 3GB | | | . | . | 1.92 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=========================================================================================================================================================================================

では、最後に
ソートやハッシュ結合のサイズが8GB程度ならどうでしょうか?
結果は、ソートは4GBを使い切ったのち、Temp落ち、ハッシュ結合は、やはり、3GBまで利用したのちTemp落ちでした。なんと!(ちょっとわざとらしいリアクションしてすみませんw)

8GBのソート

ORCL@SCOTT> @auto_sortwk8gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C090000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C090000'
16 UNION ALL
17 SELECT *
18 FROM
19 m1 m13
20 WHERE
21 id <= 'C090000'
22 UNION ALL
23 SELECT *
24 FROM
25 m1 m14
26 WHERE
27 id <= 'C090000'
28 ORDER BY
29* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=946172832)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 279 | +2 | 1 | 4M | | | | | . | . | 5.79 | Cpu (6) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| | | | | | | | | | | | | | | | | local write wait (7) |
| 1 | SORT ORDER BY | | 4M | 363K | 279 | +2 | 1 | 4M | 38603 | 8GB | 32072 | 8GB | 4GB | 8GB | 84.30 | Cpu (49) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| | | | | | | | | | | | | | | | | direct path read temp (28) |
| | | | | | | | | | | | | | | | | direct path write temp (126) |
| 2 | UNION-ALL | | | | 176 | +2 | 1 | 4M | | | | | . | . | | |
| 3 | TABLE ACCESS FULL | M1 | 907K | 90828 | 16 | +1 | 1 | 900K | 2633 | 3GB | | | . | . | 4.55 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (9) |
| 4 | TABLE ACCESS FULL | M1 | 907K | 90828 | 8 | +17 | 1 | 900K | 2633 | 3GB | | | . | . | 2.48 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 5 | TABLE ACCESS FULL | M1 | 907K | 90828 | 79 | +26 | 1 | 900K | 2633 | 3GB | | | . | . | 2.48 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 6 | TABLE ACCESS FULL | M1 | 907K | 90828 | 72 | +106 | 1 | 900K | 2633 | 3GB | | | . | . | 0.41 | Cpu (1) |
=========================================================================================================================================================================================

8GBのハッシュ結合

ORCL@SCOTT> @auto_hashwk8gb_optimal.sql

・・・中略・・・

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 (
10 SELECT * FROM m1 m11
11 UNION ALL
12 SELECT * FROM m1 m12
13 UNION ALL
14 SELECT * FROM m1 m13
15 ) m1
16 INNER JOIN
17 (
18 SELECT * FROM m2 m21
19 UNION ALL
20 SELECT * FROM m2 m22
21 UNION ALL
22 SELECT * FROM m2 m23
23 ) m2
24 ON
25 m1.id = m2.id
26 AND m1.rev# = m2.rev#
27 WHERE
28* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=2506347387)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 350 | +2 | 1 | 7M | | | | | . | . | 9.18 | Cpu (18) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| 1 | HASH JOIN | | 5M | 1M | 350 | +2 | 1 | 7M | 31032 | 7GB | 31032 | 7GB | 3GB | 8GB | 67.15 | Cpu (81) |
| | | | | | | | | | | | | | | | | direct path read temp (36) |
| | | | | | | | | | | | | | | | | direct path write temp (22) |
| 2 | VIEW | | 2M | 272K | 27 | +2 | 1 | 2M | | | | | . | . | | |
| 3 | UNION-ALL | | | | 27 | +2 | 1 | 2M | | | | | . | . | 0.97 | Cpu (2) |
| 4 | TABLE ACCESS FULL | M1 | 752K | 90828 | 16 | +1 | 1 | 750K | 2633 | 3GB | | | . | . | 7.25 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (12) |
| 5 | TABLE ACCESS FULL | M1 | 752K | 90828 | 5 | +18 | 1 | 750K | 2633 | 3GB | | | . | . | 1.45 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 6 | TABLE ACCESS FULL | M1 | 752K | 90828 | 7 | +23 | 1 | 750K | 2633 | 3GB | | | . | . | 1.93 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (2) |
| 7 | VIEW | | 2M | 272K | 130 | +97 | 1 | 2M | | | | | . | . | | |
| 8 | UNION-ALL | | | | 130 | +97 | 1 | 2M | | | | | . | . | 0.48 | Cpu (1) |
| 9 | TABLE ACCESS FULL | M2 | 759K | 90581 | 47 | +88 | 1 | 750K | 2618 | 3GB | | | . | . | 4.83 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (7) |
| 10 | TABLE ACCESS FULL | M2 | 759K | 90581 | 41 | +134 | 1 | 750K | 2618 | 3GB | | | . | . | 4.35 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 11 | TABLE ACCESS FULL | M2 | 759K | 90581 | 53 | +174 | 1 | 750K | 2618 | 3GB | | | . | . | 2.42 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (4) |
=========================================================================================================================================================================================

 

 

これ、Linuxのカーネルパラメータのvm.max_map_count

/proc/sys/vm/max_map_count

65530

が絡んでるよねという話と、隠しパラメータの realfree_heap関連のパラメータでも調整できそうだよね。という話はあるんですが...それはPL/SQLのはなし...で


_realfree_heap_pagesize 65536 TRUE
_use_realfree_heap TRUE TRUE

とりあえず、
vm/max_map_countを196608

にして
_realfree_heap_pagesize=65536 や
_realfree_heap_pagesize=256K

などとして戯れてみましたが

ソートやハッシュ結合で利用可能なサイズは、それらで制御できるものでもありません。いまのところ。

ORCL@SCOTT> @auto_sortwk8gb_optimal.sql

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ------------- ------------ ----------
global memory bound 879609302016 bytes 0

/proc/sys/vm/max_map_count
196608

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C090000'
10 UNION ALL
11 SELECT *
12 FROM
13 m1 m12
14 WHERE
15 id <= 'C090000'
16 UNION ALL
17 SELECT *
18 FROM
19 m1 m13
20 WHERE
21 id <= 'C090000'
22 UNION ALL
23 SELECT *
24 FROM
25 m1 m14
26 WHERE
27 id <= 'C090000'
28 ORDER BY
29* 1, 2
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

SQL Plan Monitoring Details (Plan Hash Value=946172832)
=========================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=========================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 295 | +2 | 1 | 4M | | | | | . | . | 3.38 | Cpu (8) |
| 1 | SORT ORDER BY | | 4M | 363K | 297 | +1 | 1 | 4M | 38603 | 8GB | 38600 | 8GB | 4GB | 8GB | 91.56 | Cpu (43) |
| | | | | | | | | | | | | | | | | direct path read temp (12) |
| | | | | | | | | | | | | | | | | direct path write temp (162) |
| 2 | UNION-ALL | | | | 205 | +2 | 1 | 4M | | | | | . | . | 0.84 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M1 | 907K | 90828 | 6 | +2 | 1 | 900K | 2633 | 3GB | | | . | . | 2.11 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (4) |
| 4 | TABLE ACCESS FULL | M1 | 907K | 90828 | 7 | +8 | 1 | 900K | 2633 | 3GB | | | . | . | 0.42 | Cpu (1) |
| 5 | TABLE ACCESS FULL | M1 | 907K | 90828 | 100 | +14 | 1 | 900K | 2633 | 3GB | | | . | . | 0.84 | Cpu (2) |
| 6 | TABLE ACCESS FULL | M1 | 907K | 90828 | 94 | +113 | 1 | 900K | 2633 | 3GB | | | . | . | 0.84 | Cpu (2) |
=========================================================================================================================================================================================

ちなみに、PL/SQLで巨大なPGAメモリーを利用する場合は、しっかり9GBとか...やばい状態になったら、pga_aggregate_limitで抑えてくれると思いますが.....

[oracle@localhost ˜]$ ulimit -v -m
virtual memory (kbytes, -v) unlimited
max memory size (kbytes, -m) unlimited

 

orcl12c@SYS> @show_param

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
_pga_max_size 4398046511103 FALSE
_realfree_heap_pagesize 65536 TRUE
_use_realfree_heap TRUE TRUE
pga_aggregate_limit 0 FALSE
pga_aggregate_target 4398046511103 FALSE


/proc/sys/vm/max_map_count
65530
PGAを大量に消費するスクリプトは以下のブログを参考に
Max PGA Research: Check that a process can allocate a large volume of memory / Yury's Blog

ORCL@SCOTT> @plsql_pga8gb 8192
old 2: c_count number := 1024*&1;
new 2: c_count number := 1024*8192;

PL/SQL procedure successfully completed.
実際に確保されたPGAサイズなど。。。
ORCL@SCOTT> r
1 select
2 vss.value/1024/1024/1024 "GB"
3 ,vsn.name
4 ,vss.sid
5 from
6 v$sesstat vss
7 inner join v$statname vsn
8 on
9 vss.statistic# = vsn.statistic#
10 and vss.con_id = vsn.con_id
11 and (vsn.name like '%pga%' or vsn.name like '%uga%')
12 where
13 sid IN (select sid from v$session where username='SCOTT')
14 order by
15* sid,name

GB NAME SID
---------- ---------------------------------------------------------------- ----------
9.49698084 session pga memory 321
9.49698084 session pga memory max 321
1.48221001 session uga memory 321
3.99991362 session uga memory max 321

[oracle@localhost ˜]$ free -h
total used free shared buff/cache available
Mem: 22G 1.0G 9.6G 11G 11G 10G
Swap: 4.0G 8.7M 4.0G

・・・中略・・・

[oracle@localhost ˜]$ free -h
total used free shared buff/cache available
Mem: 22G 10G 128M 11G 11G 705M
Swap: 4.0G 8.8M 4.0G

ここまでくればおわかりだと思いますが、global memory bound (約839GB)からは巨大なPGAサイズになりそうに思えますが、Temp落ちせずにソート可能なサイズは、隠しパラメータを調整した場合でも最大4GB、ハッシュ結合は、2GBが上限、となっています。いまのところ。。。。

ということは、2014年の以下のプレゼンでも話題になっていましたよね。:)
Overcome Oracle PGA Memory Limits Mar.5.2014 Alex Fatkulin / Enkatic

 

だったら、どのようにして、Temp落ち に立ち向かえばいいんだろう.....次回へ続く :)

 


10gR2(64bit)のころのままなので、以下のシリーズも合わせて読んでおくといいですよ:)


Mac De Oracle なんですが、Windows(32bit)でのOracleな話
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #2
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #8
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #9
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #10
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #11
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #12
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #13
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #14
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #15
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #16
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #17
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #18
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #19
_pga_max_sizeってOracle11gではどうなったっけ? という確認。
_pga_max_sizeってOracle11gではどうなったっけ? という確認。シーズン2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8

 


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版
Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版
Temp落ち #8 - 自動PGA管理でパラメータ上設定可能な最大サイズは?(実際に利用可能なサイズとは限りませんが。意味深) 12.2.0.1版

| | | コメント (0) | トラックバック (0)

2018年3月12日 (月)

Temp落ち #8 - 自動PGA管理でパラメータ上設定可能な最大サイズは?(実際に利用可能なサイズとは限りませんが。意味深) 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理下では、SQL Work Areaサイズは、pga_aggregate_targetのサイズに応じて変化し、最大1GBまで調整され、それのサイズ以上のソートやハッシュ結合は、もれなく、"Temp落ち" する。
手動PGA管理下では、最大2GBまで指定できました。。自動だと1GBまでなのか。。。と残念がる声が、昔は海だった方面から聞こえた気がしましたが、たぶん、気のせいw

というところまででした。


今日は、自動PGA管理下ではそれが最大なのか? のか? 自己責任で試してみることにしますw

まずはこれまでの復習。

自動PGA管理で、SQL Work Areaサイズの算出に関わるパラメータは以下の通り

pga_aggregate_target = 10MB 〜 4TB - 1
_pga_max_size = 200MB 〜 2GB
Oracleが動的に値を調整している_pga_max_sizeパラメータへユーザーが値を設定してしまうとOracleの自動調整が無効化され、設定した値で固定されてしまうので注意が必要です。

私の観測範囲だとおおよそ以下のように変化します。最近は大量のメモリーを搭載したサーバーが多いので、pga_aggregate_targetが10GB以上という状況も普通になってきたので、自動PGA管理下のSQL Work Areaサイズは最大1GBとざっくり丸暗記しても困ることは思いますw(ちょっと乱暴かw)

pga_aggregate_target = 10MB〜10GB - 1 :
_pga_max_size = 200MB 〜 2047MB
GREATEST(pga_aggregate_target*0.2 ,200MB)

pga_aggregate_target = 10GB以上〜4TB-1 :
_pga_max_size = 2GB
LEAST(pga_aggregate_target*0.2 ,2GB)


_pga_max_sizeはpga_aggregate_targetの値に応じて動的に変化し、それらを元に _smm_max_size が算出される
LEAST(pga_aggregate_target * 0.2, _pga_max_size * 0.5)

pga_aggregate_target = 10MB〜10GB-1
_pga_max_size = 200MB〜2047MB
_smm_max_size = 2MB〜1023MB


pga_aggregate_target = 10GB〜4TB-1
_pga_max_size = 2048MB
_smm_max_size = 1024MB
_smm_max_sizeは、v$pgastatのglobal memory boundだろうということは確からしいということまでは確認しました。

20180307_143308
20180307_143330
いままでの結果をまとめると
PGAのSQL Work Areaサイズの最大サイズを示す global memory boundは、pga_aggregate_targetが10MB〜10GB-1までは2MB〜1023MBで調整され、pga_aggregate_targetが10GB〜4TB-1では、1024MB (1GB) で固定されている、というのが、Oracle 10GR2〜12cR2まで動きであることは間違いなさそう:)

自動PGA管理下でSQL Work Areaを1GBを超えるサイズにするにはどのパラメータをどのような値に設定すればよいか。。。
その鍵を握るパラメータは

_pga_max_size



(いまいちピンとこないという方は、過去のエントリーを参照していただけるとスッキリすると思います。

ただ、冒頭にも書きましたが(昔それでハマったw)、(Oracleが動的に値を調整している_pga_max_sizeパラメータへユーザーが値を設定してしまうとOracleの自動調整が無効化され、設定した値で固定されてしまうので注意が必要です。
という点はお忘れなく。また、今後も同仕様のままである保証はなく、世界中のOracle使いの方達が調べ上げた結果、現状はこんな感じ。
という状況だと認識しておいたほうが無難だと思っています。

とはいえ、実際どこまで設定可能なのか?  1GBを以上使えるのか? 知りたいですよね...

長い前置きはこれぐらいにして、
実際にSQL Work Areaサイズに対応したメモリーが実際にどの程度割り当てられるのかということの確認は後回しですが、 パラメータの上では、どこまで設定できるのかというところを確認しました。


なお、
以下のエラーメッセージから、_pga_max_sizeに設定可能な値は、pga_aggregate_targetと同様(Big Integer)、10MB〜4TB-1までであると思われます。

orcl12c@SYS> show parameter _pga_max_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 2G

orcl12c@SYS> alter system set "_pga_max_size" = 4T scope=memory;
alter system set "_pga_max_size" = 4T scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。 ORA-00093:
_pga_max_sizeは、10Mから4096G-1の間に設定する必要があります。

ということで、
_pga_max_sizeを1GB/10GB/100GB/1TB/4T-1のそれぞれに設定したうえで、Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 で利用したスクリプトを利用し、global memory boundやその他パラメータの変化を確認しました。

ログの例

orcl12c@SYS> alter system set "_pga_max_size" = 10m scope=both;

システムが変更されました。

orcl12c@SYS> show parameter _pga_max_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
_pga_max_size big integer 10m


orcl12c@SYS> @dotest_auto_workarea
<< 10m >>
接続されました。
1* alter system set pga_aggregate_target=&1 scope=spfile
旧 1: alter system set pga_aggregate_target=&1 scope=spfile
新 1: alter system set pga_aggregate_target=10m scope=spfile

システムが変更されました。

データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ORACLEインスタンスが起動しました。

Total System Global Area 2147483648 bytes
Fixed Size 8794848 bytes
Variable Size 603983136 bytes
Database Buffers 1526726656 bytes
Redo Buffers 7979008 bytes
データベースがマウントされました。
データベースがオープンされました。
+++ initial parameters +++

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
__pga_aggregate_target 16777216 FALSE

・・・中略・・・

pga_aggregate_limit 0 FALSE
pga_aggregate_target 10485760 FALSE

50行が選択されました。

/proc/sys/vm/max_map_count

65530

+++ v$pgastat +++

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
aggregate PGA target parameter 10 MB 0
aggregate PGA auto target 4 MB 0
global memory bound 2 MB 0

・・・中略・・・


結果は以下のグラフと表にまとめたとおり。
_pga_max_size=4TB-1、pga_aggregate_target=4TB-1の時のglobal memory bound (v$pgastat) = _smm_max_size が最大となり、約839GBとなることがわかりました。
パラメータ上は839GB程度まで増加しますが、ほんとうにPGAがそんなサイズまで利用可能なのでしょうか?....(怪しいです。答え、知ってるんですけどねw。。

1g
10g
100g
1t
4t1
Globalmemorybound

ということで、

次回は実際に、そんなに使えんのかよ! という実験をしてみたいと思います :)

つづく。




10gR2(64bit)のころのままなので、以下のシリーズも合わせて読んでおくといいですよ:)
Mac De Oracle なんですが、Windows(32bit)でのOracleな話
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #2
Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #8
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #9
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #10
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #11
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #12
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #13
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #14
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #15
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #16
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #17
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #18
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? #19
_pga_max_sizeってOracle11gではどうなったっけ? という確認。
_pga_max_sizeってOracle11gではどうなったっけ? という確認。シーズン2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #1
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #2
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #3
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #4
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #5
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #6
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #7
pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版
Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版


| | | コメント (0) | トラックバック (0)

2018年3月 8日 (木)

Temp落ち #7 - 自動PGA管理で到達可能な最大サイズ de Temp落ちの確認 12.2.0.1版

Previously on Mac De Oracle

自動PGA管理で利用可能なSQL Work Areaサイズはいくつなのか?の確認でした。
これまで同様、隠しパラメータ等の変更をしないデフォルトの設定では最大1GBまで到達することを確認しました。
また、手動PGA管理で利用可能な最大サイズより小さいことも再確認しました。


今日は自動PGA管理下ではオンメモリーで処理可能なサイズは1GBまでなのか、それを超えた場合はもれなく "Temp落ち" なのか確認することにします。

 

確認用データおよび方法は前々回のエントリーを参照ください。

自動PGA管理でGlobal Memory Bound = 1GB(自動PGA管理での最大 Sort作業領域サイズ)

メモリーソートで1GBぐらいまで利用していることが確認できます。(ソートされるデータ量は1GB以下程度に制限しています。)
ORCL@SCOTT> @auto_sortwk1gb_optimal

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C045000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 4.78 | 3.85 | 0.34 | 0.59 | 30001 | 334K | 2633 | 3GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
==================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 10 | +2 | 1 | 450K | | | . | 33.33 | Cpu (2) |
| 1 | SORT ORDER BY | | 456K | 302K | 11 | +1 | 1 | 450K | | | 1GB | 33.33 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 456K | 90827 | 3 | +2 | 1 | 450K | 2633 | 3GB | . | 33.33 | Cpu (2) |
==================================================================================================================================================

 

Temp落ちする程度のデータ量でソートさせた場合も、PGAの作業領域は最大1GBまで利用されていたことが確認できます。
ORCL@SCOTT> @auto_sortwk1gb_mpass    

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C050000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 16 | 6.91 | 7.91 | 0.71 | 33335 | 334K | 7996 | 4GB | 4431 | 1GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 22 | +2 | 1 | 500K | | | | | . | . | 6.25 | Cpu (1) |
| 1 | SORT ORDER BY | | 507K | 326K | 22 | +2 | 1 | 500K | 5363 | 1GB | 4431 | 1GB | 1GB | 1GB | 68.75 | Cpu (4) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (6) |
| 2 | TABLE ACCESS FULL | M1 | 507K | 90828 | 13 | +1 | 1 | 500K | 2633 | 3GB | | | . | . | 25.00 | Cpu (3) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=====================================================================================================================================================================================

自動PGA管理でGlobal Memory Bound = 1GB(自動PGA管理での最大 Hash作業領域サイズ)

Hash Joinの場合も、自動PGA管理の最大サイズ程度まで利用されていることが確認できます。(Temp落ちしない程度のデータ量にしています。)
ORCL@SCOTT> @auto_hashwk1gb_optimal

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C042000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 7.29 | 5.05 | 0.68 | 1.55 | 28001 | 695K | 5251 | 5GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
==================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 14 | +2 | 1 | 420K | | | . | 22.22 | Cpu (2) |
| 1 | HASH JOIN | | 423K | 268K | 15 | +1 | 1 | 420K | | | 1GB | 22.22 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 432K | 90827 | 2 | +2 | 1 | 420K | 2633 | 3GB | . | 11.11 | Cpu (1) |
| 3 | TABLE ACCESS FULL | M2 | 423K | 90580 | 13 | +3 | 1 | 420K | 2618 | 3GB | . | 44.44 | Cpu (4) |
==================================================================================================================================================
Hash Joinの場合もSort時と同じように、一時表領域も利用が必要となるデータ量になると、一旦、自動PGA管理の最大サイズ程度まで利用したうえでTemp落ちしていることが確認できます。
ORCL@SCOTT> @auto_hashwk1gb_mpass

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
workarea_size_policy string AUTO

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 4398046511103

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
global memory bound 1073741824 bytes 0

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C055000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
============================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
============================================================================================
| 24 | 7.18 | 15 | 1.99 | 36668 | 687K | 10829 | 6GB | 5578 | 1GB |
============================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
======================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
======================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 33 | +2 | 1 | 550K | | | | | . | . | 14.29 | Cpu (4) |
| 1 | HASH JOIN | | 551K | 293K | 33 | +2 | 1 | 550K | 5578 | 1GB | 5578 | 1GB | 1GB | 1GB | 67.86 | Cpu (8) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (10) |
| 2 | TABLE ACCESS FULL | M1 | 551K | 90828 | 16 | +1 | 1 | 550K | 2633 | 3GB | | | . | . | 7.14 | Cpu (1) |
| | | | | | | | | | | | | | | | | direct path read (1) |
| 3 | TABLE ACCESS FULL | M2 | 557K | 90580 | 16 | +16 | 1 | 550K | 2618 | 3GB | | | . | . | 10.71 | Cpu (3) |
======================================================================================================================================================================================

PGAの最大サイズは異なりますが、自動PGA管理、手動PGA管理いずれでの場合でも制限値を超えた場合は、もれなく "Temp落ち" するということはご理解いただけたのではないかと思います。

では、次回は恒例?w の隠しパラメータと戯れた場合、自動PGA管理下ではどこまで増加させることができるのか?。。。。確認してみたいと思います。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)
Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版

 

| | | コメント (0) | トラックバック (0)

2018年3月 7日 (水)

Temp落ち #6 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版

Previously on Mac De Oracle
pga_aggregate_target scope=memory or bothでそれまでとはことなる動きとエラーは発生するところの話でした。


きょうは、自動PGA管理下ではどうなるか確認しておきます。
以前、簡単に確認した範囲結果から、これまでの仕様と大きな違いはないとみています。(変わっていないと思っていたのでブログでも書いていなかったのですが、"Temp落ち"ネタの準備運動も兼ね現状を確認しながら進めてみたいと思います)


まず環境情報から、
確認に利用するインスタンスのPGA以外のパラメータは以下の通りです。
(隠しパラメータは必要がある場合には適宜変更します。また、いくつかのパラメータは確認の都合上物理メモリーサイズ以上に設定することがあります。それらの変更が必要な場合には事前に解説する予定です。)

OS等の情報は以前のエントリーを参照のこと。

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
sga_max_size big integer 12G
sga_min_size big integer 0
sga_target big integer 12G

今回の主役である自動PGA管理のパラメータの初期設定は以下の通り(隠しパラメータを除く)
pga_aggregate_limit = 0 に設定し、pga_aggregate_targetの上限値制限を仕様上のサイズ4096GB - 1まで利用できるようにしておきます。(以前と同様の手順で確認するために、pga_aggregate_limitによる制限を無効化しています。なお、pga_aggregate_limitを0以外に設定して行う場合には、pga_aggregate_targetを4TB - 1まで設定することを考慮すると、pga_aggregate_limitは、8TB以上に設定する必要があります。)

orcl12c@SYS> show parameter pga_aggregate

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 0
pga_aggregate_target big integer 10M

上記設定からスタートして、pga_aggregate_targetを10MB/50MB/100MB/500MB/1GB/5GB/10GB/50GB/100GB/500GB/1T/4TB - 1と増加させながら、pgaサイズおよび、関連する隠しパラメータ(_pga_max_sizeや_smm_*など)がどのように変化するか、pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8の手順で確認します。

昔のように実行ログをペタペタ貼っていると長くなるので、確認結果を表とグラフにしました。
想定通り以前と変わっていませんでした:)

デフォルト設定のままであれば、pga_aggregate_targetを限界値まで大きく増やしたとしても、10GB以降、個々のソート操作やハッシュ結合操作で利用できるPGAのSQL work areaサイズはv$pgastatのglobal memory boundの示す通り、1GB(シリアル実行時)が最大であることが確認できます。 えーーー。手動PGA管理より最大サイズ小さいじゃんと、今更驚かないようにしましょうねw (以前からそうなのですからw)
つまり、自動PGA管理の場合、特定の隠しパラメータを変更しない限り、1GBを超えるソート処理やハッシュ結合操作は全て、"Temp落ち" する宿命にあるわけです。12cR2であっても。
え"〜〜〜〜〜っ! と一応、驚いておきましょうw。 メモリーを沢山積んでるマシンは最近多いわけですが、このあたりはなぜか変わってません。
20180307_143101

ちなみに、v$pgastatの示すglobal memory boundは、どこから? という確認がしたくて、 _smm_max_sizeの変化と比較しているグラグも作成しておきました。
_smm_max_sizeパラメータはpga_aggregate_targetのサイズ変化に伴い変化する隠しパラメータですが、このパラメータがglobal memory boundの元になっているのは確からしいですね。
20180307_143308

最後のグラフは、pga_aggregate_targetの値とv$pgastatのglobal memory boundの変化をまとめたグラフです。
global memory boundで示されるPGAのSQL Work Areaサイズの最大サイズには上限があることがわかります:)
20180307_143330

次回は、自動PGA管理では、1GBを超えるソートやハッシュ結合はもれなく"Temp落ち"するのか確認してみることにします。


参考:このエントリーで利用したスクリプト
(今後の確認も兼ねてw このエントリーでネタにしている以外で関連しそうなパラメータも取得して変化を確認できるようにしてあります。)

orcl12c@SYS> !cat show_param.sql
set linesize 200
col ksppinm for a46
col ksppstvl for a30
col ksppstdf for a30
select
a.ksppinm
,b.ksppstvl
,b.ksppstdf
from
x$ksppi a join x$ksppcv b
on a.indx = b.indx
where
a.ksppinm in (
'pga_aggregate_target'
,'pga_aggregate_limit'
,'_use_realfree_heap'
,'_realfree_heap_pagesize'
)
or a.ksppinm like '%\_smm%' escape '\'
or a.ksppinm like '%\_pga%' escape '\'
order by
a.ksppinm
/

!echo /proc/sys/vm/max_map_count
!more /proc/sys/vm/max_map_count

orcl12c@SYS> !cat show_pgstat.sql
select
name
,case
when unit='bytes' then
value/1024/1024
else value
end "VALUE"
,case
when unit='bytes' then
'MB'
else unit
end "UNIT"
,con_id
from
v$pgastat
where
name in (
'aggregate PGA target parameter'
,'aggregate PGA auto target'
,'global memory bound'
,'total PGA used for auto workareas'
,'maximum PGA used for auto workareas'
,'total PGA used for manual workareas'
,'maximum PGA used for manual workareas'
)
;
orcl12c@SYS> !cat auto_workarea.sql
conn sys/oracle@orcl12c as sysdba
alter system set pga_aggregate_target=&1 scope=spfile
.
r
shutdown immediate
startup

prompt +++ initial parameters +++
@show_param

prompt +++ v$pgastat +++
@show_pgstatå
orcl12c@SYS> !cat doTest_auto_workarea.sql
prompt << 10m >>
@auto_workarea 10m

prompt << 50m >>
@auto_workarea 50m

prompt << 100m >>
@auto_workarea 100m

prompt << 500m >>
@auto_workarea 500m

prompt << 1g >>
@auto_workarea 1g

prompt << 5g >>
@auto_workarea 5g

prompt << 10g >>
@auto_workarea 10g

prompt << 50g >>
@auto_workarea 50g

prompt << 100g >>
@auto_workarea 100g

prompt << 500g >>
@auto_workarea 500g

prompt << 1t >>
@auto_workarea 1t

prompt << 4t - 1 >>
@auto_workarea 4398046511103


実行例

orcl12c@SYS> @doTest_auto_workarea.sql
<< 10m >>
Connected.
1* alter system set pga_aggregate_target=&1 scope=spfile
old 1: alter system set pga_aggregate_target=&1 scope=spfile
new 1: alter system set pga_aggregate_target=10m scope=spfile

System altered.

Database closed.
Database dismounted.
ORACLE instance shut down.
ORACLE instance started.

Total System Global Area 1.2885E+10 bytes
Fixed Size 8807168 bytes
Variable Size 1375735040 bytes
Database Buffers 5033164800 bytes
Redo Buffers 24743936 bytes
In-Memory Area 6442450944 bytes
Database mounted.
Database opened.
+++ initial parameters +++

KSPPINM KSPPSTVL KSPPSTDF
---------------------------------------------- ------------------------------ ------------------------------
__pga_aggregate_target 33554432 FALSE

・・・略・・・

pga_aggregate_limit 0 FALSE
pga_aggregate_target 10485760 FALSE

50 rows selected.

/proc/sys/vm/max_map_count

65530

+++ v$pgastat +++

NAME VALUE UNIT CON_ID
---------------------------------------------------------------- ---------- ------------ ----------
aggregate PGA target parameter 10 MB 0
aggregate PGA auto target 4 MB 0
global memory bound 2 MB 0
total PGA used for auto workareas .106445313 MB 0
maximum PGA used for auto workareas .106445313 MB 0
total PGA used for manual workareas 0 MB 0
maximum PGA used for manual workareas 0 MB 0

7 rows selected.

・・・略・・・


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認
Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)

| | | コメント (0) | トラックバック (0)

2018年2月24日 (土)

Temp落ち #5 - pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? 12.2.0.1版 (その前に少し脱線)



Previously on Mac De Oracle

手動PGA管理下で利用される4つのパラメータ(HASH_AREA_SIZE / SORT_AREA_SIZE / BITMAP_MERGE_AREA_SIZE / CREATE_BITMAP_AREA_SIZE)は、Integer型であり2,147,483,647バイト(つまり2GB - 1)までは指定可能、かつ、個々の作業領域は同程度確保されること。そのサイズを超えるデータ量の場合は一時表領域が利用される。つまり"Temp落ち"が発生するというところまでした。

SQL> show parameter _area_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
bitmap_merge_area_size integer 2147483647
create_bitmap_area_size integer 2147483647
hash_area_size integer 2147483647
sort_area_size integer 2147483647

64bit環境でInteger型なので、そんなもんでしょうね。 仕様ですからね、こればっかりはどうにもならない。


で、自動PGA管理で確認しようとパタパタ準備していたところ、12.1.0.2まではなかった12.2.0.1での変更点に気付いたので、そちらを先に。
(少し脱線)

pga_aggregate_targetをscope=memoryで設定する際、物理メモリーの空きサイズ以上に設定しようとするとORA-00855エラーなり設定できないよう動作が変更されたようです。

また、spfileにのみ設定する場合には動作が異なり、設定時には、ORA-00855は発生しません。
アラートログファイルには、起動時のワーニングとして記録され、アラートログにもなにも記録されれず、正常起動しpga_aggregate_targetには指定したサイズで設定されるという、
少々分かりづらい仕様に変わったようです。 (今頃気づくとは、遅い!w

(18cではどうなるんだろう。。なんとなく、Autonomousを意識してるようなアトモスフィアを感じます..が、どうなんでしょうw?)

以下、動作確認の記録。
Guest OSのメモリーサイズは以下の通り。空いているメモリーサイズは500MB〜600MB程度です。

Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
orcl12c@SYS> !cat /proc/meminfo | grep Mem
MemTotal: 3781732 kB
MemFree: 43300 kB
MemAvailable: 559076 kB


orcl12c@SYS> show parameter sga

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
allow_group_access_to_sga boolean FALSE
lock_sga boolean FALSE
pre_page_sga boolean TRUE
sga_max_size big integer 2G
sga_min_size big integer 0
sga_target big integer 2G
unified_audit_sga_queue_size integer 1048576

orcl12c@SYS> show parameter pga_agg

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 2G
pga_aggregate_target big integer 200M


この状態で、pga_aggregate_limitの50%のサイズでpga_aggregate_limitの制限内、pga_aggregate_target=1g としてメモリー上でのみ変更してみると

orcl12c@SYS> alter system set pga_aggregate_target=1g scope=memory;
alter system set pga_aggregate_target=1g scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


空きメモリーサイズ以下ならどうなるか試して見ます。

orcl12c@SYS> alter system set pga_aggregate_target=500m scope=memory;

システムが変更されました。


空きメモリーサイズを多少上回るサイズでは。。。(やはり、ORA-00855となります)

orcl12c@SYS> alter system set pga_aggregate_target=700m scope=memory;
alter system set pga_aggregate_target=700m scope=memory
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


scope=bothとなる状態で、空きメモリー以上のサイズを設定しようとしても同様のエラーになりますが.....

orcl12c@SYS> alter system set pga_aggregate_target=1g;
alter system set pga_aggregate_target=1g
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。
ORA-00855: PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory.


scope=spfileとした場合には、エラーにならず変更可能です。このあたりの動きは、他のパラメータと同様、起動時にエラーで起動しないという動きになるのかな??? と思いきや.....
なんと、正常起動し、pga_aggregate_targetにはしっかり、1GBが設定されています。!!!!! (動きが違う!!!)

orcl12c@SYS> alter system set pga_aggregate_target=1g scope=spfile;

システムが変更されました。

orcl12c@SYS> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
orcl12c@SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 2147483648 bytes
Fixed Size 8794848 bytes
Variable Size 603983136 bytes
Database Buffers 1526726656 bytes
Redo Buffers 7979008 bytes
データベースがマウントされました。
データベースがオープンされました。
orcl12c@SYS>
orcl12c@SYS> show parameter pga_agg

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_limit big integer 2G
pga_aggregate_target big integer 1G

なぜ、動作が違うんだろう........


2018/2/25訂正
以下のメッセージよくよく見たら、pga_aggregate_limitパラメータへの警告でpga_aggregate_targetのORA-00855とは無関係なワーニングでした。orz.

pga_aggregate_limitの下限値は2G、sea_max_size=2Gなのに、Guest OSへのメモリー割り当て4Gなのでそりゃでるわね。というこでした。

とはいえ、 pga_aggreagte_target scope=memoryとspfileではORA-00855エラーの有無の違いはあるようで、spfileに設定した場合にはORA-00855は発生せず、アラートログファイルには何も記録されていない(謎


なお、起動時のアラートログファイルには以下の"WARNING"メッセージが記録されます。しかも、メッセージを見る限り、pga_aggregate_limit へのワーニングと読めるが....

2018-02-24T22:16:38.907017+09:00
WARNING: pga_aggregate_limit value is too high for the
amount of physical memory on the system
PGA_AGGREGATE_LIMIT is 2048 MB
PGA_AGGREGATE_TARGET is 1024 MB.
physical memory size is 3693 MB
limit based on physical memory and SGA usage is 1275 MB
SGA_TARGET is 2048 MB

おまけ


12.1.0.2までは該当するメッセージはありません。
[oracle@vbgeneric ˜]$ sqlplus -version

SQL*Plus: Release 12.1.0.2.0 Production

[oracle@vbgeneric ˜]$ oerr ORA 855
[oracle@vbgeneric ˜]$



12.2.0.1で実装されたメッセージと機能だということがわかります。
[oracle@localhost ˜]$ sqlplus -version

SQL*Plus: Release 12.2.0.1.0 Production

[oracle@localhost ˜]$ oerr ORA 855
00855, 00000, "PGA_AGGREGATE_TARGET cannot be set because of insufficient physical memory."
// *Cause: PGA_AGGREGATE_TARGET value was too high for the current system global area (SGA) size and amount of physical memory available.
// *Action: Reduce the SGA size or increase the physical memory size.

ということで、次回こそw 自動PGA管理下での確認へ

続く。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ
Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認

| | | コメント (0) | トラックバック (0)

2018年2月22日 (木)

Temp落ち #4 - 手動PGA管理で作業領域として指定可能な最大サイズ de Temp落ちの確認


Previously on Mac De Oracle
手動PGA管理で作業領域として指定可能な最大サイズは、2GB - 1までということの確認でした。
本当に、その程度のサイズまでPGAの作業領域が利用されるのでしょうか?。 念のために確認しておきましょう。 実は、それより少ないサイズで頭打ちなんてことは、ないかな〜 と わざとらしく言ってみたりして(意味深w

その前に、指定した作業領域を使い切れるぐらい(つまり、Temp落ちさせられる程度)のデータ量の表を準備しておきます。
今回は、Nested Loop Join(NLJ)やソート回避などのための索引は作成しません。Temp落ちのネタなので。

M1とM2の2表を作成し、それぞれ、2.5GB程度のセグメントサイズにしておきます。 なお、以下の無名PL/SQLブロックでは、FORALLを利用して1000行単位でバルク処理しています。配列を利用するのでメモリー使用量にはそれなりに配慮が必要ですが。:)
単純なぐるぐる系INSERTにしてしまうとデータ量が多い場合、性能的に辛くなってしまうので、ここ重要!

ORCL@SCOTT> l
1 CREATE TABLE m1
2 (
3 id CHAR(7) NOT NULL
4 ,rev# NUMBER NOT NULL
5 ,value NUMBER NOT NULL
6 ,description VARCHAR2(4000)
7 ,additional_info CHAR(200)
8* ) NOLOGGING
ORCL@SCOTT> /

Table created.

ORCL@SCOTT> l
1 DECLARE
2 TYPE IDS_t IS TABLE OF m1.id%TYPE INDEX BY PLS_INTEGER;
3 TYPE REV#S_t IS TABLE OF m1.rev#%TYPE INDEX BY PLS_INTEGER;
4 TYPE VALS_t IS TABLE OF m1.value%TYPE INDEX BY PLS_INTEGER;
5 IDs IDS_t;
6 REV#s REV#S_t;
7 VALs VALS_t;
8 k PLS_INTEGER := 1;
9 BEGIN
10 FOR i IN 1..100000 LOOP
11 FOR j IN 1..10 LOOP
12 IDs(k) := 'C' || TO_CHAR(i, 'FM000000');
13 REV#s(k) := j;
14 VALs(k) := i + j;
15 k := k + 1;
16 END LOOP;
17 IF MOD(i, 100) = 0 THEN
18 FORALL l in 1..1000 EXECUTE IMMEDIATE
19 'INSERT /*+ APPEND_VALUE NO_GATHER_OPTIMIZER_STATISTICS */ INTO m1 '
20 || 'VALUES(:1, :2, :3, LPAD(''X'',2000, ''X''), LPAD(''9'',200,''9''))'
21 USING IDs(l), REV#s(l), VALs(l);
22 COMMIT;
23 k := 1;
24 END IF;
25 END LOOP;
26* END;
ORCL@SCOTT> /

PL/SQL procedure successfully completed.

Elapsed: 00:01:22.45
ORCL@SCOTT> select count(1) from m1;

COUNT(1)
----------
1000000

ORCL@SCOTT> select segment_name,sum(bytes)/1024/1024/1024 "GB" from user_segments where segment_name='M1' group by segment_name;

SEGMENT_NAME GB
------------------------------ ----------
M1 2.5625

ORCL@SCOTT> l
1 CREATE TABLE m2
2 (
3 id CHAR(7) NOT NULL
4 ,rev# NUMBER NOT NULL
5 ,value NUMBER NOT NULL
6 ,description VARCHAR2(4000)
7* ) NOLOGGING
ORCL@SCOTT> /

Table created.

ORCL@SCOTT> l
1 INSERT /*+ APPEND NO_GATHER_OPTIMIZER_STATISTICS */ INTO m2
2* SELECT id,rev#,value,description FROM m1
ORCL@SCOTT> /

1000000 rows created.

Elapsed: 00:00:40.22
ORCL@SCOTT> commit;

Commit complete.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'M1',no_invalidate=>false,method_opt=>'FOR ALL COLUMNS SIZE SKEWONLY');

PL/SQL procedure successfully completed.

ORCL@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'M2',no_invalidate=>false,method_opt=>'FOR ALL COLUMNS SIZE SKEWONLY');

PL/SQL procedure successfully completed.

これで、準備完了。
ちなみに、NO_GATHER_OPTIMIZER_STATISTICSヒントを利用していますが、データ登録後に、ヒストグラムも含めて取得したかったため、バルクロード時のオンラインオプティマイザ統計収集を行わないようにするためのヒントです。
データ登録後オプティマイザ統計を取得するため、データ登録時のオンラインオプティマイザ統計のオーバーヘッドは無駄となるためです。利用できるなら利用したほうがよいとは思いますが、制限もあるのでご一読を(バルク・ロードのためのオンライン統計収集


 

手動PGA管理で2GB - 1(手動PGA管理での最大 Sort作業領域サイズ)

メモリーソートで2GBぐらいまで利用していることが確認できます。(ソートされるデータ量が2GB以下程度に制限しています。)
ORCL@SCOTT> @manual_sortwk2gb_optimal    
1* alter session set workarea_size_policy=manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 WHERE
9 id <= 'C075000'
10 ORDER BY
11 id
12* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 5.42 | 4.13 | 1.29 | 50001 | 334K |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
===================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
===================================================================================================================================
| 0 | SELECT STATEMENT | | | | 15 | +2 | 1 | 750K | . | 20.00 | Cpu (1) |
| 1 | SORT ORDER BY | | 752K | 439K | 16 | +1 | 1 | 750K | 2GB | 60.00 | Cpu (3) |
| 2 | TABLE ACCESS FULL | M1 | 752K | 90828 | 3 | +2 | 1 | 750K | . | 20.00 | Cpu (1) |
===================================================================================================================================

 

Temp落ちする程度のデータ量でソートさせた場合も、PGAの作業領域は一旦、2GBまで利用されていることが確認できます。
ORCL@SCOTT> @manual_sortwk2gb
1* alter session set workarea_size_policy=manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 */
5 *
6 FROM
7 m1
8 ORDER BY
9 id
10* ,rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 46 | 11 | 15 | 20 | 66668 | 334K | 2148 | 2GB | 2146 | 2GB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=3534657201)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 60 | +2 | 1 | 1M | | | | | . | . | 6.38 | Cpu (2) |
| | | | | | | | | | | | | | | | | PGA memory operation (1) |
| 1 | SORT ORDER BY | | 1M | 553K | 61 | +1 | 1 | 1M | 2148 | 2GB | 2146 | 2GB | 2GB | 2GB | 91.49 | Cpu (32) |
| | | | | | | | | | | | | | | | | direct path read temp (10) |
| | | | | | | | | | | | | | | | | direct path write temp (1) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90822 | 27 | +2 | 1 | 1M | | | | | . | . | 2.13 | Cpu (1) |
=====================================================================================================================================================================================

 

手動PGA管理で2GB - 1(手動PGA管理での最大 Hash作業領域サイズ) Hash Joinの場合も、手動PGA管理で設定可能な最大サイズ程度まで利用されていることが確認できます。(Temp落ちしない程度のデータ量にしています。)

ORCL@SCOTT> @manual_hashwk2gb_optimal
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set hash_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13 AND m1.rev# = m2.rev#
14 WHERE
15* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 8.05 | 4.66 | 0.16 | 3.23 | 50001 | 717K | 2618 | 3GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
==================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
==================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 21 | +2 | 1 | 750K | | | . | 12.50 | Cpu (1) |
| 1 | HASH JOIN | | 752K | 181K | 22 | +1 | 1 | 750K | | | 2GB | 25.00 | Cpu (2) |
| 2 | TABLE ACCESS FULL | M1 | 752K | 90828 | 2 | +2 | 1 | 750K | | | . | 25.00 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M2 | 759K | 90581 | 20 | +3 | 1 | 750K | 2618 | 3GB | . | 37.50 | Cpu (3) |
==================================================================================================================================================

 

Hash Joinの場合もSort時と同じように、一時表領域も利用させる程度のデータ量になると、一旦、手動PGA管理で設定可能な最大サイズ程度まで利用したうえでTemp落ちしていることが確認できます。

ORCL@SCOTT> @manual_hashwk2gb
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set hash_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 LEADING(m1 m2)
5 USE_HASH(m2)
6 */
7 *
8 FROM
9 m1
10 INNER JOIN m2
11 ON
12 m1.id = m2.id
13* AND m1.rev# = m2.rev#
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read | Write | Write |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes | Reqs | Bytes |
===========================================================================================
| 12 | 5.68 | 2.17 | 4.59 | 66668 | 706K | 3133 | 3GB | 515 | 511MB |
===========================================================================================

SQL Plan Monitoring Details (Plan Hash Value=1822065247)
=====================================================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Write | Write | Mem | Temp | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | Reqs | Bytes | (Max) | (Max) | (%) | (# samples) |
=====================================================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 30 | +2 | 1 | 1M | | | | | . | . | 25.00 | Cpu (4) |
| 1 | HASH JOIN | | 1M | 189K | 31 | +1 | 1 | 1M | 515 | 511MB | 515 | 511MB | 2GB | 515MB | 43.75 | Cpu (5) |
| | | | | | | | | | | | | | | | | direct path read temp (1) |
| | | | | | | | | | | | | | | | | direct path write temp (1) |
| 2 | TABLE ACCESS FULL | M1 | 1M | 90822 | 6 | +0 | 1 | 1M | | | | | . | . | 12.50 | Cpu (2) |
| 3 | TABLE ACCESS FULL | M2 | 1M | 90574 | 24 | +5 | 1 | 1M | 2618 | 3GB | | | . | . | 18.75 | Cpu (2) |
| | | | | | | | | | | | | | | | | direct path read (1) |
=====================================================================================================================================================================================

おまけ 1つのSQLの実行で利用されるPGAの作業領域は1つだけではないということも確認しておきましょうかね。(知ってる方はスルーしてよいですよ:)
以下の例では、MERGE JOINさせていますが、表M1と表M2それぞれのSort作業領域は、Merge Join終了時まで保持されることになるため、最大3GBのSort作業領域が利用されています。(SQLモニターの結果ではわかりにくいのですが。。。)
なお、手動PGA管理、自動PGA管理に関係なく、実行される操作により複数の作業領域が同時に確保されることがあります。
(ちなみに、自動PGA管理で利用されるPGA_AGGREGATE_LIMITがPGA_AGGREGATE_TARGETの2倍とされるのも、このような動きが考慮された結果だと知っていると、納得感はあるかもしれません。)

 

前回使った図で朱色で示したSQL Work Areaが複数ありますが、Hash/Sort/Bitmap系など複数のタイプおよび同一タイプの作業領域が同時に確保されることも意図した図になっているのは、これが理由なんです。

Structure_of_pga
ORCL@SCOTT> @manual_sortwk2gb2_optimal
1* alter session set workarea_size_policy = manual

Session altered.

1* alter session set sort_area_size = 2147483647

Session altered.

1 SELECT
2 /*+
3 MONITOR
4 USE_MERGE(m1 m2)
5 */
6 *
7 FROM
8 m1
9 INNER JOIN m2
10 ON
11 m1.id = m2.id
12 AND m1.rev# = m2.rev#
13 WHERE
14* m1.id <= 'C075000'
old 1: select dbms_sqltune.report_sql_monitor(sql_id=>'&1', type=>'text') from dual
new 1: select dbms_sqltune.report_sql_monitor(sql_id=>'', type=>'text') from dual

DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'',TYPE=>'TEXT')
-------------------------------------------------------------------------------------------
SQL Monitoring Report

・・・中略・・・

Global Stats
===========================================================================
| Elapsed | Cpu | IO | Other | Fetch | Buffer | Read | Read |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls | Gets | Reqs | Bytes |
===========================================================================
| 17 | 10 | 4.75 | 2.19 | 50001 | 667K | 5251 | 5GB |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=1391069689)
========================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (Max) | (%) | (# samples) |
========================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 23 | +10 | 1 | 750K | | | . | 14.29 | Cpu (2) |
| 1 | MERGE JOIN | | 752K | 849K | 23 | +10 | 1 | 750K | | | . | | |
| 2 | SORT JOIN | | 752K | 439K | 32 | +1 | 1 | 750K | | | 2GB | 21.43 | Cpu (3) |
| 3 | TABLE ACCESS FULL | M1 | 752K | 90828 | 8 | +2 | 1 | 750K | 2633 | 3GB | . | 35.71 | Cpu (3) |
| | | | | | | | | | | | | | direct path read (2) |
| 4 | SORT JOIN | | 759K | 410K | 23 | +10 | 750K | 750K | | | 1GB | 14.29 | Cpu (2) |
| 5 | TABLE ACCESS FULL | M2 | 759K | 90581 | 5 | +10 | 1 | 750K | 2618 | 3GB | . | 14.29 | Cpu (2) |
========================================================================================================================================================

SQLモニターの結果ではわかりにくいわけですが、v$sesstatをから眺めれば状況がよくわかります!
2つのソート作業領域が同時に確保されたことで、2GB + 1GB = 3GB程度のサイズにまで達していることが確認できます。

ORCL@SYSTEM> r
1 select
2 vss.value/1024/1024/1024 "GB"
3 ,vsn.name
4 ,vss.sid
5 from
6 v$sesstat vss
7 inner join v$statname vsn
8 on
9 vss.statistic# = vsn.statistic#
10 and vss.con_id = vsn.con_id
11 and vsn.name like '%pga%'
12 where
13 sid IN (select sid from v$session where username='SCOTT')
14 order by
15* sid,name

GB NAME SID
---------- ---------------------------------------------------------------- ----------
3.04611414 session pga memory 203
3.04611414 session pga memory max 203

では、次回はやっとw、真打、自動PGA管理下での確認。(引っ張り過ぎかもしれないw)
つづく。


Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)
Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ

 

| | | コメント (0) | トラックバック (0)

2018年2月20日 (火)

Temp落ち #3 - 手動PGA管理で作業領域として指定可能な最大サイズ

自動PGA管理は12cでどうなんだっけ?という確認の前に、
いままで何度か質問されたことがあり、FAQだと思っているので

手動PGA管理で利用する以下パラメータの最大サイズはいくつ? 

HASH_AREA_SIZE
SORT_AREA_SIZE
BITMAP_MERGE_AREA_SIZE
CREATE_BITMAP_AREA_SIZE

ということを書いておきたいと思います。

これからしばらく続く Temp落ち ネタで利用する環境で固定部分は以下のとおり
(初期化パラメータ等は必要に応じて載せるつもりです。)


環境は以下のとおり。
host osとguest osのバージョンやメモリーサイズなど

discus:˜ oracle$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.13.3
BuildVersion: 17D47

discus:˜ oracle$ system_profiler SPHardwareDataType | grep -E 'Processor Name|Cores|Memory'
Processor Name: 6-Core Intel Xeon
Total Number of Cores: 12
Memory: 32 GB

discus:˜ oracle$ VBoxManage -v
5.2.6r120293

discus:˜ oracle$ VBoxManage showvminfo e3d4f948-b2e6-4db3-a89d-df637d87a372 | grep -E 'Memory size|OS type|Number of CPUs'
Memory size: 23569MB
Number of CPUs: 12
OS type: Linux26_64


orcl12c@SYS> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production 0
PL/SQL Release 12.2.0.1.0 - Production 0
CORE 12.2.0.1.0 Production 0
TNS for Linux: Version 12.2.0.1.0 - Production 0
NLSRTL Version 12.2.0.1.0 - Production 0


orcl12c@SYS> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 ORCL READ WRITE NO

さて、今日の本題

手動PGA管理で各SQL Work Area Sizeを決定する以下の初期化パラメータの最大サイズは? いくつでしょう? 
すでにご存知のかたはスキップしていいですよ:)

マニュアルを読んでみるときづくのですが、手動PGA管理で各SQL Work Area Sizeを決定する4パラメータとも、「0以上、上限はOS依存」のような記述になっています。

そう、マニュアルを読んだだけではまったく参考にならないわけです。(え〜〜っ!)

そこで、みなさんサポートを頼るなり、ご自分でMOSを検索するなり、そこそこの苦労をして入手することになります。
私みたいな性格だと、どのマニュアルでもいいからまとめて載せてよーめんどくさいから。と、めんどくさい病の発作がでたりしますw

なので、環境があるのなら、you 試しちゃいなよー。が手っ取り早いかなーと(最終的にMOSとかサポートを頼るにしてもw)

上限はOS依存とだけしか記載されていませんが、私の観測範囲では、2GB - 1 が上限 となっています。
以下、Linux/Solaris/Windowsでの検証ログ。

Oracle® Databaseリファレンス 12cリリース2 (12.2) E72905-03より

HASH_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/HASH_AREA_SIZE.htm

SORT_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/SORT_AREA_SIZE.htm

BITMAP_MERGE_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/BITMAP_MERGE_AREA_SIZE.htm

CREATE_BITMAP_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/CREATE_BITMAP_AREA_SIZE.htm


Linux

orcl12c@SYS> !uname -r; cat /etc/redhat-release /etc/oracle-release
4.1.12-94.3.6.el7uek.x86_64
Red Hat Enterprise Linux Server release 7.3 (Maipo)
Oracle Linux Server release 7.3

orcl12c@SYS> create pfile from spfile;

File created.

orcl12c@SYS> --2GB
orcl12c@SYS> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

orcl12c@SYS> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
ERROR at line 1:
ORA-02017: integer value required

orcl12c@SYS> --2GB - 1
orcl12c@SYS> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

orcl12c@SYS> alter system set hash_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set sort_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

System altered.

orcl12c@SYS> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
orcl12c@SYS> create spfile from pfile;

File created.

orcl12c@SYS>


Solaris 11 (x86)

SQL> !uname -r; cat /etc/release
5.11
Oracle Solaris 11.3 X86
Copyright (c) 1983, 2015, Oracle and/or its affiliates. All rights reserved.
Assembled 06 October 2015

SQL>
SQL> create pfile from spfile;

ファイルが作成されました。

SQL> --2GB
SQL> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

SQL> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> --2GB - 1
SQL> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

SQL> alter system set hash_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set sort_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ERROR:
ORA-12514: TNS:
リスナーは接続記述子でリクエストされたサービスを現在認識していません


警告: Oracleにはもう接続されていません。
SQL> conn sys/oracle@orcl122 as sysdba
ERROR:
ORA-12514: TNS:
リスナーは接続記述子でリクエストされたサービスを現在認識していません


SQL> exit
bash-4.1$ export ORACLE_SID=orcl122
bash-4.1$ sqlplus / as sysdba

SQL*Plus: Release 12.2.0.1.0 Production on 月 2月 19 22:44:47 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SQL> startup
ORACLEインスタンスが起動しました。

Total System Global Area 838860800 bytes
Fixed Size 8790120 bytes
Variable Size 356519832 bytes
Database Buffers 465567744 bytes
Redo Buffers 7983104 bytes
データベースがマウントされました。
データベースがオープンされました。
SQL> show parameter _area_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
bitmap_merge_area_size integer 2147483647
create_bitmap_area_size integer 2147483647
hash_area_size integer 2147483647
sort_area_size integer 2147483647
workarea_size_policy string AUTO
SQL>
SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SQL>
SQL> create spfile from pfile;

File created.


Microsoft Windows

Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
SQL>
SQL> $ver

Microsoft Windows [Version 10.0.16299.125]

SQL> create pfile from spfile;

ファイルが作成されました。

SQL> --2GB
SQL> select 2*1024*1024*1024 from dual;

2*1024*1024*1024
----------------
2147483648

SQL> alter system set hash_area_size = 2147483648 scope=spfile;
alter system set hash_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set sort_area_size = 2147483648 scope=spfile;
alter system set sort_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set bitmap_merge_area_size = 2147483648 scope=spfile;
alter system set bitmap_merge_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。

SQL> alter system set create_bitmap_area_size = 2147483648 scope=spfile;
alter system set create_bitmap_area_size = 2147483648 scope=spfile
*
行1でエラーが発生しました。:
ORA-02017: 整数値が必要です。


SQL> --2GB - 1
SQL> select 2*1024*1024*1024-1 from dual;

2*1024*1024*1024-1
------------------
2147483647

SQL> alter system set hash_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set sort_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set bitmap_merge_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> alter system set create_bitmap_area_size = 2147483647 scope=spfile;

システムが変更されました。

SQL> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
ERROR:ORA-12514: TNSリスナーは接続記述子でリクエストされたサービスを現在認識していません
SQL> 警告: Oracleにはもう接続されていません。
SQL> exit
Disconnected
C:\Users\discus>set ORACLE_SID=orcl122SQL>
C:\Users\discus>sqlplus / as sysdba
SQL*Plus: Release 12.2.0.1.0 Production on 月 2月 19 22:28:45 2018
Copyright (c) 1982, 2016, Oracle. All rights reserved.
SQL>
アイドル・インスタンスに接続しました。
SQL> create spfile from pfile;
SQL>
File created.
SQL> exit
Disconnected

C:\Users\discus>


ということで、

手動PGA管理でSQL Work Area Sizeを制御する初期化パラメータで指定可能な最大サイズは

2GB -1

ということになります。(HP-UXやAIXは未確認なので情報いただけたら、ここに追記しまーす。:)

次回へつづく。




Temp落ち #1 - "Temp落ち" って?
Temp落ち #2 - PGA (Program Global Area)

| | | コメント (0) | トラックバック (0)

2018年2月18日 (日)

Temp落ち #2 - PGA (Program Global Area)

Previously on Mac De Oracle
”Temp落ち”ってなに? 
という話と、それを確認できるツールをいくつか紹介したところまででした。



余談
このネタを書きながら、"Temp落ち"とは異なる理由で今回ネタにするメモリー領域の事を調べていた事を思い出しました。(懐かしい)

当時、自動PGA管理に関する情報があまりなく、新・ソートに関する検証 その1 ペンネーム グリーンペペを隅から隅まで読み試していました:)。その何年か後に、グリーンペペさんが、だれなのかを知ることに。。業界狭いですw

むか〜し、Windows(32bit)環境のOracleで、」ORA-12518が頻発していたトラブルの原因は今回のネタで取り上げるメモリー領域だった!なんてこともあるので、構造等、知ってて損はないですよ:)


では、今回のお話。

"Hash JoinやSortなどの処理をできる限りメモリー上で行う"という、そのメモリー領域とは???
PGA (Program Global Area)と呼ばれるメモリー領域のSQL Work Areaに確保されます。
また、それらのサイズを制御する初期化パラメータがあります。(後述)
PGAは、おおよそ以下のような構造で、サーバープロセスやバックグランドプロセス毎に確保されます。”ここ大切"
Pga

Pgas_processes2_2

Structure_of_pga




参考)
PGAのメモリーサイズを制御するパラメータは、以下の通り(隠しパラメータもありますが、とりあえずデフォルト前提で:)

以下の4パラメータは、手動PGA管理で利用されるパラメータで、自動PGA管理が主流となった今ではあまり利用されることはありません。(たま〜〜に使いたくなるときはありますが、結局使わなかったり、それは別の機会にでも書くことにします)

HASH_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/HASH_AREA_SIZE.htm

SORT_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/SORT_AREA_SIZE.htm

BITMAP_MERGE_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/BITMAP_MERGE_AREA_SIZE.htm

CREATE_BITMAP_AREA_SIZE
https://docs.oracle.com/cd/E82638_01/REFRN/CREATE_BITMAP_AREA_SIZE.htm

Oracle database 9.2.0以降、自動PGA管理が登場し、上記4パラメータで個別にサイズを管理する必要がなくなりました。
PGA_AGGREGATE_TARGET/PGA_AGGREATE_LIMITの2つのパラメータで自動PGA管理で行う方法に慣れておいた方が良いと思います。

余談
ちなみに、10gR1〜11gR2までは、PGA_AGGREGATE_TARGETパラメータだけだったのですが、その頃、自動PGA管理で割り当てられるサイズを検証していたネタは以下:) pga_aggregate_targetでPGA?、_pga_max_sizeでPGA? Season2 #8 https://discus-hamburg.cocolog-nifty.com/mac_de_oracle/2010/09/pga_aggregate-7.html

ところで、ひさびさに以下マニュアルを読んで見たのですが、何度見てもモヤモヤは消えない感じのままなのがつらいです。もう少しなんとかならんかなー。
と思わなくもないです...w 作業領域の割り当てルールを明記しないのは、何故なんだろうと、ズーーーーっと考えてる。書いてくれてれば楽なのにな〜と。

Oracle® Databaseパフォーマンス・チューニング・ガイド 12cリリース2 (12.2) E72901-03
16 プログラム・グローバル領域のチューニング
https://docs.oracle.com/cd/E82638_01/TGDBA/tuning-program-global-area.htm


Oracle® Databaseリファレンス 12cリリース2 (12.2) E72905-03
PGA_AGGREGATE_LIMIT
https://docs.oracle.com/cd/E82638_01/REFRN/PGA_AGGREGATE_LIMIT.htm

12.2からResource Managerを利用して特定のコンシューマ・グループ内の各セッションに割り当てられるようになったようで、 PGAメモリー使用量に絶対制限を設定し、超過した場合にはORA-10260エラーとさせることができるようになったようですね。(まだ試したことはないのですが)

16.3.2 リソース・マネージャを使用したプログラム・グローバル領域のサイズ設定
https://docs.oracle.com/cd/E82638_01/TGDBA/tuning-program-global-area.htm

PGA_AGGREGATE_TARGET
https://docs.oracle.com/cd/E82638_01/REFRN/PGA_AGGREGATE_TARGET.htm


12cの自動PGA管理について軽く確認した感触だと、12cR2でPDB毎の制御ができるようになった事以外、大きな変化ないんじゃないかと思っているのですが、そういえばちゃんと確認したことないな。。。
次回は、昔試した方法で、12cでもPGA割り当てルールに変化はないのか確認しておきますか。。。18cはどうなるんだろ:)


続く。



Temp落ち #1 - "Temp落ち" って?

| | | コメント (0) | トラックバック (0)

2017年12月 4日 (月)

”utl_file I/O” - この症状はあれの可能性が高いですね。

JPOUG Advent Calendar 2017の4日目のエントリーです。

さて、最近あまりお目にかかってなかったUTL_FILEパッケージで表データをcsvに書きだすネタにしました。

先日、UTL_FILEパッケージを利用した処理が想定以上に遅いという相談をうけました。
AWRレポートをみると、上位の待機イベントは、”utl_file I/O"。

!!!!おーーーこれは、珍しいというか、久々にみた病気w

UTL_FILEパッケージを利用したI/Oをグルグルしているとか、でかいファイル読み書きしているかの、どちらかですよねw
ということで、この症状の治療法を考えてみます。


<参考 - 環境>

MacBook:˜ system_profiler SPHardwareDataType | grep -E 'Model|Processor|Cores|Memory'
Model Name: MacBook
Model Identifier: MacBook9,1
Processor Name: Intel Core m5
Processor Speed: 1.2 GHz
Number of Processors: 1
Total Number of Cores: 2
Memory: 8 GB

ホストOS
MacBook:˜ discus$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.13.1
BuildVersion: 17B1003

MacBook:˜ discus$ VBoxManage -version
5.1.30r118389


ゲストOSとOracle Database
orcl@SYS> !uname -a
Linux localhost.localdomain 4.1.12-94.3.6.el7uek.x86_64 #2 SMP Tue May 30 19:25:15 PDT 2017 x86_64 x86_64 x86_64 GNU/Linux

orcl@SYS> !cat /etc/oracle-release
Oracle Linux Server release 7.3

orcl@SYS>
orcl@SYS> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production 0
PL/SQL Release 12.2.0.1.0 - Production 0
CORE 12.2.0.1.0 Production 0
TNS for Linux: Version 12.2.0.1.0 - Production 0
NLSRTL Version 12.2.0.1.0 - Production 0

UTL_FILEでファイル出力する際、ちょっとした手順の漏れが性能差として現れてしまうことがあります。
表の行長は数百バイト程度ですが、行数は数千万〜数億行なんていう状況だと、性能差が顕著に現れてしまうので注意が必要です。

以下の表、セグメントサイズは1GB程度ですが、行数は1千万行以上あります。
この表データをUTL_FILEパッケージを利用してcsvに出力してみます。

ORCL@SCOTT> select segment_name,sum(bytes)/1024/1024/1024 "GB" from user_segments group by segment_name;

SEGMENT_NAME GB
------------------------------ ----------
ABOUT_100BYTES_ROWS .9140625
PK_ABOUT_100BYTES_ROWS .171875


ORCL@SCOTT> select count(1) from about_100bytes_rows;

COUNT(1)
----------
10737420

表の1行は100bytes(改行コード含)です。

ORCL@SCOTT%gt; r
1 SELECT
2 LENGTH(
3 TO_CHAR(id,'FM000000000000000000000000000009')
4 ||','||FOO
5 ) row_length
6 FROM
7 about_100bytes_rows
8 WHERE
9* rownum <= 1

ROW_LENGTH
----------
99

次の2つのコードの赤太文字部分に着目してください。
その部分が異なるだけで処理時間に大きな差が出ます。

DECLARE
cDIR_NAME CONSTANT VARCHAR2(30) := 'FILES_DIR';
cFILE_NAME CONSTANT VARCHAR2(128) := 'no_writebuffering_'||TO_CHAR(systimestamp, 'rrmmddhh24miss.ff')||'.txt';
cBufferSize CONSTANT BINARY_INTEGER := 32767;
cOpenMode CONSTANT VARCHAR2(2) := 'w';
fileHandle UTL_FILE.FILE_TYPE;

cBulkReadLimit CONSTANT PLS_INTEGER := 324;
TYPE tBulkReadArray IS TABLE OF VARCHAR2(8192) INDEX BY BINARY_INTEGER;
bulkReadArray tBulkReadArray;
CURSOR cur_about100bytesRow IS
SELECT
TO_CHAR(id,'FM000000000000000000000000000009')
||','||FOO
AS csvrow
FROM
about_100bytes_rows
;
BEGIN
OPEN cur_about100bytesRow;
fileHandle := UTL_FILE.FOPEN(cDIR_NAME, cFILE_NAME, cOpenMode, cBufferSize);
LOOP
FETCH cur_about100bytesRow
BULK COLLECT INTO bulkReadArray
LIMIT cBulkReadLimit;

EXIT WHEN bulkReadArray.COUNT = 0;

FOR i IN bulkReadArray.FIRST..bulkReadArray.LAST LOOP
UTL_FILE.PUT_LINE(
file => fileHandle
, buffer => bulkReadArray(i)
, autoflush => true
);
END LOOP;
END LOOP;
UTL_FILE.FFLUSH(fileHandle);
UTL_FILE.FCLOSE(fileHandle);
CLOSE cur_about100bytesRow;
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fileHandle) THEN
UTL_FILE.FCLOSE(fileHandle);
END IF;

IF cur_about100bytesRow%ISOPEN THEN
CLOSE cur_about100bytesRow;
END IF;
RAISE;
END;
/


PL/SQLプロシージャが正常に完了しました。

経過: 00:02:27.05


DECLARE
cDIR_NAME CONSTANT VARCHAR2(30) := 'FILES_DIR';
cFILE_NAME CONSTANT VARCHAR2(128) := 'writebuffering_'||TO_CHAR(systimestamp, 'rrmmddhh24miss.ff')||'.txt';
cBufferSize CONSTANT BINARY_INTEGER := 32767;
cOpenMode CONSTANT VARCHAR2(2) := 'w';
fileHandle UTL_FILE.FILE_TYPE;
buffer VARCHAR2(32767);

cBulkReadLimit CONSTANT PLS_INTEGER := 324;
TYPE tBulkReadArray IS TABLE OF VARCHAR2(8192) INDEX BY BINARY_INTEGER;
bulkReadArray tBulkReadArray;
CURSOR cur_about100bytesRow IS
SELECT
TO_CHAR(id,'FM000000000000000000000000000009')
||','||FOO
AS csvrow
FROM
about_100bytes_rows
;
BEGIN
OPEN cur_about100bytesRow;
fileHandle := UTL_FILE.FOPEN(cDIR_NAME, cFILE_NAME, cOpenMode, cBufferSize);
LOOP
FETCH cur_about100bytesRow
BULK COLLECT INTO bulkReadArray
LIMIT cBulkReadLimit;

EXIT WHEN bulkReadArray.COUNT = 0;

buffer := NULL;
FOR i IN bulkReadArray.FIRST..bulkReadArray.LAST LOOP
buffer := buffer || bulkReadArray(i) || UTL_TCP.CRLF;
END LOOP;
UTL_FILE.PUT(fileHandle, buffer);
UTL_FILE.FFLUSH(fileHandle);
END LOOP;
UTL_FILE.FFLUSH(fileHandle);
UTL_FILE.FCLOSE(fileHandle);
CLOSE cur_about100bytesRow;
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fileHandle) THEN
UTL_FILE.FCLOSE(fileHandle);
END IF;

IF cur_about100bytesRow%ISOPEN THEN
CLOSE cur_about100bytesRow;
END IF;
RAISE;
END;
/


PL/SQLプロシージャが正常に完了しました。

経過: 00:00:43.96


何が違うかお分ですよね!
UTL_FILE.PUT_LINE/UTL_FILE.PUTが違う!!w
その部分は重要ではなく、100Bytes単位に書き込んでいるか、約32KB単位で書き込んでいるかが重要なんです。

UTL_FILEは最大32767バイトのバッファを利用できますが、前者はバッファを有効利用せず、100Bytes毎に書き出しています。後者は約32KB単位で書き出しています。

その差はAWRレポートからも見えてきます。


AWRレポート(一部抜粋)
UTL_FILEパッケージによるI/Oの待機は、”utl_file I/O”という待機イベントで現れます。

Avg Waitは短いですが、理由は100バイト単位の小さいなサイズの書き込みを繰り返しているわけなので、そんなとこでしょう。

Top 10 Foreground Events by Total Wait Time

Total Wait Avg % DB Wait
Event Waits Time (sec) Wait time Class
------------------------------ ----------- ---------- --------- ------ --------
DB CPU 147.4 99.3
utl_file I/O 32,212,266 49.5 1.54us 33.3 User I/O
direct path read 1,881 1.2 620.61us .8 User I/O
resmgr:cpu quantum 1 .1 85.85ms .1 Schedule
db file sequential read 17 0 .98ms .0 User I/O
PGA memory operation 90 0 35.13us .0 Other
latch: shared pool 1 0 1.05ms .0 Concurre
control file sequential read 80 0 12.83us .0 System I
Disk file operations I/O 6 0 73.67us .0 User I/O
SQL*Net message to client 2 0 9.00us .0 Network


一方、約32KB単位でバッファリングして書き出している場合、Waits回数が激減しています。
Avg Waitsは大きくなっていますが、書き出しサイズにほぼ比例しているので想定通りというところ。

Top 10 Foreground Events by Total Wait Time

Total Wait Avg % DB Wait
Event Waits Time (sec) Wait time Class
------------------------------ ----------- ---------- --------- ------ --------
DB CPU 45 98.5
utl_file I/O 66,288 27.4 413.54us 60.1 User I/O
direct path read 1,881 1 550.71us 2.3 User I/O
db file sequential read 88 0 443.38us .1 User I/O
direct path write 2 0 4.69ms .0 User I/O
control file sequential read 80 0 33.25us .0 System I
latch: shared pool 1 0 2.38ms .0 Concurre
Disk file operations I/O 4 0 323.50us .0 User I/O
PGA memory operation 79 0 15.73us .0 Other
SQL*Net message to client 4 0 20.25us .0 Network

utl_file.put/put_lineでcsvを出力しているdeviceのiostat(util%)
まだまだ余裕があるのでI/Oで詰まっているのではなく、UTL_FILE.PUT/PUT_LINEの使い方の影響が大きいということですね。
20171203_202342


最後に、
UTL_FILEパッケージの入出力時にはバッファの有効利用をお忘れなく。(つい忘れちゃうこともあるので、急いでるときとかw)
扱うデータが多い場合は得に。

そして、みなさま、
メリークリスマス(まだエントリーを書くかもしれませんがw)
#JPOUG

| | | コメント (0) | トラックバック (0)

2017年11月11日 (土)

SDCLI Error Out "Failed to create the processor for command format"

昨日 Windowsの SQL Developer 17.2のsdcliコマンドで整形しようと思ったらハマったので、備忘録


SDCLI Error Out "Failed to create the processor for command format" (Doc ID 2297353.1)

Oracle SQL Developer - Version 17.2 and later
と記載されているようですが、Version4.2で発生していたのでそのまま新版でも引き続き発生してるという感じですね。
改善されるまでの間、面倒くさいので、Version 4.0.2〜4.1.5あたりをインストールしてSQLを整形したほうがイライラしなくてよいと思います。

discus-mother:bin oracle$ cat version.properties
COMPANY=Oracle
PRODUCT=SQL Developer
VERSION=4.2000170891709f
VER=4.2.0
VER_FULL=4.2.0.17.089.1709
BUILD_LABEL=17.089.1709
BUILD_NUM=17.089.1709
EDITION=
discus-mother:bin oracle$
discus-mother:bin oracle$ sdcli format intput=sample.sql output=sample_fmt.sql

Oracle SQL Developer
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.

Failed to create the processor for command format

私の場合、SQL Developer 4.0.3をインストールしてSQL文整形オンリー使いで回避しました。



参考
SQL Developer 4の素敵なコマンドライン de SQL整形 :)

Note:
ディレクトリ指定の一括変換も動作が怪しいことがあるので、単一のSQL文整形を繰り返すようなshell作ったほうがいいかもしれません。

| | | コメント (0) | トラックバック (0)

2017年11月 7日 (火)

DBMS_ADVISOR.CREATE_FILE()プロシージャは、Diagnostic/Tuning Pack不要らしいということの確認

Previously on Mac De Oracle



余談

DBMS_ADVISOR.CREATE_FILEとなっているがライセンス上、diagnostic/tuning packライセンスはなくてもアクセスしてできるように読み取れるのだけと、もしかすると、この影響で、DBMS_ADVISOR.CREATE_FILEの存在は知っていても使ってないのかあるのかな??

以下のマニュアルを読む限り、Diagnostic/Tuning Packでは、DBMS_ADVISORパッケージ全体ではなく、Diagnostic/Tuning Packに関連するパラメータを与える必要のある機能についての制限であることしか記載されていない。
つまり、DBMS_ADVISOR.CREATE_FILE()に関して言えば、Diagnostic/Tuning Pack特有の機能ではないから利用可能なはず。 ←間違ってたらツッコミ希望w

Oracle® Databaseライセンス情報 12cリリース1 (12.1) 2 オプションおよびパック
https://docs.oracle.com/cd/E49329_01/license.121/b71334/options.htm



なんてことを書いたところ、

control_management_pack_access=none

でも使えるとなら間違いなくないんじゃないの?
というコメントがあったので試してみました。 マニュアルの通りだと思われます。ということですね。FAQ!

orcl@SYS> show parameter control_management_pack_access

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
control_management_pack_access string NONE
orcl@SYS>
orcl@SYS> l
1 DECLARE
2 reportClob CLOB := EMPTY_CLOB();
3 BEGIN
4 reportClob := 'test test test';
5 DBMS_ADVISOR.CREATE_FILE(
6 buffer => reportClob
7 ,location => 'REP_DIR'
8 ,filename => 'test.txt'
9 );
10* END;
orcl@SYS> /

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.23
orcl@SYS> !ls report
test.txt

orcl@SYS> !cat report/test.txt
test test test



DBMS_SQLTUNE.PACK_STGTAB_SQLSETって、例外投げんのかよw
SQL Tuning Setのキャプチャから退避までのスクリプト(やっつけ)
SQL Tuning Set (STS)のフィルタリング
DBMS_SQLTUNE.REPORT_ANALYSIS_TASK()ファンクションで生成されるCLOB型のレポートをファイルに保存する簡単な方法

| | | コメント (0) | トラックバック (0)

2017年9月 1日 (金)

ORA-12034 #2

前回の通り、高速リフレッシュの間にdata pump export / importを完了させれば、ORA-12034なんて起きないはず。

VirtualBoxの環境は試験開始前のスナップショットで戻してあります
20170415_14044_2

SQL*Plus: Release 12.1.0.2.0 Production on Mon Aug 28 20:57:49 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl@MVIEW_SCHEMA1> @mview_info_c
Connected.

¥Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/08/28 21:00:54 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 20:55:54 2017/08/28 21:00:54 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

前回は5分間隔だったので、余裕をもたせて30分にしてあります。
本番環境で5分間隔の高速リフレッシュを30分にしたり、止めたりってことはかなり敷居が高いとは思いますが、そのあたりは空気を読んで対応する必要があるかと。 :)

orcl12c@SYS> conn mview_schema1@orcl
Connected.
orcl@MVIEW_SCHEMA1> alter materialized view mv_master refresh next sysdate+30/1440;

Materialized view altered.

orcl@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.


ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+30/1440 2017/08/28 21:30:36 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 20:55:54 2017/08/28 21:30:36 sysdate+30/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

準備は整ったので、高速リフレッシュが終わったタイミングでexportからimportまでをやっつけちゃいます!
まずはエクスポート

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=mview_schema1.dmp logfile=exp_mview_schema1.log schemas=mview_schema1
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/mview_schema1.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Mon Aug 28 21:05:00 2017 elapsed 0 00:00:56

[oracle@vbgeneric ˜]$ sqlplus master_schema@orcl

SQL*Plus: Release 12.1.0.2.0 Production on Mon Aug 28 21:05:19 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl@MASTER_SCHEMA> select count(*) from mlog$_master;

COUNT(*)
----------
0

orcl@MASTER_SCHEMA> conn mview_schema1@orcl
Connected.

orcl@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.


ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+30/1440 2017/08/28 21:30:36 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 20:55:54 2017/08/28 21:30:36 sysdate+30/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

次にインポートしてMviewを複製! 

orcl12c@SYS> conn system@orcl2
Connected.
orcl2@SYSTEM> create directory workdir as '/u01/userhome/oracle';

Directory created.

orcl2@SYSTEM> exit
Disconnected from Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
[oracle@vbgeneric ˜]$ impdp system@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp

Import: Release 12.1.0.2.0 - Production on Mon Aug 28 21:07:13 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Mon Aug 28 21:07:40 2017 elapsed 0 00:00:23

複製完了!!!!
結果はいかに...

[oracle@vbgeneric ˜]$ sqlplus mview_schema1@orcl2

SQL*Plus: Release 12.1.0.2.0 Production on Mon Aug 28 21:08:09 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl2@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+30/1440 2017/08/28 22:00:46 5
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+30/1440 2017/08/28 22:00:46 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 21:30:46 2017/08/28 22:00:46 sysdate+30/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 21:30:46 2017/08/28 22:00:46 sysdate+30/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

予想通り成功しました!

エクスポート〜インポートによるMVIEW複製までを高速リフレッシュの間に行うことが可能ならば例のエラーは回避できることが確認できました。
が、
小さなMViewならまだしも、巨大なMView、巨大なMViewが複数あるリフレッシュグループだったりすると、それはもう大変な作業になることは想像できます。
大人の事情でMViewの高速リフレッシュを止められない、とか高速リフレッシュ間隔が非常短い場合には無理しないで、ほかの手を考えた方が良いと思います。
ただし、他の手を進めるにも時間には余裕を持った方がいいですよね。なんでギリギリなんだろうねと、よく思います。
夏休みの最後の1週間で宿題全部やりきるみたいなのは嫌ですよねw
20170802_140801




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)
Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...)
ORA-12034

| | | コメント (0) | トラックバック (0)

2017年8月31日 (木)

ORA-12034

一ヶ月の家庭内、ワンオペも無事終了したので、ブログも今年前半のペースで再開か!?w

ということで、
以前ちょっとだけ書いた高速リフレッシュを止めてないと完全リフレッシュが必要になってしまう。タイミングの問題にフォーカスしてみようと思います。

どのようなタイミングの問題かというと、

ORA-12034: materialized view log on "xxxxxx"."xxxxxxx" younger than last refresh

出会った方も意外と多かったりしてw 

前述のエラーは、materialized view logが絡んいるので、”高速リフレッシュ”時に発生するエラーです!
高速リフレッシュを行なっている環境でこの状態になってしまうと、”高速リフレッシュ”の再開には”完全リフレッシュ”が必須となってしまうところが怖いというか面倒くさいところ。
マスターサイトも含めて同期するサイズが小さければ完全リフレッシュも面倒なことにならない場合もありますが、数十GB以上の巨大なマテビューだったら、どうします???
マテリアライズドビューのリフレッシュ間隔が短いシステムだと、完全リフレッシュに要する時間が大問題になることも... (色々な状況が想定されていない構成だと、そうなりやすい.....なw

もう少し簡単に言うと、
materialized view logの伝播が必要なデータ有無に関係なく、リフレッシュ時刻を跨いてしまうかどうか! なんですよね、これ。

実際にどうなるか、materialized view logは空の状態で、Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
の環境を利用して再現させてみます。環境の詳細は、Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製参照のこと。


VirtualBoxのスナップショットから基本レプリケーション環境構築済みの状態(以前いろいろ行った試験前の状態)に戻してあります。
20170415_14044

まず、高速リフレッシュの状態がどうなっているか確認してみると....5分間隔(INTERVAL列)で動作中であることがわかりますよね!(DBMS_JOBのINTERVALの指定方法っ非直感的わかりづらいので早くなくなってほしいw)
直近の高速リフレッシュが終わり、MLOGも0件。つまりマテリアライズドビューに反映する必要のあるデータは存在しない状態にしてあります。


orcl@MVIEW_SCHEMA1> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 09:00:44 2017/08/28 09:05:44 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

前述の状態(5分間隔の高速リフレッシュが動作中の状態)で、次の高速リフレッシュが実行されるまでの間にマテリアライズドビュー関連オブジェクトを含むschemaを丸ごとエクスポートします(他のデータベースへ複製するために)
エラーを再現するData Pump Export、Importと高速リフレッシュ間隔との間合いは下図の通り
20170802_140338

上図の通りの流れでData Pump ExportからImportまでを行い、ORA-12034の発生状況を確認

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=mview_schema1.dmp logfile=exp_mview_schema1.log schemas=mview_schema1
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/mview_schema1.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Mon Aug 28 09:02:48 2017 elapsed 0 00:00:54


念のためマスターサイトでMLOGの件数を確認しておきます。
マテビューサイトと同期する必要のあるデータはないことが確認できます。

orcl@MASTER_SCHEMA> select count(*) from mlog$_master;

COUNT(*)
----------
0


という確認を行なっている間いに高速リフレッシュが実行されました! (LAST_DATE列、NEXT_DATE列、FAILURES列から正常にリフレッシュされたことを確認できます)

orcl@MVIEW_SCHEMA1> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;
JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 09:05:48 2017/08/28 09:10:48 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

まず、異なるPDBにディレクトリオブジェクトを作成して、schemaごとインポートします。
この時点で以前のエントリにもあるようにORCL:master_schema.masterをマスターサイトするmviewが、ORCL:mview_schema1.mview_masterとORCL2:mview_schema1.mview_masterという構成になります。


orcl2@SYSTEM> create directory workdir as '/u01/userhome/oracle';

Directory created.


Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Mon Aug 28 10:02:09 2017 elapsed 0 00:00:22


インポートが無事終わったので、高速リフレッシュの状態を確認!!

おおおおおおおお〜〜、インポートしたschemaに含まれるMVIEWは高速リフレッシュに失敗しています。
なんということでしょうw(狙い通りなので、わざとらしいですねw)

なお、LAST_REF列がCOMPLETEという完全リフレッシュを示すステータスになっていますが、これはで確認下通り、昔からこんな動作だった曖昧な記憶があるので仕様だと思われますが、
インポート時に完全リフレッシュしているわけでもないのに、完全リフレッシュ扱いとされている紛らわしい状態になります。(このステータスが影響している可能性もありそうな...)

orcl2@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/08/28 10:06:46 3
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/08/28 10:04:22 5


MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID


JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 10:01:46 2017/08/28 10:06:46 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 10:04:22 sysdate+5/1440 1 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');


高速リフレッシュのが実行され2度目の失敗、failusers列が2になっています。いずれ高速リフレッシュジョブは自動的に停止されます。
(自動停止される前に問題に対処すればそのまま継続されます)

orcl2@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/08/28 10:08:26 5
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/08/28 10:06:46 3


MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID


JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 10:01:46 2017/08/28 10:06:46 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/08/28 10:08:26 sysdate+5/1440 2 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

では、原因を調査してみましょう!


P列(POSSIBLE列)の REFRESH_FAST行がNになっていて高速リフレッシュできない状態になっています。
コメントにはmv log is newer than last full refresh 記載されています。 このメッセージ冒頭で書いたメッセージと微妙に違うのですが、

orcl2@MVIEW_SCHEMA1> @?/rdbms/admin/utlxmv

Table created.

orcl2@MVIEW_SCHEMA1> exec dbms_mview.explain_mview('MV_MASTER','TEST01');

PL/SQL procedure successfully completed.

orcl2@MVIEW_SCHEMA1> SELECT mvname,capability_name,related_text,related_num,msgno,possible,msgtxt,seq FROM mv_capabilities_table;

MVNAME CAPABILITY_NAME RELATED_TEXT RELATED_NUM MSGNO P MSGTXT SEQ
------------------------------ ------------------------------ -------------------- ----------- ---------- - ------------------------------------------------------------------------------------------ ----------
MV_MASTER PCT N 1
MV_MASTER REFRESH_COMPLETE Y 1002
MV_MASTER REFRESH_FAST N 2003
MV_MASTER REWRITE N 3004
MV_MASTER PCT_TABLE MASTER 52 2068 N relation is not a partitioned table 4005
MV_MASTER REFRESH_FAST_AFTER_INSERT MASTER_SCHEMA.MASTER 2077 N mv log is newer than last full refresh 5006
MV_MASTER REFRESH_FAST_AFTER_ONETAB_DML 2146 N see the reason why REFRESH_FAST_AFTER_INSERT is disabled 6007
MV_MASTER REFRESH_FAST_AFTER_ANY_DML 2161 N see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled 7008
MV_MASTER REFRESH_FAST_PCT 2197 N PCT FAST REFRESH is not possible if query contains a remote table 8009
MV_MASTER REWRITE_FULL_TEXT_MATCH MASTER 52 2099 N mv references a remote table or view in the FROM list 9010
MV_MASTER REWRITE_FULL_TEXT_MATCH 2159 N query rewrite is disabled on the materialized view 9011
MV_MASTER REWRITE_PARTIAL_TEXT_MATCH 2159 N query rewrite is disabled on the materialized view 10012
MV_MASTER REWRITE_GENERAL 2159 N query rewrite is disabled on the materialized view 11013
MV_MASTER REWRITE_PCT 2158 N general rewrite is not possible or PCT is not possible on any of the detail tables 12014
MV_MASTER PCT_TABLE_REWRITE MASTER 52 2068 N relation is not a partitioned table 13015

15 rows selected.


試しに手動で高速リフレッシュさせてみます。

orcl2@MVIEW_SCHEMA1> exec dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
BEGIN dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); END;

*
ERROR at line 1:
ORA-12034: materialized view log on "MASTER_SCHEMA"."MASTER" younger than last refresh
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2821
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 3058
ORA-06512: at "SYS.DBMS_IREFRESH", line 687
ORA-06512: at "SYS.DBMS_REFRESH", line 195
ORA-06512: at line 1


冒頭で説明した高速リフレッシュ時のエラーが発生していることがわかります。
そして、.......
このエラーが発生してしまうと、完全リフレッシュで回復させるしかありません!!!!! 


参考:
新規マテリアライズド・ビュー・サイトでの高速リフレッシュ・エラー

"新規マテリアライズド・ビュー・サイトでマテリアライズド・ビュー作成中に、マスター表またはマスター・マテリアライズド・ビューのマテリアライズド・ビュー・ログがパージされる場合があります。これが発生すると、次のエラーが検出される場合があります。

ORA-12004 REFRESH FAST cannot be used for materialized view materialized_view_name
ORA-12034 materialized view log on materialized_view_name younger than last refresh"


回避策として使えそうな方法は....
高速リフレッシュを停止して行う方法もあります(止められないシステムもあるので大人の事情しだいでしょうけど)
デプロイメントテンプレートで対応できそうな要件か十分検討、検証した上でやる必要があると思います。(完全リフレッシュするマテビューが巨大すぎて完全リフレッシュだけで作業時間オーバーなんてこともありえますから)

マニュアルに記載されている回避方法ってのもあるけど、手順がめんどくさい>< な。 可能なら止めちゃった方が楽そうだが、大人の事情が絡んでるとそうもいかないだろうし、結局めんどくさいw
めんどくさいの嫌いなので試したこともないんだけど、時間があったら回避できるか試してみるか....な。

新規マテリアライズド・ビュー・サイトを追加するときの問題の回避


次回は、高速リフレッシュを跨がないように、したら回避できるよね。と言うお話へつづく
リフレッシュの間に終わらないとNGだけどw 、高速リフレッシュの間にexport/importが終わればこの問題は発生しない。。わけで。
次回のネタのイメージ(リフレッシュをまた跨らなければ例のエラーは発生しないという確認:)
20170802_140351




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング

Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する

Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係

Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製

Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)

Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)

Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...)

| | | コメント (0) | トラックバック (0)

2017年6月17日 (土)

SQL Developer de Real time SQL monitoring / FAQ

Oracle Database 12c EEかつ、Diagnostic Pack / Tuning Packもあるのに
SQL real time monitoringしないのはもったいないという話から、
http://www.oracle.com/technetwork/jp/ondemand/db-basic/d-16-ssqltuning-1448439-ja.pdf

EMなくても簡単にできないのか? 

と聞かれたので

おすすめなのが、SQL Developer

SQL Developer 4.2
http://www.oracle.com/technetwork/jp/developer-tools/sql-developer/downloads/index.html

テキストよりGUIでという方にはおすすめ。

細けーはなしはいつか書くつもりですが、とりあえず、こんなことを確認できるんでっす。

20170617_110017

20170617_110411


20170617_110436


!!!!!ここ重要!!!!!
以下のダイアログには、Tuning Packが必要とありますが、Tuning Packを利用するにはDiagnostic Packが必要なので、 Real time SQL monitoringを行うには、Diagnostic PackとTuning Packの両方が必要というこなので、間違わないようにしてくださいませ。マニュアルにはしっかり書かれてますが、折角ダイアログで警告してるのにコメントが残念な。
https://docs.oracle.com/cd/E57425_01/121/DBLIC/options.htm#CIHFIHFG

20170617_110454

20170617_121606


20170617_121624


20170617_121629


20170617_121637


| | | コメント (0) | トラックバック (0)

2017年6月 9日 (金)

Known Issues for Oracle Database Cloud Service / 始めるまえに読んでおこうね

Known Issues for Oracle Database Cloud Service
https://docs.oracle.com/cd/E60665_01/dbcs_dbaas/KIDBR/toc.htm

を見ると既知の問題が結構あります。 エグいのもw

でも Oracle iASやらOracle Portal Server (もう17年ぐらい前だよねw)の時は、そんなの比べ物にならないくらい、既知の問題があって、やる気あんのかw ぐらいに思ってたことがあったので、これぐらいの量じゃ驚かないw
(麻痺してる?w)


課金そのままで、「Standard Edition OneデータベースがEnterprise Editionになっている可能性がある」なんてのもあるらしい。
https://docs.oracle.com/cd/E60665_01/dbcs_dbaas/KIDBR/index.html#kidbr-GUID-EF96077B-2E54-4461-A825-51F8E8194D77

「15.3.5イメージを使用して作成されたサービス・インスタンスのロールバックでv$optionsが無効になる」
v$optionsってなに? とおもったが、v$optionのことだったやつとか
https://docs.oracle.com/cd/E60665_01/dbcs_dbaas/KIDBR/index.html#kidbr-#GUID-0AFC0771-700C-43BC-93B7-BA072768D928

とにかく、やる前に読んでおくと少し幸せになれるかも。

| | | コメント (0) | トラックバック (0)

2017年5月21日 (日)

FAQ:Solaris 11.3とZFSという組み合わせだとFAQなのな、ZFS ARCに関する動きについて

有名すぎるネタらしいけど、Solaris + ZFSを使うことなんて、、久々すぎて、あーあったっけ〜、そんなOSという感じの久しぶり感。もう二桁何年ぶりちょい前ぶりぐらいに、遠い目w 

ということで、せっかく調べたので、備忘録。



Oracle® Solaris 11.3 カーネルのチューンアップ・リファレンスマニュアル
第 3 章 Oracle Solaris ZFS チューニング可能パラメータ
ZFS メモリー管理パラメータ

カーネルゾーンのメモリーを予約するための ZFS ARC のチューニング
Tuning the ZFS ARC to Reserve Memory for Kernel Zones
ZFS Memory Tuning for Oracle Databases & Application on Oracle Solaris 11
Activity of the ZFS ARC
ZFS Performance Analysis and Tools - Brendan Gregg
Solaris っていまどうなってるの? 2016 末
Oracle® Solaris カーネルゾーンの作成と使用


Solaris 11.3とOracle Database 12.1.0.2を利用して、11.3でもZFS ARCが暴れてしまう状況を再現して見ます。
Oracle Solaris 11.3 VM Template for Oracle VM VirtualBox

bash-4.1$ pkg list entire
NAME (PUBLISHER) VERSION IFO
entire 0.5.11-0.175.3.1.0.5.0 i--
bash-4.1$ uname -r
5.11
bash-4.1$ cat /etc/release
Oracle Solaris 11.3 X86
Copyright (c) 1983, 2015, Oracle and/or its affiliates. All rights reserved.
Assembled 06 October 2015

SQL> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production 0
PL/SQL Release 12.1.0.2.0 - Production 0
CORE 12.1.0.2.0 Production 0
TNS for Solaris: Version 12.1.0.2.0 - Production 0
NLSRTL Version 12.1.0.2.0 - Production 0


起動直後の状態、freelistは5.9Gほどあり、約3GB程度空いていれば起動できる設定のOracle Databaseを起動するだけの空きメモリがある。

root@angelfish:˜# echo ::memstat | mdb -k
Page Summary Pages Bytes %Tot
----------------- ---------------- ---------------- ----
Kernel 193500 755.8M 9%
ZFS Metadata 23285 90.9M 1%
ZFS File Data 159905 624.6M 8%
Anon 106662 416.6M 5%
Exec and libs 4207 16.4M 0%
Page cache 29519 115.3M 1%
Free (cachelist) 1 4k 0%
Free (freelist) 1561934 5.9G 74%
Total 2097039 7.9G

ZFS File Dataって、そんなに簡単にFreelistから取れるだけ取って増加しちゃうのかなぁ〜 (しらなかったのでどうなるか再現してみることに)
でかいファイルを大量にコピーして、ZFS File Dataをでかくしてみる! と......

-bash-4.1$ cd orcl12c/
-bash-4.1$ ls -lh
total 4091341
-rw-r----- 1 oracle oinstall 17M 5月 8日 20:56 control01.ctl
drwxr-x--- 2 oracle oinstall 7 4月 29日 01:39 pdborcl
drwxr-x--- 2 oracle oinstall 5 4月 29日 01:27 pdbseed
-rw-r----- 1 oracle oinstall 50M 5月 8日 20:48 redo01.log
-rw-r----- 1 oracle oinstall 50M 5月 8日 20:48 redo02.log
-rw-r----- 1 oracle oinstall 50M 5月 8日 20:56 redo03.log
-rw-r----- 1 oracle oinstall 690M 5月 8日 20:56 sysaux01.dbf
-rw-r----- 1 oracle oinstall 790M 5月 8日 20:56 system01.dbf
-rw-r----- 1 oracle oinstall 197M 5月 8日 20:49 temp01.dbf
-rw-r----- 1 oracle oinstall 340M 5月 8日 20:56 undotbs01.dbf
-rw-r----- 1 oracle oinstall 5.0M 5月 8日 20:56 users01.dbf
-bash-4.1$ cd ..
-bash-4.1$ cp -r oradata/ oradata.org


ZFS ARCがあっというまにfreelistを食いつぶしていきます!
cpでOracle Databaseのデータファイルをコピーし終わったらfreelistの残りは、3%!!!!!

なんだ!
簡単に再現するのな。

root@angelfish:˜# echo ::memstat | mdb -k
Page Summary Pages Bytes %Tot
----------------- ---------------- ---------------- ----
Kernel 303609 1.1G 14%
ZFS Metadata 65375 255.3M 3%
ZFS File Data 1417002 5.4G 68%
Anon 112798 440.6M 5%
Exec and libs 66749 260.7M 3%
Page cache 37322 145.7M 2%
Free (cachelist) 5294 20.6M 0%
Free (freelist) 62403 243.7M 3%
Total 2097039 7.9G


この状態でOracle Databaseを起動すると....だよね〜。ZFS File Dataを簡単には解放してくれない..といういうSolaris / ZFS界隈では有名?な動きが再現します。

-bash-4.1$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on 日 5月 14 02:19:56 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SQL> startup
ORA-27102: out of memory
Solaris-AMD64 Error: 12: Not enough space
Additional information: 1671
Additional information: 2147483648
Additional information: 2566914048
SQL>

以下マニュアルにも書かれていますが、

Oracle® Solaris 11.3 カーネルのチューンアップ・リファレンスマニュアル
第 3 章 Oracle Solaris ZFS チューニング可能パラメータ
ZFS メモリー管理パラメータ
https://docs.oracle.com/cd/E62101_01/html/E62779/chapterzfs-3.html

Doc Id:1663862.1 からダウンロードできるset_user_reserve.shを利用して再起動なしで空きを確保した後、/etc/systemで永続的に設定しておくのがおすすめ。
実際に何%をを確保するかは、試験や運用開始後の継続的なモニタリングで調整するしかないだろうね。

root@angelfish:˜# ./set_user_reserve.sh -fp 60
Current user_reserve_hint_pct value is 0.
Adjusting user_reserve_hint_pct from 0 to 60
2017年05月14日 (日) 02時30分41秒 JST : waiting for current value : 18 to grow to target : 20
2017年05月14日 (日) 02時30分49秒 JST : waiting for current value : 22 to grow to target : 25

...中略...

2017年05月14日 (日) 02時32分12秒 JST : waiting for current value : 50 to grow to target : 55
2017年05月14日 (日) 02時32分34秒 JST : waiting for current value : 55 to grow to target : 60
Adjustment of user_reserve_hint_pct to 60 successful.
Make the setting persistent across reboot by adding to /etc/system

root@angelfish:˜# echo ::memstat | mdb -k
Page Summary Pages Bytes %Tot
----------------- ---------------- ---------------- ----
Kernel 303754 1.1G 14%
ZFS Metadata 64652 252.5M 3%
ZFS File Data 369117 1.4G 18%
Anon 112217 438.3M 5%
Exec and libs 66805 260.9M 3%
Page cache 37142 145.0M 2%
Free (cachelist) 8453 33.0M 0%
Free (freelist) 1107868 4.2G 53%
Total 2097039 7.9G

あとは/etc/systemににuser_reserve_hint_pctを追記する!

root@angelfish:˜# tail -10 /etc/system

...略...
* set nautopush=32
* set maxusers=40
*
* To set a variable named 'debug' in the module named 'test_module'
*
* set test_module:debug = 0x13
*
*
*
set user_reserve_hint_pct=60


起動した!!

SQL> startup

Total System Global Area 2583691264 bytes
Fixed Size 3006976 bytes
Variable Size 687869440 bytes
Database Buffers 1879048192 bytes
Redo Buffers 13766656 bytes
データベースがマウントされました。
データベースがオープンされました。


root@angelfish:˜# echo ::memstat | mdb -k
Page Summary Pages Bytes %Tot
----------------- ---------------- ---------------- ----
Kernel 312326 1.1G 15%
ZFS Metadata 64712 252.7M 3%
ZFS File Data 361163 1.3G 17%
Anon 216190 844.4M 10%
Exec and libs 78651 307.2M 4%
Page cache 37647 147.0M 2%
OSM 631296 2.4G 30%
Free (cachelist) 264 1.0M 0%
Free (freelist) 367545 1.4G 18%
Total 2097039 7.9G


色々あるのな、SolarisとZFS。メモメモ

| | | コメント (0) | トラックバック (0)

2017年5月 7日 (日)

dbms_stats.export_[schema|table]_stats/dbms_import_[schema|table]_stats : 索引名の変化に追随できるようになった?のか?

Oracle Database PL/SQLパッケージおよびタイプ・リファレンス 12c リリース2 (12.2) DBMS_STATSサブプログラムの要約
Oracle Databaseユーティリティ 12c リリース2 (12.2) 3 データ・ポンプ・インポート



dbms_stats.export_[schema|table]_stats/import_[schema|table]_statsを利用してオプティマイザ統計を移植する際、
一意制約や主キー制制約作成でシステム生成の索引名を持つ索引へオプティマイザ統計をインポートできない。(しかもエラーもワーニングも表示されない)
この問題、私が遭遇したのは11g R1のころだったと記憶している(詳しく調べたわけではないので、11g R1で初めて発覚した問題なのかそれ以前からあった問題なのか不明です)が、
オプティマイザ統計をdbms_stats.export_[schema|table]_stats/import_[schema|table]_statsを利用して、
以下のような内部的に付与される索引名を持つ索引へオプティマイザ統計を移植するのはたーーーいへん。というお話です。

主キー制約の例ですが、制約に名称を付与せず名称をOracle任せにする記述方法と、制約名と同じ名称で関連する索引を作成する記述方法があります。(事前に索引を作成しておく方法もありますが今回はあまり関係ないので省略)

例1

SCOTT> create table hoge
2 (
3 id number primary key
4 ,foo number
5 );

表が作成されました。


例2

SCOTT> create table hoge2
2 (
3 id number not null
4 ,foo number
5 ,constraint pk_hoge2 primary key(id) using index
6 );

表が作成されました。

例1の主キー制約で作成された制約及び索引は以下のようにSYS_Cnnnnnnという形式でOracleが名称を生成します!
data pumpによりインポートややDDLを再利用して他のスキーマやデータベースへ複製すると、都度、名称が異なることを意味します。それが問題の根っこ。

SCOTT> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE SYS_C009918
HOGE2 PK_HOGE2


Data Pumpやdbms_stats.export_[schema|table]_stats/dbms_import_[schema|table]_statsを利用したエクスポート/インポートで、元と先の名称の違いを解決できないとオプティマイザ統計がインポートできないという事態になります。
そこで、エクスポートした索引名とインポート先の索引名が異なっていてもオプティマイザ統計を正しくインポートする為には、元と先の名称をマッピングする機能の登場となるわけです。

マニュアルを読むと、Data Pumpにもdbms_stats.export_[schema|table]_stats/import_[schema|table]_statsにも、マッピングに関する記述があるので、その点は意識されている仕様になっている、ように見えます。
が、マッピングに関する部分を読んでも、Data Pumpとdbms_stats.export_[schema|table]_stats/import_[schema|table]_statsに同様のマッピング機能があるようには見えないんですよ。これがw。そうなれば試してガッテンw 差異を以下にまとめました!

知ってる方々にはFAQ(多分、ハマった経験からか?w)なのですが、意外に知られてないようなので忘れないうちに書いておきます:)
なお、12cになってから少々動きが変わったのでその点にも注意が必要でっす!


11.1.0.7 → 11.1.0.7 : NG (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード不要)
11.1.0.7 → 11.2.0.1 : NG (同上)
11.1.0.7 → 12.1.0.2 : NG (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード必要)
11.1.0.7 → 12.2.0.1 : NG (同上)

11.2.0.1 → 11.2.0.1 : NG (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード不要)
11.2.0.1 → 12.1.0.2 : NG (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード必要)
11.2.0.1 → 12.2.0.1 : NG (同上)

12.1.0.2 → 12.1.0.2 : OK (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード不要)
12.1.0.2 → 12.2.0.1 : OK (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード必要)

12.2.0.1 → 12.2.0.1 : OK (dbms_stats.upgrade_stat_tableプロシージャでのSTATTABのアップグレード不要)


ちなみに、Data Pumpによるexport/importでは索引名の変化には追随しているようなので、dbms_statsを利用した移植のみ注意が必要です。
Data Pumpを利用したオプティマイザ統計の移植ではdbms_stats利用時と同パターンであっても以下の通りの違いがあります。(とは言っても簡単な確認だけなので落とし穴がないとはいえませんけど)

索引名をOracleの自動付与にしないのが一番の対策だと思います。


11.1.0.7 → 11.1.0.7 : OK
11.1.0.7 → 11.2.0.1 : OK
11.1.0.7 → 12.1.0.2 : OK
11.1.0.7 → 12.2.0.1 : OK

11.2.0.1 → 11.2.0.1 : OK
11.2.0.1 → 12.1.0.2 : OK
11.2.0.1 → 12.2.0.1 : OK

12.1.0.2 → 12.1.0.2 : OK
12.1.0.2 → 12.2.0.1 : OK

12.2.0.1 → 12.2.0.1 : OK

結果だけ見ても、どういうことなのかピンと来ていない方向けに典型的な例を2つ載せておきます。




src:11.2 / dest:11.2 (STATTABのアップグレード不要)のケースで名称が変わった索引へ統計がインポートできない状況の例
SCOTT> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production

STATTABの作成
以下、11.1~12.2共通の準備

SCOTT> exec dbms_stats.create_stat_table(ownname=>'SCOTT',stattab=>'STATTAB');

PL/SQLプロシージャが正常に完了しました。

SCOTT> create table hoge
2 (
3 id number primary key
4 ,foo number
5 );

表が作成されました。

SCOTT> create table hoge2
2 (
3 id number not null
4 ,foo number
5 ,constraint pk_hoge2 primary key(id) using index
6 );

表が作成されました。

SCOTT> select table_name,index_name from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME
------------------------------ ------------------------------
HOGE SYS_C0013274
HOGE2 PK_HOGE2

SCOTT> alter session set nls_date_format='yyyy/mm/dd hh24:mi:ss';

セッションが変更されました。

SCOTT> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE SYS_C0013274
HOGE2 PK_HOGE2

SCOTT> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE
HOGE2

SCOTT> begin for i in 1..100 loop insert into hoge values(i,i); end loop; commit; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT> insert into hoge2 select * from hoge;

100行が作成されました。

SCOTT> commit;

コミットが完了しました。


データを登録したらオプティマイザ統計を取得して、STATTABへエクスポートしておきます。これで準備完了

SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'HOGE2',cascade=>true,no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT> exec dbms_stats.export_table_stats(ownname=>'SCOTT',tabname=>'HOGE',cascade=>true,stattab=>'STATTAB');

PL/SQLプロシージャが正常に完了しました。

SCOTT> exec dbms_stats.export_table_stats(ownname=>'SCOTT',tabname=>'HOGE2',cascade=>true,stattab=>'STATTAB');

PL/SQLプロシージャが正常に完了しました。


オプティマイザ統計は100行ですが、実データは10000行に増量しておきます。理由は、インポートされた統計なのか再取得された統計か区別するためです。オプティマイザ統計が再取得されていれば、NUM_ROWS列は10000行となります。LAST_ANALYZED列を確認するより楽なので!:)

SCOTT> begin for i in 101..10000 loop insert into hoge values(i,i); end loop; commit; end;
2 /

PL/SQLプロシージャが正常に完了しました。

SCOTT> insert into hoge2 select * from hoge where id between 101 and 10000;

9900行が作成されました。

SCOTT> commit;

コミットが完了しました。


NUM_ROWSは100ですよね!(実データは10000ですが)

SCOTT> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE SYS_C0013274 100 2017/05/06 15:25:11
HOGE2 PK_HOGE2 100 2017/05/06 15:25:23

SCOTT> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE 100 2017/05/06 15:25:11
HOGE2 100 2017/05/06 15:25:23

SCOTT> select count(1) from hoge;

COUNT(1)
----------
10000

SCOTT> select count(1) from hoge2;

COUNT(1)
----------
10000


前述の状態でhoge表とhoge2表及び、stattab表をエクスポートします。データは10000行ですが、STATABへエクスポートしたオプティマイザ統計のNUM_ROWSは100行です。

接続先: Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
"SYSTEM"."SYS_EXPORT_TABLE_01"を起動しています: system/******** directory=workdir dumpfile=11gr2stats_exp.dmp logfile=11gr2stats_exp.log tables=scott.hoge,scott.hoge2,scott.stattab
BLOCKSメソッドを使用して見積り中です...
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
BLOCKSメソッドを使用した見積り合計: 512 KB
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
. . "SCOTT"."HOGE" 122.1 KB 10000行がエクスポートされました
. . "SCOTT"."HOGE2" 122.1 KB 10000行がエクスポートされました
. . "SCOTT"."STATTAB" 15.49 KB 8行がエクスポートされました
マスター表"SYSTEM"."SYS_EXPORT_TABLE_01"は正常にロード/アンロードされました
******************************************************************************
SYSTEM.SYS_EXPORT_TABLE_01に設定されたダンプ・ファイルは次のとおりです:
/home/oracle/11gr2stats_exp.dmp
ジョブ"SYSTEM"."SYS_EXPORT_TABLE_01"が15:32:05で正常に完了しました

ここからが本番
11.2でエクスポートしたSCOTT.HOGE表、SCOTT.HOGE2表、SCOTT.STATTAB表を同じ11.2のSCOTT2へインポート
オプティマイザ統計のインポートは抑止して試験上のノイズは排除しておきます。索引統計は索引作成時に自動取得され10000行になりますが、表統計は空の状態となります。

接続先: Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
マスター表"SYSTEM"."SYS_IMPORT_FULL_01"は正常にロード/アンロードされました
"SYSTEM"."SYS_IMPORT_FULL_01"を起動しています: system/******** directory=workdir dumpfile=11gr2stats_exp.dmp logfile=11gr2stats_imp.log remap_schema=scott:scott2 exclude=statistics
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
. . "SCOTT2"."HOGE" 122.1 KB 10000行がインポートされました
. . "SCOTT2"."HOGE2" 122.1 KB 10000行がインポートされました
. . "SCOTT2"."STATTAB" 15.49 KB 8行がインポートされました
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
ジョブ"SYSTEM"."SYS_IMPORT_FULL_01"が15:35:21で正常に完了しました


インポートされたSTATTABの主要列は以下の通り、C5列にはエクスポート元のSCOTTそして、C1列の赤字部分には、エクスポート元の索引名(SYS_C0013274)が確認できます。

SCOTT2> select c1,c2,c3,c4,c5 from stattab;

C1 C2 C3 C4 C5
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT
SYS_C0013274 SCOTT
HOGE ID SCOTT
HOGE FOO SCOTT
HOGE2 SCOTT
PK_HOGE2 SCOTT
HOGE2 ID SCOTT
HOGE2 FOO SCOTT

8行が選択されました。


C5列にスキーマ名を持っているのでそのままではインポートできず、”SCOTT”を”SCOTT2”にupdateする必要あり!。
なお、12c以降はremapするためのdbms_stats.remap_stat_tableプロシージャを利用すること!

SCOTT2> update stattab set c5='SCOTT2';

8行が更新されました。

SCOTT2> commit;

コミットが完了しました。


C5がSCOTTからSCOTT2になりました。(C1の赤字部分、索引名(SYS_C0013274)は以前のままです。この名称を元にインポートするとするとなるとうまく行く気がしませんよね!w
インポート先の自動名称付与索引の名称は異なるわけですから(たまたま同一になることもありますが)

SCOTT2> select c1,c2,c3,c4,c5 from stattab;

C1 C2 C3 C4 C5
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT2
SYS_C0013274 SCOTT2
HOGE ID SCOTT2
HOGE FOO SCOTT2
HOGE2 SCOTT2
PK_HOGE2 SCOTT2
HOGE2 ID SCOTT2
HOGE2 FOO SCOTT2

import_table_statsプロシージャでSTATTABからオプティマイザ統計をインポート

SCOTT2> exec dbms_stats.import_table_stats(ownname=>'SCOTT2',tabname=>'HOGE',cascade=>true,stattab=>'STATTAB',no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。

SCOTT2> exec dbms_stats.import_table_stats(ownname=>'SCOTT2',tabname=>'HOGE2',cascade=>true,stattab=>'STATTAB',no_invalidate=>false);

PL/SQLプロシージャが正常に完了しました。


元SYS_C0013274だった索引ですが、インポート先ではSYS_C0013288と異なっており、(11gまでの)import_stat_tableプロシージャでは変化に追随できていないことがわかります。また、インポート時(dbms_stats.import_table_statsプロシージャ)、エラーは発生しないのでインポート時の問題に気づきにくいの点も問題の一つ! 注意してね

SCOTT2> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE SYS_C0013288 10000 2017/05/06 15:35:21
HOGE2 PK_HOGE2 100 2017/05/06 15:25:23

SCOTT2> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE 100 2017/05/06 15:25:11
HOGE2 100 2017/05/06 15:25:23


import_table_statsプロシージャ実行後のSTATTABの内容を今一度確認。12c版ではちょっとした変化が起きるので、この部分よーく覚えておいてください!

SCOTT2> select c1,c2,c3,c4,c5 from stattab;

C1 C2 C3 C4 C5
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT2
SYS_C0013274 SCOTT2
HOGE ID SCOTT2
HOGE FOO SCOTT2
HOGE2 SCOTT2
PK_HOGE2 SCOTT2
HOGE2 ID SCOTT2
HOGE2 FOO SCOTT2



12.1から12.2へ移植する例。こちらは成功します。
12.1でエクスポートしたSCOTT.HOGE表、SCOTT.HOGE2表、SCOTT.STATTAB表を12.2のSCOTT2へインポートする例 (STATTABのアップグレード必須)


...略...

HOGE表、HOGE2表、及び、STATTAB表をData Pumpでエクスポートするところまでは、11.2と同じ。
違いは、12.1で行なっているという点のみなので前半略。
12.1.0.2からエクスポートしたHOGE表、HOGE2表、及びSTATTAB表を12.2のSCOTT2へインポートするところから。

...略...

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production 0
PL/SQL Release 12.2.0.1.0 - Production 0
CORE 12.2.0.1.0 Production 0
TNS for Linux: Version 12.2.0.1.0 - Production 0
NLSRTL Version 12.2.0.1.0 - Production 0


11.2と同様にimpdp時に統計情報は除外するようにしています。 インポート直後のオプティマイザ統計の状態は、表統計は空、索引統計は再取得され10000行となります

Connected to: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl directory=workdir dumpfile=12c1stats_exp.dmp logfile=12c1stats_imp.log remap_schema=scott:scott2 exclude=statistics
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
. . imported "SCOTT2"."HOGE" 122.2 KB 10000 rows
. . imported "SCOTT2"."HOGE2" 122.2 KB 10000 rows
. . imported "SCOTT2"."STATTAB" 17.89 KB 8 rows
Processing object type TABLE_EXPORT/TABLE/INDEX/INDEX
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sat May 6 07:21:19 2017 elapsed 0 00:00:10


インポートされたSTATTABの内容は以下の通り。12.1.0.2のSTATTABですが、11gまでとは多少違いがあります。

SQL> select c1,c2,c3,c4,c5,c6,cl1 from stattab;

C1 C2 C3 C4 C5 C6 CL1
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT
SYS_C0011035 HOGE SCOTT SCOTT 2.ID
HOGE ID SCOTT
HOGE FOO SCOTT
HOGE2 SCOTT
PK_HOGE2 HOGE2 SCOTT SCOTT 2.ID
HOGE2 ID SCOTT
HOGE2 FOO SCOTT

8 rows selected.


オプティマイザ統計はインポートしていませんが、索引作成時に自動収集されたため索引だけは統計がセットされています。(STATTABからインポートするとどうなるか要確認)

SQL> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE2 PK_HOGE2 10000 2017/05/06 07:21:19
HOGE SYS_C0011800 10000 2017/05/06 07:21:19

SQL> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE2
HOGE


12.1と12.2間でもSTATTABに変更があるようで、アップグレードが必要です! アップグレードを忘れると以下のエラーが返されます!!

SQL> exec dbms_stats.remap_stat_table(ownname=>'SCOTT2',stattab=>'STATTAB',src_own=>'SCOTT',src_tab=>null,tgt_own=>'SCOTT2',tgt_tab=>null); 
BEGIN dbms_stats.remap_stat_table(ownname=>'SCOTT2',stattab=>'STATTAB',src_own=>'SCOTT',src_tab=>null,tgt_own=>'SCOTT2',tgt_tab=>null); END;

*
ERROR at line 1:
ORA-20002: Version of statistics table "SCOTT2"."STATTAB" is too old. Please try upgrading it with dbms_stats.upgrade_stat_table
ORA-06512: at "SYS.DBMS_STATS", line 12442
ORA-06512: at "SYS.DBMS_STATS", line 12465
ORA-06512: at "SYS.DBMS_STATS", line 3619
ORA-06512: at "SYS.DBMS_STATS", line 12319
ORA-06512: at line 1

SQL> exec dbms_stats.upgrade_stat_table(ownname=>'SCOTT2',stattab=>'STATTAB');

PL/SQL procedure successfully completed.


アップグレードごのSTATTAB主要部分には違いは見えないので、他の列で変更があるようですね。

SQL> select c1,c2,c3,c4,c5,c6,cl1 from stattab;

C1 C2 C3 C4 C5 C6 CL1
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT
SYS_C0011035 HOGE SCOTT SCOTT 2.ID
HOGE ID SCOTT
HOGE FOO SCOTT
HOGE2 SCOTT
PK_HOGE2 HOGE2 SCOTT SCOTT 2.ID
HOGE2 ID SCOTT
HOGE2 FOO SCOTT

8 rows selected.


SCOTTの所有するオブジェクトをSCOTT2へ移植するのでスキーマのリマップを行います。Data Pumpのremap_schemaみたいなもんです。
11gまではC5列を直接更新していましたが、12c以降、それはだめですよ!。スキーマ名を保持している列も増えているので、dbms_stats.remap_stat_tableを使ってスキーマをリマップ!!!

SQL> exec dbms_stats.remap_stat_table(ownname=>'SCOTT2',stattab=>'STATTAB',src_own=>'SCOTT',src_tab=>null,tgt_own=>'SCOTT2',tgt_tab=>null); 

PL/SQL procedure successfully completed.

SQL> select c1,c2,c3,c4,c5,c6,cl1 from stattab;

C1 C2 C3 C4 C5 C6 CL1
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT2
SYS_C0011035 HOGE SCOTT2 SCOTT2 2.ID
HOGE ID SCOTT2
HOGE FOO SCOTT2
HOGE2 SCOTT2
PK_HOGE2 HOGE2 SCOTT2 SCOTT2 2.ID
HOGE2 ID SCOTT2
HOGE2 FOO SCOTT2

8 rows selected.


STATTABからオプティマイザ統計を表と索引へインポートします。

SQL> exec dbms_stats.import_table_stats(ownname=>'SCOTT2',tabname=>'HOGE',cascade=>true,stattab=>'STATTAB',no_invalidate=>false);

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.import_table_stats(ownname=>'SCOTT2',tabname=>'HOGE2',cascade=>true,stattab=>'STATTAB',no_invalidate=>false);

PL/SQL procedure successfully completed.


おおおおおお〜っ。 12c同士であれば、索引名の変化に追随して統計がインポート可能に!。ただし、11gからの移植には未対応なので、ほかの問題がなければ、Data Pumpを使って統計を移植するのが王道かな。

SQL> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE2 PK_HOGE2 100 2017/05/06 15:57:43
HOGE SYS_C0011800 100 2017/05/06 15:57:39

SQL> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE2 100 2017/05/06 15:57:43
HOGE 100 2017/05/06 15:57:39


面白い点がもう一つ、import_table_stats後のSTATTABのC1列が、SYS_C0011035からSYS_C0011800へ更新されています。SYS_C0011800はインポート先の索引名なので、インポート先の索引名でSTATTABが更新されたことを意味します。

SQL> select c1,c2,c3,c4,c5,c6,cl1 from stattab;

C1 C2 C3 C4 C5 C6 CL1
------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------ ------------------------------
HOGE SCOTT2
SYS_C0011800 HOGE SCOTT2 SCOTT2 2.ID
HOGE ID SCOTT2
HOGE FOO SCOTT2
HOGE2 SCOTT2
PK_HOGE2 HOGE2 SCOTT2 SCOTT2 2.ID
HOGE2 ID SCOTT2
HOGE2 FOO SCOTT2

8 rows selected.



ところで、Data Pumpで可能なことが、dbms_stats.import_[schema|table]_statsで対応できないのはなんでだろう? とは思わなくもない。(どんな大人の事情なんだろう...w)
ということで今日はこのへんで。


==== おまけ ===
あ、そうそう、Data Pumpで統計情報表と索引のインポートと同時行う例も載せておきますね。 11.1から12.2への例。

エクポート元(11.1)

SCOTT> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE SYS_C009918 100 2017/05/06 14:42:25
HOGE2 PK_HOGE2 100 2017/05/06 14:42:32

SCOTT> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE 100 2017/05/06 14:42:25
HOGE2 100 2017/05/06 14:42:32

接続先: Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
"SYSTEM"."SYS_EXPORT_TABLE_01"を起動しています: system/******** directory=workdir dumpfile=11gr1stats_exp.dmp logfile=11gr1stats_exp.log tables=scott.hoge,scott.hoge2
BLOCKSメソッドを使用して見積り中です...
オブジェクト型TABLE_EXPORT/TABLE/TABLE_DATAの処理中です
BLOCKSメソッドを使用した見積り合計: 384 KB
オブジェクト型TABLE_EXPORT/TABLE/TABLEの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/INDEXの処理中です
オブジェクト型TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINTの処理中です
オブジェクト型TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICSの処理中です
オブジェクト型TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICSの処理中です
. . "SCOTT"."HOGE" 122.1 KB 10000行がエクスポートされました
. . "SCOTT"."HOGE2" 122.1 KB 10000行がエクスポートされました
マスター表"SYSTEM"."SYS_EXPORT_TABLE_01"は正常にロード/アンロードされました
******************************************************************************
SYSTEM.SYS_EXPORT_TABLE_01に設定されたダンプ・ファイルは次のとおりです:
/home/oracle/11gr1stats_exp.dmp
ジョブ"SYSTEM"."SYS_EXPORT_TABLE_01"が22:38:22で正常に完了しました


インポート先 (12.2)

Connected to: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl directory=workdir dumpfile=11gr1stats_exp.dmp logfile=11gr1stats_imp.log remap_schema=scott:scott2
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
. . imported "SCOTT2"."HOGE" 122.1 KB 10000 rows
. . imported "SCOTT2"."HOGE2" 122.1 KB 10000 rows
Processing object type TABLE_EXPORT/TABLE/INDEX/INDEX
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sat May 6 12:57:48 2017 elapsed 0 00:00:32


SCOTT2> select table_name,index_name,num_rows,last_analyzed from user_ind_statistics where table_name like 'HOGE%';

TABLE_NAME INDEX_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ------------------------------ ---------- -------------------
HOGE2 PK_HOGE2 100 2017/05/06 14:42:32
HOGE SYS_C0011856 100 2017/05/06 14:42:25

SCOTT2> select table_name,num_rows,last_analyzed from user_tab_statistics where table_name like 'HOGE%';

TABLE_NAME NUM_ROWS LAST_ANALYZED
------------------------------ ---------- -------------------
HOGE2 100 2017/05/06 14:42:32
HOGE 100 2017/05/06 14:42:25

| | | コメント (0) | トラックバック (0)

2017年5月 3日 (水)

XQuartz で X forwarding

ちょっと前に、macOSでssh接続してOracle Universal InstallerとかDBCAとか起動するのはどうするの?
みないな話に割り込んだので、FAQとして書いておきますね。

随分前にも書いてたきがしてけたけど、X11 X forwardingのことを書いてたわけではなかったことに気づいた。orz

X forwardingしたい場合には、terminal.appの代わりにXQuartzを使います

https://www.xquartz.org
20170502_92713
20170502_92412

例えば、macOSのVirtualBoxのLinux/Solarisがguest OSで、そこにOracleをインストールするような場合、
以下のように XQuartsを起動し、X forwarding !!

-Y をお忘れなく!

$ ssh -Y oracle@192.168.1.246

...中略...

-bash-4.1$ ./runInstaller &

...中略...

これだけ。

20170502_92639

20170502_92654
20170502_104316


| | | コメント (0) | トラックバック (0)

2017年5月 2日 (火)

あまりにもネタになってないので、少しだけ sysresv のことを書いといた

共有メモリやセマフォセットの数などの確認は、ipcsでほぼ足りてるわけですが。(複数インスタンスが起動している場合を除く)
あまりにもネタになってないので、少しだけ sysresvのことを書いといた:)

[oracle@vbgeneric ˜]$ sysresv

IPC Resources for ORACLE_SID "orcl12c" :
Maximum shared memory segment size (shmmax): 4398046511104 bytes
Total system shared memory (shmall): 4398046511104 bytes
Total system shared memory count (shmmni): 4096
*********************** Dumping ipcs output ********************

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 oracle 600 8802304 173
0x00000000 32769 oracle 600 822083584 87
0x00000000 65538 oracle 600 7974912 87
0x21485470 98307 oracle 600 16384 87
0x00000000 262148 oracle 600 524288 2 dest
0x00000000 294917 oracle 600 4194304 2 dest
0x00000000 393222 oracle 600 33554432 2 dest
0x00000000 491527 oracle 600 4194304 2 dest
0x00000000 524296 oracle 600 1048576 2 dest

------ Semaphore Arrays --------
key semid owner perms nsems
0x245b195c 163840 oracle 600 152
0x245b195d 196609 oracle 600 152
0x245b195e 229378 oracle 600 152

*********************** End of ipcs command dump **************


***************** Dumping Resource Limits(s/h) *****************
core file size 0 KB/UNLIMITED
data seg size UNLIMITED/UNLIMITED
scheduling priority 0 KB/0 KB
file size UNLIMITED/UNLIMITED
pending signals 30 KB/30 KB
max locked memory 128 GB/128 GB
max memory size UNLIMITED/UNLIMITED
open files 64 KB/64 KB
POSIX message queues 800 KB/800 KB
real-time priority 0 KB/0 KB
stack size 32 MB/32 MB
cpu time UNLIMITED/UNLIMITED
max user processes 16 KB/16 KB
virtual memory UNLIMITED/UNLIMITED
file locks UNLIMITED/UNLIMITED

***************** End of Resource Limits Dump ******************
Maximum map count configured per process: 65530
Total /dev/shm size: 4050014208 bytes, used: 98304 bytes
Shared Memory:
ID KEY
32769 0x00000000
65538 0x00000000
0 0x00000000
98307 0x21485470
Semaphores:
ID KEY
163840 0x245b195c
196609 0x245b195d
229378 0x245b195e
Oracle Instance alive for sid "orcl12c"
[oracle@vbgeneric ˜]$
[oracle@vbgeneric ˜]$  ipcs -sb

------ Semaphore Arrays --------
key semid owner perms nsems
0x245b195c 163840 oracle 600 152
0x245b195d 196609 oracle 600 152
0x245b195e 229378 oracle 600 152
[oracle@vbgeneric ˜]$  ipcs -st

------ Semaphore Operation/Change Times --------
semid owner last-op last-changed
163840 oracle Tue May 2 01:35:32 2017 Tue May 2 01:26:30 2017
196609 oracle Not set Tue May 2 01:26:30 2017
229378 oracle Tue May 2 01:26:35 2017 Tue May 2 01:26:30 2017

| | | コメント (0) | トラックバック (0)

2017年4月30日 (日)

Data Pumpも癖モノだよね〜w その6 - schemaモードでMviewを他のPDBへ複製(オプジェクトパスが不足すると...)

Previously on Mac De Oracle

前回は、schemaモードのData PumpでMviewを他のPDBへ複製する際、必要最小限のオブジェクト絞っても可能か? という確認でした。

なんだ、簡単じゃんと、思ったあなた!!
そうでもないんですよ!

SCHEMA_EXPORT_OBJECTSビューより、
オブジェクトパスの親子関係から親をエクスポートすれば子も一緒だねぇ〜。と想像はできるわけですが、
兄弟(TABLEやDB_LINKやMATERIALIZED_VIEW)の場合はそう簡単ではなくて、そもそも依存関係を把握してないとうまく使いこなせないんですよ!

というのが今日のネタ

FAQ!



前回同様、基本レプリケーション環境が完成した状態からエクスポートしてみます。

その前にパラメータファイルに含めるincludeパラメータを確認しておきましょう。
複製に成功したオブジェクトパスは以下でした。
SCHEMA_EXPORT_OBJECTSビューより)

OBJECT_PATH                     COMMENTS
------------------------------- ------------------------------------------------------------
SCHEMA_EXPORT/DB_LINK Private database links in the selected schemas
SCHEMA_EXPORT/TABLE Tables in the selected schemas and their dependent objects
SCHEMA_EXPORT/MATERIALIZED_VIEW Materialized views
SCHEMA_EXPORT/JOB Jobs in the selected schemas
SCHEMA_EXPORT/REFRESH_GROUP Refresh groups in the selected schemas

上記の赤字のオブジェクト(DB_LINK/TABLE/MATERIALIZED_VIEW/JOB/REFRESH_GROUP)はSCHEMA_EXPORTを親とする兄弟という関係なのは見ての通り。

MVIEWをTABLEとして複製の回でお見せしたとおり、MVIEWであってもTABLEのパスでエクスポートすれば、それはMATERIALIZED_VIEWではなく、TABLEだけ(正確に言うと、TABLEとその子孫のオブジェクト)がエクスポートされていました。

つまり、兄弟の場合、親子の場合と異なり関連はあっても自動でエクスポートしてくれるわけではない。という癖が見えてきます。:)
はっ! そうだったのか! という方のために試してみます!


INCLUDE=TABLEとすると、Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行するに書いたようにMviewではなくTABLEとしてエクスポートされ、インポートしてもTABLEのままでリフレッユジョブもリフレッシュグループもありませんでした。

では、INCLUDE=MATERIALIZED_VIEWとした場合には、リフレッシュジョブもリフレッシュグループもエクスポートされるんじゃないか? (そうは、うまくはいかないんです!)

[oracle@vbgeneric ˜]$ cat exp_mviewonly.par 
schemas=mview_schema1
include=MATERIALIZED_VIEW:"IN ('MV_MASTER')"

では、その様子を再現w よーく見ててね。

えーーーーーーっ!
うまくいった前回との違いおわかりですよね。 Mviewに含まれるデータもエクスポートされていませんし、索引、制約、リフレッシュジョブや、リフレッシュグループもエクスポートされていません!!!
なんということでしょうw 

オブジェクトパス:MATERIALIZED_VIEWに紐づくと思っていたオブジェクトは自動ではされてエクスポートされません!!!w 

[Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=exp_mviewonly.dmp logfile=exp_mviewonly.log parfile=exp_mviewonly.par
Estimate in progress using BLOCKS method...
Total estimation using BLOCKS method: 0 KB
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
*****************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/exp_mviewonly.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Tue Apr 25 23:40:55 2017 elapsed 0 00:00:15


この状態で、他のPDBにインポートとしてみます。ユーザ及び、権限とData Pump作業用ディレクリオブジェクトは事前に作成しておきます。

なんということでしょう! Mviewが作成できません!
別な意味で、これにハマっている方もいるようです。
Problems creating MVIEW DDL using Data Pump and the SQLFILE option / Simon DBA Advanced Oracle Tips, Tricks, and How-To's

[oracle@vbgeneric ˜]$ sqlplus sys/oracle@orcl2 as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on Tue Apr 25 23:52:49 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl2@SYS> create directory workdir as '/u01/userhome/oracle';

Directory created.

orcl2@SYS> create user mview_schema1 identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl2@SYS> grant create session, create table, create database link, create materialized view to mview_schema1;

Grant succeeded.

orcl2@SYS> exit
Disconnected from Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
[oracle@vbgeneric ˜]$
[oracle@vbgeneric ˜]$ impdp system/oracle@orcl2 directory=workdir dumpfile=exp_mviewonly.dmp logfile=imp_mviewonly.log

Import: Release 12.1.0.2.0 - Production on Tue Apr 25 23:53:58 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=exp_mviewonly.dmp logfile=imp_mviewonly.log
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
ORA-39083: Object type MATERIALIZED_VIEW:"MVIEW_SCHEMA1"."MV_MASTER" failed to create with error:
ORA-06550: line 1, column 9:
PLS-00352: Unable to access another database 'TO_MASTER_SCHEMA'
ORA-06550: line 1, column 9:
PLS-00201: identifier 'SYS@TO_MASTER_SCHEMA' must be declared
ORA-06550: line 1, column 9:
PL/SQL: Statement ignored
Failing sql is:
CREATE MATERIALIZED VIEW "MVIEW_SCHEMA1"."MV_MASTER" ("ID", "FOO") USING ("MV_MASTER", (10, 'ORCL', 1, 0, 0, "MASTER_SCHEMA", "MASTER", '2017-04-25 23:37:06',
Job "SYSTEM"."SYS_IMPORT_FULL_01" completed with 1 error(s) at Tue Apr 25 23:54:05 2017 elapsed 0 00:00:04

これ、どうやったら解決するんでしょう?
答えは、不足しているオプジェクトパスを指定する!。 なのですが、その前にいくつか確認。
(SCHEMA_EXPORT_OBJECTSビューのオブジェクトパスをよーく覗いて見るとよいと思います)

1つめの事実

include=TABLE だけでエクスポートするとMVIEWではなくTABLEとしてエクスポートされ、インポートしてもTABLEのまま。
だたし、CONSTARINTはincludeパラメータに含めなくても、TABLEに関連するCONSTRAINTとINDEXと統計はエクスポートされている。
かつ、データもエクスポートされる!!! (オブジェクトパスの親子関係を見ると TABLE_DATAは、TABLEの子、CONSTRAINT、INDEX、STATISTICSも子、実際の統計は孫)
DB_LINK、JOB、REFRESH_GROUPはエクスポートされない。(オブジェクトパスの階層を見るとそれらのオブジェクトはTABLEと同じ階層にある兄弟であることがわかります。)

2つめの事実

include=MATERIALIZED_VIEWだけでエクスポートするとMVIEWとしての定義はエクスポートされている。
オブジェクトパスを見ると、MATERIALIZED_VIEWには子孫は存在していません。親はSCHEMA_EXPORT。
なので、兄弟である、DB_LINK、TABLE、DB_LINK、JOB、REFRESH_GROUPエクスポートされない。かつ、どうやら、MATERIALIZED_VIEWに含まれるのはメタデータだけっぽい。(メタデータっぽいですが、それでだけMVIEWが作成できるわけでもない)

なかなかの癖モノですね。

なのであれば、
MATERIALIZED_VIEWのみエクスポートしてインポートした時に発生したMVIEW作成エラーを回避するオブジェクトパスは、DB_LINK, TABLE, MATERIALIZED_VIEWなんじゃないかなぁと思うわけです。はい。
ただし、完全ではありません。理由は、リフレッシュグループとリフレッシュジョブが含まれていなためです。前述のオブジェクトパスに加え、 JOB と REFRESH_GROUP を含めると、
前回のとおりMviewと関連オブジェクトのみエクスポート/インポートすることができるようになります。

では、DB_LINK, TABLE, MATERIALIZED_VIEWだけをエクスポートして、インポートしたら自動リフレッシュはできないけどMVIEWとして作成できるか確認してみます。

[oracle@vbgeneric ˜]$ cat exp_mviewonly.par 
schemas=mview_schema1
include=DB_LINK
include=TABLE:"IN ('MV_MASTER')"
include=MATERIALIZED_VIEW:"IN ('MV_MASTER')"

・・・中略・・・

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=exp_mviewonly.dmp logfile=exp_mviewonly.log parfile=exp_mviewonly.par
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/exp_mviewonly.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Wed Apr 26 00:20:46 2017 elapsed 0 00:00:34

うまくインポートできました。自動リフレッシュはできませんが。2行登録されているMVIEWが作成されました!!

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=exp_mviewonly.dmp logfile=imp_mviewonly.log
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Wed Apr 26 00:22:50 2017 elapsed 0 00:00:04


以下のとおり、CON_ID=5 (PDB:ORCL2)にはTABLE/INDEX/DB_LINK/MATERIALIZED_VIEWは作成されましたが、リフレッシュグループもリフレッシュジョブもありません。

orcl2@MVIEW_SCHEMA1> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MV_MASTER TABLE
SYS_C0014888 INDEX
MV_MASTER MATERIALIZED VIEW
TO_MASTER_SCHEMA DATABASE LINK

orcl2@MVIEW_SCHEMA1> select rowner,rname,refgroup,job,broken,interval,next_date from user_refresh;

no rows selected

orcl2@MVIEW_SCHEMA1> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

no rows selected

ということで、おしまい。



Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)
Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)

| | | コメント (0) | トラックバック (0)

2017年4月25日 (火)

Data Pumpも癖モノだよね〜w その5 - schemaモードでMviewを他のPDBへ複製(オプジェクトパス de 絞り込み)

Previously on Mac De Oracle

前回はschemaモードのエクスポートで、schemaごとまるっと別PDBへインポートしてMviewサイトを複製してみました。意外と簡単なんですよね。Mviewにハマりさえしなければ。
今回は前回同様の構成で、schemaモードでMviewを他のPDBへ複製するのですが、スキーマごとまるっとではなく、エクスポートするオプジェクトパスを必要最小限に絞ってMviewと関連するオブジェクトのみ複製できることを確認しておきます。

前回と同じポンチ絵ですが、
今回は、基本レプリケーション環境は構築済みの状態からスタートします。(つまり、Data Pumpでエクスポートするところから始めます)
細かい操作を確認したい方は、前々回前回のログを参照ください。

20170415_14044


Mviewの複製に必要な最小限のオブジェクトだけData Pumpで複製するために必要なオブジェクトパスはどれなのか確認しておきます。
前々回のエントリでほぼ見えてますが実際にやってみないと”不安”なので (^^;;;。

data pumpのschemaモード向けSCHEMA_EXPORT_OBJECTSにはオブジェクトパスの定義でMviewに関連するオブジェクト全てが定義されていることは何度か書きました。
また、前々回のexpdp/impdpの実行ログでどのようなオブジェクトがエクスポートされ、インポートされていたかも赤字で示しておきました。

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sat Apr 15 00:39:00 2017 elapsed 0 00:00:23


インポートされたオブジェクト、ジョブ、そして、リフレッシュグループの情報から、schemaモードでMVIEWの複製に必要な必要最小限のオブジェクトパスは以下と推測しました。
なお、CONSTRAINTはTABLEに紐づくので自動的にエクスポートされるのであえて選んでいません。
(SCHEMA_EXPORT_OBJECTSビューより)

OBJECT_PATH                     COMMENTS
------------------------------- ------------------------------------------------------------
SCHEMA_EXPORT/DB_LINK Private database links in the selected schemas
SCHEMA_EXPORT/TABLE Tables in the selected schemas and their dependent objects
SCHEMA_EXPORT/MATERIALIZED_VIEW Materialized views
SCHEMA_EXPORT/JOB Jobs in the selected schemas
SCHEMA_EXPORT/REFRESH_GROUP Refresh groups in the selected schemas

上記オブジェクトパスをincludeパラメータに指定し、schemaモードで必要最小限のオブジェクトのみエクスポート可能か確認します。これがうまくできれば、成功することはほぼ確実!
PDB:ORCLにて、Data Pumpのschemaモードで必要最小限のオブジェクトのみexportするため以下のパラメータファイルを利用。
対象Mviewと関連オブジェクト以外はエクスポートしないようにするため対象Mviewのみに限定するようにしてあります。なお、パラメータファイルを利用している理由は、コマンドラインだとなんだかんだと面倒くさいData Pumpの癖を回避するためですw

[oracle@vbgeneric ˜]$ cat exp_mviewonly.par 
schemas=mview_schema1
include=DB_LINK
include=TABLE:"IN ('MV_MASTER')"
include=MATERIALIZED_VIEW:"IN ('MV_MASTER')"
include=JOB
include=REFRESH_GROUP


エクスポート!
ログを見る限り、想定通りのオブジェクトがエクスポートされています。オブジェクトにはユーザや権限を含めていないので、インポートする前にユーザ作成と必要最小限の権限を付与する必要があります。ログを見る限り狙い通りになっている模様です。

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=exp_mviewonly.dmp logfile=exp_mviewonly.log parfile=exp_mviewonly.par
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/exp_mviewonly.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Sat Apr 15 23:56:33 2017 elapsed 0 00:00:35


では、複製の開始!

ユーザはロールや権限など含めてエクスポートしていないのでData Pump作業用ディレクトリオブジェクトの作成に加え、ユーザ作成、必要最小限の権限付与をインポート前に行っています。

PDB:ORCL2にて

[oracle@vbgeneric ˜]$ sqlplus sys/oracle@orcl2 as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on Sat Apr 15 23:57:17 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

PDB:ORCL2にて、Data Pump向けディレクトリオブジェクト作成


orcl2@SYS> create directory workdir as '/u01/userhome/oracle';

Directory created.


PDB:ORCL2にて、Mviewと関連オブジェクトをインポートするユーザを作成と必要最小限の権限付与
このユーザ名はコピー元と同一ユーザ名にしてあります。異なる場合にはremap_schemaパラメータが必要になり一手間増えるので同一ユーザ名で作成してあります。

orcl2@SYS> create user mview_schema1 identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl2@SYS> grant create session, create table, create database link, create materialized view to mview_schema1;

Grant succeeded.


PDB:ORCL2にて、インポート。エクスポート時に必要オブジェクトのみにしてあるので単純にインポートだけ!
想定しているオブジェクトがインポートされているように見えますね!!!!

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=exp_mviewonly.dmp logfile=imp_mviewonly.log
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sat Apr 15 23:58:25 2017 elapsed 0 00:00:04


PDB:ORCL2のMview向けユーザにて、インポート後のオブジェクトを確認
必要なオブジェクトは正しくインポートされており、停止状態でエクスポートされたリフレッシュジョブも停止状態であることが確認できます。

できてた!
この時点で、マスターサイトに対して、ORCLとORCL2という2つのMViewサイトができあがり!

[oracle@vbgeneric ˜]$ sqlplus mview_schema1/welcome1@orcl2

SQL*Plus: Release 12.1.0.2.0 Production on Sat Apr 15 23:58:46 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl2@MVIEW_SCHEMA1> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MV_MASTER MATERIALIZED VIEW
SYS_C0014888 INDEX
MV_MASTER TABLE
TO_MASTER_SCHEMA DATABASE LINK

orcl2@MVIEW_SCHEMA1> @mview_info

Session altered.

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 4000/01/01 00:00:00 sysdate+5/1440 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE
-------------------- -------------------- ---------- ---------- - -------------------- -------------------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00


最後に、高速リフレッシュが停止中である2つのMviewの高速リフレッシュの再開を確認しておきます。
schemaモードの単純な複製でも問題はなかったのでここでもジョブを再開するだけで、高速リフレッシュが再開されるはず。。。です。


事前に作成したContaners句を利用したスクリプト(mview_info_c.sql(前回のエントリの最後にあります))で確認します。
2つあるMviewサイトの高速リフレッシュジョブは停止中であることが確認できます。

CDB$RORRTのSYSユーザにて
LAST_REF列がCOMPELETEになっていますが、前回のエントリのとおりの紛らわしいステータスに設定されているだけで実際には完全リフレッシュされていません。勘違いのないように!

orcl12c@SYS> @mview_info_c
Connected
Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00 5
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 4000/01/01 00:00:00 sysdate+5/1440 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/15 23:53:39 4000/01/01 00:00:00 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

リフレッシュジョブを再開すれば高速リフレッシュで同期されるようになります!!

orcl12c@SYS> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

orcl@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl2
Connected.
orcl2@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

orcl2@MVIEW_SCHEMA1> @mview_info_c
Connected.
Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/16 00:08:24 3
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/16 00:07:19 5

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE BUILD_MOD
------------------------------ ------ -------- -------- ------------------- ------------------- ---------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID IMMEDIATE
MV_MASTER DEMAND FAST FAST UNDEFINED VALID IMMEDIATE

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/16 00:02:19 2017/04/16 00:07:19 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/16 00:03:24 2017/04/16 00:08:24 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');


本文中で利用していたスクリプト: mview_info.sql

alter session set nls_date_format='yyyy/mm/dd hh24:mi:ss';
col mview_name for a30
select mview_name,refresh_mode,refresh_method,last_refresh_type,after_fast_refresh,compile_state from user_mviews;

col log_user for a20
col schema_user for a20
col interval for a20
col what for a60
select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

col rowner for a20
col rname for a20
select rowner,rname,refgroup,job,broken,interval,next_date from user_refresh;


ということで、次回、癖モノシリーズ最終回?へ




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製
Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)

| | | コメント (0) | トラックバック (0)

2017年4月23日 (日)

Data Pumpも癖モノだよね〜w その4と1/2 - schemaモードでMviewを他のPDBへ複製 (紛らわしいステータスw)

Previously on Mac De Oracle

前回は、Data PumpのschemaモードでschemaごとまるっとMviewサイトを複製してみたところまで、でした。

エントリには記載していなかったログを見ていて驚いた!ことを調査してみる回として”その4と1/2”にしてみましたw

ほんと、癖モノですよねーMviewも!w



なに驚いたのか、その内容から

Data Pumpを利用しMviewを複製する今回の目的の一つである、完全リフレッシュしないでMviewを複製するという目論見が外れていた!!!?

と思われるログが残っていました。

以下のログは、Data Pumpのインポートログとその後のMview関連ビューをリストしたものですが、赤字部分に注目

Starting "SYSTEM"."SYS_IMPORT_FULL_01":  system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp 
Processing object type SCHEMA_EXPORT/USER

・・・中略・・・

Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows

・・・中略・・・

Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sun Apr 23 20:30:19 2017 elapsed 0 00:00:28

インポート直後、2つのMviewの状態を確認したログですが、B列(DBA_REFRESH.BROKEN列)はどちらも”N"となっておりリフレッシュジョブは停止中です。問題は下のLAST_REF列(DBA_MVIEWS.LAST_REFRESH_TYPE列)がCOMPLETEとなっているというところです。
CON_ID=5は、PDB:ORCL2なのでインポートを行ったPDBを指します。DBA_MVIEWS.LAST_REFRESH_TYPE列は、「最新のリフレッシュに使用されるメソッド:COMPLETE- 最新のリフレッシュが完了した。」と説明されています。??
完全リフレッシュされちゃったの??? 

こうなったらData PumpでMviewが作成された時に完全リフレッシュされちゃうのか、されないのか確認するしかありませんねw。

ROWNER               RNAME                  REFGROUP        JOB B INTERVAL             NEXT_DATE               CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/23 20:38:31 5
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/23 20:43:30 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE CON_ID
------------------------------ ------ -------- -------- ------------------- ------------------- ----------
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID 5
MV_MASTER DEMAND FAST FAST UNDEFINED VALID 3

ということで、確認してみました。

最初は、前回と同じPDB:ORCLのスキーマ間で基本レプリケーション環境を作成し、リフレッシュジョブを停止するところまで(何をやっているかはDDLやSQL文を見ればわかると思うので略。わからないと言う方は前回の内容で確認ください。)

orcl12c@SYS> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 ORCL READ WRITE NO
4 ORDS READ WRITE NO
5 ORCL2 READ WRITE NO

orcl@SYS> create directory workdir as '/u01/userhome/oracle';

Directory created.

orcl@SYS> create user master_schema identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl@SYS> grant create session, create table to master_schema;

Grant succeeded.

orcl@SYS> create user mview_schema1 identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl@SYS> grant create session, create table, create database link, create materialized view to mview_schema1;

Grant succeeded.

orcl@SYS> conn master_schema/welcome1@orcl
Connected.
orcl@MASTER_SCHEMA> create table master (
2 id number primary key
3 ,foo varchar2(100));

Table created.

orcl@MASTER_SCHEMA> insert into master values(1,'foo');

1 row created.

orcl@MASTER_SCHEMA> insert into master values(2,'bar');

1 row created.

orcl@MASTER_SCHEMA> commit;

Commit complete.

orcl@MASTER_SCHEMA> create materialized view log on master;

Materialized view log created.

orcl@MASTER_SCHEMA> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> create database link to_master_schema
2 connect to master_schema identified by welcome1
3 using 'ORCL';

Database link created.

orcl@MVIEW_SCHEMA1> select count(*) from master@to_master_schema;

COUNT(*)
----------
2

orcl@MVIEW_SCHEMA1> create materialized view mv_master
2 refresh fast on demand
3 start with sysdate next sysdate+5/1440
4 as select * from master@to_master_schema;

Materialized view created.

orcl@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
2

orcl@MVIEW_SCHEMA1> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/23 20:12:06 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/23 20:07:06 2017/04/23 20:12:06 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

orcl12c@SYS> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>true);

PL/SQL procedure successfully completed.

orcl@MVIEW_SCHEMA1> @mview_info_c

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE CON_ID
------------------------------ ------ -------- -------- ------------------- ------------------- ----------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID 3

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT    CON_ID
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------ ----------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/23 20:07:06 4000/01/01 00:00:00 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); 3


ここまでは同じなのですが、Data PumpでMviewをインポートする際に完全リフレッシュしているのか?どうかを確認するため、リフレッシュジョブ停止後、マスター表を100行に増幅しておきます。
こうしておけば、Data PumpでMviewがインポートされる際、完全リフレッシュが行われていたとしたら、インポート後のMviewは100行になっているはずだし、
 逆に2行のままなら、LAST_REFRESH_TYPE=COMPLETEにはなっているものの実際には完全リフレッシュしなていない、という、なんともわかりづらい状況になるから気にすんな、ってことが見えてくるはず。

orcl12c@SYS> conn master_schema/welcome1@orcl
Connected.
orcl@MASTER_SCHEMA> begin for i in 3..100 loop insert into master values(i,'data#'||i); end loop; end;
2 /

PL/SQL procedure successfully completed.

orcl@MASTER_SCHEMA> commit;

Commit complete.

orcl@MASTER_SCHEMA> select count(1) from master;

COUNT(1)
----------
100

orcl@MASTER_SCHEMA> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
2

エクスポートは2行となっているので、Mviewから2行エクスポートされているのは間違いない!(マスター表は100行!)

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=mview_schema1.dmp logfile=exp_mview_schema1.log schemas=mview_schema1
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/mview_schema1.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Sun Apr 23 20:28:25 2017 elapsed 0 00:00:54

PDB:ORCL2にて、Data Pump作業向けディレクトリオブジェクトを作成

[oracle@vbgeneric oracle]$ sqlplus sys/oracle@orcl2 as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on Sun Apr 23 20:29:07 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl2@SYS> create directory workdir as '/u01/userhome/oracle';

Directory created.


インポートでも見ての通り、2行だけインポートされています!(ステータスが紛らわしい説が強くなってきました!!!)

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sun Apr 23 20:30:19 2017 elapsed 0 00:00:28


インポート後、どちらのリフレッシュジョブも停止中ですが、1箇所違いがみられます。
そう、インポートしたMviewのLAST_REF(DBA_MVIEWS.LAST_REFRESH_TYPE)がCOMPLETEになっています。前半に書いたとおりの状態が再現!!! なにっ!!

orcl12c@SYS> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00 3
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00 5

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE CON_ID
------------------------------ ------ -------- -------- ------------------- ------------------- ----------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID 3
MV_MASTER DEMAND FAST COMPLETE UNDEFINED VALID 5

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT CON_ID
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------ ----------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/23 20:07:06 4000/01/01 00:00:00 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); 3
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 4000/01/01 00:00:00 sysdate+5/1440 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); 5

PDB_NAME CON_ID
------------------------------ ----------
PDB$SEED 2
ORCL 3
ORDS 4
ORCL2 5

DBA_MVIEWSビューのLAST_REFRESH_TYPEはCOMPLETE(完全リフレッシュされた)と設定されていますが、本当なのでしょうか? PDB:ORCL2のMV_MASTER表が本当に完全リフレッシュされていたとしたら100行になっているはずですが。。。。

PDB:ORCLのMViewの行数をカウント、2行です(リフレッシュジョブが停止しているので変化なし)

orcl12c@SYS> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> show con_id

CON_ID
------------------------------
3

orcl@MVIEW_SCHEMA1> show con_name

CON_NAME
------------------------------
ORCL

orcl@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
2


さて、問題のPDB:ORCL2。。。。2行のまま!!!!! 
ということは、Data PumpによるMviewのインポートでは完全リフレッシュは行われず、エクスポートされた行数がそのままインポートされている!! ステータスが紛らわしい!! だけというのが真相のようです!

orcl@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl2
Connected.
orcl2@MVIEW_SCHEMA1> show con_id

CON_ID
------------------------------
5

orcl2@MVIEW_SCHEMA1> show con_name

CON_NAME
------------------------------
ORCL2

orcl2@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
2


ジョブを再開すると。。。マスターサイトと同期され各MViewは100行なっています。めでたしめでたし。

@> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

orcl@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl2
Connected.
orcl2@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

orcl12c@SYS> @mview_info_c
Connected.

Session altered.

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/23 20:44:05 5
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/23 20:43:30 3

MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE CON_ID
------------------------------ ------ -------- -------- ------------------- ------------------- ----------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID 3
MV_MASTER DEMAND FAST FAST UNDEFINED VALID 5

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT CON_ID
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------ ----------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/23 20:38:30 2017/04/23 20:43:30 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); 3
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/23 20:39:05 2017/04/23 20:44:05 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"'); 5

orcl12c@SYS> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
100

orcl@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl2
Connected.
orcl2@MVIEW_SCHEMA1> select count(1) from mv_master;

COUNT(1)
----------
100

やれやれ、お騒がせすぎるw 

次回へつづく。


参考:ログ中で利用したSQLスクリプト(mview_info_c.sql)のソース

conn sys/oracle@orcl12c as sysdba

col rowner for a20
col rname for a20
col mview_name for a30
col log_user for a20
col schema_user for a20
col interval for a20
col what for a60
alter session set nls_date_format='yyyy/mm/dd hh24:mi:ss';
select rowner,rname,refgroup,job,broken,interval,next_date,con_id from containers(dba_refresh) where rowner='MVIEW_SCHEMA1';
select mview_name,refresh_mode,refresh_method,last_refresh_type,after_fast_refresh,compile_state from containers(dba_mviews) where mview_name='MV_MASTER';
select job,log_user,schema_user,last_date,next_date,interval,failures,what from containers(dba_jobs) where log_user='MVIEW_SCHEMA1';




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係
Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製

| | | コメント (0) | トラックバック (0)

2017年4月22日 (土)

Data Pumpも癖モノだよね〜w その4 - schemaモードでMviewを他のPDBへ複製

一回、おやすみしましたが、Previously on Mac De Oracle

前々回は、Data Pump、dbms_jobとdbms_schedulerの複雑な関係を紐解いてみました。

今回は、箸休めとして、schemaモードより上位のモードならMviewはMviewのまま複製や移行することは簡単に行えるよね。という確認だけしておこうと思います。
シンプルだと思わせておいて、細けーことやろうとすると一癖ある、まさに癖モノw ということで :)


まずは、おさらいから

data pumpのschemaモード向けSCHEMA_EXPORT_OBJECTSにはオブジェクトパスの定義でMviewに関連するオブジェクト全てが定義されています。

OBJECT_PATH                     COMMENTS
------------------------------- ------------------------------------------------------------
SCHEMA_EXPORT/DB_LINK Private database links in the selected schemas
SCHEMA_EXPORT/TABLE Tables in the selected schemas and their dependent objects
SCHEMA_EXPORT/MATERIALIZED_VIEW Materialized views
SCHEMA_EXPORT/JOB Jobs in the selected schemas
SCHEMA_EXPORT/REFRESH_GROUP Refresh groups in the selected schemas

一方、tableモード向けTABLE_EXPORT_OBJECTSには、Mview、リフレッシュに必要なJOBやREFRESH GROUPなどのオブジェクトパスが定義されていません。
tableモードでMviewを複製しようとすること自体に無理があるのは明らかですね。MViewとしてエクスポートしたくてもオプジェクトパスが定義されていないのですから。
表を対象としているモードだからそれ以外のオブジェクトパスが定義されていないんだよね〜と、無理やり納得しています:)

orcl@SYSTEM> r
1 select * from TABLE_EXPORT_OBJECTS where
2 object_path like '%/JOB'
3 or object_path like '%/MATERIALIZED_VIEW'
4* or object_path like '%/REFRESH_GROUP'

no rows selected

おさらいはこれくらいにして、schemaモードエクスポートで以下の図に示したようなMviewの複製が行えるか確認しておきます。

ポンチ絵のとおりではあるのですが、簡単に説明すると、
PDB:ORCLの異なるschema間で高速リフレッシュ可能なMviewがあり、Data Pumpのschemaモードを利用して異なるPDBにMviewを複製するというシナリオです。
複製後はMviewサイトが2つになります。

20170415_14044


なぜ、異なるDB(PDB)にMVIEWを複製するシナリオにしたかって?
理由は、DBMS_JOBのジョブは、同一データベース(MTA構成であればPDB毎)でJOB番号により一意に管理されています。
DBMS_JOBのジョブを同一データベース内でexport/importした場合、ジョブが単純に複製される事になりJOB番号の一意制約エラーとなりimportに失敗します。
importできなければ再作成すれば問題ないわけですが、MTA環境なのでわざわざ同一PDB内に作成する必要はないわけです。(手数を減らせるならその方が楽ですから)

エクスポートする前にリフレッシュジョブを一時停止する理由は?
一時停止している理由は静止点を作りたいこともありますが、それをサボると、なかなか理解しにくいタイミングイシューと言われてる事象に遭遇しやすくなるんですよ。(感覚的に)
前述の状況になると高速リフレッシュを再開するには、一度、完全リフレッシュする必要があります。
ここで利用するMviewは2行しかないので完全リフレッシュは苦でもないですが、巨大サイズの表だったら完全リフレッシュはできることなら避けたいですよね。


PDB:ORCLの異なるschema間でmaster表→mv_master表で基本レプリケーション環境作成からData PumpでOmv_master表のオーナースキーマごとPDB:ORCL2へ複製までをつらつらと記録してあります。:)

MTA環境となっています。PDB:ORCL内で基本レプリケーション環境を作り、PDB:ORCL2にDBリンクやMVIEW等関連オブジェクトをData Pumpを利用して複製します。

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl12c@SYS> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 ORCL READ WRITE NO
4 ORDS READ WRITE NO
5 ORCL2 READ WRITE NO


PDB:ORCLデータベースにディレクトリオブジェクトを作成します。(Data Pump向け作業ディレクトリ)

orcl@SYS> create directory workdir as '/u01/userhome/oracle';

Directory created.

PDB:ORCLデータベースにマスターサイト向けユーザを作成し、必要最低限の権限を付与

orcl@SYS> create user master_schema identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl@SYS> grant create session, create table to master_schema;

Grant succeeded.

PDB:ORCL、作成したユーザに接続し、マスター表を作成

orcl@SYS> conn master_schema/welcome1@orcl
Connected.
orcl@MASTER_SCHEMA> create table master (
2 id number primary key
3 ,foo varchar2(100));

Table created.

orcl@MASTER_SCHEMA> insert into master values(1,'foo');

1 row created.

orcl@MASTER_SCHEMA> insert into master values(2,'bar');

1 row created.

orcl@MASTER_SCHEMA> commit;

Commit complete.


PDB:ORCL、高速リフレッシュに必要なMaterialized View LogをMaster表に作成します。

orcl@MASTER_SCHEMA> create materialized view log on master;

Materialized view log created.


同じくPDB:ORCLデータベースにMviewサイト向けユーザを作成し、必要最低限の権限を付与します。

orcl@SYS> create user mview_schema1 identified by welcome1
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

User created.

orcl@SYS> grant create session, create table, create database link, create materialized view to mview_schema1;

Grant succeeded.


PDB:ORCL、Mviewサイト向けに作成したユーザに接続し、マスターサイトへのデータベースリンクとMaterialized Viewを作成します。

orcl@SYS> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> create database link to_master_schema
2 connect to master_schema identified by welcome1
3 using 'ORCL';

Database link created.

orcl@MVIEW_SCHEMA1> select count(*) from master@to_master_schema;

COUNT(*)
----------
2

orcl@MVIEW_SCHEMA1> create materialized view mv_master
2 refresh fast on demand
3 start with sysdate next sysdate+5/1440
4 as select * from master@to_master_schema;

Materialized view created.


PDB:ORCL、MViewサイトのオブジェクト確認からDBMS_JOBとリフレッシュグループの高速リフレッシュされているところまでを確認しています。

Mviewを作成すると、作成したMviewは、TABLEでもあり、MVIEWでもある。ということが確認できます。これ重要ですよ!
data pumpのschemaモード向けSCHEMA_EXPORT_OBJECTSビューにTABLEとMATERIALIZED_VIEWの2つのオブジェクトパスがあるにも関わらず、
tableモードのTABLE_EXPORT_OBJECTSビューにはTABLEオブジェクトパスは定義されているのにMATERIALIZED_VIEWオブジェクトパスされていないからMVIEWとしてはエクポートできないと言っていた理由なんですよ!!!!

orcl@MVIEW_SCHEMA1> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MV_MASTER TABLE
SYS_C0014880 INDEX
MV_MASTER MATERIALIZED VIEW
TO_MASTER_SCHEMA DATABASE LINK

次はuser_jobsからリフレッシュジョブを確認しておきます。これもMviewをリフレッシュするのに必要なオブジェクトです。
data pumpのschemaモード向けSCHEMA_EXPORT_OBJECTSビューでJOBオブジェクトパスとして定義されています。これも重要なんです。
前回JOBオブジェクトパスに対応するオブジェクトは、DBMS_JOBのJOBだということを書きましたが、思い出していただけましたか? 

orcl@MVIEW_SCHEMA1> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/15 00:09:04 2017/04/15 00:14:04 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

最後にリフレッシュグループの確認、意外とこれの存在を忘れてしまうんですよ。私もそうでした。
単一MVIEWのリフレッシュでもリフレッシュグループが自動的に作成されてしまうことを...複数のMVIEWをリフレッシュする時だけに必要なのかと思ったら大間違いw
いつも、つい、忘れちゃいますw 影薄過ぎ! ですが、前述のWHAT列の内容を見れば、なるほど! となるはずです。
dbms_refresh.refreshプロシージャは、リフレッシュグループ名が引数!!

orcl@MVIEW_SCHEMA1> select rowner,rname,refgroup,job,broken,interval,next_date from user_refresh;

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE
-------------------- -------------------- ---------- ---------- - -------------------- -------------------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/15 00:29:29


PDB:ORCL、リフレッシュジョブを一時停止し、expdpのschemaモードでMviewサイト向けスキーマを丸ごとエクスポートします。

orcl@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>true);

PL/SQL procedure successfully completed.

orcl@MVIEW_SCHEMA1> select rowner,rname,refgroup,job,broken,interval,next_date from user_refresh;

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE
-------------------- -------------------- ---------- ---------- - -------------------- -------------------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00


Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=workdir dumpfile=mview_schema1.dmp logfile=exp_mview_schema1.log schemas=mview_schema1
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
. . exported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/u01/userhome/oracle/mview_schema1.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Sat Apr 15 00:30:55 2017 elapsed 0 00:00:55


無事エクスポートできたようなので、次は、
PDB:ORCL2データベースで、ディレクトリオブジェクトを作成します。2つめのMviewサイトの準備です。

orcl2@SYSTEM> create directory workdir as '/u01/userhome/oracle';

Directory created.


PDB:ORCL2、schemaモードでインポート。(対象PDB:ORCL2には同一スキーマは存在しない状態で実施)

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_FULL_01": system/********@orcl2 directory=workdir dumpfile=mview_schema1.dmp logfile=imp_mview_schema1.dmp
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/TABLESPACE_QUOTA
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/DB_LINK
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "MVIEW_SCHEMA1"."MV_MASTER" 5.5 KB 2 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
Processing object type SCHEMA_EXPORT/MATERIALIZED_VIEW
Processing object type SCHEMA_EXPORT/JOB
Processing object type SCHEMA_EXPORT/REFRESH_GROUP
Job "SYSTEM"."SYS_IMPORT_FULL_01" successfully completed at Sat Apr 15 00:39:00 2017 elapsed 0 00:00:23

と、すんなり終了w

PDB:ORCL2、インポートで複製されたMVIEW_SCHEMA1ユーザに接続しオブジェクトを確認
リフレッシュジョブは、エクスポート元と同一ジョブ番号でインポートされ、停止状態。想定通りで一安心:)

orcl2@MVIEW_SCHEMA1> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MV_MASTER TABLE
SYS_C0014884 INDEX
MV_MASTER MATERIALIZED VIEW
TO_MASTER_SCHEMA DATABASE LINK

orcl2@MVIEW_SCHEMA1> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 4000/01/01 00:00:00 sysdate+5/1440 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

orcl2@MVIEW_SCHEMA1> select rowner,rname,refgroup,job,broken,interval,next_date from user_refresh;

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE
-------------------- -------------------- ---------- ---------- - -------------------- -------------------
MVIEW_SCHEMA1 MV_MASTER 61 81 Y sysdate+5/1440 4000/01/01 00:00:00


PDB:ORCLとORCL2、両MViewサイトのリフレッシュジョブを再開

orcl2@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl2
Connected.
orcl2@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

orcl2@MVIEW_SCHEMA1> conn mview_schema1/welcome1@orcl
Connected.
orcl@MVIEW_SCHEMA1> exec dbms_job.broken(job=>81,broken=>false,next_date=>sysdate);

PL/SQL procedure successfully completed.

最後に、containers句を使って全体を確認っと!

orcl2@MVIEW_SCHEMA1> conn sys/oracle@orcl12c as sysdba
Connected.
orcl12c@SYS> select rowner,rname,refgroup,job,broken,interval,next_date,con_id from containers(dba_refresh) where rowner='MVIEW_SCHEMA1';

ROWNER RNAME REFGROUP JOB B INTERVAL NEXT_DATE CON_ID
-------------------- -------------------- ---------- ---------- - -------------------- ------------------- ----------
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/15 22:25:47 5
MVIEW_SCHEMA1 MV_MASTER 61 81 N sysdate+5/1440 2017/04/15 22:26:52 3

orcl12c@SYS> select mview_name,refresh_mode,refresh_method,last_refresh_type,after_fast_refresh,compile_state from containers(dba_mviews) where mview_name='MV_MASTER';
MVIEW_NAME REFRES REFRESH_ LAST_REF AFTER_FAST_REFRESH COMPILE_STATE
------------------------------ ------ -------- -------- ------------------- -------------------
MV_MASTER DEMAND FAST FAST UNDEFINED VALID
MV_MASTER DEMAND FAST FAST UNDEFINED VALID

orcl12c@SYS> select job,log_user,schema_user,last_date,next_date,interval,failures,what from containers(dba_jobs) where log_user='MVIEW_SCHEMA1';
JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/15 22:20:47 2017/04/15 22:25:47 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');
81 MVIEW_SCHEMA1 MVIEW_SCHEMA1 2017/04/15 22:21:52 2017/04/15 22:26:52 sysdate+5/1440 0 dbms_refresh.refresh('"MVIEW_SCHEMA1"."MV_MASTER"');

何事もなく複製できたところで、次回へ続く。

あ、その前に、環境を初期状態に戻さないと。。



Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する
Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係

| | | コメント (0) | トラックバック (0)

2017年4月10日 (月)

Data Pumpも癖モノだよね〜w その3 - dbms_job と dbms_scheduler との複雑な関係

Previously on Mac De Oracle

前回は、Data PumpとMaterialized Viewという癖モノ二大巨頭を絡ませて見ました。

今回は、三つ巴?な感じでお送りしたいと思います。:)



以下は、Materialized ViewをリフレッシュするDBMS_JOBです。

今頃気づいたか! という感じですが、dbms_job で作成されるJOBは、ALL/DBA/USER_OBJECTSには含まれないのです!!

orcl@USERS> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
21 USERS USERS 2017/04/09 19:18:34 2017/04/09 19:19:34 sysdate+1/1440 0 dbms_refresh.refresh('"USERS"."MVIEW_MASTER"');

orcl@USERS> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MVIEW_MASTER MATERIALIZED VIEW
SYS_C0014637 INDEX
MVIEW_MASTER TABLE
TOUSERM DATABASE LINK


ここで、注目!!
dbms_jobは、dba_objectsにはカウントされませんが、dbms_schedulerは、object_type=JOBとしてdba_objectsにカウントされるということ!!
以下にある、OBJECT_TYPE=JOBは、OBJECT_NAME=TESTという直前に作成されたdbms_schedulerのJOBだということ!!

ORCL@USERS> begin
dbms_scheduler.create_job (
job_name=>'test'
,job_type=>'PLSQL_BLOCK'
,job_action=>'BEGIN dbms_refresh.refresh(''USERS.MVIEW_MASTER''); END;'
,start_date=>systimestamp
,repeat_interval=>'FREQ=MONTHLY'
,end_date=>systimestamp + interval '1' year
,enabled=>true
,comments=>null
);
end;
/

PL/SQL procedure successfully completed.


なんとまぁ、面倒くさい、DBMS_JOBのJOBも、DBMS_SCHEDULERのJOBも、同じ初期化パラメータ(job_queue_processes)を利用するのにオブジェクト扱いされたりされなかったり、面倒くさい癖モノです。注意しましょうね!!

Oracle® Databaseリファレンス 12c リリース1 (12.1) 1.126 JOB_QUEUE_PROCESSES
https://docs.oracle.com/cd/E57425_01/121/REFRN/GUID-B8B68D16-00A3-43DD-BE39-01F877880955.htm

ORCL@USERS> select object_name,object_type from user_objects;

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MVIEW_MASTER TABLE
SYS_C0014637 INDEX
MVIEW_MASTER MATERIALIZED VIEW
TEST JOB
TOUSERM DATABASE LINK

という準備運動が終わったところで、本題の Data Pumpでのお話です。
癖モノData Pumpと癖モノ感たっぷりの2種類のJOB、2時間ドラマの複雑でドロドロした関係が予想される展開になってきましたw

orcl@USERS> select job,log_user,schema_user,last_date,next_date,interval,failures,what from user_jobs;

JOB LOG_USER SCHEMA_USER LAST_DATE NEXT_DATE INTERVAL FAILURES WHAT
---------- -------------------- -------------------- ------------------- ------------------- -------------------- ---------- ------------------------------------------------------------
21 USERS USERS 2017/04/09 19:18:34 2017/04/09 19:19:34 sysdate+1/1440 0 dbms_refresh.refresh('"USERS"."MVIEW_MASTER"');

orcl@USERS> select job_name,job_action,repeat_interval,start_date from user_scheduler_jobs;

JOB_NAME JOB_ACTION REPEAT_INTERVAL START_DATE
---------- ------------------------------------------------------------------- -------------------- ----------------------------------------
TEST BEGIN dbms_refresh.refresh('USERS.MVIEW_MASTER'); END; FREQ=MONTHLY 09-APR-17 10.36.05.814317 PM +09:00


まず、Data Pumpで扱える"OBJECT”が定義されているSCHEMA_EXPORT_OBJECTSをみると以下の"OBJECT”定義が見つかります。
JOBというオブジェクトのコメントを読んでも、2種類あるJOBのどちらを指しているのか、はたまた、いずれか一つなのかさっぱりわかりません。

OBJECT_PATH                     COMMENTS
------------------------------- ------------------------------------------------------------
SCHEMA_EXPORT/DB_LINK Private database links in the selected schemas
SCHEMA_EXPORT/TABLE Tables in the selected schemas and their dependent objects
SCHEMA_EXPORT/MATERIALIZED_VIEW Materialized views
SCHEMA_EXPORT/JOB Jobs in the selected schemas
SCHEMA_EXPORT/REFRESH_GROUP Refresh groups in the selected schemas

これは試して、ガッテン!! するしかありません!!!

schemaモードでエクスポートします。このとき、includeパラメータでJOBだけをエクスポートするよう指定します!! 

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=homedir dumpfile=jobs.dmp logfile=jobsexp.log schemas=users include=job
Estimate in progress using BLOCKS method...
Total estimation using BLOCKS method: 0 KB
Processing object type SCHEMA_EXPORT/JOB
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/home/oracle/jobs.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Sun Apr 9 23:13:47 2017 elapsed 0 00:00:04

SQLFILEパラメータを指定して内容を確認してみます。(SQLFILEパラメータを指定したimpdpコマンドでは実際にインポートは行われません)

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_SQL_FILE_SCHEMA_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_SQL_FILE_SCHEMA_01": system/********@orcl directory=homedir dumpfile=jobs.dmp logfile=jobsexp.log schemas=users sqlfile=jobsexp_ddl.txt
Processing object type SCHEMA_EXPORT/JOB
Job "SYSTEM"."SYS_SQL_FILE_SCHEMA_01" successfully completed at Sun Apr 9 23:20:44 2017 elapsed 0 00:00:03


おおおおお、これは!!! 

なんということでしょう!
Data Pumpでは、JOBオブジェクトは、DBMS_JOBのJOBオブジェクト(dba_objectsではオブジェクト扱いされていないのに!!)

だとすると、dba_objectsでは”OBJECT"として扱われている、DBMS_SCHEDULERのJOBはの扱いはいかに。。。

以下のとおり、DBMS_IJOBと内部的プロリージャに置き換わっていますが、パラメータを見れば一目瞭然、DBMS_JOBの定義しかありません。
つまり、Data PumpのJOBオブジェクトは、DBMS_JOBのJOBであることがわかりました!!

[oracle@catfish ˜]$ cat jobsexp_ddl.txt 

・・・中略・・・

-- new object type path: SCHEMA_EXPORT/JOB
BEGIN SYS.DBMS_IJOB.SUBMIT(
JOB=> 21,
LUSER=> 'USERS',
PUSER=> 'USERS',
CUSER=> 'USERS',
NEXT_DATE=> TO_DATE('2017-04-09 23:13:51', 'YYYY-MM-DD:HH24:MI:SS'),
INTERVAL=> 'sysdate+1/1440',
BROKEN=> FALSE,
WHAT=> 'dbms_refresh.refresh(''"USERS"."MVIEW_MASTER"'');',

・・・中略・・・

では、DBMS_SCHEDULERのJOBは。。。それは。。。PROCOBJというOBJECT_PATHに含まれているようで(ほぼ誰にも読み取れないw)。。。

MOSにもありそうだけど、この辺りをまとめてて疲れて、めんどくさい病の発作がw 
もっと深掘りする気力があったら探すかもw
Export/Import Scheduler Jobs


手取り早く、試して、ガッテン! includeパラメータで”PROCOBJ”を指定します。

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@orcl directory=homedir dumpfile=scheduler_jobs.dmp logfile=scheduler_jobsexp.log schemas=users include=procobj
Estimate in progress using BLOCKS method...
Total estimation using BLOCKS method: 0 KB
Processing object type SCHEMA_EXPORT/POST_SCHEMA/PROCOBJ
Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is:
/home/oracle/scheduler_jobs.dmp
Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed at Sun Apr 9 23:49:20 2017 elapsed 0 00:00:04

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "SYSTEM"."SYS_SQL_FILE_SCHEMA_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_SQL_FILE_SCHEMA_01": system/********@orcl directory=homedir dumpfile=scheduler_jobs.dmp logfile=jobsddldump.log schemas=users sqlfile=jobsexp2_ddl.txt
Processing object type SCHEMA_EXPORT/POST_SCHEMA/PROCOBJ
Job "SYSTEM"."SYS_SQL_FILE_SCHEMA_01" successfully completed at Sun Apr 9 23:50:05 2017 elapsed 0 00:00:02


確かに、PROCOBJにDBMS_SCHEDULERのJOBが含まれている!!

[oracle@catfish ˜]$ cat jobsexp2_ddl.txt

・・・中略・・・

BEGIN
dbms_scheduler.create_job('"TEST"',
job_type=>'PLSQL_BLOCK'
, job_action=>'BEGIN dbms_refresh.refresh(''USERS.MVIEW_MASTER''); END;'
, number_of_arguments=>0
, start_date=>TO_TIMESTAMP_TZ('09-APR-2017 10.36.05.814317000 PM +09:00','DD-MON-RRRR HH.MI.SSXFF AM TZR','NLS_DATE_LANGUAGE=english')
, repeat_interval=> 'FREQ=MONTHLY'
, end_date=>TO_TIMESTAMP_TZ('09-APR-2018 10.36.05.814344000 PM +09:00','DD-MON-RRRR HH.MI.SSXFF AM TZR','NLS_DATE_LANGUAGE=english')
, job_class=>'"DEFAULT_JOB_CLASS"'
, enabled=>FALSE
, auto_drop=>TRUE
,comments=>NULL
);
dbms_scheduler.enable('"TEST"');
COMMIT;
END;
/

・・・中略・・・

登場人物の関係が複雑すぎてよくわからなくなってきたので、まとめ。

DBMS_JOBのJOBは、DBA_OBJECTS上、オブジェクトとは扱われていない。
DBMS_SCHEDULEのJOBは、DBA_OBJECTS上、オブジェクトとして扱われている。
どちらのJOBも、初期化パラメータ、job_queue_processesにより制御されている。
Data PumpのSCHEMA_EXPORT_OBJECTSにて定義されているJOBオブジェクトは、DBMS_JOBのJOBのことである。
Data PumpのSCHEMA_EXPORT_OBJECTSにて定義されているPROCOBJオブジェクトが、DBMS_SCHEDULERのJOBのこと!?であるようだ。
(オブジェクト名からは想像できない可読性の悪さはなんとかしてくれ!)

DBMS_SCHEDULERへの移行が推奨されながらDBMS_JOBが未だに存在していることの弊害のようにも思えてきた。
このあたり、理解しやすいように改善してもらいたい癖モノの一つとしてリストに加えておこう。



満開の桜なのに残念な天気の日曜日終わってしまった!!
ということで今日はここまで。




Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング
Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する

| | | コメント (0) | トラックバック (0)

2017年4月 9日 (日)

Data Pumpも癖モノだよね〜w その2 - Materialized ViewをTableとして移行する

Previously on Mac De Oracle

前回はMac De OracleはData Pumpのqueryパラメーターの解析タイミングについて調べたメモでした。

今回はタイトルのとおり、Materialized ViewをTableとして移行していしまおうというお題。
実は、Materialized Viewとして移行しようとして失敗ったのがきっかけで知ったんですけど、あまり書かれていないので仕様だとは思うんですが(そうなんですよね?w)
(癖モノData Pumpと癖モノMaterialized Viewを扱おうとする時点で、すんなり行くわけがない、ぐらいの覚悟はしておいたほうが無難でしょうけど)


ちなみに、今回の内容とは関係ないですが、ViewをTableとしてエクスポートする機能も12cR1から提供されていますね(使ったことはまだないですが)
VIEWS_AS_TABLES
https://docs.oracle.com/database/121/SUTIL/GUID-E4E45E81-5391-43BE-B27D-B763EF79A885.htm#SUTIL3904

Exporting views as tables Oracle Database 12C release 1 (12.1)
http://dbaora.com/exporting-views-as-tables-oracle-database-12c-release-1-12-1/



以下のようなMVIEWがあります。
エクスポート側ではMaterialize Viewとして定義され、自動的にリフレッシュされています。

orcl@USERS>  select object_name,object_type from user_objects where object_name='MVIEW_MASTER';

OBJECT_NAME OBJECT_TYPE
------------------------------ ------------------------------
MVIEW_MASTER MATERIALIZED VIEW
VIEW_MASTER TABLE

orcl@USERS> select job,log_user,last_date,next_date,interval,failures,broken,what from user_jobs;

JOB LOG_USER LAST_DATE NEXT_DATE INTERVAL FAILURES B WHAT
---------- ------------------------------ ------------------- ------------------- -------------------- ---------- - --------------------------------------------------
21 USERS 2017/04/09 14:16:53 2017/04/09 14:17:53 sysdate+1/1440 0 N dbms_refresh.refresh('"USERS"."MVIEW_MASTER"');

表モードでMviewからTableへの変換!
tablesパラメータでMviewを指定しているところがポイント! MVIEWでもありTABLEでもあるのでTABLEとしてエクスポートができます。

表モードで有効なオブジェクトをTABLE_EXPORT_OBJECTSで確認してみて気づいたのですが、TABLE_EXPORT_OBJECTSには MATERIALIZED_VIEW は存在しません。

orcl@SYSTEM^> r
1 select * from TABLE_EXPORT_OBJECTS where
2 object_path like '%/JOB'
3 or object_path like '%/MATERIALIZED_VIEW'
4* or object_path like '%/REFRESH_GROUP'

no rows selected


つまり、表モードではMaterialized ViewをMaterizlized Viewとしてエクスポート/インポートすることはできないということ!!(?)のようです。

[oracle@catfish ˜]$ expdp users/********@orcl directory=homedir dumpfile=mv2table.dmp logfile=mv2table.log tables=mview_master

Export: Release 12.1.0.2.0 - Production on Sun Apr 9 14:46:27 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "USERS"."SYS_EXPORT_TABLE_01": users/********@orcl directory=homedir dumpfile=mv2table.dmp logfile=mv2table.log tables=mview_master
Estimate in progress using BLOCKS method...
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/COMMENT
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/MARKER
. . exported "USERS"."MVIEW_MASTER" 5.492 KB 2 rows
Master table "USERS"."SYS_EXPORT_TABLE_01" successfully loaded/unloaded
******************************************************************************
Dump file set for USERS.SYS_EXPORT_TABLE_01 is:
/home/oracle/mv2table.dmp
Job "USERS"."SYS_EXPORT_TABLE_01" successfully completed at Sun Apr 9 14:47:09 2017 elapsed 0 00:00:38

同一DB(PDB)の異なるschemaへ表としてインポート(異なるschemaへインポートするのでremap_schamaパラメータをお忘れなく)

[oracle@catfish ˜]$ impdp hr/********@orcl directory=homedir dumpfile=mv2table.dmp logfile=mv2table.log remap_schema=users:hr

Import: Release 12.1.0.2.0 - Production on Sun Apr 9 14:49:56 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "HR"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "HR"."SYS_IMPORT_FULL_01": hr/********@orcl directory=homedir dumpfile=mv2table.dmp logfile=mv2table.log remap_schema=users:hr
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
. . imported "HR"."MVIEW_MASTER" 5.492 KB 2 rows
Processing object type TABLE_EXPORT/TABLE/COMMENT
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/MARKER
Job "HR"."SYS_IMPORT_FULL_01" successfully completed at Sun Apr 9 14:50:19 2017 elapsed 0 00:00:22


表としてインポートされています!

orcl@HR> select object_name,object_type from user_objects where object_name='MVIEW_MASTER';

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MVIEW_MASTER TABLE

orcl@HR> select job,log_user,last_date,next_date,interval,failures,broken,what from user_jobs;

no rows selected

orcl@HR> select count(1) from mview_master;

COUNT(1)
----------
2


さて、お次、
表モードとは異なり、schemaモードのオブジェクトを定義しているSCHEMA_EXPORT_OBJECTSには、Materialized ViewをMaterialized Viewとしてエクスポート/インポートするために必要な以下のオブジェクト定義が存在します。
ということは、schemaモードではMviewをMviewとして移行することも、MviewをTableとして移行することもできそうな設定になっているようです、

OBJECT_PATH                     COMMENTS
------------------------------- ------------------------------------------------------------
SCHEMA_EXPORT/DB_LINK Private database links in the selected schemas
SCHEMA_EXPORT/TABLE Tables in the selected schemas and their dependent objects
SCHEMA_EXPORT/MATERIALIZED_VIEW Materialized views
SCHEMA_EXPORT/JOB Jobs in the selected schemas
SCHEMA_EXPORT/REFRESH_GROUP Refresh groups in the selected schemas


ということで、schemaモードでも同様の変換が可能かためしてみます。
schemaモードでinclude=TABLEとして表関連オブジェクトのみエクスポート。
schemaモードを選択した場合は、materialized view、job、refresh groupもスコープに入ってしまうので、余分なオブジェクトや定義を取り込まないようにするのがポイント(でした)

[oracle@catfish ˜]$ expdp users/******** directory=homedir dumpfile=test.dmp logfile=testexp.log include=TABLE

Export: Release 12.1.0.2.0 - Production on Wed Apr 5 00:40:17 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "USERS"."SYS_EXPORT_SCHEMA_01": users/******** directory=homedir dumpfile=test.dmp logfile=testexp.log include=TABLE
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
. . exported "USERS"."MVIEW_MASTER" 5.484 KB 1 rows
Master table "USERS"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded
******************************************************************************
Dump file set for USERS.SYS_EXPORT_SCHEMA_01 is:
/home/oracle/test.dmp
Job "USERS"."SYS_EXPORT_SCHEMA_01" successfully completed at Wed Apr 5 00:40:43 2017 elapsed 0 00:00:25

同一DB(PDB)の異なるschemaへ表としてインポート(異なるschemaへインポートするのでremap_schamaパラメータをお忘れなく)

[oracle@catfish ˜]$ impdp users2/******** directory=homedir dumpfile=test.dmp logfile=testexp.log include=TABLE remap_schema=users:users2

Import: Release 12.1.0.2.0 - Production on Wed Apr 5 00:42:18 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Master table "USERS2"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "USERS2"."SYS_IMPORT_FULL_01": users2/******** directory=homedir dumpfile=test.dmp logfile=testexp.log include=TABLE remap_schema=users:users2
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "USERS2"."MVIEW_MASTER" 5.484 KB 1 rows
Processing object type SCHEMA_EXPORT/TABLE/COMMENT
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Job "USERS2"."SYS_IMPORT_FULL_01" successfully completed at Wed Apr 5 00:42:24 2017 elapsed 0 00:00:05


マスター表からDBリンク経由でCTASするという手もありますが、Materialized ViewではなくTableとして移行する場合、Data Pumpを利用する方法もあるなぁと。
そして、includeパラメータやエクスポートモードには注意しないと、いろいろハマりそうだなぁ、と思ったのでしたw


ということで、schemaモードでもMaterialized ViewをTableとして移行できるよ! めでたしめでたしw (失敗は気づきの母w)

[oracle@catfish ˜]$ sqlplus users2/********

SQL*Plus: Release 12.1.0.2.0 Production on Wed Apr 5 00:42:39 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Last Successful login time: Wed Apr 05 2017 00:42:18 +09:00

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

ORCL@USERS2> select object_name,object_type from user_objects where object_name='MVIEW_MASTER';

OBJECT_NAME OBJECT_TYPE
------------------------------ -----------------------
MVIEW_MASTER TABLE

ORCL@USERS2> select * from user_mviews;
no rows selected

ORCL@USERS2> select job,log_user,last_date,next_date,interval,failures,broken,what from user_jobs;

no rows selected

ORCL@USERS2> select * from mview_master;

ID DATA
---------- ----------
1 test1


今日はここまで。



Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング

| | | コメント (0) | トラックバック (0)

2017年4月 8日 (土)

Data Pumpも癖モノだよね〜w その1 - queryパラメーターの解析タイミング

AWS、MS、Oracle、それぞれのクラウド環境へのデータ移行なんてことも珍しくなくなってきました。
そして移行ツールとしても利用されるData Pump。

でも細かい機能を使おうと調べていると、Data Pump、いろいろ癖があるよなぁ〜。と改めて気づくんですw

ということで、そんな癖もの! Data PumpのFAQっぽいことを備忘録として書いておきますね。(要するに、ハマったこと集w)


 

queryパラメータに記述したwhere句っていつ解析されるかご存知ですか?
expdpコマンドを実行した時点でqueryパラメータのwhere句のシンタックスがチェックされると思う方、手をあげて!

私もそう思ってました、最近までw
あえて書いてるのですから、勘の良いかたなら、ちがうのか! と気づいちゃいますよねw 

そう!ちがうんです!

では、シンタックスエラーのあるqueryパラメータで実行してみますよ。 赤字の部分、where であるべきですが、 while としてあります。パースされればシンタックスエラーとなるはずです!!
いいですか〜〜〜、よ〜〜〜〜く見ててくださいよ〜。みて、みて、みてみてみてみて〜〜〜〜っ

パラメータファイルは以下のとおり

[oracle@crayfish ˜]$ cat query.par
tables=hoge
query=hoge:"while id = 1"


実行してみると、あーら不思議、正常終了してしまいます!

[oracle@crayfish ˜]$ expdp hr/hr@orcl directory=homedir dumpfile=testhr_query45.dmp logfile=testhr_query45.log parfile=query.par

Export: Release 12.1.0.2.0 - Production on Sat Apr 8 00:34:48 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "HR"."SYS_EXPORT_TABLE_01": hr/********@orcl directory=homedir dumpfile=testhr_query45.dmp logfile=testhr_query45.log parfile=query.par
Estimate in progress using BLOCKS method...
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/MARKER
. . exported "HR"."HOGE" 0 KB 0 rows
Master table "HR"."SYS_EXPORT_TABLE_01" successfully loaded/unloaded
******************************************************************************
Dump file set for HR.SYS_EXPORT_TABLE_01 is:
/home/oracle/testhr_query45.dmp
Job "HR"."SYS_EXPORT_TABLE_01" successfully completed at Sat Apr 8 00:35:13 2017 elapsed 0 00:00:24

なぜなのかわかりますか????

queryパラメータでデータを選択する対象の表が空だと、queryパラメータに指定したwhere句はパースされない! 
そもそも取り出すデータが無いので無駄なことはしない!
ということのようです。

ということで、queryパラメータのシンタックスチェックだけを事前に行いたい場合、1行以上のデータが必要でっす!
0件の状態で実行してもqueryパラメータのシンタックスチェックにはならないのでご注意を!!

[oracle@crayfish ˜]$ sqlplus hr/hr@orcl

SQL*Plus: Release 12.1.0.2.0 Production on Sat Apr 8 00:36:29 2017

Copyright (c) 1982, 2014, Oracle. All rights reserved.

Last Successful login time: Sat Apr 08 2017 00:34:48 +09:00

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

orcl@HR> insert into hoge values(1,'test');

1 row created.

orcl@HR> commit;

Commit complete.

orcl@HR> select count(1) from hoge;

COUNT(1)
----------
1

空の表に1行だけデータを登録すると! queryパラメターのwhere句が解析されシンタックスエラーとなります!

[oracle@crayfish ˜]$ cat query.par
tables=hoge
query=hoge:"while id = 1"

[oracle@crayfish ˜]$
[oracle@crayfish ˜]$ expdp hr/hr@orcl directory=homedir dumpfile=testhr_query46.dmp logfile=testhr_query46.log parfile=query.par

Export: Release 12.1.0.2.0 - Production on Sat Apr 8 00:37:08 2017

Copyright (c) 1982, 2014, Oracle and/or its affiliates. All rights reserved.

Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
Starting "HR"."SYS_EXPORT_TABLE_01": hr/********@orcl directory=homedir dumpfile=testhr_query46.dmp logfile=testhr_query46.log parfile=query.par
Estimate in progress using BLOCKS method...
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 64 KB
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type TABLE_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type TABLE_EXPORT/TABLE/STATISTICS/MARKER
ORA-31693: Table data object "HR"."HOGE" failed to load/unload and is being skipped due to error:
ORA-00933: SQL command not properly ended
Master table "HR"."SYS_EXPORT_TABLE_01" successfully loaded/unloaded
******************************************************************************
Dump file set for HR.SYS_EXPORT_TABLE_01 is:
/home/oracle/testhr_query46.dmp
Job "HR"."SYS_EXPORT_TABLE_01" completed with 1 error(s) at Sat Apr 8 00:37:34 2017 elapsed 0 00:00:24

ということで本日はここまで。

| | | コメント (0) | トラックバック (0)

2017年4月 6日 (木)

複数のApple Watchとペアリングできるんですよ!

開発用途などで、Series 2など複数のApple Watchを持っている方も少なくないと思います。

そして、iPhoneとペリングしないとな〜〜〜にもできないこともご存知かと思います。

でも、でもですねぇ、複数のApple Watchとペアリングできることを知らない方も多いんじゃないかと思う今日この頃w


Apple Watchとのペリングって1つだけしかできないと勘違いしていませんか?

複数の Apple Watch を iPhone につなぐ

できるんですよ! 

Img_1433


| | | コメント (0) | トラックバック (0)

2016年12月17日 (土)

スタースキーマを扱う実行計画の特徴

JPOUG Advent Calendar 2016の17日目のエントリです。
昨日は、id:kenken08さんのMySQLのsql_modeにあるORACLEとは - kenken0807_DBメモでした。

第三の柴田さんのネタを見て、急遽内容を変更しました。:)
SQLチューニングと対戦格闘ゲームの類似性について語る。- JPOUG Advent Calendar 2016 Day 15 - - ねら~ITエンジニア雑記


DWH系のスタースキーマを扱う実行計画の特徴を簡単にまとめておきたいと思います。(個人的には、in-memory aggregationが今年のハイライトだったのでw)

※サンプルスキーマ:SHスキーマを利用しています。
Installing Sample Schemas

まず、ハッシュ結合とBloom Filterを利用した実行計画です。面倒な準備もなく、癖も少ないので力技でなんとかする系ではよく見かける実行計画です。
Right-Deep Join + Bloom Filter
Right-Deep Joinが可能なのはHash Joinのみです。 意図的に行う場合は、LEADING/USE_HASH/SWAP_JOIN_INPUTSを利用します。
Right-Deep Join Trees and Star Schema Queries
津島博士のパフォーマンス講座 - 第46回 パーティション・プルーニングとハッシュ結合について

スタースキーマでない結合や、NLJではLeft-Deep Joinとなるのが一般的なので見慣れない実行計画だと思う方もいると思いますが、巨大なファクト表よりサイズの小さいディメンジョン表が常にハッシュ結合のビルド表(外部表)になるように結合順序が入れ替えられています。

ハッシュ結合の実行計画としては理にかなっているのですが、超巨大なファクト表との結合がある場合、Exadataをもってしても倒すことができない敵に出会うこともありますw
弱点といえば弱点ですが、方式上難しいところでもあります。
パラレル度を増加させたとしても太刀打ちできないケースもね。。。。とほほ。

Execution Plan
----------------------------------------------------------
Plan hash value: 2503647845

------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1546 | 137K| 3879 (1)| 00:00:01 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10003 | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | P->S | QC (ORDER) |
| 3 | SORT GROUP BY | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 5 | PX SEND RANGE | :TQ10002 | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,02 | P->P | RANGE |
| 6 | HASH GROUP BY | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 7 | HASH JOIN | | 580K| 50M| 3875 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 8 | PX RECEIVE | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 9 | PX SEND BROADCAST | :TQ10000 | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | P->P | BROADCAST |
| 10 | PX BLOCK ITERATOR | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
|* 12 | HASH JOIN | | 580K| 35M| 3872 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 13 | PX RECEIVE | | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,02 | PCWP | |
| 14 | PX SEND BROADCAST | :TQ10001 | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | P->P | BROADCAST |
| 15 | PX BLOCK ITERATOR | | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | PCWC | |
| 16 | TABLE ACCESS INMEMORY FULL| CUSTOMERS | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | PCWP | |
|* 17 | HASH JOIN | | 580K| 29M| 3864 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 18 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 19 | HASH JOIN | | 1161K| 36M| 3862 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 20 | PART JOIN FILTER CREATE | :BF0000 | 1845 | 22140 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 21 | TABLE ACCESS INMEMORY FULL| TIMES | 1845 | 22140 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 22 | PX BLOCK ITERATOR | | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWC | |
| 23 | TABLE ACCESS FULL | SALES | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 1 Sql Plan Directive used for this statement

Star Transformation
巨大なファクト表とディメンジョン表の結合が辛いなら、昔からあるスター変換だ! 
とも思うのですが、スター変換は頑固ものかつ、曲者なのが難点。

頑固さ
ディメンジョン表やファクト表にビットマップ索引や参照整合性制約作成等、利用できるようにするお膳立てができてないと、ピクリとも動きませんw
巨大なファクト表の外部キー列にビットマップ索引を1つ作成するのに、数時間w 複数作成して、さらに、ディメンジョン表との参照整合性制約まで必要なのでまともにやっていると、1日では終わらないことも><
(俺を信じろ、 RELYが利用できるデータの状態であれば楽ではありますが

スター変換の最大の弱点は、ビットマップ索引を利用したROWIDアクセス(以下の実行計画ではId=42の部分)による読み込み件数が多すぎるケースです。性能が伸びなかったり、または、悪化することもあります。
スター変換を利用するかどうかは、ディメンジョン表との結合でファクト表が十分に絞り込めるかにかかっています。

ディメンジョン表との結合キーがビットマップ索引中にあるため、ディメンジョン表とファクト表を結合することなく、ROWIDでファクト表をアクセスして集計することができます。
ファクト表のアクセス量が少ない場合はROWIDアクセスがメリットとなるわけですが、その逆のケースでは、ファクト表はROWIDで1行ごとにアクセスされることになるため、アクセスするファクト表の行数が多くなればなるほど不利になります。

ROWIDによるシングルブロックリードが数十億回繰り返されるとしたら、待機イベントのほとんどが、db file sequential readやcell single block physical read(Exadata)になってしまうことになります。

使いどころを見誤らないようにしたいものです。

Execution Plan
----------------------------------------------------------
Plan hash value: 2513598833

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6490 | 627K| | 5584 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | P->S | QC (RAND) |
| 4 | LOAD AS SELECT (TEMP SEGMENT MERGE) | SYS_TEMP_0FD9D662C_336236 | | | | | | | | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 6 | TABLE ACCESS INMEMORY FULL | TIMES | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 7 | PX COORDINATOR | | | | | | | | | | | |
| 8 | PX SEND QC (ORDER) | :TQ20003 | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,03 | P->S | QC (ORDER) |
| 9 | SORT GROUP BY | | 6490 | 627K| 60M| 5582 (1)| 00:00:01 | | | Q2,03 | PCWP | |
| 10 | PX RECEIVE | | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,03 | PCWP | |
| 11 | PX SEND RANGE | :TQ20002 | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,02 | P->P | RANGE |
| 12 | HASH GROUP BY | | 6490 | 627K| 60M| 5582 (1)| 00:00:01 | | | Q2,02 | PCWP | |
|* 13 | HASH JOIN | | 580K| 54M| | 4334 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 14 | PX RECEIVE | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
| 15 | PX SEND BROADCAST | :TQ20000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | P->P | BROADCAST |
| 16 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWC | |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662C_336236 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
|* 18 | HASH JOIN | | 580K| 48M| | 4332 (1)| 00:00:01 | | | Q2,02 | PCWP | |
|* 19 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 20 | HASH JOIN | | 580K| 36M| | 4329 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 21 | PX RECEIVE | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,02 | PCWP | |
| 22 | PX SEND BROADCAST | :TQ20001 | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | P->P | BROADCAST |
|* 23 | HASH JOIN | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | PCWP | |
| 24 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,01 | PCWP | |
| 25 | PX BLOCK ITERATOR | | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,01 | PCWC | |
| 26 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,01 | PCWP | |
| 27 | VIEW | VW_ST_A44449E3 | 580K| 16M| | 4319 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 28 | NESTED LOOPS | | 580K| 28M| | 4315 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 29 | PX PARTITION RANGE SUBQUERY | | 580K| 12M| | 15 (14)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q2,02 | PCWC | |
| 30 | BITMAP CONVERSION TO ROWIDS | | 580K| 12M| | 15 (14)| 00:00:01 | | | Q2,02 | PCWP | |
| 31 | BITMAP AND | | | | | | | | | Q2,02 | PCWP | |
| 32 | BITMAP MERGE | | | | | | | | | Q2,02 | PCWP | |
| 33 | BITMAP KEY ITERATION | | | | | | | | | Q2,02 | PCWP | |
| 34 | BUFFER SORT | | | | | | | | | Q2,02 | PCWP | |
|* 35 | TABLE ACCESS INMEMORY FULL| CHANNELS | 2 | 26 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 36 | BITMAP INDEX RANGE SCAN | SALES_CHANNEL_BIX | | | | | |KEY(SQ)|KEY(SQ)| Q2,02 | PCWP | |
| 37 | BITMAP MERGE | | | | | | | | | Q2,02 | PCWP | |
| 38 | BITMAP KEY ITERATION | | | | | | | | | Q2,02 | PCWP | |
| 39 | BUFFER SORT | | | | | | | | | Q2,02 | PCWP | |
| 40 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662C_336236 | 1845 | 14760 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 41 | BITMAP INDEX RANGE SCAN | SALES_TIME_BIX | | | | | |KEY(SQ)|KEY(SQ)| Q2,02 | PCWP | |
| 42 | TABLE ACCESS BY USER ROWID | SALES | 1 | 29 | | 4304 (1)| 00:00:01 | ROWID | ROWID | Q2,02 | PCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- star transformation used for this statement
- 1 Sql Plan Directive used for this statement

長い前置きでしたが、やっと真打の登場です!
in-memory aggregationとして解説されていることが多いのですが、実行計画を眺めている人間からすると機能名よりvector transformationの方がイメージしやすいので、以下、Vector Tranformation/ベクター変換とします。

Vector Transformation
スター変換同様に、巨大なファクト表とディメンジョン表を直接結合しない点や、パラレル実行時も他の実行計画ではみられない(誤解をおそれずにいうと、全力投球に近いかもw)特徴があります。
索引や参照整合性制約などの作成は不要。
in-memory database関連の機能ではあるのですが、全ての表がinmemory化されていなくても発動させることができます。(inmemory_sizeパラメータの設定は必要となる模様。後述)
最強の力を発揮するのは、全てがinmemoryで動作した場合であることは間違いないわけですが、巨大過ぎるファクト表がinmemory化できるほどメモリが潤沢にあるかというと、そうじゃなかっりしますし。


ベクター変換の動きを簡単に説明すると以下のような感じです。
ディメンジョン表を元に集計結果相当の構造体(in-memory accumulatorと呼ばれる多次元構造体)をメモリ上に構築後、ファクト表を読みながらin-memory accumulator上で集計します!!!(画期的!)
ハッシュ結合が全くなくなるわけではないですが、集計終了後に読み替え目的で少量(この部分が少量じゃないと辛くなるはずなのでよーく確認しておくことをおすすめします)はのハッシュ結合が行われるだけなので、冒頭で紹介した巨大なファクト表とディメンジョン表の結合によるCPUネック部分を華麗に回避していることがわかります。
Right-Deep Join+Bloom Filterで苦しい状況になったら、in-memory aggregationのことを思い出してあげてください。

助けてくれるかもしれません。



Oracle Database In-Memory: In-Memory Aggregation - Oracle White Paper JANUARY 2015


Execution Plan
----------------------------------------------------------
Plan hash value: 3211261687

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 483 | 60858 | | 3840 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6630_336236 | | | | | | | | | | |
| 3 | PX COORDINATOR | | | | | | | | | | | |
| 4 | PX SEND QC (RANDOM) | :TQ10001 | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | P->S | QC (RAND) |
| 5 | BUFFER SORT | | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 6 | VECTOR GROUP BY | | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 7 | KEY VECTOR CREATE BUFFERED | :KV0000 | 1845 | 29520 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 8 | PX RECEIVE | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,01 | PCWP | |
| 9 | PX SEND HASH | :TQ10000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 11 | TABLE ACCESS INMEMORY FULL | TIMES | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9D6631_336236 | | | | | | | | | | |
| 13 | PX COORDINATOR | | | | | | | | | | | |
| 14 | PX SEND QC (RANDOM) | :TQ20001 | 23 | 851 | | 12 (25)| 00:00:01 | | | Q2,01 | P->S | QC (RAND) |
| 15 | HASH GROUP BY | | 23 | 851 | 2624K| 12 (25)| 00:00:01 | | | Q2,01 | PCWP | |
| 16 | KEY VECTOR CREATE BUFFERED | :KV0001 | | | | | | | | Q2,01 | PCWP | |
| 17 | PX RECEIVE | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | PCWP | |
| 18 | PX SEND HASH | :TQ20000 | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,00 | P->P | HASH |
|* 19 | HASH JOIN | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,00 | PCWP | |
| 20 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 21 | PX BLOCK ITERATOR | | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,00 | PCWC | |
| 22 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,00 | PCWP | |
| 23 | LOAD AS SELECT | SYS_TEMP_0FD9D6632_336236 | | | | | | | | | | |
| 24 | PX COORDINATOR | | | | | | | | | | | |
| 25 | PX SEND QC (RANDOM) | :TQ30001 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | P->S | QC (RAND) |
| 26 | BUFFER SORT | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 27 | VECTOR GROUP BY | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 28 | KEY VECTOR CREATE BUFFERED | :KV0002 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 29 | PX RECEIVE | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,01 | PCWP | |
| 30 | PX SEND HASH | :TQ30000 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | P->P | HASH |
| 31 | PX BLOCK ITERATOR | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWC | |
|* 32 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWP | |
| 33 | PX COORDINATOR | | | | | | | | | | | |
| 34 | PX SEND QC (ORDER) | :TQ40003 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | P->S | QC (ORDER) |
| 35 | SORT GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 36 | PX RECEIVE | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 37 | PX SEND RANGE | :TQ40002 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | P->P | RANGE |
| 38 | HASH GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | PCWP | |
|* 39 | HASH JOIN | | 483 | 60858 | | 3820 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 40 | PX RECEIVE | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 41 | PX SEND BROADCAST | :TQ40000 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | P->P | BROADCAST |
| 42 | PX BLOCK ITERATOR | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWC | |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6631_336236 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWP | |
|* 44 | HASH JOIN | | 483 | 42987 | | 3818 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 45 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6630_336236 | 21 | 252 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
|* 46 | HASH JOIN | | 483 | 37191 | | 3816 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 47 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6632_336236 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 48 | VIEW | VW_VT_AF0F4755 | 483 | 27048 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 49 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 50 | PX RECEIVE | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 51 | PX SEND HASH | :TQ40001 | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | P->P | HASH |
| 52 | VECTOR GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 53 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 54 | KEY VECTOR USE | :KV0001 | 580K| 18M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 55 | KEY VECTOR USE | :KV0002 | 580K| 16M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 56 | KEY VECTOR USE | :KV0000 | 1161K| 27M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 57 | PX BLOCK ITERATOR | | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWC | |
|* 58 | TABLE ACCESS FULL| SALES | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 2 Sql Plan Directives used for this statement
- vector transformation used for this statement

超絶必殺技にも思えるベクター変換ですが、癖がないわけではありません。(大技につきものの反動というか、なんというかw)

癖 その1)
パラレルクエリー時に割り当てられるサーバープロセスが多く、割り当てられるサーバー数は、KEY VECTORの作成数により大きく変化します。
以下、SQL MONITORのParallel Execution Detailsセクション(抜粋)の比較

並列度は同じでも割り当てるサーバー数はこんなに違う!
VECTOR TRANSFORMATION
KEY VECTORが3つ作成されるベクター変換の場合
(SQL MONITORのParallel Execution Detailsセクションより抜粋)
Parallel Execution Details (DOP=4 , Servers Allocated=32)

KEY VECTORが2つ作成されるベクター変換の場合
Parallel Execution Details (DOP=4 , Servers Allocated=24)

STAR TRANSFORMATION
Parallel Execution Details (DOP=4 , Servers Allocated=12)

Hash Joinのみ
Parallel Execution Details (DOP=4 , Servers Allocated=8)


癖 その2)
スター変換を発動させるための索引作成や、制約作成の煩雑さは無く、全ての表がinmemoryになっていなくても発動させることはできるのですが、発動させるためは、最低限設定しなればならない(ほんと? 不具合?)パラメータが存在します。(

inmemory化する表は無くとも、inmemory_size=100m(設定可能な最小サイズ)に設定しないとVECTOR_TRANSFORMヒントが無視されるという点です。
この制限?を記載しているマニュアルなどは探し出せていないのですが、どこかに記載されているのでしょうか?(いまのところ見つけることができず。。。教えていただけるとうれしいです)


では、最後に、inmemory_size初期化パラメータの設定有無による変化をみてみましょう。

inmemory_size初期化パラメータが設定されている場合にはVECTOR_TRANSFORMヒントでベクター変換を強制できています。

10:36:23 orcl12c@SYSTEM> show parameter inmemory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default string
inmemory_force string DEFAULT
inmemory_max_populate_servers integer 3
inmemory_query string ENABLE
inmemory_size big integer 512M
inmemory_trickle_repopulate_servers_ integer 1
percent
optimizer_inmemory_aware boolean TRUE


10:37:04 ORCL@SH> set autot trace exp stat
10:56:18 ORCL@SH> @sample3_2

135 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 3211261687

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 483 | 60858 | | 3967 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6609_33B383 | | | | | | | | | | |
| 3 | PX COORDINATOR | | | | | | | | | | | |
| 4 | PX SEND QC (RANDOM) | :TQ10001 | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | P->S | QC (RAND) |
| 5 | BUFFER SORT | | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 6 | VECTOR GROUP BY | | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 7 | KEY VECTOR CREATE BUFFERED | :KV0000 | 1845 | 29520 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 8 | PX RECEIVE | | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,01 | PCWP | |
| 9 | PX SEND HASH | :TQ10000 | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 11 | TABLE ACCESS FULL | TIMES | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9D660A_33B383 | | | | | | | | | | |
| 13 | PX COORDINATOR | | | | | | | | | | | |
| 14 | PX SEND QC (RANDOM) | :TQ20001 | 23 | 851 | | 122 (3)| 00:00:01 | | | Q2,01 | P->S | QC (RAND) |
| 15 | HASH GROUP BY | | 23 | 851 | 2624K| 122 (3)| 00:00:01 | | | Q2,01 | PCWP | |
| 16 | KEY VECTOR CREATE BUFFERED | :KV0001 | | | | | | | | Q2,01 | PCWP | |
| 17 | PX RECEIVE | | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,01 | PCWP | |
| 18 | PX SEND HASH | :TQ20000 | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,00 | P->P | HASH |
|* 19 | HASH JOIN | | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 20 | TABLE ACCESS FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 21 | PX BLOCK ITERATOR | | 55500 | 541K| | 117 (0)| 00:00:01 | | | Q2,00 | PCWC | |
| 22 | TABLE ACCESS FULL | CUSTOMERS | 55500 | 541K| | 117 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 23 | LOAD AS SELECT | SYS_TEMP_0FD9D660B_33B383 | | | | | | | | | | |
| 24 | PX COORDINATOR | | | | | | | | | | | |
| 25 | PX SEND QC (RANDOM) | :TQ30001 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | P->S | QC (RAND) |
| 26 | BUFFER SORT | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 27 | VECTOR GROUP BY | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 28 | KEY VECTOR CREATE BUFFERED | :KV0002 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 29 | PX RECEIVE | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,01 | PCWP | |
| 30 | PX SEND HASH | :TQ30000 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | P->P | HASH |
| 31 | PX BLOCK ITERATOR | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWC | |
|* 32 | TABLE ACCESS FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWP | |
| 33 | PX COORDINATOR | | | | | | | | | | | |
| 34 | PX SEND QC (ORDER) | :TQ40003 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | P->S | QC (ORDER) |
| 35 | SORT GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 36 | PX RECEIVE | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 37 | PX SEND RANGE | :TQ40002 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | P->P | RANGE |
| 38 | HASH GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | PCWP | |
|* 39 | HASH JOIN | | 483 | 60858 | | 3820 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 40 | PX RECEIVE | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 41 | PX SEND BROADCAST | :TQ40000 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | P->P | BROADCAST |
| 42 | PX BLOCK ITERATOR | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWC | |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_33B383 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWP | |
|* 44 | HASH JOIN | | 483 | 42987 | | 3818 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 45 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6609_33B383 | 21 | 252 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
|* 46 | HASH JOIN | | 483 | 37191 | | 3816 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 47 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660B_33B383 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 48 | VIEW | VW_VT_AF0F4755 | 483 | 27048 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 49 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 50 | PX RECEIVE | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 51 | PX SEND HASH | :TQ40001 | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | P->P | HASH |
| 52 | VECTOR GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 53 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 54 | KEY VECTOR USE | :KV0001 | 580K| 18M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 55 | KEY VECTOR USE | :KV0002 | 580K| 16M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 56 | KEY VECTOR USE | :KV0000 | 1161K| 27M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 57 | PX BLOCK ITERATOR | | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWC | |
|* 58 | TABLE ACCESS FULL| SALES | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - filter("T"."FISCAL_YEAR"=2000 OR "T"."FISCAL_YEAR"=2005 OR "T"."FISCAL_YEAR"=2010 OR "T"."FISCAL_YEAR"=2015 OR "T"."FISCAL_YEAR"=2016)
19 - access("C"."COUNTRY_ID"="R"."COUNTRY_ID")
32 - filter("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Partners')
39 - access("ITEM_13"=INTERNAL_FUNCTION("C0") AND "ITEM_14"="C4")
44 - access("ITEM_17"=INTERNAL_FUNCTION("C0") AND "ITEM_18"="C2")
46 - access("ITEM_15"=INTERNAL_FUNCTION("C0") AND "ITEM_16"="C2")
58 - filter(SYS_OP_KEY_VECTOR_FILTER("S"."TIME_ID",:KV0000) AND SYS_OP_KEY_VECTOR_FILTER("S"."CHANNEL_ID",:KV0002))

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 2 Sql Plan Directives used for this statement
- vector transformation used for this statement

inmemory_size初期化パラメータを0にし、同一SQL文を実行すると。。。。なんということでしょう。ベクター変換は発動しません!

10:46:22 orcl12c@SYSTEM> show parameter inmemory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default string
inmemory_force string DEFAULT
inmemory_max_populate_servers integer 0
inmemory_query string ENABLE
inmemory_size big integer 0
inmemory_trickle_repopulate_servers_ integer 1
percent
optimizer_inmemory_aware boolean TRUE


10:47:56 ORCL@SH> @sample3_2

135 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2503647845

--------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1546 | 137K| 4006 (1)| 00:00:01 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10003 | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | P->S | QC (ORDER) |
| 3 | SORT GROUP BY | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 5 | PX SEND RANGE | :TQ10002 | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,02 | P->P | RANGE |
| 6 | HASH GROUP BY | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 7 | HASH JOIN | | 580K| 50M| 4002 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 8 | PX RECEIVE | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 9 | PX SEND BROADCAST | :TQ10000 | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | P->P | BROADCAST |
| 10 | PX BLOCK ITERATOR | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS FULL | COUNTRIES | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
|* 12 | HASH JOIN | | 580K| 35M| 3999 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 13 | PX RECEIVE | | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 14 | PX SEND BROADCAST | :TQ10001 | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | P->P | BROADCAST |
| 15 | PX BLOCK ITERATOR | | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | PCWC | |
| 16 | TABLE ACCESS FULL | CUSTOMERS | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | PCWP | |
|* 17 | HASH JOIN | | 580K| 29M| 3882 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 18 | TABLE ACCESS FULL | CHANNELS | 2 | 42 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 19 | HASH JOIN | | 1161K| 36M| 3879 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 20 | PART JOIN FILTER CREATE| :BF0000 | 1845 | 22140 | 19 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 21 | TABLE ACCESS FULL | TIMES | 1845 | 22140 | 19 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 22 | PX BLOCK ITERATOR | | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWC | |
| 23 | TABLE ACCESS FULL | SALES | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWP | |
--------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - access("C"."COUNTRY_ID"="R"."COUNTRY_ID")
12 - access("S"."CUST_ID"="C"."CUST_ID")
17 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
18 - filter("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Partners')
19 - access("S"."TIME_ID"="T"."TIME_ID")
21 - filter("T"."FISCAL_YEAR"=2000 OR "T"."FISCAL_YEAR"=2005 OR "T"."FISCAL_YEAR"=2010 OR "T"."FISCAL_YEAR"=2015 OR
"T"."FISCAL_YEAR"=2016)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 1 Sql Plan Directive used for this statement

参考
Database Virtual Box Appliance / Virtual Machine
Installing Sample Schemas
Getting started with Oracle Database In-Memory Part V - Aggregation
津島博士のパフォーマンス講座 - 第54回 Oracle Database In-Memoryについて(2)

利用したSQL
sample1_2.sql
Right-Deep Join (Right-Deep Treeにならない場合には SWAP_JOIN_INPUTSで制御する必要がありますが、SHスキーマでオプティマイザ統計が取得されているのであれば不要.今回の例ではSWAP_JOIN_INPUTSは利用していませんが、オプティマイザは判断を誤るようであれば利用したほうがよいと思います。)

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/

sample2_2.sql
スター変換ヒントが必要です。スター変換はデフォルトでOFFに設定されています。
SHスキーマはスタースキーマかつ、スター変換をすぐに試せる環境(ファクト表の外部キーのビットマップ索引やデョメンジョン表への参照整合性制約等)になっています。

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
/*+
STAR_TRANSFORMATION
*/
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/

sample3_2.sql
ベクター変換の例です。ベクター変換のヒントは数種類(ファクト表を記述場合、ディメンジョン表を記述場合)あります。(詳細はv$sql_hintを参照のこと)

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
/*+
VECTOR_TRANSFORM
*/
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/


明日は、@yoshikawさんです! お楽しみに!


俺のターンおわたー!:)

| | | コメント (0) | トラックバック (0)

2016年12月 3日 (土)

macOS : カレンダーへのスパム参加通知をなんとかする方法を考えてみた

最近多くなってきているスパムメールを利用したカレンダーへのスパム参加通知。
はっきりいってうざいです。

https://discussionsjapan.apple.com/thread/10180695

https://discussionsjapan.apple.com/thread/10179171

https://discussions.apple.com/message/30890223#30890223


Spam iCloud Calendar Invitations


メールアドレス変えるという方法もあるけど、変えない対処方法を考えてみました。
Appleさん根本的な対策考えてくれるといいけど。


私が今試している方法は、簡単に言うと、メールの”スパムフォルダ振り分け”に近いのですが、スパムな参加通知を振り分けられないので、

デフォルトカレンダーをスパム参加通知用にしちゃうという

方法です。

通常の参加通知は、個別にカレンダーを作って参加依頼時や予定を入れる時にカレンダー指定で振り分けてもらってます。(想像しやすいカレンダー名にしてるとそれもねらわれるかも。。。)

(性悪説的な対応しか、いまのところできないのが辛いが)


いま試している方法は次のとおり。

20161130_214621

最初に新規カレンダーを作ります。
複数のアカウントを設定している場合は、サブメニューでどのアカウントにカレンダーを作成するかも選択できます。

20161130_214650


カレンダーのチェックボックスをオフにしておきます。
オフしたカレンダーのイベントは表示されなくなります!

20161130_214709

この例ではカレンダー名をspamとしていますが他と区別できればなんでもいかと。

20161130_214721

ついでに色もブラックに変更

20161203_141109

20161130_214746

「カレンダー」→ポップアップメニュー→「情報をみる」

20161203_142148

「通知を無視」をチェック

20161130_214758


「メニュー」→「カレンダー」→「環境設定...」
作成したカレンダーをデフォルトにします。

20161130_214841

20161130_214907

これでスパム通知用デフォルトカレンダーのできあがり、表示もされないし。
ある程度、スパム通知たまったらフォーラムでのディスカッションにもあるように、カレンダーごと、「通知しないで削除」すればイベントごとの対処は不要なので、楽になるかなぁ〜と。

ただいま試行中。

20161203_134018


| | | コメント (0) | トラックバック (0)

2016年11月19日 (土)

macOS SierraのiMovie10.1.3がクラッシュしまくる時の対処 / FAQ

まあ、OSアップグレードとかiMovieのアップグレードとかではありがちなのですが....
キャッシュ系かとおもったら違ったので、自分メモ


Why does iMovie 10.1.3 always crashes right after starting?

上記、スレにある対処法で一発解決しまっす。

lampeye:˜ lempeyes$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.12.1
BuildVersion: 16B2555
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$
lampeye:˜ lampeyes$ /usr/sbin/system_profiler SPApplicationsDataType | grep iMovie -A 2 | grep -E 'Version|iMovie'
iMovie:
Version: 10.1.3
Location: /Applications/iMovie.app
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$ ll
total 24
drwx------@ 12 lempeyes staff 408B 11 19 12:04 Dropbox
drwx------+ 21 lempeyes staff 714B 11 19 00:50 Downloads

・・・中略・・・

-rw------- 1 lempeyes staff 3B 1 22 2011 dead.letter
drwxr-xr-x 10 lempeyes staff 340B 12 19 2007 Image Kit Browser
drwxr-xr-x 10 lempeyes staff 340B 12 19 2007 Image Browser
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$
lampeye:˜ lempeyes$ cd Library/Containers/
lampeye:Containers lempeyes$
lampeye:Containers lempeyes$
lampeye:Containers lempeyes$ ls -l | grep iMovie*
drwx------ 4 lempeyes staff 136 10 26 22:51 com.apple.iMovieApp
lampeye:Containers lempeyes$
lampeye:Containers lempeyes$
lampeye:Containers lempeyes$ rm -rf com.apple.iMovieApp
lampeye:Containers lempeyes$
lampeye:Containers lempeyes$ ls -l | grep iMovie*
lampeye:Containers lempeyes$

| | | コメント (0) | トラックバック (0)

2016年9月24日 (土)

VirtualBoxとSIMD

Previously on Mac De Oracle
SIMDのメモをあれこれ貼り付けただけでしたが、実は今回のことを調べてる次いでに見つけたものだったんですよ。



OracleもSIMDを使う操作が多くなりつつあるようなのでどうなんだろというわけで、今回もメモです。

元ネタは以下のエントリ

VirtualBoxゲストでSIMD命令: DB In-Memory
https://blogs.oracle.com/LetTheSunShineIn/entry/virtualboxゲストでsimd命令_db_in_memory

上記は約2年前の記事で現在のVirtualBoxでは、SSE4.1/SSE4.2は有効化されています。
以下ドキュメントを参照のこと。

9.27. Experimental support for passing through SSE4.1 / SSE4.2 instructions
https://www.virtualbox.org/manual/ch09.html#sse412passthrough


ただ、AVXはどうなのか書かれてはいないのでどうなってんのかな〜とか、ググってたら

Ticket #14427 (new defect)
https://www.virtualbox.org/ticket/14427#comment:3
こんな記事を見つけたのであー有効になってるよねーということで、うちの環境での確認

確認に利用したVirtualBoxは現時点の最新版です。

MacBook:˜ lampeyes$ VBoxManage -version
5.1.6r110634
MacBook:˜ lampeyes$


うちのMacファミリーでは最古参w (そろそろなんとか新しいのにしたいなぁ〜w)
古いCPUだけど、マニュアル通り、SSE4.1/4.2はguestでも有効化されていました。おし!

MacPro:˜ lampeyes$ /usr/sbin/system_profiler SPHardwareDataType | grep -E 'Model|Processor|Core|Cache'
Model Name: Mac Pro
Model Identifier: MacPro5,1
Processor Name: 6-Core Intel Xeon
Processor Speed: 2.4 GHz
Number of Processors: 2
Total Number of Cores: 12
L2 Cache (per Core): 256 KB
L3 Cache (per Processor): 12 MB
Processor Interconnect Speed: 5.86 GT/s
MacPro:˜ lampeyes$
MacPro:˜ lampeyes$ grep -E 'Mnemonic|SSE4|AVX' '/Users/lampeyes/VirtualBox VMs/Linux Group/OralceLinux (Oracle12c R1)/Logs/VBox.log'
00:00:02.472827 Mnemonic - Description = guest (host)
00:00:02.472916 SSE4_1 - SSE4_1 support = 1 (1)
00:00:02.472919 SSE4_2 - SSE4_2 support = 1 (1)
00:00:02.472934 AVX - AVX support = 0 (0)
00:00:02.472942 Mnemonic - Description = guest (host)
00:00:02.472951 AVX2 - Advanced Vector Extensions 2 = 0 (0)
00:00:02.472968 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:02.472978 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:02.472980 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:02.472981 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:02.473041 Mnemonic - Description = guest (host)
00:00:02.473103 SSE4A - SSE4A instructions = 0 (0)
MacPro:˜ lampeyes$

最近のメインマシンw
SSE4.1/4.2に加えてAVXもguestで有効化されていました。
(探したかぎりではドキュメントには見当たらないかった。もしご存知の方がいたら教えてくだい!

ただし、AVX2はguestでは無効化されています。。。。大人の事情でもあるのかとググってますが今の所見つからず。

MacBookAir:˜ lampeyes$ /usr/sbin/system_profiler SPHardwareDataType | grep -E 'Model|Processor|Core|Cache'
Model Name: MacBook Air
Model Identifier: MacBookAir7,2
Processor Name: Intel Core i7
Processor Speed: 2.2 GHz
Number of Processors: 1
Total Number of Cores: 2
L2 Cache (per Core): 256 KB
L3 Cache: 4 MB
MacBookAir:˜ lampeyes$ grep -E 'Mnemonic|SSE4|AVX' '/Users/lampeyes/VirtualBox VMs/OracleLinux/Logs/VBox.log'
00:00:02.654824 Mnemonic - Description = guest (host)
00:00:02.654876 SSE4_1 - SSE4_1 support = 1 (1)
00:00:02.654877 SSE4_2 - SSE4_2 support = 1 (1)
00:00:02.654886 AVX - AVX support = 1 (1)
00:00:02.654890 Mnemonic - Description = guest (host)
00:00:02.654894 AVX2 - Advanced Vector Extensions 2 = 0 (1)
00:00:02.654903 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:02.654909 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:02.654909 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:02.654910 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:02.654987 Mnemonic - Description = guest (host)
00:00:02.655021 SSE4A - SSE4A instructions = 0 (0)


AVX2をguestで有効化することは可能なようです。

MacBookAir:˜ lampeyes$ 
MacBookAir:˜ lampeyes$ VBoxManage setextradata "OracleLinux" VBoxInternal/CPUM/IsaExts/AVX2 1

MacBookAir:˜ lampeyes$  grep -E 'Mnemonic|SSE4|AVX' '/Users/lampeyes/VirtualBox VMs/OracleLinux/Logs/VBox.log'
00:00:02.557798 AVX2 = 0x0000000000000001 (1)
00:00:02.815177 Mnemonic - Description = guest (host)
00:00:02.815226 SSE4_1 - SSE4_1 support = 1 (1)
00:00:02.815227 SSE4_2 - SSE4_2 support = 1 (1)
00:00:02.815235 AVX - AVX support = 1 (1)
00:00:02.815248 Mnemonic - Description = guest (host)
00:00:02.815253 AVX2 - Advanced Vector Extensions 2 = 1 (1)
00:00:02.815262 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:02.815267 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:02.815268 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:02.815269 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:02.815348 Mnemonic - Description = guest (host)
00:00:02.815381 SSE4A - SSE4A instructions = 0 (0)
MacBookAir:˜ lampeyes$

最後に Intel Core m5のMacBookですが、こちらは、MacBook Airと同様で、AVX2がguest側で無効化されていて、同様に手順で有効化できました。

何か不都合があったらオフればいいので、このまま使ってみようかと。:)

MacBook:˜ lampeyes$  /usr/sbin/system_profiler SPHardwareDataType | grep -E 'Model|Processor|Core|Cache'
Model Name: MacBook
Model Identifier: MacBook9,1
Processor Name: Intel Core m5
Processor Speed: 1.2 GHz
Number of Processors: 1
Total Number of Cores: 2
L2 Cache (per Core): 256 KB
L3 Cache: 4 MB
MacBook:˜ lampeyes$
MacBook:˜ lampeyes$ grep -E 'Mnemonic|SSE4|AVX' '/Users/lampeyes/VirtualBox VMs/DOC-921720/Logs/VBox.log'
00:00:02.791014 Mnemonic - Description = guest (host)
00:00:02.791076 SSE4_1 - SSE4_1 support = 1 (1)
00:00:02.791077 SSE4_2 - SSE4_2 support = 1 (1)
00:00:02.791088 AVX - AVX support = 1 (1)
00:00:02.791093 Mnemonic - Description = guest (host)
00:00:02.791100 AVX2 - Advanced Vector Extensions 2 = 0 (1)
00:00:02.791112 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:02.791119 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:02.791120 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:02.791121 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:02.791227 Mnemonic - Description = guest (host)
00:00:02.791270 SSE4A - SSE4A instructions = 0 (0)
MacBook:˜ lampeyes$
MacBook:˜ lampeyes$ VBoxManage setextradata "DOC-921720" VBoxInternal/CPUM/IsaExts/AVX2 1
MacBook:˜ lampeyes$
MacBook:˜ lampeyes$
MacBook:˜ lampeyes$ grep -E 'Mnemonic|SSE4|AVX' '/Users/lampeyes/VirtualBox VMs/DOC-921720/Logs/VBox.log'
00:00:01.243213 AVX2 = 0x0000000000000001 (1)
00:00:01.405462 Mnemonic - Description = guest (host)
00:00:01.405592 SSE4_1 - SSE4_1 support = 1 (1)
00:00:01.405593 SSE4_2 - SSE4_2 support = 1 (1)
00:00:01.405604 AVX - AVX support = 1 (1)
00:00:01.405610 Mnemonic - Description = guest (host)
00:00:01.405616 AVX2 - Advanced Vector Extensions 2 = 1 (1)
00:00:01.405628 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:01.405635 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:01.405636 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:01.405637 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:01.405758 Mnemonic - Description = guest (host)
00:00:01.405836 SSE4A - SSE4A instructions = 0 (0)
MacBook:˜ lampeyes$



一つ忘れてた、うちで一番古いやつ。(これもそろそろ刷新したいw)
古いのでAVX以降はないですね。Windows 10 Proのリアルマシンなのでコマンドプロンプトからbashで情報取得。う~~ん便利bash w
lampeyes@LAMPEYES:/mnt/c$ cat /proc/cpuinfo | grep -E 'model name|cpu core|siblings' | sort -n | uniq

cpu cores : 6
model name : Intel(R) Core(TM) i7 CPU X 980 @ 3.33GHz
siblings : 12
lampeyes@LAMPEYES:/mnt/c$
lampeyes@LAMPEYES:/mnt/c$ grep -E 'Mnemonic|SSE4|AVX' '/mnt/c/Users/lampeyes/VirtualBox VMs/CentOS6.5 64bit/Logs/VBox.log'
00:00:04.805674 Mnemonic - Description = guest (host)
00:00:04.805719 SSE4_1 - SSE4_1 support = 1 (1)
00:00:04.805720 SSE4_2 - SSE4_2 support = 1 (1)
00:00:04.805728 AVX - AVX support = 0 (0)
00:00:04.805732 Mnemonic - Description = guest (host)
00:00:04.805756 AVX2 - Advanced Vector Extensions 2 = 0 (0)
00:00:04.805765 AVX512F - AVX512 Foundation instructions = 0 (0)
00:00:04.805770 AVX512PF - AVX512 Prefetch instructions = 0 (0)
00:00:04.805771 AVX512ER - AVX512 Exponential & Reciprocal instructions = 0 (0)
00:00:04.805772 AVX512CD - AVX512 Conflict Detection instructions = 0 (0)
00:00:04.805811 Mnemonic - Description = guest (host)
00:00:04.805842 SSE4A - SSE4A instructions = 0 (0)
lampeyes@LAMPEYES:/mnt/c$

| | | コメント (0) | トラックバック (0)

2016年9月22日 (木)

メモ:SIMDあれこれ

SIMDあれこれ自分メモ

primitive: blog / introdunction to SIMD programming
http://i-saint.hatenablog.com/entry/2015/05/26/212441

wikipedia / Streaming SIMD Extensions
https://ja.wikipedia.org/wiki/Streaming_SIMD_Extensions

IA Software User Society / コンパイラー最適化入門: 第1回 SIMD 命令とプロセッサーの関係
http://www.isus.jp/products/c-compilers/compiler_part1/

IA Software User Society / コンパイラー最適化入門: 第2回 SIMD 命令と伝統的な IA 命令
http://www.isus.jp/products/c-compilers/compiler_part2/

IA Software User Society / コンパイラー最適化入門: 第3回 インテル® コンパイラーのベクトル化レポートを活用する
http://www.isus.jp/products/c-compilers/compiler_part3/

IA Software User Society / コンパイラー最適化入門: 第4回 自動ベクトル化はどんな時に行われるか
http://www.isus.jp/products/c-compilers/compiler_part4/

IA Software User Society / コンパイラー最適化入門: 第5回 明示的にベクトル化されたコードを記述する
http://www.isus.jp/products/c-compilers/compiler_part5/

IA Software User Society / コンパイラー最適化入門: 第6回 ベクトル化の裏技集
http://www.isus.jp/products/c-compilers/compiler_part6/

Oracle In-Memory Column Store Internals – Part 1 – Which SIMD extensions are getting used?
http://blog.tanelpoder.com/2014/10/05/oracle-in-memory-column-store-internals-part-1-which-simd-extensions-are-getting-used/

SIMD-Scan: Ultra Fast in-Memory Table Scan using on- Chip Vector Processing Units
http://www.vldb.org/pvldb/2/vldb09-327.pdf

Rethinking SIMD Vectorization for In-Memory Databases
http://www.cs.columbia.edu/~orestis/sigmod15.pdf

Oracle … as usual / SIMD Extensions in and out Oracle 12.1.0.2
https://laurent-leturgez.com/2015/04/22/simd-extensions-in-and-out-oracle-12-1-0-2/

Laurent Leturgez, / Ukoug15 SIMD outside and inside Oracle 12c (12.1.0.2)
http://www.slideshare.net/lolo115/ukoug15-simd-outside-and-inside-oracle-12c-12102

Cellプログラミングチュートリアル Version 0.2c
http://cell.fixstars.com/ps3linux/tutorial/index.html

| | | コメント (0) | トラックバック (0)

2016年9月20日 (火)

Boot Camp de Windows 10 Pro

Previously on Mac De Oracle から結構間が空いてしまった m(_ _)m

VIrtualBoxで一つ、エクスリームなPCで一つWindows Pro環境があるのですが、訳あって、Boot CampでMacBookにもう一つのWindows 10 Pro環境を作った。

Boot Camp を使って Mac で Windows 10 を使う
https://support.apple.com/ja-jp/HT204990

Appleサポートに記載されている手順で楽チン構築でした。ニッコリ。

Windows10pro_withbootcamp_2


Boot CampでWindows 10 Proインストールまでの流れ.(OS X側)
詳細はAppleサポートに記載されている手順通りに進めればOK

20160908_222628
20160908_222646
20160908_222650

事前にダウンロードしておいた.ISOファイルを選択して..
20160908_222701
20160908_222706
20160908_222714
20160908_222900

OS XとWindowsのパーティション配分は以下がデフォルトになっているので、使用サイズに合わせて変える必要があります。今回は50%/50%に設定してみました。いろいろお試し環境なので作り変えちゃうかもしれないですが...
20160908_225407
20160908_225411

20160908_225422

この後は、Windowsのインストールになるので省略w

MacBookでのインストール意外に速くてびっくり:)


| | | コメント (0) | トラックバック (0)

2016年8月10日 (水)

Safari起動時に前回参照ページではなく、ホームページを表示したい etc.

Mac で前回と同じウインドウ、App、書類を自動的に開くという機能が働いてSafariを起動すると前回開いていたページを表示したりするようになった。

便利なんだけど、そのページがパスワード変更依頼ぺージだったりすると、次回起動時に再度そのページを送信したりすることがあるw

逆にめんどくさいなー、この機能と思ったら固定ページを表示するようできるんですよね。
(再開機能がなかった頃のようにする方法です)

Safari


再開機能で前回参照していたページを開かないようにしたうえで、起動直後の新規ページには固定ページを開き、新規タブでは空のページを表示するようにしてみました。:)

このあたりなんとなく、Appleらしくない面倒くさいUIだなぁ〜と思いますよ。直感的じゃないんでw
Photo

設定後はこんな感じになります。なんとなく落ち着く :)

起動直後はブログを固定表示
20160810_04236


新規タプを開くと空白ページ。こんな感じになります。
20160810_04230


| | | コメント (0) | トラックバック (0)

2016年7月31日 (日)

個人的に気なるVirtualBox Extension PackのLincenseダイアログの「同意します」ボタンの位置w

久々に役に立たねいネタですw

VirtualBox Extension Packライセンスダイアログの「同意しません」、「同意します」ボタンの位置が、OS Xだけ違ってて、WindowsやLinuxの位置だと思い込み、「同意しません」ボタンをクリックしてしまい、イラッとすることがたまに。

デフォルトボタンになっているので、スペースバー対応すれば逆でも問題はないわけですが、気になる気になるw

OS XのVirtualBox Extension Packライセンス同意ダイアログのボタンの配置
Virtualbox_licenseconfermation_maco


WindowsのVirtualBox Extension Packライセンス同意ダイアログのボタンの配置

Virtualbox_license_confermation_d_2


LinuxのVirtualBox Extension Packライセンス同意ダイアログのボタンの配置
Virtualboxlicensecofermation_linux


ね。

| | | コメント (0) | トラックバック (0)

2016年5月 6日 (金)

AirMacベースステーションの拡張ワイヤレスネットワーク設定

AirMacベースステーションの拡張ネットワーク設定のハマリどころ。(含む自分メモ)

昨年末にAirMac ExtremeとAirMac Time Capsuleの2台構成にしようとしてハマったところを書き残そうと思いつつすっかり忘れていた。 orz


マニュアルちゃんと読めよ! 

と妻にも言っている私が、

Apple Supportのエントリに書かれていた一文を読み飛ばすなんて、なんってこったいw
というのがオチだったわけですが。。。 (^^;;;;


以下をしっかりw 読めばハマることはないと思いまっす。  


AirMac ベースステーション:拡張ワイヤレスネットワーク (802.11n) の設定と構成

Wi-Fi base stations: Extending the range of your wireless network by adding additional Wi-Fi base stations

私が読み飛ばしてしまったのは、以下。
”これまでに Wi-Fi ベースステーションを構成したことがある場合は、構成に着手する前に、拡張ワイヤレスネットワークを構成する各 Wi-Fi ベースステーションを工場出荷時の状態にリセットしておいた方がよいでしょう。”

Time Capsule単体のネットワーク構成だったところへ、Extremeを追加して拡張しようとしたのですが、Time Capsule側を工場出荷状態にリセットしないで拡張しようとししため失敗していたのでした。。。。とほほ。

工場出荷状態にリセットしたら、あら不思議、 あっというまに拡張終了w (当たり前かw)

でAirMac ExtremeとTime Capsuleの2台構成で1つのSSIDで接続できる拡張ワイヤレスネットワーク構成のできあがり〜!
12191541_980958431970436_1074074517


| | | コメント (0) | トラックバック (0)

2016年3月27日 (日)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:) その3



前回までのMac De Oracle


OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:) その2



ということで、続きで〜〜すっ。

こんな表定義で

orcl@SCOTT> desc tab01
Name Null? Type
--------- -------- --------
FOO NUMBER
BAR NUMBER
HOGE NOT NULL CHAR(2)
ID NOT NULL NUMBER

こんな索引があって

****** Index column info : tab01 ******

INDEX_NAME COLUMN_NAME DESC
------------------------------ ------------------------------ ----
IX1_TAB01 FOO ASC

IX2_TAB01 BAR ASC
FOO ASC

IX3_TAB01 ID ASC
FOO ASC

IX4_TAB01 ID ASC
BAR ASC
FOO ASC

PK_TAB01 ID ASC


こんなデータで

orcl@SCOTT> set null [NULL]
orcl@SCOTT> select * from tab01

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1
1 [NULL] ** 2
[NULL] 1 ** 3
1 1 ** 4


2列の複合索引、どちらの列もNULLだと、やはり、NULLは索引に含まれないので IS NULL検索だと索引は利用されないですよねぇ〜。このような状態ではヒントで索引利用を強制利用させようとしても無理です。

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 index(tab01 ix2_tab01)
5 no_index(tab01 ix4_tab01)
6 no_index(tab01 ix3_tab01)
7 */
8 *
9 from
10 tab01
11 where
12 foo is null
13* and bar is null
     
FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 8 |
|* 1 | TABLE ACCESS FULL| TAB01 | 1 | 1 | 1 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NULL AND "BAR" IS NULL))

でも、NOT NUL制約の列が1列でも含まれている索引であればNULLは索引に含まれます。(前回までの復習も兼ねた確認)
第1列がWHERE句で記述されていないので索引スキップスキャンになっていますが.....IS NULL検索を索引アクセスだけで行っているのがよく分かる例の一つです:)

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 */
5 *
6 from
7 tab01
8 where
9 foo is null
10* and bar is null

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 | 1 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
|* 2 | INDEX SKIP SCAN | IX4_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 | 1 |
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("BAR" IS NULL AND "FOO" IS NULL)
filter(("FOO" IS NULL AND "BAR" IS NULL))


最後にもう少しわかりやすい例を。

SQL文を少々書き換えてIndex Only Scanになるようにしました。索引にNULLが含まれていないと索引だけのアクセスで済むわけがないわけで、これ以上わかりやすい例はないと思います:)

まず、索引が利用できない例から。
FOO列とBAR列だけの複合索引をヒントで強制利用させようとしていますが、この索引は2列ともnullableなので2列をIS NULL検索しても索引が利用されません!
全ての列がNULLである場合、キーエントリーは索引に作成されない。単一列でも複合索引でも同じであることが確認できます。

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 index(tab01 ix2_tab01)
5 no_index(tab01 ix4_tab01)
6 no_index(tab01 ix3_tab01)
7 */
8 *
9 from
10 tab01
11 where
12 foo is null
13* and bar is null

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 8 |
|* 1 | TABLE ACCESS FULL| TAB01 | 1 | 1 | 1 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NULL AND "BAR" IS NULL))

おっと、
大切なのを忘れてました。

FOO列(nullable(、BAR列(nullable)の複合索引を IS NOT NULL AND IS NULLで検索した場合はどうなるか?
答えは以下の通り。IS NULL と IS NOT NULLの組み合わせでも、索引が利用されます。:)

BAR IS NULLで範囲検索しFOO IS NOT NULLでフィルタリングしています。 NULLが含まれていないと不可能な索引レンジスキャンと索引読み時のフィルタリング!

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is not null
13* and bar is null

FOO BAR
---------- ----------
1 [NULL]

・・・略・・・
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.02 | 1 | 1 |
|* 1 | INDEX RANGE SCAN| IX2_TAB01 | 1 | 1 | 1 |00:00:00.02 | 1 | 1 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("BAR" IS NULL)
filter("FOO" IS NOT NULL)

その逆も!

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is null
13* and bar is not null

FOO BAR
---------- ----------
[NULL] 1

・・・略・・・
----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX SKIP SCAN | IX2_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("FOO" IS NULL)
filter(("FOO" IS NULL AND "BAR" IS NOT NULL))


もういっちょ!
2列とものnullableな索引だとindex fast full scanにはできないけど、index full scanにはできるんですよ〜。

orcl@SCOTT7gt; r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is not null
13* and bar is not null

FOO BAR
---------- ----------
1 1

・・・略・・・
----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX FULL SCAN | IX2_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NOT NULL AND "BAR" IS NOT NULL))

Note
-----
- statistics feedback used for this statement

後に、NOT NULL列が含まれる索引なら index fast full scanでもできるはず!
独り言;index only scan+index fast full scanなんてのもセグメントサイズが小さければ物理読み込み量削減には効果があるんですよねぇ〜 ;-)

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 index_ffs(tab01 ix4_tab01)
6 no_index(tab01 ix2_tab01)
7 */
8 foo
9 , bar
10 from
11 tab01
12 where
13 foo is not null
14* and bar is not null

FOO BAR
---------- ----------
1 1

・・・略・・・
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.03 | 4 | 1 |
|* 1 | INDEX FAST FULL SCAN| IX4_TAB01 | 1 | 1 | 1 |00:00:00.03 | 4 | 1 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NOT NULL AND "BAR" IS NOT NULL))


オラクルの索引にNULLは絶対含まれない、というのは都市伝説! 
IS NOT NULLは索引使えないとかIS NULLは索引使えないというというのも違うんですよね。 

OracleのB*Tree索引では、索引に含まれる全列がNULLの場合以外はNULLが含まれてまっす! というのが正しいですよね!?

この手の問題でピンチになったら、思い出してみてください ;)
USE THE INDEX ONLY SCAN, LUKE! w

| | | コメント (0) | トラックバック (0)

2016年3月20日 (日)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:)

先日、OracleのB*Tree索引には絶対にNULLが含まれないって思い込んでる人が意外にいるよね〜とか。
OracleのB*Tree索引には絶対NULLが含まれないって都市伝説があるのはなんでだろう。
Oracle® Database概要12cリリース1 (12.1) - 一意索引と非一意索引

みたいなことが話題になって、
ある一人が、「だよね〜、ダンプ見れば含まれてるのわかります。」って言ってて、それ、ふつ〜の人は見ないからw

と思いつつ、私の周りには、やはり、変態が多いことに改めて気づいた次第です。:) はい。


で、
変態じゃない、ごく一般的なエンジニアの方々(ブロックダンプを華麗かつ自然にキメちゃわない方々)向けに、

OracleのB*Tree索引にNULLが含まれているか、NULLが含まれていないかの簡単な確認方法をお伝えしなければ!w

ということで、数回にわけて書いておこうかと思ってます。(予定は未定w)

(実は、このネタとほぼ同じことを1年前ぐらい前に、ローカルかつクローズドな勉強会?、でも使ってました。 最近、やってないみたいですけど)

環境は最近の定番 Oracle Database 12c R1 EE

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE 12.1.0.2.0 Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production


SCOTTスキーマに以下のような表と索引を作成し少ないですが、データを登録
今回の主役は索引とNULLなのでデータ量少なくても必要なパターンが登録できていれば十分です。
(データ量を増やせばチューニングのお題の元ネタにもなると思います。)

orcl@SCOTT> create table tab01 (foo number, bar number, hoge char(2) not null,id number not null);

Table created.

orcl@SCOTT> insert into tab01 values(null,null,'**',1);

1 row created.

orcl@SCOTT> insert into tab01 values(1,null,'**',2);

1 row created.

orcl@SCOTT> insert into tab01 values(null,1,'**',3);

1 row created.

orcl@SCOTT> insert into tab01 values(1,1,'**',4);

1 row created.

orcl@SCOTT> commit;

Commit complete.

orcl@SCOTT> alter table tab01 add constraint pk_tab01 primary key(id) using index;

Table altered.

orcl@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'TAB01',cascade=>true,no_invalidate=>false);

PL/SQL procedure successfully completed.

orcl@SCOTT> create index ix1_tab01 on tab01 (foo);

Index created.

orcl@SCOTT> create index ix2_tab01 on tab01 (bar,foo);

Index created.

orcl@SCOTT> create index ix3_tab01 on tab01(id,foo);

Index created.

orcl@SCOTT> create index ix4_tab01 on tab01(id,bar,foo);

Index created.


FOOとBAR列はNullableにしています

orcl@SCOTT> desc tab01
Name Null? Type
----------------------------------------- -------- ----------------------------
FOO NUMBER
BAR NUMBER
HOGE NOT NULL CHAR(2)
ID NOT NULL NUMBER

主演の索引たち

NULLが索引でどう扱われるかを確認するため、事前作成の索引をたくさん用意してしました。後から作るの面倒なのでw
これだけ類似索引も含めて多数の索引があると確認時に意図した索引が利用されない可能性も高いため、検証では内容に合わせて利用する索引をヒントで指定することにします。

ちなみに、
普通の環境でこんなに索引があったら、アンチパターン:インデックスショットガンですからね。ご注意ください:) たまにこのような環境に遭遇することはありますが。。。

IX1_TAB01はnullableなFOO列だけの単一列索引
IX2_TAB01はnullableなBAR列とFOO列からなる複合索引
IX3_TAB01とTX4_TAB01は上記の列に加え主キー列のID列を第1キーとする複合索引

としてあります。

orcl@SCOTT> break on table_name on index_name skip 1
orcl@SCOTT> select table_name,index_name,column_name from user_ind_columns where table_name='TAB01' order by table_name,index_name,column_position;

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
TAB01 IX1_TAB01 FOO

IX2_TAB01 BAR
FOO

IX3_TAB01 ID
FOO

IX4_TAB01 ID
BAR
FOO

PK_TAB01 ID

登録したデータは以下の通り
NULLは索引とともに今回の主役なので、どこがNULLになっているかメモしておいてくださいませ。

orcl@SCOTT> set null [NULL]
orcl@SCOTT> select * from tab01 order by id;

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1
1 [NULL] ** 2
[NULL] 1 ** 3
1 1 ** 4

さて、索引にNULLが含まれているか、いないか、どうやって確認すると思います? ブロックダンプを華麗にキメきめる以外の方法でw

SQLチューニングをしたことがある方なら一般的に利用してい(と思ってる)機能で簡単に確認できちゃうんですよ。これが。

SQL*Plusのautotraceや、explain plan それに、DBMS_XPLAN.DISPLAY* なファンクションでも確認できます。
SQLclはautotraceにはまだ未対応となっているようですが、それ以外の方法ならはできるようです。
(今回はSQL*Plusを使い、dbms_xplan.display_cursor(format=>'ALLSTATS LAST'))で確認します)


どうやって、どの部分で確認するのか? 
autottraceやDBMS_XPLAN.DISPLAY*などでリストされるpredicate information部分のaccess/filter predicate部分で確認できるんです!


例1)FOO = 1 で検索した例

注):索引が多いので意図した索引を利用するようにヒントで固定しています

おわかりでしょうか?
Predicate Infomation部分から、Id=2のINDEX RANGE SCANで IX1_TAB01索引を FOO=1でアクセスしていることが読み取れますよね?
つまり、1 という値が索引に含まれている(含まれる)からaccess pathとしてIX1_TAB01索引を参照しているわけです。

orcl@SCOTT> r
1 SELECT
2 /*+
3 gather_plan_statistics
4 index(tab01 ix1_tab01)
5 */
6 *
7 FROM
8 tab01
9 WHERE
10* foo = 1

FOO BAR HO ID
---------- ---------- -- ----------
1 [NULL] ** 2
1 1 ** 4

orcl@SCOTT> select * from table(dbms_xplan.display_cursor(format=>'ALLSTATS LAST'));

ーーー中略ーーー

Plan hash value: 3795960549

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 4 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 2 | 2 |00:00:00.01 | 4 |
|* 2 | INDEX RANGE SCAN | IX1_TAB01 | 1 | 2 | 2 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("FOO"=1)


ということで、今日は馬事公苑の散歩は気持ちいいよ〜。

次回へつづく。

| | | コメント (0) | トラックバック (0)

2016年3月13日 (日)

FAQ になりそうな、12c MTA環境での統計履歴管理 ー CDBとPDBの間で迷子になりそう PART2

PART1ってあったっけ? というのは置いといて、
PART III以降もなにかありそうなアトモスフィアなのですが、ここで書いておかないとハマりそうなので備忘録代わりに。


みなさん、統計履歴って知ってます?
dbms_stats.gather_*_stats を実行するとオプティマイザ統計情報が取得された表、索引、列統計が自動的にバックアップされることを。
そのバックアップは統計履歴と呼ばれています。(履歴統計と呼んでる人のほうが多いと思うのですが。。。マニュアルだと統計履歴って書いてますね。。。。誰も履歴統計を統計履歴だろ、それ! と突っ込んでくれなてなかった気がw)

Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) オプティマイザ統計の保存の管理
Oracle® Database PL/SQLパッケージおよびタイプ・リファレンス 12c リリース1(12.1) DBMS_STATS.GET_STATS_HISTORY_RETENTIONファンクション
Oracle® Database PL/SQLパッケージおよびタイプ・リファレンス 12c リリース1(12.1) DBMS_STATS.ALTER_STATS_HISTORY_RETENTIONプロシージャ
Oracle® Database PL/SQLパッケージおよびタイプ・リファレンス 12c リリース1(12.1) PURGE_STATSプロシージャ
Oracle® Database SQL言語リファレンス 12cリリース1 (12.1) SYS_CONTEXT


で、オプティマイザ統計収集が原因と思われる事象があった場合、統計履歴をリストアしてトラブルを華麗に回避なんて手法もあるんです。
古くは小田さんの記事でも取り上げられています。
門外不出のOracle現場ワザ 第4章 Oracleデータベースの頭脳 「オプティマイザ」徹底研究


と、ここまでは、11gとか12cでも非MTA環境のお話です。

先日、12cのMTA環境で統計履歴をリストアしようとしたら。。。統計履歴。。。なんとなく言いづらい。。。履歴統計が無いw
履歴統計の保存期間はデフォルトで31日なのでパージされる前に保存期間を無期限に変更しました。。。(その時はMTA環境の罠にハマったとは気付かず、無期限にしたからもう気にしなくていいや! と思ったんです)

ところが、いざ履歴統計をみると。。。。パージされてる。。。。。焦りましたw 

どういうことだったのか、最初に書いてしまうと、

履歴統計は、CDB/PDB、それぞれ独立して管理されてまっす!。

(え〜〜〜〜〜〜〜〜っ。知らんかったというか、マニュアルのどこかに記載されているのなら、どなたかそのページへのリンクをおしえてくだしぁ。。。)


久々に長〜い前置きはこれぐらいにして確認してみましょう。(--;

PDBが1つあるシングルテナント構成(1 PDB/CDB)で確認してみました。

CDB及び、PDBの履歴統計保存期間を確認します。(デフォルト設定のままです)
なにもしてないデフォルト状態では、CDBもPDBも履歴統計保存期間は31日となっています。


CDB側の履歴統計保存期間

SYSTEM@orcl12c> @show_stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c CDB$ROOT

stats history retention
-----------------------
31

PDB側の履歴統計保存期間

SYSTEM@pdborcl12c> @show_stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c PDBORCL12C

stats history retention
-----------------------
31


では最初にどちらも履歴統計を保存しない状態にしてみます。

CDB
履歴統計を保存なしに設定して既存履歴統計をパージ。
事前作成しておいた stats_hist_retention.sqlを実行。(スクリプトはエントリの最後に記載しておきました。)

DBMS_STATS.GATHER_DATABASE_STATSを実行しても履歴統計数の増加なし。想定通り。

SYSTEM@orcl12c> exec dbms_stats.alter_stats_history_retention(0);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@orcl12c> exec dbms_stats.purge_stats(null);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@orcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c CDB$ROOT


stats history retention
-----------------------
0

PL/SQLプロシージャが正常に完了しました。


number of tab stats histories
-----------------------------
0


PDB

SYSTEM@pdborcl12c> exec dbms_stats.alter_stats_history_retention(0);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@pdborcl12c> exec dbms_stats.purge_stats(null);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@pdborcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c PDBORCL12C

stats history retention
-----------------------
0

PL/SQLプロシージャが正常に完了しました。

number of tab stats histories
-----------------------------
0

さて、次にCDBの履歴統計保存期間を無期限に変更し、PDBは履歴統計保存なしにしてみます。

CDBの履歴統計数は増加し、PDB側は変化なしということになるはず。。。


CDB

SYSTEM@orcl12c> exec dbms_stats.alter_stats_history_retention(-1);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@orcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c CDB$ROOT

stats history retention
-----------------------
-1

PL/SQLプロシージャが正常に完了しました。

number of tab stats histories
-----------------------------
2499

CDB側の履歴統計数が増加した。まあ、そうだよね〜。

PDB
PDB側は履歴統計無しのままなので変化はないです。個別に設定できてるからそうなんでしょうね。多分。

SYSTEM@pdborcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c PDBORCL12C


stats history retention
-----------------------
0

PL/SQLプロシージャが正常に完了しました。

number of tab stats histories
-----------------------------
0


逆にしてみます。CDBは履歴統計無し、PDBの履歴統計保存期間を無期限にしてみます。
履歴統計はCDB/PDB個別管理ならCDBの履歴統計なし、PDB側には履歴統計ありという構成もできるはず!!

なお、確認しやすくするために、CDB側は履歴統計無しに変更後、履歴統計を手動パージしておきます。

CDB

SYSTEM@orcl12c> exec dbms_stats.alter_stats_history_retention(0);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@orcl12c> exec dbms_stats.purge_stats(null);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@orcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c CDB$ROOT

stats history retention
-----------------------
0

PL/SQLプロシージャが正常に完了しました。

number of tab stats histories
-----------------------------
0




PDB
これが確認できれば問題なさそうですね。CDB/PDBの履歴統計保存期間の管理及びパージは個別管理できるってことで。。。。
Workload RepositoryはCDBでのみ管理できるようになっているので間違いやすいぞ、と
この辺りをまとめたマニュアルがあれば解りやすいのになぁ(ボソっ

SYSTEM@pdborcl12c> exec dbms_stats.alter_stats_history_retention(-1);

PL/SQLプロシージャが正常に完了しました。

SYSTEM@pdborcl12c> @stats_hist_retention

cdb name container name
-------------------- --------------------
orcl12c PDBORCL12C

stats history retention
-----------------------
-1

PL/SQLプロシージャが正常に完了しました。

number of tab stats histories
-----------------------------
2659


履歴統計は、CDB/PDB個別管理なので、履歴統計保存期間、履歴統計のパージはCDB/PDBそれぞれで行えるようですが、
Workload Repository:AWRのスナップショットの保存期間やパージなどは、CDBのみでしか行えない。(AWRのスナップショットはCDB/PDBどこで実行しても全体が取得されます。)


それに、PDBで変更できる初期化パラメータと変更できないパラメータのまとめがあればもっと便利かも。
以前調べたPDB側で変更できる初期化パラメータの保存場所とかも合わせてまとめとくか。。。

PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #1
PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #2 - PDBの初期化パラメータは何処!?
PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #3 - 消えたPDBの初期化パラメータの謎... Truth is out there.

いろいろ整理できてないので只今混乱中w



今回試した環境
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE 12.1.0.2.0 Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production


使ったクエリーなど
stats_hist_retention.sql

SELECT 
SYS_CONTEXT('USERENV', 'CDB_NAME') AS "cdb name"
, SYS_CONTEXT('USERENV', 'CON_NAME') AS "container name"
FROM
dual;

SELECT
DBMS_STATS.GET_STATS_HISTORY_RETENTION AS "stats history retention"
FROM
dual;

EXEC DBMS_STATS.GATHER_DATABASE_STATS;

SELECT
COUNT(1) AS "number of tab stats histories"
FROM
dba_tab_stats_history;


show_stats_hist_retention.sql

SELECT 
SYS_CONTEXT('USERENV', 'CDB_NAME') AS "cdb name"
, SYS_CONTEXT('USERENV', 'CON_NAME') AS "container name"
FROM
dual;

SELECT
DBMS_STATS.GET_STATS_HISTORY_RETENTION AS "stats history retention"
FROM
dual;

| | | コメント (0) | トラックバック (0)

2015年12月 4日 (金)

OTHER_XMLの中身

JPOUG Advent Calendar 2015 - 4日目のエントリーです。

役に立たないから、絶対最後まで読まないでね!!! (^^)

さて、
みなさん、マニュアル読んでますか? 
最近は量が多いので、必要になってから、なりそうだから読むことが多いです。
Oracle® Databaseリファレンス 12cリリース1 (12.1) なんて特にそうです。
ただ、一度読み始めると、いろいろと気づくところもあるわけです...こんな情報が取れるのか! とか。


あ〜、これは、禁断のw マニュアルに記載されてないパラメータを指定すると見れるやつだ!!! とか気づくこともあったり。

例えば、V$SQL_PLANDBA_HIST_SQL_PLANのOTHER_XML列。

この列の説明には実に興味深いことが書かれています。以下の説明、読んでてワクワクしますw 
何が格納されているのでしょう???(もう、みんな知ってるくせに〜)

”アウトライン・データ(同じ計画の再作成に使用できる一連のオプティマイザ・ヒント)”

見たくないですか? 中身。

見たいですよね。私もそうです!


ただ、OTHER_XML列、列名の通り、CLOB型で中身はXMLです!!!
SQL*Plusで普通に表示しようとすると長すぎて読みにくいってのが難点w


ならばXMLにも対応しているSQL文で何とかしてみたいと思います。

ゴニョゴニョ、パタパタ。

できました。

dba_hist_sql_plan向けですが。(v$sql_planビューでも同様のことができます!)
(注:dba_hist_*ビューを参照するにはOracle Diagnostics Packが必要でっす。ですが、v$sql_planを参照するのならオプションはいらないですよね.)

こんな感じです

SYSTEM> !cat report_outline_hints.sql
col outline for a200
set linesize 300
set pagesize 10000
set veri off

SELECT '/*+' AS outline FROM DUAL
UNION ALL
SELECT ' '||'BEGIN_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT
' '||o.outlinehint
FROM
(
SELECT
EXTRACTVALUE(
VALUE(x)
, '/hint/text()'
) AS outlinehint
FROM
XMLTABLE(
'/*/outline_data/hint'
PASSING(
SELECT
XMLTYPE(other_xml)
FROM
dba_hist_sql_plan
WHERE
other_xml IS NOT NULL
AND sql_id = '&1'
AND plan_hash_value = &2
)
) x
) o
UNION ALL
SELECT ' '||'END_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT '*/' FROM DUAL
/
set veri on

実行すると以下のような、:) 情報を取り出すことができます!!

SYSTEM> @report_outline_hints gdtyuqcyk8x1c 2236229349

OUTLINE
------------------------------------------------------------------------------------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

表示された内容に、身に覚え、
いや、見覚えのある方も多いことと思います :) あれでね。そう、あれです。
マニュアルに記載されいないパラメータを利用しなくても取り出せるんです。

ついでなので、マニュアルに記載のないパラメータを使ったDBMS_XPLAN.DISPLAY_AWRで同じ情報をリストしてみました。DBMS_XPLAN.DISPLAY_AWR以外のDISPLAY*関数のformatパラメータでもできます:)
(注: DISPLAY_AWRでAWRを参照するのでOracle Diagnostics Packが必要でっす。 ですが、DISPLAY_CURSOR(),DISPLAY()とDISPLAY_PLAN()ならAWRは参照しないのでオプションはいらないですよね。)

PROMPT
PROMPT ****** display_awr with outline option *********
SELECT
plan_table_output as outline
FROM
TABLE(DBMS_XPLAN.DISPLAY_AWR(sql_id=>'&1',plan_hash_value=>&2,format=>'OUTLINE'))
/


****** DBMS_XPLAN.DISPLAY_AWR with OUTLINE option *******

OUTLINE
------------------------------------------------------------------------------------------
SQL_ID gdtyuqcyk8x1c
--------------------
SELECT B.EXECUTION_ID, NVL(A.N_COUNT,0), A.COMP_TIME FROM ( SELECT
A.EXECUTION_ID, COUNT(*) N_COUNT, NVL(MAX(B.COMPLETION_TIME), SYSDATE)
COMP_TIME FROM SYS.ILM_EXECUTION$ A, SYS.ILM_RESULTS$ B WHERE
EXECUTION_STATE = :B7 AND A.EXECUTION_ID = B.EXECUTION_ID AND
B.JOB_STATUS NOT IN (:B6 , :B5 , :B4 , :B3 , :B2 , :B1 ) GROUP BY
A.EXECUTION_ID )A, ILM_EXECUTION$ B WHERE B.EXECUTION_ID =
A.EXECUTION_ID (+) AND EXECUTION_STATE = :B7 AND (ROWNUM <= :B9 OR :B9
= :B8 )

Plan hash value: 2236229349

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | COUNT | | | | | |
| 2 | FILTER | | | | | |
| 3 | MERGE JOIN OUTER | | 1 | 65 | 4 (50)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | ILM_EXECUTION$ | 1 | 26 | 0 (0)| |
| 5 | INDEX FULL SCAN | PK_TASKID | 1 | | 0 (0)| |
| 6 | SORT JOIN | | 1 | 39 | 4 (50)| 00:00:01 |
| 7 | VIEW | | 1 | 39 | 3 (34)| 00:00:01 |
| 8 | HASH GROUP BY | | 1 | 65 | 3 (34)| 00:00:01 |
| 9 | MERGE JOIN | | 1 | 65 | 3 (34)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID| ILM_EXECUTION$ | 1 | 26 | 0 (0)| |
| 11 | INDEX FULL SCAN | PK_TASKID | 1 | | 0 (0)| |
| 12 | SORT JOIN | | 1 | 39 | 3 (34)| 00:00:01 |
| 13 | TABLE ACCESS FULL | ILM_RESULTS$ | 1 | 39 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Outline Data
-------------

/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

ちなみに、11g R2でも動作します!


どうでしたか?役に立たなかったですよね?
以上、OUTLINE HINTSより愛を込めてお送りしました!



参考: (日本語で書かれたエントリーは見当たらない。初か、もしかして!w)

Oracle SQL Plan Stability
http://blog.tanelpoder.com/oracle/performance/sql/oracle-sql-plan-stability/


Plan stability in 10g - using existing cursors to create Stored Outlines and SQL profiles
http://oracle-randolf.blogspot.jp/2009/03/plan-stability-in-10g-using-existing.html


FORCE_MATCH for Stored Outlines and/or SQL Baselines????? – follow up
https://tonyhasler.wordpress.com/2011/12/

How to hint – 1
https://jonathanlewis.wordpress.com/2011/06/08/how-to-hint-1/

dbms_xplan(3)
https://jonathanlewis.wordpress.com/2008/03/06/dbms_xplan3/



追記:

@yoshikawさんに指摘され、EXTRACTVALUE()が11.2で非推奨になっていたとことに気づく orz.

@yoshikawさん、指摘ありがとうございます。 

XMLTABLE().... COLUMNSを使えばEXTRACTVALUE()はいらなかった!!!! ということでEXTRACTVALUEなし版も作りました!!

ただ、CON_IDも見ないとマルチテナントに対応できず、エラーになると気づいたが、
11gにも対応しようとすると、もう一工夫必要なことに気づき、再び、orz.

つづきは、...いずれ...

変更したSQL文は以下のとおり。

SYSTEM> !cat report_outline_hints.sql
col outline for a200
set linesize 300
set pagesize 10000
set veri off

SELECT '/*+' AS outline FROM dual
UNION ALL
SELECT ' '||'BEGIN_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT
' '||x.outline_hint
FROM
XMLTABLE(
'/*/outline_data/hint/text()'
PASSING(
SELECT
XMLTYPE(other_xml)
FROM
dba_hist_sql_plan
WHERE
other_xml IS NOT NULL
AND sql_id = '&1'
AND plan_hash_value = &2
)
COLUMNS outline_hint PATH '/text()'
) x
UNION ALL
SELECT ' '||'END_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT '*/' FROM DUAL
/

SYSTEM> @report_outline_hints gdtyuqcyk8x1c 2236229349

OUTLINE
--------------------------------------------------------------------------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

JPOUG Advent Calendar 2015、12/5は、Yohei Azekatsu さんです。どんな変態ネタが飛び出すのか、乞うご期待!!

| | | コメント (0) | トラックバック (0)

2015年5月18日 (月)

Xcode Serverの暴走 @ Yosemite

Yosemiteの影響なのか不明だけどMavericksでもハマった方はいるようなのと日本語のネタが少なそうなのでメモ代わりに書いておきますね。

やったこと
OS X ServerでXcode Serverを設定 →試しに、Xcode Serverの設定をした。
動作確認岳済んだので、一旦無効化したのですが......

その時の記憶は曖昧なのだけど、無効化したのは確かなので画面のコピペを。
20150518_22611


その後、OS Xをシャットダウンしようとすると、fast user switchingで他のユーザにログインしたままの状態だと表示されるワーニングダイアログが表示されるようになった。(他のユーザでログインしてないのに!!!!!!

20150303_190815

シャットダウンしようとするたびに表示されるので面倒だし、気になるし。
ということで調べてらバグっぽいw

アクティビティモニタで見ると _xcsbuildd ってユーザのプロセスがすげーことになっているw
20150518_23433


これ、無効化したはずの Xcode Serverなんですよ!!!

さらに調べていくと、developer forum でも幾つかthreadがあり、とりあえずの解決策が! (シンプルな解決作だけど、これが大当たりでした :)

_mdworker が大暴れしてたことを思いだしてブルーな気分だったんだけど、なんとか落ち着いたw

https://devforums.apple.com/message/1047083#1047083
https://devforums.apple.com/message/1053273#1053273


以下、解決までの記録です。

discus-mother:˜ discus$ stat -f '%u %Su' /dev/console
263 _xcsbuildd


discus-mother:˜ discus$ sudo xcrun xcscontrol --reset
Password:
2015-03-09 20:14:42.716 xcscontrol[2954:79163] I am xcscontrol (version 2.0 from /Applications/Xcode.app)
2015-03-09 20:14:42.717 xcscontrol[2954:79163] Arguments: (
"/Applications/Xcode.app/Contents/Developer/usr/bin/xcscontrol",
"--reset"
)
2015-03-09 20:14:43.490 xcscontrol[2954:79163] Xcode version: 6.1.1 (6A2008a) (OS X SDK 13F26, iOS SDK 12B411)
2015-03-09 20:14:43.492 xcscontrol[2954:79163] Server version: 4.0.3 (14S350)
2015-03-09 20:14:43.492 xcscontrol[2954:79163] OS X version: 10.10.2 (14C109)
2015-03-09 20:14:43.495 xcscontrol[2954:79163] Launching task: /bin/launchctl unload -w /System/Library/LaunchDaemons/com.apple.xcsd.plist
2015-03-09 20:14:44.490 xcscontrol[2954:79163] Launching task: /bin/launchctl unload -w /System/Library/LaunchDaemons/com.apple.xcscouch.plist
2015-03-09 20:14:44.566 xcscontrol[2954:79163] Launching task: /bin/launchctl unload -w /System/Library/LaunchDaemons/com.apple.xcsredis.plist
2015-03-09 20:14:44.729 xcscontrol[2954:79163] Launching task: /bin/launchctl unload -w /System/Library/LaunchDaemons/com.apple.xcscontrol.plist
2015-03-09 20:14:47.985 xcscontrol[2954:79163] Launching task: /usr/bin/killall -u 263 -9
2015-03-09 20:14:48.661 xcscontrol[2954:79163] Launching task: /usr/bin/xcode-select --reset
2015-03-09 20:14:49.745 xcscontrol[2954:79163] Reset completed successfully

これで再起動すれば解決 :)

| | | コメント (0) | トラックバック (0)

2014年6月13日 (金)

SQL Developer 4の素敵なコマンドライン de SQL整形 :)

Version 4.0.1ではWindowsでも同じ問題がでていたようですね。以下OTNフォーラムにも投稿されていたようです。
OS X版 Version 4.0.1でも同じだったので不具合だったようです。
https://community.oracle.com/thread/3562219


Version 4.0.2がリリースされたので改善されたか確かめてみました。 OS X版のSQLDeveloper 4.0.2 で!

OTN-JPではまだ、4.0.1のようですが、USでは 4.0.2のようです。OTN-JPでもそのうち4.0.2になるのでしょうね :)
以下はUSへリンクです。
http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html

20140613_45308


OS X版のsdcliを実行するには実行権を付与する必要があります。sdcliは以下のパスにあります。かなり深いところにあります!
(OS X版は以下深いところにあるので、SQLDeveloperのアイコン上で右クリック>コンテクストメニュー>パッケージの内容を表示から辿ってパスを探してもいいですし、以下のパス直接コピペでも行けます。)

20140613_45551

20140613_45613


discus-mother:˜ oracle$ cd /Applications/SQLDeveloper.app/Contents/Resources/sqldeveloper/sqldeveloper/bin
discus-mother:bin oracle$ chmod +x sdcli
discus-mother:bin oracle$ ll
total 672
-rw-r--r--@ 1 oracle staff 49279 5 3 02:48 SQLDeveloperIcons.icns
-rw-r--r--@ 1 oracle staff 0 5 3 02:48 jdk.conf
-rw-r--r--@ 1 oracle staff 440 5 3 02:48 logging-debug.conf
-rw-r--r--@ 1 oracle staff 363 5 3 02:48 logging.conf
-rwxrwxr-x@ 1 oracle staff 586 5 3 02:50 sdcli
-rw-r--r--@ 1 oracle staff 99 5 3 02:50 sdcli-Darwin.conf
-rw-r--r--@ 1 oracle staff 446 5 3 02:50 sdcli.boot
-rw-r--r--@ 1 oracle staff 854 5 3 02:50 sdcli.conf
-rw-r--r--@ 1 oracle staff 71205 5 3 02:48 splash.gif
-rw-r--r--@ 1 oracle staff 71205 5 3 02:48 splash.png
-r-xr-xr-x@ 1 oracle staff 3220 5 3 02:48 sqldeveloper
-rw-r--r--@ 1 oracle staff 415 5 3 02:48 sqldeveloper-Darwin.conf
-rw-r--r--@ 1 oracle staff 661 5 3 02:48 sqldeveloper-debug.conf
-rw-r--r--@ 1 oracle staff 204 5 3 02:48 sqldeveloper-nondebug.conf
-rw-r--r--@ 1 oracle staff 445 5 3 02:48 sqldeveloper.bat
-rw-r--r--@ 1 oracle staff 340 5 3 02:48 sqldeveloper.boot
-rw-r--r--@ 1 oracle staff 1175 5 3 02:48 sqldeveloper.conf
-rw-r--r--@ 1 oracle staff 83456 5 3 02:48 sqldeveloper64W.exe
-rw-r--r--@ 1 oracle staff 131 5 3 02:54 version.properties
discus-mother:bin oracle$


パスは通っていないので設定する必要があります。

discus-mother:˜ oracle$ cat .bashrc
alias ll='ls -lv'

#for SQLDeveloper sdcli
export PATH=$PATH:/Applications/SQLDeveloper.app/Contents/Resources/sqldeveloper/sqldeveloper/bin

discus-mother:˜ oracle$


Version 4.0.1では以下の状況でExceptionが発生していましたが、Version 4.0.2では解決してます!!! 

discus-mother:˜ oracle$ sdcli

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

使用可能な機能:
cart: データベース・カート・バッチ・タスク
dba: 基本バッチDBAタスク
format: SQL Format Task
migration: Database Migration Tasks
reports: 基本バッチ・レポート・タスク
unittest: ユニット・テスト・バッチ・タスク
discus-mother:˜ oracle$

ということで、
前から試したかった SQL文の整形機能。 GUIだとSQLワークシートでSQL文整形機能が提供されているためデータベースへ接続必要だったんですよね。
機能としては素敵だったのですが、データベース接続が行えない環境では使えない機能だったんですよ。
そんな環境で読むに耐えない巨大で未整形なSQL文を渡され涙目だった日々を思い出しますw

コマンドラインならデータベース接続不要なのがいいっす!

discus-mother:˜ oracle$ sdcli format

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

format input=<入力ファイルまたはディレクトリ> output=<出力ファイルまたはディレクトリ>
成功しました。
discus-mother:˜ oracle$


以下のようなSQL文を整形してみます。

discus-mother:˜ oracle$ cat sample_unformated.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


意外と地味な感じの処理終了メッセージですが、整形できたようです。整形結果を確認!

discus-mother:˜ oracle$ sdcli format input=sample_unformated.sql output=sample_formated.sql

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

成功しました。
discus-mother:˜ oracle$


いいですね〜 :) 最後の改行がないのですは、ご愛嬌ということで.

discus-mother:˜ oracle$ cat sample_formated.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;discus-mother:˜ oracle$
discus-mother:˜ oracle$

では、超便利な一括整形のテスト。 以下のような未整形なSQLファイルを含むディレクトリと整形後SQLファイルを格納するディレクトリを用意して....

discus-mother:˜ oracle$ mkdir unformated_sqls
discus-mother:˜ oracle$ mkdir formated_sqls
discus-mother:˜ oracle$ dir=unformated_sqls; for fname in `ls $dir`; do echo -e \\n\\nfile : $fname; cat $dir/$fname; done;


file : sample1.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


file : sample2.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


一括整形の場合はディレクトリ名のみ指定すればOK!
おおおおおおおおお〜〜〜〜、できた〜〜〜〜SQL文の一括整形!!!  素敵!!

discus-mother:˜ oracle$ sdcli format input=unformated_sqls output=formated_sqls

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

成功しました。


かくに〜〜〜〜〜ん!!

discus-mother:˜ oracle$ dir=formated_sqls; for fname in `ls $dir`; do echo -e \\n\\nfile : $fname; cat $dir/$fname; done;


file : sample1.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;

file : sample2.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;


Good job!!!!! SQL Developer Team!


| | | コメント (0) | トラックバック (0)

2014年4月29日 (火)

Oracle Database 12cで実装されたidentity columnのメモ

Oracle® Database SQL言語リファレンス 12cリリース1 (12.1) CREATE TABLE identity_clause


制限事項に、「CREATE TABLE AS SELECTを使用すると、列に対するIDのプロパティが継承されなくなります。」と記載されているのでかるく確認 :)

SCOTT@nonpdb12c> CREATE TABLE table_foobar_identity2
2 (
3 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY
4 ,data VARCHAR2(10)
5 );

表が作成されました。

SCOTT@nonpdb12c> create table table_foobar_identity2_tmp as select * from table_foobar_identity2;

表が作成されました。

SCOTT@nonpdb12c> break on table_name skip 1
SCOTT@nonpdb12c> select table_name,column_name,identity_column from user_tab_columns where table_name like 'TABLE_FOOBAR%' order by table_name,column_id;

TABLE_NAME COLUMN_NAME IDE
------------------------------ ------------------------------ ---
TABLE_FOOBAR_IDENTITY2 ID YES
DATA NO

TABLE_FOOBAR_IDENTITY2_TMP ID NO
DATA NO

CREATE TABLE AS SELECTで復籍してもidentity columnは継承されてない。ふむふむ。


同マニュアルの注意には、「ID列を作成するときには、パフォーマンスを向上するために、デフォルトの20よりも大きな値を使用してCACHE句を指定してください。」
とある.
シーケンスを利用しているので注意するところは一緒ということね。

SCOTT@nonpdb12c> select * from user_sequences;

SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER PARTITION_COUNT S K
------------------------------ ---------- ---------- ------------ - - ---------- ----------- --------------- - -
ISEQ$$_93570 1 1.0000E+28 1 N N 20 1 N N

参考
ORACLE-BASE:Identity Columns in Oracle Database 12c Release 1 (12.1)
Inside Oracle – Julian Dyke:Oracle 12c New Feature – Identity Columns

| | | コメント (0) | トラックバック (0)

2014年4月20日 (日)

シーケンス.NEXTVALが使えないからぐるぐる〜〜〜っとしていいですか? (30歳 エンジニア 男性)

CREATE TABLE 〜 AS SELECT文でシーケンス.NEXTVALって使えないからぐるぐる〜〜〜っとしたループ処理しないといけないですよね〜。

と質問されたのですが、1文で書けますからね!
(SQL文でワンライナーって言いそうになったけど、飲み込んだw)


準備

SCOTT> l
1 CREATE TABLE table_foobar
2 (
3 id NUMBER PRIMARY KEY
4 ,data VARCHAR2(10)
5* )
SCOTT> /

表が作成されました。

SCOTT> l
1 INSERT INTO table_foobar
2 SELECT
3 LEVEL
4 ,'D'||TO_CHAR(LEVEL,'FM099999999')
5 FROM
6 dual
7 CONNECT BY
8* LEVEL <= 100.
SCOTT> /

100行が作成されました。

SCOTT> commit;

コミットが完了しました。

SCOTT> SELECT * FROM table_foobar ORDER BY id;

ID DATA
---------- ----------
1 D000000001
2 D000000002
3 D000000003
4 D000000004
5 D000000005
6 D000000006
7 D000000007
8 D000000008
9 D000000009
10 D000000010

   ...中略...

90 D000000090
91 D000000091
92 D000000092
93 D000000093
94 D000000094
95 D000000095
96 D000000096
97 D000000097
98 D000000098
99 D000000099
100 D000000100

100行が選択されました。


前述のデータを複製しtable_foobar_tmp表を作成するとします。
なお、DATA列はそのままで、ID列は、次のシーケンスを利用してID = 1..100の順にシーケンスから採番しなおしたい。
(以下のシーケンスの定義からすると、 ID=1 は、1000、 ID=2は、1001にしたい。)

SCOTT> CREATE SEQUENCE seq_foobar start with 1000 maxvalue 999999999;

順序が作成されました。

1文で書けますよね!

SCOTT> l
1 CREATE TABLE table_foobar_tmp
2 AS
3 SELECT
4 seq_foobar.NEXTVAL AS id
5 ,t01.data
6 FROM
7 (
8 SELECT
9 id
10 ,data
11 FROM
12 table_foobar
13 ORDER BY
14 id
15* ) t01
SCOTT> /

表が作成されました。

SCOTT> SELECT * FROM table_foobar_tmp ORDER BY ID;

ID DATA
---------- ----------
1000 D000000001
1001 D000000002
1002 D000000003
1003 D000000004
1004 D000000005
1005 D000000006
1006 D000000007
1007 D000000008
1008 D000000009
1009 D000000010
1010 D000000011

   ...中略...

1090 D000000091
1091 D000000092
1092 D000000093
1093 D000000094
1094 D000000095
1095 D000000096
1096 D000000097
1097 D000000098
1098 D000000099
1099 D000000100

100行が選択されました。

はい、できました!


マニュアルには、「NEXTVALへの参照が含まれる単一のSQL文の中では、Oracleは、次の各行につき1回順序を増加させます。」
と記載されているので、質問してきた方は、マニュアルを読んでいないか、マニュアル読んでなくても実際に試していない食わず嫌い状態だったか、
以下のようなシーケンスの制限に遭遇して、できないんだ!と思い込んでしまった。

病は気からという状態だったのでしょうね。:)


SCOTT> l
1 CREATE TABLE table_foobar_tmp_NG
2 AS
3 SELECT
4 seq_foobar.NEXTVAL AS ID
5 ,t01.data
6 FROM
7 table_foobar t01
8 ORDER BY
9* t01.id
SCOTT> /
seq_foobar.NEXTVAL AS ID
*
行4でエラーが発生しました。:
ORA-02287: ここでは順序番号は使用できません。

エラーメッセージを見ても、Action がRemove the sequence numberだけですからね....

SCOTT> !oerr ORA 2287
02287, 00000, "sequence number not allowed here"
// *Cause: The specified sequence number (CURRVAL or NEXTVAL) is inappropriate
// here in the statement.
// *Action: Remove the sequence number.

そんな時はマニュアルを..
Oracle® Database SQL言語リファレンス 11gリリース2 (11.2) 順序疑似列

順序値の使用方法には、CREATE TABLE ... AS SELECTで使用できると書いてるのに。なんでだろう?
と,、なるかもしれないですが、よ〜〜〜くマニュアルを読んでくださいよ〜っ。

順序値の制限事項にGROUP BY句やORDER BY句を持つSELECT文では使用できないとも書かれているところにちゅうも〜〜〜〜く!

ORDER BY句を含んでいるのでORA-02287回避のために、サブクエリにして別クエリブロック化、シーケンスを利用しているクエリブロックにはORDER BY句を含まないようにしているところがポイント :)

テストデータ作るときとか、知ってると便利ですよ〜と。

SCOTT> l
1 CREATE TABLE table_foobar_tmp
2 AS
3 SELECT
4 seq_foobar.NEXTVAL AS id
5 ,t01.data
6 FROM
7 (
8 SELECT
9 id
10 ,data
11 FROM
12 table_foobar
13 ORDER BY
14 id
15* ) t01
SCOTT> /

Enjoy SQL!

| | | コメント (0) | トラックバック (0)

2013年12月21日 (土)

Windows XPのサポート終了 @  うち

Windows XP、Office 2003 のサポート終了まで、12 月 30 日であと 100 日を迎えます。早めの移行をオススメします。 (The Official Microsoft Japan Blogより)

ということで、一足早く、Window XPのサポート終了 @ うち :)


20131221_211712

20131221_211941


20131221_211738

20131221_211752


これで終わり。 :)


20131221_214417


| | | コメント (0) | トラックバック (0)

2013年9月 6日 (金)

shutdown immeidateしない、ほかの理由に遭遇! は修正されていた! (12c)

数年前に、shutdown immeidateしない、ほかの理由に遭遇!
というエントリを書いていたことをご存知の方も多いと思います。

リリースされたばかりのOracle Database 12c R1 12.1.0.1.0ではどうなったか知りたくないですか? (俺だけかw)

Oracle Database 12c R1 12.1.0.1.0をリスナーも含め起動します。

※ /optの下がu01なのはこちらの事情なので突っ込みなしでお願いします m(_ _)m

[oracle@emperortetra ˜]$ lsnrctl start

LSNRCTL for Linux: Version 12.1.0.1.0 - Production on 06-9月 -2013 13:27:09

Copyright (c) 1991, 2013, Oracle. All rights reserved.

/opt/u01/product/12.1.0/dbhome_1/bin/tnslsnrを起動しています。お待ちください...

TNSLSNR for Linux: Version 12.1.0.1.0 - Production
システム・パラメータ・ファイルは/opt/u01/product/12.1.0/dbhome_1/network/admin/listener.oraです。
ログ・メッセージを/opt/u01/diag/tnslsnr/emperortetra/listener/alert/log.xmlに書き込みました。
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
リスニングしています: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=emperortetra)(PORT=1521)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 12.1.0.1.0 - Production
開始日 06-9月 -2013 13:27:10
稼働時間 0 日 0 時間 0 分 0 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/u01/product/12.1.0/dbhome_1/network/admin/listener.ora
ログ・ファイル /opt/u01/diag/tnslsnr/emperortetra/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=emperortetra)(PORT=1521)))
リスナーはサービスをサポートしていません。
コマンドは正常に終了しました。
[oracle@emperortetra ˜]$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.1.0 Production on 金 9月 6 13:27:15 2013

Copyright (c) 1982, 2013, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SYS@orcl12c> startup
ORACLEインスタンスが起動しました。

Total System Global Area 534462464 bytes
Fixed Size 2290416 bytes
Variable Size 473959696 bytes
Database Buffers 50331648 bytes
Redo Buffers 7880704 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@orcl12c> alter pluggable database all open;

プラガブル・データベースが変更されました。

SYS@orcl12c>


別terminalでSQL*Plusを起動しshellに入っておきます。

そして、

今回は特別に!

なんと、もう一度、SQL*Plus経由で、shellに入っておきます。:)

[oracle@emperortetra ˜]$ 
[oracle@emperortetra ˜]$ sqlplus system

SQL*Plus: Release 12.1.0.1.0 Production on 金 9月 6 13:31:13 2013

Copyright (c) 1982, 2013, Oracle. All rights reserved.

パスワードを入力してください:
最終正常ログイン時間: 金 9月 06 2013 13:30:24 +09:00

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
SYSTEM@orcl12c> !
[oracle@emperortetra ˜]$ sqlplus system

SQL*Plus: Release 12.1.0.1.0 Production on 金 9月 6 13:31:22 2013

Copyright (c) 1982, 2013, Oracle. All rights reserved.

パスワードを入力してください:
最終正常ログイン時間: 金 9月 06 2013 13:31:14 +09:00

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
SYSTEM@orcl12c>


前述の状態でpstreeでプロセスツリーを見ると以下のようになっています。

[oracle@emperortetra ˜]$ pstree -ualp oracle
VBoxClient,2099 --draganddrop
├─{VBoxClient},2104
└─{VBoxClient},2105

...中略...

sshd,3854
└─bash,3855
└─sqlplus,3876 \040\040\040\040\040\040
├─bash,3878
│ └─sqlplus,3891 \040\040\040\040\040\040
│ ├─bash,4019
│ └─oracle_3894_orc,3894 (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
└─oracle_3877_orc,3877 (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

tnslsnr,3304 LISTENER -inherit
└─{tnslsnr},3305
[oracle@emperortetra ˜]$

確認してみましょう!。11gではshutdown timeoutすら効かず停止できない状態になっていたのですが.....

SYS@orcl12c> shutdown immediate
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@orcl12c>

おおおおおおおお〜〜〜、shutdown immediateできた〜〜〜〜! 12cでは修正されたようですね。:)




shutdown immeidateしない、ほかの理由に遭遇!
shutdown immeidateしない、ほかの理由に遭遇! #2
shutdown immeidateしない、ほかの理由に遭遇! #3
shutdown immeidateしない、ほかの理由に遭遇! おまけ
shutdown immeidateしない、ほかの理由に遭遇! おまけのおまけ(でた〜最近、よくあるパターンw)

ablog - shutdown immeidate ...

| | | コメント (0) | トラックバック (0)

2013年8月21日 (水)

PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #3 - 消えたPDBの初期化パラメータの謎... Truth is out there.

CDB、PDBの各データベースで変更可能な初期化パラメータを変更してもspfileに書き出しているのはCDBのパラメータだけなのです。

spfileのタイムスタンプを確認してもCDBの時だけしかタイムスタンプは更新されません。もちろん心の目で見てもw

同じことで不思議に感じている方々も多いようです
Living with Oracle / oracle 12c database - pluggable database and spfile/parameter - how does that work?

いったい、どこに書き込んでるんでしょう....

PDB個別に設定した初期化パラメータは、spfileに書き込まれていないのは確かです。

どこかしらない、Cloud上に書いてくれちゃってるのでしょうかw 12c だけに... (ありえんw たぶん...

ということは、DBに書くしかないじゃないですか! 


どうやって調べるか....

あ、そうだ、 alter database set system hogehoge = xxxx scope=spfile をPDBでタイプしてるところを、心の目で見ればいいんだ :)

dbms_monitor.session_trace_enable.............awrレポートでも見えるわな、これw なんで気づかんのだ、俺 ><

....


見つけた

SYS@orcl12c> show con_name

CON_NAME
------------------------------
CDB$ROOT
SYS@orcl12c> select table_name from dba_tables where table_name like '%SPFILE%';

TABLE_NAME
------------------------------
PDB_SPFILE$


SYS@orcl12c> conn sys@pdborcl as sysdba
パスワードを入力してください:
接続されました。
SYS@pdborcl> show con_name

CON_NAME
------------------------------
PDBORCL
SYS@pdborcl> select table_name from dba_tables where table_name like '%SPFILE%';

TABLE_NAME
------------------------------
PDB_SPFILE$


SYS@pdborcl> conn sys@pdbdiscus as sysdba
パスワードを入力してください:
接続されました。
SYS@pdbdiscus> show con_name

CON_NAME
------------------------------
PDBDISCUS
SYS@pdbdiscus> select table_name from dba_tables where table_name like '%SPFILE%';

TABLE_NAME
------------------------------
PDB_SPFILE$


PDB_SPFILE$というSYSユーザの表の書き込んでいました!

CDBのPDB_SPFILE$を覗いてみます。

SYS@orcl12c> show con_name

CON_NAME
------------------------------
CDB$ROOT
SYS@orcl12c> l
1 select
2 db_uniq_name
3 ,sid
4 ,pdbs.name as "PDB NAME"
5 ,psf.name as "PARAMETER NAME"
6 ,value$
7 from
8 pdb_spfile$ psf
9 join v$pdbs pdbs
10 on
11 psf.pdb_uid = pdbs.con_uid
12 order by
13 db_uniq_name
14 ,sid
15 ,pdbs.name
16 ,psf.name
17*
SYS@orcl12c> /

DB_UNIQ_NAME SID PDB NAME PARAMETER NAME VALUE$
-------------- -------------- -------------- ------------------------------ --------------------------------------------------
orcl12c * PDBDISCUS open_cursors 500

DB_UNIQ_NAME SID PDB NAME PARAMETER NAME VALUE$
-------------- -------------- -------------- ------------------------------ --------------------------------------------------
orcl12c * PDBORCL open_cursors 200

いました! いましたよ〜〜〜〜。 PDBで設定した初期化パラメータが! (ちなみに、alter system resetコマンドで上記表から削除されます!)


実は、謎は、まだまだ、続くんです。

上記クエリはCDB上で実行しました。 PDB上で設定した初期化パラメータなのだから、PDB側のpdb_spfile$には対象PDBの初期化パラメータが見えるはず...

PDB上のpdb_spfile$を問い合わせてみると.....

SYS@pdborcl> show con_name

CON_NAME
------------------------------
PDBORCL

SYS@pdborcl> select name,value$ from pdb_spfile$;

レコードが選択されませんでした。


SYS@pdbdiscus> show con_name

CON_NAME
------------------------------
PDBDISCUS

SYS@pdbdiscus> select name,value$ from pdb_spfile$;

レコードが選択されませんでした。

むむ! むむむむむむ!!!!! 私個人は実に普通というか常識的な想像を働かせてPDB上のpdb_spfile$表を覗いたのに、裏切られた!!!

PDB側で設定した初期化パラメータなのにPDB側のpdb_spfile$表には格納されていなんです。 CDB側のpdb_spfile$しか格納されていません。(実はBugなんてことはないですよね)

俺の初期化パラメータなのに!


部下の手柄は上司の手柄....的なw いや〜〜〜な感じですね。


PDB側のpdb_spfile$表には、いつ初期化パラメータが入るのでしょうか?

それとも、いまの仕様では入ることはない....まだ見ぬ先のリリースでは利用されるのでしょうか....謎は深まる。というか気になって寝れないw 

何か気づいたら。続きはあるかもしれません. 一旦シリーズ終了 :)




PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #1
PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #2 - PDBの初期化パラメータは何処!?

| | | コメント (0) | トラックバック (0)

2013年8月20日 (火)

PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #2 - PDBの初期化パラメータは何処!?

制限はあるものの、PDBごとに変更可能な初期化パラメータの存在は確認できました。 :)

試してみます!

CDBのOPEN_CURSORSを変更してみます。
他のPDBで変更していないのでCDBの値が継承されるはずです。

$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.1.0 Production on 月 8月 19 23:12:19 2013

Copyright (c) 1982, 2013, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SYS@orcl12c> startup
ORACLEインスタンスが起動しました。

Total System Global Area 534462464 bytes
Fixed Size 2290416 bytes
Variable Size 473959696 bytes
Database Buffers 50331648 bytes
Redo Buffers 7880704 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@orcl12c> alter pluggable database all open;

プラガブル・データベースが変更されました。

SYS@orcl12c>
SYS@orcl12c> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 300
SYS@orcl12c>
SYS@orcl12c> alter system set open_cursors=400 scope=both;

システムが変更されました。

SYS@orcl12c> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 400
SYS@orcl12c>


PDBの初期化パラメータ値はCDBの初期化パラメータ値を継承しています!

SYS@orcl12c> conn sys@pdborcl as sysdba
パスワードを入力してください:
接続されました。
SYS@pdborcl>
SYS@pdborcl> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 400


PDBの初期化パラメータを変更してみます。(MEMORYとSPFILEを変更)

SYS@pdborcl> alter system set open_cursors=200 scope=both;

システムが変更されました。

SYS@pdborcl> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 200

変更できますね :)


ついでなので、もひとつのPDBの初期化パラメータも他の値に変更してみます。(同じくscope=bothで)

変更前なのでOPEN_CURSORSパラメータはCDBの同パラメータを継承しています!

SYS@pdborcl> conn sys@pdbdiscus as sysdba
パスワードを入力してください:
接続されました。
SYS@pdbdiscus>
SYS@pdbdiscus> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 400

400から500に増やてみます。

SYS@pdbdiscus> alter system set open_cursors=500 scope=both;

システムが変更されました。

SYS@pdbdiscus> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 500

問題なく変更できました。:)  順調、順調


SQL*Plusのshow parameter同様に、v$parameterではCDB,PDB毎のパラメータのみ参照できるようですね。ふむふむ。

SYS@orcl12c> select name,value from v$parameter where name='open_cursors';

NAME VALUE
------------------------------ ------------------------------
open_cursors 400

SYS@orcl12c> conn sys@pdborcl as sysdba
パスワードを入力してください:
接続されました。
SYS@pdborcl> /

NAME VALUE
------------------------------ ------------------------------
open_cursors 200

SYS@pdborcl> conn sys@pdbdiscus as sysdba
パスワードを入力してください:
接続されました。
SYS@pdbdiscus> /

NAME VALUE
------------------------------ ------------------------------
open_cursors 500

ついでなので、もう少し検証してみましょう。

初期化パラメータを変更する際、scope=bothとしたのでSPFILEにも格納されているはずですよね。

マルチテナント化されてもSPFILEは一つ!  確認しておきましょう。

SYS@orcl12c> !find $ORACLE_BASE -name spfile*.ora
/opt/u01/product/12.1.0/dbhome_1/dbs/spfileorcl12c.ora

一つだけです!


中身がどうなっているか確認しておきましょう。

SYS@orcl12c> !strings $ORACLE_HOME/dbs/spfileorcl12c.ora
orcl12c.__data_transfer_cache_size=0
orcl12c.__db_cache_size=33554432
orcl12c.__java_pool_size=16777216
orcl12c.__large_pool_size=50331648
orcl12c.__oracle_base='/opt/u01'#ORACLE_BASE set from environment
orcl12c.__pga_aggregate_target=570425344
orcl12c.__sga_target=1090519040
orcl12c.__shared_io_pool_size=0
orcl12c.__shared_pool_size=213909504
orcl12c.__streams_pool_size=0
*.audit_file_dest='/opt/u01/admin/orcl12c/adump'
*.audit_trail='db'
*.compatible='12.1.0.0.0'
*.control_files='
/opt/u01/oradata/orcl12c/control01.ctl','/opt/u01/fast_recovery_area/orcl12c/control02.ctl'
*.db_block_size=8192
*.db_domain=''
*.db_name='orcl12c'
*.db_recovery_file_dest='/opt/u01/fast_recovery_area'
*.db_recovery_file_dest_size=4800m
*.diagnostic_dest='/opt/u01'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=orcl12cXDB)'
*.enable_pluggable_database=true
*.memory_max_target=0
*.memory_target=0
*.open_cursors=400
*.pga_aggregate_target=536870912
*.processes=300
*.remote_login_passwordfile='
EXCLUSIVE'
*.sga_max_size=536870912
*.undo_tablespace='UNDOTBS1'

あれれれれれれ〜〜、 なんで CDBのパラメータしかないんでしょう!!!!!!! 

scope=bothと指定してもエラーのでずに設定されてる。 

SPFILEのバイナリ部分だけに記録さされているのか?。イヤそんなことはないだろ!

scope=memoryと同じではないことを確認するために一旦、再起動!! 

SYS@orcl12c> alter pluggable database all close;

プラガブル・データベースが変更されました。

SYS@orcl12c> shutdown
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。
SYS@orcl12c>
SYS@orcl12c> startup
ORACLEインスタンスが起動しました。

Total System Global Area 534462464 bytes
Fixed Size 2290416 bytes
Variable Size 473959696 bytes
Database Buffers 50331648 bytes
Redo Buffers 7880704 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS@orcl12c>
SYS@orcl12c> alter pluggable database all open;

プラガブル・データベースが変更されました。

SYS@orcl12c>
SYS@orcl12c> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 400
SYS@orcl12c>
SYS@orcl12c> conn sys@pdborcl as sysdba
パスワードを入力してください:
接続されました。
SYS@pdborcl> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 200
SYS@pdborcl>
SYS@pdborcl> conn sys@pdbdiscus as sysdba
パスワードを入力してください:
接続されました。
SYS@pdbdiscus> show parameter open_cursors

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_cursors integer 500


むむ!  むむむ!!!!!  SPFILEには格納されていない..ようなのに......

PDBの初期化パラメータは、何処にあるんだ〜〜〜〜!。 

次回へつづく!

次回、心の目を研ぎすませば、見えてくるものがある....みえないものもある...かもしれない

...乞うご期待w




PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #1

| | | コメント (0) | トラックバック (0)

2013年8月18日 (日)

PDB毎に初期化パラメータを変更できるんですよ!(制限はあるけど) #1

11g R2と12c R1の初期化パラメータ(隠しパラメータを含む)の差分をチェックしようとして気づいたのですが....

いままでとちがうんですよ。 シングルインスタンスなのに、なぜ、同じパラメータが4つあるんだ?

SYS@orcl12c> l
1 select
2 count(ksppinm)
3 from
4 x$ksppi
5 where
6* ksppinm='open_cursors'
SYS@orcl12c> /

COUNT(KSPPINM)
--------------
4
SYS@orcl12c> l
1 select
2 count(distinct ksppinm)
3 from
4 x$ksppi
5 where
6* ksppinm='open_cursors'
SYS@orcl12c> /

COUNT(DISTINCTKSPPINM)
----------------------
1

実は、CDB (Container Database)が1つ、PDB SEED (Pluggable Database Seed)が1つ、PDB (Pluggable Database)が2つあるんです.....1インスタンスに

SYS@orcl12c> l
1 select
2 inst_id
3 ,cons.name
4 ,ksppinm
5 from
6 x$ksppi params
7 join v$containers cons
8 on
9 params.con_id = cons.con_id
10 where
11* ksppinm='open_cursors'
SYS@orcl12c> /

INST_ID NAME KSPPINM
---------- ------------------------------ ----------------------------------------
1 CDB$ROOT open_cursors
1 PDB$SEED open_cursors
1 PDBORCL open_cursors
1 PDBDISCUS open_cursors

初期化パラメータはCDBだけでなく、PDB毎に設定できるようになっているようです!


ただし、よくよく調べてみると、PDBで変更可能な初期化パラメータは制限されているんです。

今後のためにPDBで変更可能な初期化パラメータ一覧(隠しパラメータ含む)をHTML形式でスプールしておきました。
Oracle Database 12c r1 12.1.0.1.0 - Pluggable Database Modifiable Parameters (including hidden parameters)

何かのときに役にたつかも... :)

...え、どうやって隠しパラメータの分もリストしたのかって?、 PDBで変更できる初期化パラメータはv$parameterのISPDB_MODIFIABLE列を見ればわかるのですが、隠しパラメータの分まではでてないですものね〜、

v$parameterを心の目で見ればいいんですよ。(謎


次回は、PDBで設定できる初期化パラメータを変更していて気づいたことを書く予定 :)

| | | コメント (0) | トラックバック (0)

2013年8月 8日 (木)

Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0) - including hidden params

※2014/8/4 修正、追記
比較したバージョンが誤っていたため訂正しました。正しくは、11g R1 11.1.0.6.0でした。


型の差分までは取得しませんでしが、Oracle11g R2 (11.2.0.1.0)とOracle12c R1 (12.1.0.1.0)の初期化パラメータ(隠しパラメータ含む)の差分をHTML化しました。(クエリ結果をHTML形式でspoolしただけですが)

まだどのような差分があるのか見切れていませんが、じ〜〜〜っくりと見ようと思っています :)

参考 (パラメータ数)
11g R2 11.2.0.1.0
11g R1 11.1.0.6.0
パラメータ総数 : 2049
隠しパラメータ数 : 1755 (うち ダブルアンダースコア隠しパラメータ数 : 10 )

11g R1 11.2.0.1.0
パラメータ総数 : 2399
隠しパラメータ数 : 2057 (うち ダブルアンダースコア隠しパラメータ数 : 10 )

12c R1 12.1.0.1.0
パラメータ総数 : 3351
隠しパラメータ数 : 2984 (うち ダブルアンダースコア隠しパラメータ数 : 11 )

隠しパラメータの増加が目立ちます.... (@@)

Difference of Initialization Parameters between 11g r1 (11.1.0.6.0) and 12c r1 (12.1.0.1.0)

| | | コメント (0) | トラックバック (0)

2013年7月25日 (木)

Oracle Database 12c New Feature : Invisible Columnsの怪w s/怪w/謎/

Oracle® Database New Features Guide 12c Release 1 (12.1) - 1.5.5.3 Invisible Columns
Oracle® Database Administrator's Guide 12c Release 1 (12.1) - Understand Invisible Columns


予告通り Invisible Columnの小ネタを。
(祭りに呼び出されて書けないかかもしれない、という状況にはならずw この時間に自宅にいるのはラッキーw)

本題とは関係ないですが、改行されないのはご愛嬌:)

SYS@orcl12c> startup pluggable database pdborcl;
ラガブル・データベースがオープンされました。SYS@orcl12c>
SYS@orcl12c>
SYS@orcl12c> conn scott@pdborcl
パスワードを入力してください:
接続されました。
SCOTT@pdborcl>

Invisible columnを含む表を作成します。(問題なく作成されました。当たり前か..)

SCOTT@pdborcl> create table hoge (a number, b number, c number invisible);

表が作成されました。


SQL*Plusのdescコマンドで見てみます。マニュアルの通り、Invisible columnはリストされません。

SCOTT@pdborcl> desc hoge
名前 NULL? 型
----------------------------------------- -------- ----------------------------
A NUMBER
B NUMBER


さあ、ここから、気持ち悪い事が起きますよ〜〜。よ〜〜〜く見ててくださいね〜〜。

列は隠れていますが、存在を知って、Invisible Columnでもカラムを指定すればデータを登録できるんです。見えてないだけなんです!

SCOTT@pdborcl> insert into hoge(a,b,c) values(1,2,4);

1行が作成されました。


カラム指定しないとa列と、b列しかないものとして扱われ、見慣れたエラー、「ORA-00913: 値の個数が多すぎます。」となります。

いい感じの気持ち悪さでしょ :)

SCOTT@pdborcl> insert into hoge values(11,12,14);
insert into hoge values(11,12,14)
*
行1でエラーが発生しました。:
ORA-00913: 値の個数が多すぎます。

2列分なら正常に登録できます。見えるのはa列とb列の2列なので当然と言えば当然。

SCOTT@pdborcl> insert into hoge values(11,12);

1行が作成されました。

SELECTリストに * を指定すれば、ほらほら、自然です。 a列とb列だけがリストされています。

SCOTT@pdborcl> select * from hoge;

A B
---------- ----------
1 2
11 12


でも...c列の存在を知っている人には....見えちゃうんです。

SCOTT@pdborcl> select a, b, c from hoge;

A B C
---------- ---------- ----------
1 2 4
11 12 [null]

同様に削除や更新もできちゃいます。見えないものが見えてる人にはw。
(その存在を知らない人には見えないわですが...)

SCOTT@pdborcl> update hoge set c = 14 where c is null;

1行が更新されました。

SCOTT@pdborcl> select a, b, c from hoge where c = 14;

A B C
---------- ---------- ----------
11 12 14

SCOTT@pdborcl> delete from hoge where c = 14;

1行が削除されました。

SCOTT@pdborcl> select a, b, c from hoge;

A B C
---------- ---------- ----------
1 2 4


ORDER BY句でも、その存在さえ知っていればいろいろイタズラできますね。

SCOTT@pdborcl> select a,b,c from hoge order by c desc;

A B C
---------- ---------- ----------
11 12 14
1 2 4


おもしろいですね。

SCOTT@pdborcl> select * from hoge order by c desc;

A B
---------- ----------
11 12
1 2

Invisible columnには索引も作れます!

SCOTT@pdborcl> create index hoge_ix on hoge(c);

索引が作成されました。

SCOTT@pdborcl> set autot trace exp
SCOTT@pdborcl> select * from hoge where c = 14;

実行計画
----------------------------------------------------------
Plan hash value: 3349209795

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| HOGE | 1 | 39 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | HOGE_IX | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("C"=14)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)

SCOTT@pdborcl>

次のようなイタズラもできます! うひゃw うひゃw 

相手の腕試をしするにはぴったりなイタズラです(嘘

SCOTT@pdborcl> alter table hoge modify (c number not null);

表が変更されました。

SCOTT@pdborcl>
SCOTT@pdborcl> insert into hoge(a, b) values(100,111);
insert into hoge(a, b) values(100,111)
*
行1でエラーが発生しました。:
ORA-01400: ("SCOTT"."HOGE"."C")にはNULLは挿入できません。


イタズラはこれくらいにして、Invisible Columnをディクショナリビューから覗いてみましょう。
Invisible Indexもディクショナリビューからいろいろと情報を得ることができましたよね! 
(多分、同じように見れるのでは...と思っていたが...

desc[ribe]コマンドではInvisible Columnは見えませんが、 ALL/DBA/USER_TAB_COLUMNSからは見る事ができます。

racle® Database Reference 12c Release 1 (12.1) - ALL_TAB_COLUMNS


ただし、見る事はできるのですが、その列がInvisible Columnかどうかを確認する列がないんですよ。(ぱっと見)

例えば、Invisible IndexだとALL/DBA/USER_INDEXES確認できます

SCOTT@pdborcl> select index_name,visibility from user_indexes where table_name='HOGE';

INDEX_NAME VISIBILIT
------------------------------ ---------
HOGE_IX INVISIBLE
HOGE_PK VISIBLE


そこで疑問!
SQL*PLusのdesc[ribe]コマンドはどのような情報を基にVisible Columeだけを表示してるんでしょうねぇ〜。

これ、探すのにてこずりましたよ!

以下のマニュアルにも記載されていますが、Visible/InvisibleにするとColumn Orderが変化することにも関連しているようです。
Oracle® Database Administrator's Guide 12c Release 1 (12.1) - Understand Invisible Columns

desc[ribe]ではInvisible Columnは見えません

SCOTT@pdborcl> desc hoge
名前 NULL? 型
----------------------------------------- -------- ----------------------------
A NUMBER
B NUMBER


どのような情報を見てInvisibleであると判断しているのか....

SCOTT@pdborcl> select column_name,data_type,nullable,column_id from user_tab_columns where table_name='HOGE' order by column_id;

COLUMN_NAME DATA_TYPE N COLUMN_ID
------------------------------ ---------- - ----------
A NUMBER Y 1
B NUMBER Y 2
C NUMBER N [null]


COLUMN_IDがNULLである列がInvisible Columns !!!!

SCOTT@pdborcl> l
1 select
2 column_name
3 ,data_type
4 ,nullable
5 ,column_id
6 from
7 user_tab_columns
8 where
9 table_name='HOGE'
10 and column_id is not null
11 order by
12* column_id
SCOTT@pdborcl> /

COLUMN_NAME DATA_TYPE N COLUMN_ID
------------------------------ ---------- - ----------
A NUMBER Y 1
B NUMBER Y 2

いずれ、ALL/DBA/USER_TAB_COLUMNSに、VISIBILITYという列が追加されるんじゃないかと想像してます :)

それまでは俺俺 USER_TAB_COLUMNSビューでも作っておくと便利かも...しれない。

SCOTT@pdborcl> create view my_tab_columns as
2 select
3 user_tab_columns.*
4 ,case when user_tab_columns.column_id is null
5 then 'INVISIBLE' else 'VISIBLE' end as VISIBILITY
6 from
7 user_tab_columns
8 ;

ビューが作成されました。
SCOTT@pdborcl> select column_name,data_type,nullable,visibility from my_tab_columns where table_name='HOGE' order by column_id;

COLUMN_NAME DATA_TYPE N VISIBILIT
------------------------------ ---------- - ---------
A NUMBER Y VISIBLE
B NUMBER Y VISIBLE
C NUMBER N INVISIBLE


このような特性を理解した上で、どのような場面で使うと便利なのか、じ〜〜〜〜っくり考えてみたいと...と思いつつ...寝ます。パタリ。 1:48am


| | | コメント (0) | トラックバック (0)

2013年7月13日 (土)

Oracle Database 12c R1 : コンテナデータベースとプラガブルデータベースを歩きやすくしてみる

世界を見渡せば、かなりの方がOracle Database 12cのインストール手順を公開し始めているのでインストール後の話を書いておきますね。

インストール時にPluggable Databaseも同時に作成してもtnsnames.oraにはContainer Databaseへの接続定義しか無かったんです。(私のインストール手順に問題がなければ..なんですが)
無くてもEasy connectを使えば接続できるので問題はないんですが...

ただ、Easy connectで毎回タイプするのも面倒(少々長いし)なので、tnsnames.oraにPluggable Database接続用定義を作成しておきましょう。

定義内容はいままでと変わらず。 SERVICE_NAMEの箇所を接続先のPluggable Database名にするだけ。
(Pluggable Database名と書いたが、マニュアル読みきれてないので表現は間違っているかもしれません。間違っていたら後で修正します)

[oracle@emperortetra]$ cat $ORACLE_HOME/network/admin/tnsnames.ora
# tnsnames.ora Network Configuration File: /opt/u01/product/12.1.0/dbhome_1/network/admin/tnsnames.ora
# Generated by Oracle configuration tools.

# for Container Database : orcl12c
ORCL12C =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = emperortetra)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = orcl12c)
)
)

# for Pluggable Database : pdborcl
PDBORCL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = emperortetra)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = pdborcl)
)
)

本題である、歩きやすくするための設定がこれ。

複数のPluggable DatabaseとContainer Database間を旅することも多くなりそうですから、今、自分はどこにいるのか把握しやすくしておいたほうが吉かと。

12c以前から定番で設定している内容ではあるのですが。赤字部分がポイント

目立たないけど、地味に大切なのが、 set tab off だと思っているのは私だけだろうか!w 
(ここのところ、set tab offじゃない人のコピペしてきた、位置ズレまくりの実行計画見せられててイライラしている影響もある :)


それ以外の設定も、設定してるだけで、「むむむ、この方できるかも!」 と、
思わせる(思わせるだけですからね!w つかみはOK的な感じなので、あとはあなた次第)設定内容なので大切と言えば大切。
(login.sqlでもOK)

[oracle@emperortetra]$ cat $ORACLE_HOME/sqlplus/admin/glogin.sql
--
-- Copyright (c) 1988, 2005, Oracle. All Rights Reserved.
--
-- NAME
-- glogin.sql
--
-- DESCRIPTION
-- SQL*Plus global login "site profile" file
--
-- Add any SQL*Plus commands here that are to be executed when a
-- user starts SQL*Plus, or uses the SQL*Plus CONNECT command.
--
-- USAGE
-- This script is automatically run
--
set linesize 180
set pagesize 10000
set timi on
set time on
define _EDITOR=vi
col table_name for a30
col column_name for a30
col segment_name for a30
col tablespace_name for a30
col index_name for a30
col partition_name for a30
col sub_partition_name for a30
set sqlp "_USER'@'_CONNECT_IDENTIFIER> "
set tab off
[oracle@emperortetra]$


リスナーを起動してステータスを見てみましょう (u01が/optの下にあるのは内緒w じゃないw
まだ、データベースを起動していないのでこんな感じです。

[oracle@emperortetra]$ lsnrctl status

LSNRCTL for Linux: Version 12.1.0.1.0 - Production on 06-7月 -2013 09:10:04

Copyright (c) 1991, 2013, Oracle. All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 12.1.0.1.0 - Production
開始日 06-7月 -2013 09:06:38
稼働時間 0 日 0 時間 3 分 26 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/u01/product/12.1.0/dbhome_1/network/admin/listener.ora
ログ・ファイル /opt/u01/diag/tnslsnr/emperortetra/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=emperortetra)(PORT=1521)))
リスナーはサービスをサポートしていません。
コマンドは正常に終了しました。
[oracle@emperortetra]$


Container Databaseの起動(12cより前のOracle Databaseと同じですね)

Container Databaseである orcl12c がSQL*Plusのプロンプトに表示されていますよね!! :)

[oracle@emperortetra]$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.1.0 Production on 土 7月 6 09:25:22 2013

Copyright (c) 1982, 2013, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

09:25:22 SYS@orcl12c> startup
ORACLEインスタンスが起動しました。

Total System Global Area 1653518336 bytes
Fixed Size 2289016 bytes
Variable Size 1040188040 bytes
Database Buffers 603979776 bytes
Redo Buffers 7061504 bytes
データベースがマウントされました。
データベースがオープンされました。
09:25:32 SYS@orcl12c>



Pluggable Databaseを起動します!

09:25:34 SYS@orcl12c> alter pluggable database pdborcl open;

プラガブル・データベースが変更されました。

経過: 00:00:00.86


Pluggable DatabaseのSYS、SYSTEMおよび、SCOTTユーザに接続してみます!
09:25:53 SYSTEM@orcl12c> conn sys@pdborcl as sysdba
パスワードを入力してください:
接続されました。
09:26:06 SYS@pdborcl>
09:26:07 SYS@pdborcl>
09:26:07 SYS@pdborcl> conn system@pdborcl
パスワードを入力してください:
接続されました。
09:26:34 SYSTEM@pdborcl>
09:26:34 SYSTEM@pdborcl>
09:26:35 SYSTEM@pdborcl> conn scott@pdborcl
パスワードを入力してください:
接続されました。
09:26:50 SCOTT@pdborcl>
09:26:51 SCOTT@pdborcl>
09:26:51 SCOTT@pdborcl>


SQL*Plusのプロンプトに接続先の情報を表示しておくと便利ですよね。Database間を歩きやすく、迷子になりにくくなりますよ! :)

おまけ
Container Databaseおよび、Pluggable Database起動後のlistenerのステータスは以下の通り。

[oracle@emperortetra]$ lsnrctl status

LSNRCTL for Linux: Version 12.1.0.1.0 - Production on 06-7月 -2013 09:28:09

Copyright (c) 1991, 2013, Oracle. All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1521)))に接続中
リスナーのステータス
------------------------
別名 LISTENER
バージョン TNSLSNR for Linux: Version 12.1.0.1.0 - Production
開始日 06-7月 -2013 09:06:38
稼働時間 0 日 0 時間 21 分 31 秒
トレース・レベル off
セキュリティ ON: Local OS Authentication
SNMP OFF
パラメータ・ファイル /opt/u01/product/12.1.0/dbhome_1/network/admin/listener.ora
ログ・ファイル /opt/u01/diag/tnslsnr/emperortetra/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=emperortetra)(PORT=1521)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=emperortetra)(PORT=5500))(Security=(my_wallet_directory=/opt/u01/admin/orcl12c/xdb_wallet))(Presentation=HTTP)(Session=RAW))
サービスのサマリー...
サービス"orcl12c"には、1件のインスタンスがあります。
インスタンス"orcl12c"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"orcl12cXDB"には、1件のインスタンスがあります。
インスタンス"orcl12c"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"pdborcl"には、1件のインスタンスがあります。
インスタンス"orcl12c"、状態READYには、このサービスに対する1件のハンドラがあります...
コマンドは正常に終了しました。
[oracle@emperortetra]$

Enjoy Oracle Database 12c R1 :)


| | | コメント (0) | トラックバック (0)

2013年5月11日 (土)

Automator : サービスの作成から削除まで (FAQ)

私のブログでもアクセス数の多いAutomator de ファイル名一括変換のコメントへ答えた次いでなので、もう少し詳しく書いておきますね。

これもOS XではFAQだと思いますますが、Automatorの認知度が低いのかな〜。


サービスを作るほうはこのヘルプで分かるかと。
Mac 101:Automator 

削除のヘルプページを探せないw (俺の検索の仕方がまずいのかw..


ってことで、Automatorでサービス(ワークフローの仲間)を作成して、メニューから削除するまでの流れを。

Autometorでサービスを作成します。
DeveloperToolsのプロファイルをテキストに書き出すだけの簡単なサービスを作るところから。

アプリケーション>Automatorを起動

Automator_icon


  1. 「書類の種類を選択してください」ダイアログ>「サービス」アイコンをクリック>「選択」ボタンをクリック
    Making_services001
  2. 「入力なし」、「Finder.app」限定で使えるようにしておきます。利用できる範囲は適当に設定してね。
    Making_services002

  3. 「ライブラリ」ペイン>「アクション」>「ユーティリティ」>「システムプロファイル」アクションをワークフローペインへドラッグ&ドロップ
    「システムプロファイル」アクションの「書き出しフォーマット」メニューは「テキスト」を、「カテゴリ」チェックボックスは「DeveloperTools」を選択
    Making_services003

  4. 「ライブラリ」ペイン>「アクション」>「テキスト」>「新規テキストファイル」アクションをワークフローペインへドラッグ&ドロップ
    「新規テキストファイル」アクションの各パラメータを設定。(ファイルフォーマット、ファイル名、作成場所、置換有無、エンコーディング)
    Making_services004

  5.  (「メニュー」>「ファイル」>「保存」) or ⌘S >「サービスを別名で保存」テキストボックスへサービス名入力>「保存」ボタンをクリック
    Making_services005

  6.  保存すると指定した「アプリケーションメニュー」の「サービス」サブメニューに保存した名称で表示される
    Making_services006


「test」というサービスを実行すればDeveloperToolsのプロファイルがテキストファイルとしてデスクトップに保存されます。


いよいろこれからが、今回の本題。「アプリケーションメニュー」の「サービス」サブメニューに追加された「test」というサービスをどうやって削除するのか?

  1. 「アプリケーション」メニュー>「サービス」サブメニュー>「サービス環境設定...」をクリック
    Removing_service001
  2. 「キーボード」環境設定>「キーボードショートカット」タブ>「説明」ペイン>「サービス」>「test」を選択
    Removing_service002

  3. 「test」を右クリック>ポップアップメニューの「Finderに表示」を選択
    Removing_service003

  4. 「test.workflow」が表示される。
    Removing_service004

  5. 「test.workflow」を右クリック>ポップアップメニューの「ゴミ箱に入れる」を選択
    Removeing_service005

  6. 「test.workflow」はゴミ箱です:)、ゴミ箱ではなくても ~/ライブラリ/Servicesフォルダから取り除けば同じです。(また使いたくなったら戻せばいいですし)
    Removing_service007


はい、サービスメニューから消えました :)

Removing_service008


Enjoy Automator!


| | | コメント (0) | トラックバック (0)

2013年5月 6日 (月)

散らかってる部屋へ、突然の来客、散らかってるものはどうします? 

散らかってる部屋へ、突然の来客、散らかってるものはどうします? 

とりあえず、押入やクローゼットへ放り込むw

そんな、地味だけど便利な機能がこれ。

そういえば、Windowsのデスクトップが書類だらけで埋まっている方いますよね。

Macな方なら、そんなときでも慌てずに、ささっと、ね :)

20130506_222712


とりあえず、片付けちゃいたいファイルをすべて選択>ポップアップメニュー「選択項目(N項目)から新規フォルダ」を選択するだけ!

| | | コメント (0) | トラックバック (0)

2013年5月 2日 (木)

FileVault de USBメモリ暗号化

2009年にUSBメモリ de 暗号化ディスクイメージというエントリを書ていたのですが、Moutain LionではFileVaultが機能拡張され、USBメモリにディスクイメージを作成するという小細工が不要になったよ! というお話。

ネタとしては新しくないのですが意外としならない方が多いようなのでFAQとして書いておきますね。

今回利用するUSBメモリはMercedes-Benz Connection Roppingiでポイント交換してきたキー型USB 4GBを使用しました。
(普通のUSBメモリなので暗号化はありません。なのでそのままだとセキュリティー面で弱いわけです。はい)

Mercedesusb4g_2

Mercedesusb4b_1

と、いうことで、File Vault de USBメモリ暗号化作戦! 開始。

  1. USBメモリをマウント
  2. 001

  3. 「Finder」>「アプリケーション」>「ユーティリティ」>「ディスクユーティリティ」>ダブルクリックでディスクユーティリティを起動

  4. マウントしたUSBメディア>「パーティション」タブ>「パーティションレイアウトメニュー」>「1パーティション」>パーティション情報「名前」>フォーマット「MacOS拡張(ジャーナリング)」>「オプション」ボタンをクリック
  5. 002

  6. 「GUIDパーティションテーブル」ラジオボタン>「OK」ボタンクリック
  7. 003

  8. 「適用」ボタンをクリックしてパーティションを作成
  9. 004

  10. 右クリック>"USBのボリューム名"を暗号化メニュー選択
  11. 005

  12. 「暗号化パスワード」、「パスワードの確認」、「パスワードのヒント」を入力
    「ディスクを暗号化」ボタンクリックで暗号化開始
  13. 006

  14. 7.で「鍵アイコン」ボタンをクリックするとパスワードアシスタントが表示される(オプション)
  15. 007_passward_assistant

  16. 7.の「ディスクを暗号化」ボタンをクリック後、4GBなら3分余(メモリサイズ依存)で暗号化された
    暗号化処理中のポップアップメニューには、「”パーティション名”を暗号化中...」と表示される
  17. 008

    利用したマシンは、MacPro / OS X : Moutain Lion 10.8.3
    Os_version

  18. ポップアップメニューに「”パーティション名”を暗号化解除中...」と表示されたら暗号化終了
    (暗号化処理中、そして暗号化処理終了をもっと見やすくしてほしいですよね。ダウンロード時のようにアイコン上にプログレスバーを表示するとか)
  19. 009_about_3min

  20. FileVaultで暗号化されたUSBメモリーをマウントするとパスワードが要求されます! :)
    以下は1世代前のMac OS Xつまり、Lionにマウントした例です
  21. Usbmoutainlion001
    Usbmoutainlion002


| | | コメント (0) | トラックバック (0)

2013年5月 1日 (水)

TimeMachine応用編

容量不足のMBAをMountain Lionへ移行する際にTimeMachineを活用するのはありだな。

仕事でヘビーにMBAを利用していると新しいOSがリリースされたとしてもすぐには飛びつけないんですよね。昨年がそういう状況でした。:)

やっと落ち着いたのでGW前半で母艦と同じMountain Lionへ移行しようと思ったらMBAの空き容量不足が発覚!w


TimeMachineをバックアップ兼一時保管として利用することに...

・TimeMachineにバックアップを取得しておく。
・デスクトップや書類フォルダにあるファイルを一旦削除。
・Mountain Lionへ移行
・必要なファイルだけをリストア

:) 超簡単w

20130501_114312

| | | コメント (0) | トラックバック (0)

2013年3月 9日 (土)

VirtualBoxのWindows7だとPaSoRi RC-S380認識しないのな

妻がPaSoRi RC-S380を使いたいというので、ダメもとで試してみた。 ダメだった。(^^;;;

以上、ご報告までw


ちなみに、リアルなPCにWindows8インストールしたら一発で認識した。 人柱ばんざ〜い:)

PASMOの履歴って、カード内の20件しか見れないのな orz.... 無駄遣いなのは間違いないw

Pasori_with_mac

VirtualBox4.2.8+Windows7で使おうとすると以下のように認識してくれない。残念

20130305_235713


20130305_235955


| | | コメント (0) | トラックバック (0)

2012年9月30日 (日)

あ、Mountain LionにはX11含まれてなかったんだった

Mountain LionにはX11含まれてなかったことを忘れてた><


ということで、ナビゲートされるので以下、XQuartsのサイトからダウンロードするべし。という備忘録

http://xquartz.macosforge.org/landing/

20120930_213625



20120930_214535

20120930_214413

| | | コメント (0) | トラックバック (0)

2012年6月23日 (土)

Mac OS X Lion (Server) で Hyper-Threading を ON/OFFする方法

Mac OS X Lion (Server) でHyper-ThreadingのON/OFFを試してみた。

Terminalからnvramコマンドたたけばできるのね。脳のシワ1つ増えたw

(sudo nvram -h でヘルプでるから!)

HTを止めて、12coreで動作させてみる。
設定内容を確認しとく

discus-mother:˜ oracle$ sudo nvram -p
SystemAudioVolume 0
EFIBluetoothDelay %b8%0b
fmm-computer-name discus_mother
discus-mother:˜ oracle$

SMT=0を設定するとHTをOFFにできる。設定内容も確認しておくよ。

discus-mother:˜ oracle$ sudo nvram SMT=0
discus-mother:˜ oracle$ sudo nvram -p
SMT 0
fmm-computer-name discus_mother
EFIBluetoothDelay %b8%0b
SystemAudioVolume 0
discus-mother:˜ oracle$

設定内容を反映させるには再起動が必要なのでお忘れなく。
(いったん起動してから、再び、起動音とともに再起動、ようするに、2度起動音がでるので驚かないでね)

アプリケーション>ユーティリティ>アクティビティモニター>⌘2 でCPU使用率フローティングウィンドウを表示して確認!
確かに12CPUに見える。次いでなので軽く負荷をかけてみた :)


20120622_235639


再び、HTをONにして24CPUにしてみる。

discus-mother:˜ oracle$ sudo nvram -d SMT
discus-mother:˜ oracle$ sudo nvram -p
SystemAudioVolume 0
EFIBluetoothDelay %b8%0b
fmm-computer-name discus_mother
discus-mother:˜ oracle$

ここで再起動。

CPU使用率フローティングウィンドウ。こんどは思いきって負荷をかけてみた。お〜〜〜〜〜っw




20120623_01129

20120621_03925


| | | コメント (0) | トラックバック (0)

2012年6月 7日 (木)

MacOS X Lion で HP Photosmart C6175をスキャナーとして使う方法

久々にというか、MacOS X Lionにしてから初めて HP Photosmart C6175 をスキャナーとして使おうとしてハマった。

Mac OS X LionになってからApple Software Update経由でプリンタドライバが提供されるようになり、以前使ってたPhotosmartのユーティリティが無くなってしまった。ので をスキャナとして利用する方法がわかりにくい… ><

としばしぼーーーぜん。

あ、マニュアル見ればいいよね! とHPさん(HPはHOME PAGEの略じゃないからね!)のページを見てみると…

http://h10025.www1.hp.com/ewfrf/wc/document?docname=c02942039&lc=ja&cc=jp&dlc=ja&product=1153493#N979

このページだと「システム環境設定」>「プリントとスキャン」からスキャナーとして使えるような記載に読めるような読めないような…わかりずら〜〜〜い。
(あ、ちなみに私のは、Mac OS X Lion 10.7.4です)

いろいろ試してみたものの実際にはそのようなペインは無いんです。はい。

と、再び、ぼ〜〜〜〜〜ぜん…

ではどうするか、Mac OS X にはイメージキャプチャというアプリケーションが付属しています! それを使えばいいの! (キリっ

20120607_03750


| | | コメント (0) | トラックバック (0)

2012年1月30日 (月)

AWRレポート、AWR SQLレポート一括取得スクリプトを作ったよ。

Statspackレポートもそうなのですが、AWRレポート/AWR SQLレポートも個別に取得していると凄ーく辛いんですよね。一日分出力するとか、AWRレポートで処理時間の長いSQL文のAWR SQLレポートを個別に取得しようとなると...

ただでさえ忙しいのに、AWRレポート取得するのに時間掛けたくないですよね。

ということで、やっつけで作ったのですが、そのまま載せるのもあれなんで、やっつけで作った感じを多少減らしてgithubに公開しました。;)

https://github.com/discus/Oracle-AWR-batch-generation-script/blob/master/awrreport_batch.sql


もっといい感じに改造してくれるといいな〜とかとか... :)

Oracle11g R1/R2 Enterprise Edition、HTML形式で出力します。(RAC環境では試してないので多分だめかと。)。
AWRを利用するには追加ライセンスが必要なのでご注意を


使い方は・・

SQL*plusを起動し、select any dictionaryシステム権限、dbms_repositoryパッケージの実行権限が付与されたユーザで接続して実行するだけ。
SYSTEMユーザでやる事が多いけど、所変わればなんとやらなので・・・そこんとこよろしく。(w


一括取得なので実行当日を含めてn日分のAWRレポートを取得し、同時に処理時間の長いTop20のAWR SQLレポートも取得します。
レポートは各スナップショット間(今のところ固定)で取得します。

指定するパラメータは、以下の3つ。

Enter snap_id for starting AWR report generation. [NULL] : 
AWRレポートを取得する最初のSNAP_IDを指定します。 NULLがデフォルトでほとんどの場合デフォルトのままで事足りると思います。


Enter number of days for reporting period. [1] : 

一括取得する日数を指定します。当日を含みます。
当日分のAWRレポートを出力するのであれば、デフォルト値の1のままでOKです。


Enter suffix for AWR reports filename. [NULL] : test

保存するAWRレポートのファイル名に付加するsuffixを指定します。
試験名とか設定するといいですよね。

"test"と指定した場合

awrrpt_nnnn_nnnn_test.htmlや
awrsqrpt_nnnn_nnnn_test_sqlid.html

の形式で保存します。(nnnnはSNAP_ID)


実行例1)当日分の全レポートを取得する例

SYSTEM> 
SYSTEM> !ls -l awr*.sql
-rw-r--r-- 1 oracle oinstall 6065 1月 29 18:23 awrreport_batch.sql

SYSTEM> @awrreport_batch
--
-- Oracle AWR and AWR SQL report batch generation script
--
-- ***** This script always generate html format AWR reports. *****
--
Enter snap_id for starting AWR report generation. [NULL] :
Enter number of days for reporting period. [1] :
Enter suffix for AWR reports filename. [NULL] : test
--
--
--
clear break compute;
repfooter off;
ttitle off;
btitle off;

・・・中略・・・

<p />
<br /><a class="awr" href="#top">Back to Top</a><p />
</body></html>

SYSTEM>


実行例2)当日分かつsnap_id=291以降で一括取得。(事前にsnap_idを調べておいてね)

SYSTEM> 
SYSTEM> @awrreport_batch
--
-- Oracle AWR and AWR SQL report batch generation script
--
-- ***** This script always generate html format AWR reports. *****
--
Enter snap_id for starting AWR report generation. [NULL] : 291
Enter number of days for reporting period. [1] :
Enter suffix for AWR reports filename. [NULL] : test
--
--
--
clear break compute;
repfooter off;
ttitle off;
btitle off;

・・・中略・・・

<p />
<br /><a class="awr" href="#top">Back to Top</a><p />
</body></html>

SYSTEM>
SYSTEM>
SYSTEM> !ls -l *.html
-rw-r--r-- 1 oracle oinstall 379083 1月 29 22:23 awrrpt_291_292_test.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_0c83z9rqx45hu.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_0h3mfbzk6uyw0.html
-rw-r--r-- 1 oracle oinstall 11897 1月 29 22:23 awrsqrpt_291_292_test_2p7t0mw7zvu5z.html

・・・中略・・・

-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_bhtycgwkxhfj9.html
-rw-r--r-- 1 oracle oinstall 11900 1月 29 22:23 awrsqrpt_291_292_test_bpaggvtfkar9k.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_c50hdbyuwhfn6.html
-rw-r--r-- 1 oracle oinstall 11892 1月 29 22:23 awrsqrpt_291_292_test_g3f3cw3zy5aat.html

SYSTEM>

なお、Oracleインスタンスが再起動された期間でawrrpt.sqlなどを実行すると、レポートが作成できずエラーでSQL*Plusも終了してしまいますが、本スクリプトでは該当部分のレポートはスキップするようにしてあります。:)

Instance     DB Name      Snap Id   Snap Started       Level
------------ ------------ --------- ------------------ -----
lampeye LAMPEYE 274 29 1月 2012 09:33 1
275 29 1月 2012 10:00 1
276 29 1月 2012 10:30 1
277 29 1月 2012 11:00 1
278 29 1月 2012 11:30 1
279 29 1月 2012 12:00 1
280 29 1月 2012 12:30 1
281 29 1月 2012 13:00 1
282 29 1月 2012 13:30 1
283 29 1月 2012 14:00 1
284 29 1月 2012 14:30 1
285 29 1月 2012 15:00 1
286 29 1月 2012 15:30 1
287 29 1月 2012 16:00 1
288 29 1月 2012 16:30 1
289 29 1月 2012 17:00 1
290 29 1月 2012 17:30 1

291 29 1月 2012 18:31 1 ←再起動されてる
292 29 1月 2012 18:34 1

293 29 1月 2012 22:26 1 ←再起動されてる


Enjoy!

| | | コメント (2) | トラックバック (0)

2012年1月14日 (土)

shutdown immeidateしない、ほかの理由に遭遇! (FYI)


ず〜〜〜〜っと、追記しようと思ってたんだけど書いてなかったので、徹夜明けで早起きした次いでなんで書いておきます。

もう一年近く前のネタなんだけどね。「shutdown immeidateしない、ほかの理由に遭遇!」

> yoheia-a さんありがとう :)

私か書いた記事がキッカケで調べなきゃいけなくなったらしいんだけどね。 ;)

http://d.hatena.ne.jp/yohei-a/20110627/1309180675




shutdown immeidateしない、ほかの理由に遭遇!
shutdown immeidateしない、ほかの理由に遭遇! #2
shutdown immeidateしない、ほかの理由に遭遇! #3
shutdown immeidateしない、ほかの理由に遭遇! おまけ
shutdown immeidateしない、ほかの理由に遭遇! おまけのおまけ(でた〜最近、よくあるパターンw)

| | | コメント (0) | トラックバック (0)

2011年8月16日 (火)

CentOS6.0 on VirtualBox4.1.0 for MacOS X(Snow Leopard) - Can't mount VBoxGuestAdditions.iso as Virtual CD/DVD-ROM

MacOS XのVirtualBoxでGuest Additionsがインストールできないというか VBoxGuestAdditions.isoファイルを仮想CD/DVDとしてマウントできないなー、なんて状態になったら、これから紹介する方法で解決できますよ。というお話です。:)

VirtualBox for MacOS Xは他のアプリケーションと同様にアプリケーション・パッケージ形式なのでVirtualBoxアプリケーションアイコンを右クリックし、「ボップアップメニュー」>「パッケージの内容を表示」を選択します。

Menu




でFinderに表示されたVirtualBoxパッケージないを開いていくと Contents/MacOS/VBoxGuestAdditions.iso というファイルが見つかります。これがGuest Additions用の.iso形式ファイル。これがなぜかマウントできないんだな。以前はマウントできてたのに…

Iso


この VBoxGuestAdditions.iso をDesktopなどアクセスしやすい場所にコピーしちゃいます。

あとはいつものように、CD/DVD-ROMとしてマウントして…


Mount


Mount2


コマンドを打つだけ。:)


[discus@angelfish VBOXADDITIONS_4.1.0_73009]$ su -
パスワード:
[root@angelfish &]# cd /media
[root@angelfish media]# ll
合計 2
dr-xr-xr-x. 4 discus root 2048 7月 19 19:43 2011 VBOXADDITIONS_4.1.0_73009
[root@angelfish media]# cd VBOXADDITIONS_4.1.0_73009
[root@angelfish VBOXADDITIONS_4.1.0_73009]# ll
合計 41440
dr-xr-xr-x. 3 discus root 2048 7月 19 19:43 2011 32Bit
dr-xr-xr-x. 2 discus root 2048 7月 19 19:43 2011 64Bit
-r-xr-xr-x. 1 discus root 647 8月 14 00:58 2010 AUTORUN.INF
-r-xr-xr-x. 1 discus root 7525824 7月 19 19:38 2011 VBoxLinuxAdditions.run
-r-xr-xr-x. 1 discus root 14235136 7月 19 19:42 2011 VBoxSolarisAdditions.pkg
-r-xr-xr-x. 1 discus root 13097968 7月 19 19:30 2011 VBoxWindowsAdditions-amd64.exe
-r-xr-xr-x. 1 discus root 7277968 7月 19 19:25 2011 VBoxWindowsAdditions-x86.exe
-r-xr-xr-x. 1 discus root 278832 7月 19 19:24 2011 VBoxWindowsAdditions.exe
-r-xr-xr-x. 1 discus root 6966 7月 19 19:36 2011 autorun.sh
-r-xr-xr-x. 1 discus root 5523 7月 19 19:36 2011 runasroot.sh
[root@angelfish VBOXADDITIONS_4.1.0_73009]# sh ./VBoxLinuxAdditions.run
Verifying archive integrity... All good.
Uncompressing VirtualBox 4.1.0 Guest Additions for Linux.........
VirtualBox Guest Additions installer
Removing existing VirtualBox DKMS kernel modules [ OK ]
Removing existing VirtualBox non-DKMS kernel modules [ OK ]
Building the VirtualBox Guest Additions kernel modules
Building the main Guest Additions module [ OK ]
Building the shared folder support module [ OK ]
Building the OpenGL support module [ OK ]
Doing non-kernel setup of the Guest Additions [ OK ]
Starting the VirtualBox Guest Additions [ OK ]
Installing the Window System drivers
Installing X.Org Server 1.7 modules [ OK ]
Setting up the Window System to use the Guest Additions [ OK ]
You may need to restart the hal service and the Window System (or just restart
the guest system) to enable the Guest Additions.

Installing graphics libraries and desktop services componen[ OK ]
[root@angelfish VBOXADDITIONS_4.1.0_73009]#

このお遊びの続きはいずれまた。

2011/8/16追記
実は、こんな事しなくてもちゃんとマウンドできた><。

VirtualBoxの「メニュー」>「デバイス」>「Guest Addtionsのインストール(I)...」でマウントできるじゃないですかー。失礼しました。 m(_ _)m

20110816_230742



これまでのあらずじ…

CentOS6.0 on VirtualBox4.1.0 for MacOS X(Snow Leopard)

| | | コメント (0) | トラックバック (0)

2010年10月23日 (土)

Dashboard de Aquarium v1.5

Dashboard de Aquarium v1.5 の動画あるといいな〜なんて事言われててずーっと放置プレイだったので、Quicktime XのScreen recording、iMovieとGarageBandで。

PCの音量は控えめに (^^;;

さて、次はなにをするかちょいとネタ探し中。

| | | コメント (0) | トラックバック (0)

2010年10月11日 (月)

MacOSXでCD-RWやDVD-RWに記録した内容を全て消去する方法(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

さて、さて、MacOS XのTerminalからいろいろと初心者向けじゃないことを続けて書いちゃおーかなーということで。

今回は、CD-RWととかDVD-RWとか追記可能な光ディスクに記録したデータなど不要になり全部消去したい場合の方法。
quickはオプションなので時間がかかってもよい場合はquickなしでいいよ!

discus:˜ discus$ diskutil -help
Disk Utility Tool
Utility to manage local disks and volumes.
Most options require root access to the device

Usage: diskutil <verb> <options>
<verb> is one of the following:
list (List the partitions of a disk)
information | info (Get information on a disk or volume)

unmount (Unmount a single volume)
unmountDisk (Unmount an entire disk (all volumes))
eject (Eject a disk)
mount (Mount a single volume)
mountDisk (Mount an entire disk (all mountable volumes))
rename (Rename a volume)

enableJournal (Enable HFS+ journaling on a mounted HFS+ volume)
disableJournal (Disable HFS+ journaling on a mounted HFS+ volume)

verifyVolume (Verify the structure of a volume)
repairVolume (Repair the structure of a volume)

verifyPermissions (Verify the permissions of a volume)
repairPermissions (Repair the permissions of a volume)
repairOS9Permissions (Repair the permissions for the current
Classic boot volume)

eraseDisk (Erase an existing disk, removing all volumes)
eraseVolume (Erase an existing volume)
reformat (Reformat an existing volume)
eraseOptical (Erase an optical media (CD/RW, DVD/RW, etc.))
zeroDisk (Erase a disk, writing zeros to the media)
randomDisk (Erase a disk, writing random data to the media)
secureErase (Securely erase a disk or freespace on a volume)
resizeVolume (resize a volume, increasing or decreasing its size)

partitionDisk ((re)Partition a disk, removing all volumes)

createRAID (Create a RAID set on multiple disks)
destroyRAID (Destroy an existing RAID set)
checkRAID (Check a RAID set for errors)
enableRAID (Convert a disk to a degraded RAID mirror set)
convertRAID (Convert a RAID 1.x (pre-Tiger) to a RAID 2.x (Tiger))
updateRAID (Update the settings of an existing RAID)
addToRAID (Add a spare or member disk to an existing RAID)
removeFromRAID (Remove a spare or member disk from an existing RAID)
repairMirror (Repair a damaged RAID mirror set)

diskutil <verb> with no options will provide help on that verb

discus:˜ discus$ diskutil eraseOptical -help
Disk Utility Tool
Usage: diskutil eraseOptical [quick]
[Mount Point|Disk Identifier|Device Node]
Completely erase an existing optical disk. Ownership of the affected disk is required.
Example: diskutil eraseOptical quick /dev/disk2
discus:˜ discus$

消去したいCD-RWやDVD-RWを挿入後、デバイス名を確認! この例の場合、/dev/disk1ですね。

discus:˜ discus$ mount | grep discus_cd_img
/dev/disk1s1s3 on /Volumes/discus_cd_img (hfs, local, nodev, nosuid, read-only, noowners)
discus:˜ discus$

以下の例ではquickオプション付けてます!

discus:˜ discus$
discus:˜ discus$ diskutil eraseOptical quick /dev/disk1
Started erase on disk disk1
[ + 0%..10%..20%..30%..40%..50%..60%..70%..80%..90%..100% ]
Finished erase on disk disk1
discus:˜ discus$

終了!!!

まっさらになったDISCなので以下のようなダイアログが表示される.「取り出す」ボタンをクリックして取り出しましょ!
Diskutil_quick_eraseing_cd

| | | コメント (0) | トラックバック (0)

MacOSXでCDやDVDを焼く方法 #2(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

前回は、isoイメージをshell commandで焼く方法だったけど、ついでなのでdisk imageを焼く方法をdisk imageの作成、disk imageへのファイルコピー、最後にCDを焼くという流れでも書いておきますね。 :)

コマンドの詳細はhelpをどうぞ。沢山オプションあるけど。(^^;;;

G5Server:/tmp discus$ hdiutil create -help

hdiutil create: create a disk image image
Usage: hdiutil create <sizespec> <imagepath>
Size specifiers:
-size < ?? | ??b | ??k | ??m | ??g | ??t | ??p | ??e >
(mkfile(8) plus terabytes, petabytes, & exabytes)
-sectors <count>
-megabytes <count>

Image options:
-layout <layout> [SPUD unless -fs -> NONE]
SPCD - single partition CD/DVD
SPUD - 単一パーティション
UNIVERSAL CD - CD/DVD
NONE - パーティションマップなし
UNIVERSAL HD - ハードディスク
-partitionType <partitionType> [Apple_HFS or per -fs]
-align <sector alignment> [4 aka 2K]
-ov

Filesystem options:
-fs <filesystem>
Case-sensitive HFS+ - Mac OS Extended (Case-sensitive)
Journaled HFS+ - Mac OS Extended (Journaled)
Case-sensitive Journaled HFS+ - Mac OS Extended (Case-sensitive, Journaled)
HFS+ - Mac OS Extended
HFS - Mac OS Standard
MS-DOS FAT32 - MS-DOS File System (FAT32)
MS-DOS FAT16 - MS-DOS File System (FAT16)
MS-DOS - MS-DOS File System
MS-DOS FAT12 - MS-DOS File System (FAT12)
UFS - UNIX File System
-volname <volumename> ["untitled"]
-stretch < ?? | ?b | ??k | ??m | ??g | ??t | ??p | ??e > (HFS+)

New Blank Image options:
-type <image type> [UDIF]
UDIF - 読み込み/書き込みディスクイメージ
SPARSE - スパースディスクイメージ

Image from Folder options:
-srcfolder <source folder>
-(no)anyowners do (not) attempt to preserve owners
-(no)skipunreadable do (not) skip unreadable files/directories [no]
-format <image type> [UDZO]
DC42 - Disk Copy 4.2
RdWr - NDIF 読み込み/書き込み
Rdxx - NDIF 読み込み専用
ROCo - NDIF 圧縮
Rken - NDIF 圧縮(KenCode)
UDRO - 読み込み専用
UDCO - 圧縮(ADC)
UDZO - 圧縮
UDBZ - 圧縮(bzip2)
UFBI - 装置全体
IPOD - iPod イメージ
UDxx - UDIF スタブ
UDRW - 読み込み/書き込み
UDTO - DVD/CD マスター
UDSP - スパース
UDIF - 読み込み/書き込みディスクイメージ
SPARSE - スパースディスクイメージ

Image from Device options:
-srcdevice <source dev node, e.g. disk1, disk2s1>
-format <image type> [UDZO]
DC42 - Disk Copy 4.2
RdWr - NDIF 読み込み/書き込み
Rdxx - NDIF 読み込み専用
ROCo - NDIF 圧縮
Rken - NDIF 圧縮(KenCode)
UDRO - 読み込み専用
UDCO - 圧縮(ADC)
UDZO - 圧縮
UDBZ - 圧縮(bzip2)
UFBI - 装置全体
IPOD - iPod イメージ
UDxx - UDIF スタブ
UDRW - 読み込み/書き込み
UDTO - DVD/CD マスター
UDSP - スパース
-segmentSize < ?? | ??b | ??k | ??m | ??g | ??t | ??p | ??e >
(blocks, bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, exabytes)

Attach options:
-attach attach image after creation

Common options:
-encryption <crypto method>
-stdinpass
-certificate <path-to-cert-file>
-imagekey <key>=<value>
-tgtimagekey <key>=<value>
-plist return results in plist format
-puppetstrings
-verbose
-debug
-quiet
G5Server:/tmp discus$

disk imageの作成例)

CD用に500MBのdisk imageを1パーティションかつ、HFS+で、discus_cd_imgというボリューム名作成します。ファイル名は、discus.dmgというファイル名にしておきます。
disk imageが作成されたらマウントさせるように -attachオプションも付けて!

G5Server:/tmp discus$ hdiutil create -size 500m -layout SPCD -fs HFS+ -volname discus_cd_img -attach discus
...........................................................................................................
/dev/disk3 Apple_partition_scheme
/dev/disk3s1 Apple_partition_map
/dev/disk3s2 Apple_Driver_ATAPI
/dev/disk3s3 Apple_HFS /Volumes/discus_cd_img
created: /private/tmp/discus.dmg
G5Server:/tmp discus$

作成したdisk imageへファイルをコピーして、マウントしていたdisk imageをアンマウント。

G5Server:/tmp discus$
G5Server:/tmp discus$ cp -pr /Users/discus/Pictures/DesktopPics/* /Volumes/discus_cd_img
G5Server:/tmp discus$
G5Server:/tmp discus$ hdiutil detach -help
hdiutil detach: detach disk image from system
Usage: hdiutil detach <devname>

Note: you can specify a mount point (e.g. /Volumes/MyDisk)
instead of a dev node (e.g. /dev/disk1)

Options:
-force forcibly detach

Common options:
-verbose
-debug
-quiet
G5Server:/tmp discus$
G5Server:/tmp discus$
G5Server:/tmp discus$ hdiutil detach /Volumes/discus_cd_img
"disk3" unmounted.
"disk3" ejected.
G5Server:/tmp discus$


焼き方は、isoイメージの場合と同じ。

注)

hdiutil: burn: “ディスクイメージファイル名”not recognized - リソースが一時的に使用できません。
hdiutil: burn failed - リソースが一時的に使用できません。

というエラーになった場合は、burn前に、ディスクイメージをdetachしてからburnすればOKですよ〜ん。MacOS X 10.4ではattachしたままでも焼けたけど、MacOS 10.5以降では前述のエラーを返すのでご注意を!
G5Server:/tmp discus$
G5Server:/tmp discus$ hdiutil burn discus.dmg
Please insert a disc:
ディスクに書き込むデータを準備中
セッションを開いています
トラックを開いています
トラックを書き込み中
...........................................................................................................
トラックを閉じています
............................................................................................................
セッションを閉じています
ディスクの作成を終了中
ディスク作成の検証中...
検証
.............................................................................................................
ディスクの作成は問題なく完了しました
.............................................................................................................
hdiutil: burn: completed
G5Server:/tmp discus$




MacOSXでCDやDVDを焼く方法(初心者でも分かり易い方法)
MacOSXでCDやDVDを焼く方法(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

| | | コメント (0) | トラックバック (0)

2010年10月10日 (日)

MacOSXでCDやDVDを焼く方法(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

最近、Macユーザが多くなってきた影響か、MacOSXでCDやDVDを焼く方法(初心者でも分かり易い方法)ってエントリがよくググられているので、PC初心者向きではないけど、Linux使いな方でMacは初心者って方向け、terminalからcommandでisoイメージのCDやDVDを焼く方法。

hdiutilというコマンドで焼く事ができます!
オプションはhelp参照のこと。(尚、以下のログはMacOS X 10.4なのですが、10.6でも同じです。暗号化に指定できる暗号化方法は増えてたかな。その程度の差だったはず。)

G5Server:/tmp discus$
G5Server:/tmp discus$ hdiutil burn -help

hdiutil burn: burn an image to optical media
Usage: hdiutil burn <image>
Options:
-speed <speed e.g. 1, 2, 4, 8, ... max>
-device <OpenFirmware path>
-sizequery just calculate size of disc required
-testburn don't turn on laser

Toggles:
-noeject don't eject disc after burning
-noverifyburn don't verify disc contents after burn
-noaddpmap don't add partition map
-noskipfinalfree don't skip burning final free partition
-optimizeimage optimize filesystem for burning
-forceclose close disc after burning
-nounderrun disable buffer underrun protection
-[no]synth synthesize new filesystem from volume

Common options:
-encryption <crypto method>
-stdinpass
-srcimagekey <key>=<value> (-imagekey is a synonym)
-shadow <shadowfile>
-insecurehttp
-cacert <file|dir>
-puppetstrings
-verbose
-debug
-quiet

Related actions:
-erase erase disc (quickly)
-fullerase completely erase disc
-list list all burning devices, for -device
G5Server:/tmp discus$

早速、簡単な例を1つ。CentOS5.5のLiveCDのisoイメージをMacOS X上にダウンロード後、isoイメージCDを焼いちゃいます。
MacOSXの場合、Linuxと違って、wgetじゃなくてcurlなんですよ、プログラミング言語のCurlとと勘違いしちゃう人もたまにいますけどw

※まず、curlでCentOS5.5 LiveCD isoイメージのダウンロードから!

G5Server:/tmp discus$ curl -O http://ftp.osuosl.org/pub/centos/5.5/isos/x86_64/CentOS-5.5-x86_64-LiveCD.iso

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 694M 100 694M 0 0 384k 0 0:30:47 0:30:47 --:--:-- 395k

G5Server:/tmp discus$
G5Server:/tmp discus$ ll CentOS-5.5-x86_64-LiveCD.iso
-rw-r--r-- 1 discus wheel 728184832 Oct 10 10:28 CentOS-5.5-x86_64-LiveCD.iso

G5Server:/tmp discus$

※hdiutilコマンドでburnしちゃいます。簡単。コマンドを実行するとCD/DVDトレイにDISCを挿入するように要求されるので、指示通りにDISCを挿入すれば後は、勝ってにやいてくれて、焼き終わったらEjectしてくれます。

G5Server:/tmp discus$ hdiutil burn CentOS-5.5-x86_64-LiveCD.iso
Please insert a disc:
ディスクに書き込むデータを準備中
セッションを開いています
トラックを開いています
トラックを書き込み中
............................................................................................................
トラックを閉じています
............................................................................................................
セッションを閉じています
............................................................................................................
ディスクの作成を終了中
ディスク作成の検証中...
検証
.............................................................................................................
ディスクの作成は問題なく完了しました
.............................................................................................................
hdiutil: burn: completed
G5Server:/tmp discus$



MacOSXでCDやDVDを焼く方法(初心者でも分かり易い方法)
MacOSXでCDやDVDを焼く方法 #2(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

| | | コメント (0) | トラックバック (0)

2009年9月18日 (金)

QuickTime X - Screen Recorder

以前もちょっと書いたけどSnow LeopardのQuickTime XからScreen Recorder機能が追加されている。この機能、操作ガイドなど作るのには非常に便利。いままではShareware使ってたりした方が多かったと思うけどSnow LeopardならQuickTime Xを起動してレコーディングすだけでOK。

1

QuickTime X Playerを起動して「メニュー」→「新規画面収録 ^⌘N」を選択。
2

「画面収録」ダイアログが表示された録画ボタンをクリックするだけで開始される。デフォルトの保存場所は「ムービー」フォルダ」。
「収録の開始」ボタンをクリックすると操作が録画される。

6

4

実際に収録したムービーは次の記事の時にで使う予定。

| | | コメント (0) | トラックバック (0)

2009年6月29日 (月)

MacBook Pro と MacOS X 10.5.7 と Sleep と Hang-up

Macオンリーな話題です。

先日、MacBook ProのMacOS X 10.5.6を10.5.7にアップデートしてからSleepさせるとファンがスリープせずファンが凄い勢いで廻りっぱなしになったり、ハングアップしまくっていた。おかしいね、アップデータが問題なんだろうねと話していたら、すかさず、アップデータのアップデータが出ていたのでアップデートした。
すると、ファンが凄い勢いで廻りっぱなしになる現象は消えたものの99%の確立でSleepさせようとするとハングアップするようになった。なんだよ〜〜早くバグ直せ〜〜とAppleさんにお怒りモードの私。

しばらく考えこんだあげく、Apple Supportのforumを検索してみると日本のフォーラムには無かったが、USのフォーラムにはそれらしき対処方法があった。。。。。
ということで先日から試しているが、それ以降一度もハングしていない!!!!
やった〜〜〜。

とにかくAppleさん、ちゃんとバグ修正してね!!

以下、症状と対処方法を書いておきます。


症状:
MacBook Pro 15inchでMacOS X 10.5.7へアップデート後からSleepさせようとするとハングアップする。ハングアップしたまま冷却ファンが凄い勢いで廻りっぱなしになる。

Hang_when_sleep_002_2

対処方法:
システム環境設定→ネットワーク

Hang_when_sleep_001

左側のネットワークポートペインにイーサネットポートがリストされていない場合、本現象に遭遇する。"+"ボタンで使っていなくてもイーサーネットポートを追加する。
Hang_when_sleep_003
Hang_when_sleep_004
Hang_when_sleep_005

イーサーネットポートを追加したらOSを再起動する。このような対処でSleepさせようとするとhangしてしまう問題を回避できる。
(対処から4日経過したがいまのところ問題は再発していない。もし状況に変化があれば本記事を更新する予定。)

| | | コメント (0) | トラックバック (0)

2009年5月14日 (木)

Leopard de Oracle10g R2 (Intel x86-64) #8 - (APEX3.2と遊ぶ #1)

さて、前回予告していたようにOracle10g R2 10.2.0.4.0 for MacOSX (Intel x86-64)へOracle Application Express 3.2をインストールして無理矢理使ってみましょう! の第一話。

Oracle Application Express3.2(以下、APEX3.2)は、http://www.oracle.com/technology/products/database/application_express/download.htmlからダウンロード。


2

今回はOracle10g R2 10.2.0.4.0 for MacOSX (Intel x86-64)APEX3.2の英語環境をインストールするところまでを試してみる。
PL/SQLパッケージ等の作成がメインだから問題になりそうな箇所はないんだけど、なんで未サポートなんだろう?。 Oracle HTTP Serverが未サポートだからその影響か?


上記URLからダウンロードしたAPEX3.2を解凍したディレクトリに移動(MacOSX Leopardでは通常Downloadフォルダへダウンロードされる。)

guppy:˜ oracle$ 
guppy:˜ oracle$ cd /Users/oracle/Downloads/apex
guppy:apex oracle$ ll
total 424
-r--r--r--@ 1 oracle oinstall 690 9 10 2008 apex_epg_config.sql
-r--r--r--@ 1 oracle oinstall 15749 1 18 17:38 apex_epg_config_core.sql
-r--r--r--@ 1 oracle oinstall 5208 1 8 13:33 apexins.sql
-r--r--r--@ 1 oracle oinstall 8476 1 8 13:33 apexvalidate.sql
-r--r--r--@ 1 oracle oinstall 1549 1 8 13:33 apxchpwd.sql
-r--r--r--@ 1 oracle oinstall 2830 12 23 18:39 apxconf.sql

・・・・中略・・・・

drwxr-xr-x@ 873 oracle oinstall 29682 2 11 05:50 images
-r--r--r--@ 1 oracle oinstall 1295 12 23 18:39 load_trans.sql
drwxr-xr-x@ 64 oracle oinstall 2176 2 8 19:34 owa
drwxr-xr-x@ 9 oracle oinstall 306 2 8 21:30 utilities
-rw-rw-r-- 1 oracle oinstall 4803 2 17 12:13 welcome.html
guppy:apex oracle$
guppy:apex oracle$

ディレクトリを移動後、SQL*Plusから赤太文字で示したapexins.sql(前述)を実行する。
実行時のパラメータはAPEXのマニュアルを見てね

guppy:apex oracle$ sqlplus /nolog

SQL*Plus: Release 10.2.0.4.0 - Production on 水 5月 13 19:08:04 2009

Copyright (c) 1982, 2007, Oracle. All Rights Reserved.

> conn / as sysdba
接続されました。
SYS> @apexins SYSAUX SYSAUX TEMP /i/

セッションが変更されました。

. ____ ____ ____ ____
. / \ | \ /\ / | /
.| || / / \ | | |
.| ||--- ---- | | |--
.| || \ / \ | | |
. \____/ | \/ \ \____ |____ \____
.
. Application Express Installation.
...................................
.
... Checking prerequisites

・・・・中略・・・・

I. I N S T A L L P R E - C R E A T E T A B L E S P E C S
...wwv_flow_init_htp_buffer

プロシージャが作成されました。

エラーはありません。

権限付与が成功しました。

・・・・中略・・・・

II. I N S T A L L F L O W T A B L E S
...create v function stub

ファンクションが作成されました。

エラーはありません。
...create nv function stub

ファンクションが作成されました。

エラーはありません。

・・・・中略・・・・

III. I N S T A L L F L O W P A C K A G E S P E C S
...wwv_flow_hot_http_links

ファンクションが作成されました。

エラーはありません。

・・・・中略・・・・

Package Specsのタイミング。
経過: 00:00:11.13

IV. I N S T A L L F L O W P A C K A G E B O D I E S
...wwv_dbms_sql
エラーはありません。

・・・・中略・・・・

Package Bodiesのタイミング。
経過: 00:02:50.20

V. P E R F O R M F L O W G R A N T S
Installing flows_files objects 2
...create flows_files
...trigger wwv_biu_flow_file_objects
エラーはありません。
Grantsのタイミング。
経過: 00:00:00.09

VI. I N S T A L L F L O W S
define "^" (hex 5e)
...internal messages
APPLICATION 4411 - APEX - System Messages
Set Credentials...
Check Compatibility...
WWV_FLOW_API.FLOWS_API_LAST_EXTENDED20090112
WWV_FLOW_API.CURRENT_FLOWS_VERSION20090112

・・・・中略・・・・

......Message f4400_p12_howto_step7
......Message internal_users
......Message database_users
......Message f4400_p22_obj_created
......Message f4400_p10_queries_help
......Message f4400_p9_tables_help
......Message f4400_p22_table_info
......Message f4400_p8_forms_help
......Message f4400_p20_reports_help
......Message f4400_p49_defs_help
......Message wwv_flow_item_protection_save_item_internal_only_show
......Message wwv_flow_sw_parser_line_exceeds_32k
......Message wwv_flow_sw_api_check_priv_obj
......Message f4050_approved_account_req_display
......Message archivelog

・・・・中略・・・・

Install Internal Flowsのタイミング。
経過: 00:00:34.94

...Development install if necessary

セッションが変更されました。

I. O R A C L E S Y S I N S T A L L P R O C E S S
dev_grants
...grant APEX owner development privileges

・・・・中略・・・・

...done grant APEX owner core privileges
...CONNECT as the Oracle user who will own the APEX engine

セッションが変更されました。

III. I N S T A L L F L O W P A C K A G E S P E C S
wwv_flow_plsql_editor.sql

・・・・中略・・・・

Development Package Specsのタイミング。
経過: 00:00:40.17
wwv_flow_plsql_editor.plb

パッケージ本体が作成されました。

・・・・中略・・・・

Development Package Bodiesのタイミング。
経過: 00:00:29.69

権限付与が成功しました。


PL/SQLプロシージャが正常に完了しました。

VI. I N S T A L L F L O W S
define "^" (hex 5e)
APPLICATION 4000 - APEX - Application Builder
Set Credentials...
Check Compatibility...
API Last Extended:20090112
Your Current Version:20090112

・・・・中略・・・・

Install Internal Development Applicationsのタイミング。
経過: 00:09:10.55

load verification images

PL/SQLプロシージャが正常に完了しました。

エラーはありません。

・・・・中略・・・・

Thank you for installing Oracle Application Express.

Oracle Application Express is installed in the APEX_030200 schema.

The structure of the link to the Application Express administration services is as follows:
http://host:port/pls/apex/apex_admin (Oracle HTTP Server with mod_plsql)
http://host:port/apex/apex_admin (Oracle XML DB HTTP listener with the embedded PL/SQL gateway)

The structure of the link to the Application Express development interface is as follows:
http://host:port/pls/apex (Oracle HTTP Server with mod_plsql)
http://host:port/apex (Oracle XML DB HTTP listener with the embedded PL/SQL gateway)

JOB_QUEUE_PROCESSES: 10

PL/SQLプロシージャが正常に完了しました。


セッションが変更されました。


Performing Application Express component validation - please wait...

Completing registration process.
Validating installation.

PL/SQLプロシージャが正常に完了しました。

Validate Installationのタイミング。
経過: 00:02:04.77

・・・・中略・・・・

VII. L O A D E N G L I S H D I C T I O N A R Y

トリガーが変更されました。

...10000 rows
...20000 rows
...30000 rows
...40000 rows
...50000 rows
...60000 rows
...70000 rows
English Dictionaryのタイミング。
経過: 00:02:58.72
Upgradeのタイミング。
経過: 00:00:01.15
...End of install if runtime install
...create null.sql
Development Installationのタイミング。
経過: 00:17:28.33
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing optionsとの接続が切断されました。
guppy:apex oracle$
guppy:apex oracle$


特に問題もなくAPEX3.2の英語版はインストールできた。これなら次も順調かもよ・・・・・


次はAPEXのバージョン確認(FAQですが)。
apex_030200.wwv_flows_release関数を使えば確認できます。
ちなみにhtmldbなんて呼ばれていたころから flows_02000とか、リリース毎にスキーマ名が変る点にご注意を。但し関数名は変らないのでスキーマ名だけ注意すれば問題ないです。

guppy:apex oracle$ sqlplus /nolog

SQL*Plus: Release 10.2.0.4.0 - Production on 水 5月 13 19:08:04 2009

Copyright (c) 1982, 2007, Oracle. All Rights Reserved.

> conn / as sysdba
接続されました。

SYS> select apex_030200.wwv_flows_release from dual;

WWV_FLOWS_RELEASE
--------------------------------------------------------------------------------
3.2.0.00.27

SYS>

次回、日本語リソースのインストール。毎回思うけど、apexins.sqlで一緒にやってくれないかな?
jaとか指定するとenにつづいてjaをインストールしてくれるとか・・・・面倒で・・・。




バックナンバー

Oracle10g R2 for MacOSX (Intel x86-64) released !!!
Leopard de Oracle10g release 2 (Intel x86-64)
Leopard de Oracle10g R2 (Intel x86-64) #1
Leopard de Oracle10g R2 (Intel x86-64) #2
Leopard de Oracle10g R2 (Intel x86-64) #3 (ちょいと寄り道)
Leopard de Oracle10g R2 (Intel x86-64) #4 (Companion CD installation)
Leopard de Oracle10g R2 (Intel x86-64) #5 (dbstart and dbshut does not work!! But....)
Leopard de Oracle10g R2 (Intel x86-64) #6 (onsがバグっている?件..)
Leopard de Oracle10g R2 (Intel x86-64) #7 (iSQL*Plusのメッセージがものすごく怖い件)

| | | コメント (0) | トラックバック (0)

2009年5月 8日 (金)

MacOSXでCDやDVDを焼く方法(初心者でも分かり易い方法)

義理の妹が妻のお下がりのIMac G5を使いだしてからいろいろと質問されることがあるのだが専門的な用語はあまり使えない(Mac初心者なので)。多分言葉より分かり易いのはやはり絵で見る方だよね?!。
ということで、一昨日「iPhotoで管理している画像ファイルをCDやDVDに記録するにはどうするの?」という質問への回答。
(ちなみに、MacOSXでCDやDVDに画像データ等を直接書き出す方法として複数の方法があるのだが今回はMacOSX初心者でも簡単な方法を紹介しておくね。)


「Finderのメニュー」→「ファイル」→「新規ディスク作成フォルダ」をクリック。
Finder_de_make_disc_001

ディスクトップ上に以下のようなフォルダが作成されるのでお好きな名前を付けておく
Finder_de_make_disc_002

新規ディスク作成フォルダをダブルクリックして開くと以下のようになっている。
Finder_de_make_disc_003

次にiPhotoを起動してCDやDVDに書き込みたい画像を選択する。(以下の例では、イベント単位で複数選択している。イベントを複数選択するときは⌘キーを押しながら該当イベントをクリックする。)
Finder_de_make_disc_005

次に「iPhotoメニュー」→「ファイル」→「書き出し」を選択する。
Finder_de_make_disc_006

「写真の書き出し」ダイアログが表示されるので、「ファイルの書き出し」タブで「種類」は”現在”、「サイズ」は”フルサイズ”となっていることを確認し「書き出し」ボタンをクリックする。
Finder_de_make_disc_007

次に「ファイル選択」で先ほど作成した「新規ディスク作成フォルダ」を選択して「OK]ボタンをクリック。
Finder_de_make_disc_008



Finder_de_make_disc_009

書き出しが終わったら、「新規ディスク作成フォルダ」内にある「ディスクを作成」ボタンをクリック。
空のCDかDVDを挿入するよう指示されるので、CD(CD-R/RW等)又はDVD(DVD-R/RW等)を挿入する。

Finder_de_make_disc_010

Finder_de_make_disc_011

挿入すると書き込み速度やディスク名を聞かれるので必要であれば入力する。(「新規ディスク作成フォルダ」に名称を付けていればその名称が設定される)
確認後、「ディスクを作成」ボタンをクリックする。(あとはディスクが焼終わるのを待つだけ。

Finder_de_make_disc_012

Finder_de_make_disc_013_2

この例ではCD-RWを利用して約8MBの画像ファイルを書き込んだ。書き込みが完了すると以下のような感じでデスクトップ上に現れる。ダブルクリックして開けばiPhotoから書き出した画像ファイルが確認できるはず。あとはディスクを取り出せば終わり。:)

ディスクに書き出した「新規ディスク作成フォルダ」はゴミ箱へポイしてしまえばOK.

どう? わかった? 簡単でしょ?!ーー>義妹

Finder_de_make_disc_014

Finder_de_make_disc_015




おまけ。

ちなみに、もし「新規ディスク作成フォルダ」へ、挿入するCDやDVDに書き込めないくらいの画像データ等を入れてしまった場合はちゃ〜〜〜〜んと、駄目よ〜〜〜と警告を出してくれるからCDやDVDに書き込める程度になるまでファイルを削除してね!、1枚に入りきれなければまた別のディスクを焼けばいいわけだから。:)


以下の例は、「新規ディスク作成フォルダ」へ約2.6GBのデータを入れた状態。(DVDじゃないと書き込めないサイズになってます。)
「ディスク作成」ボタンをクリックすると2.63GB以上の容量のあるディスクが必要だと警告を出してくれるし、書き込みまで進めてしまってもディスクに書き込む前に容量不足で書き込めないことを知らせてくれます。

Finder_de_make_disc_017

Finder_de_make_disc_018_2

Finder_de_make_disc_019

Finder_de_make_disc_020

Finder_de_make_disc_021




2010/10/10追記

MacOSXでCDやDVDを焼く方法(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)
MacOSXでCDやDVDを焼く方法 #2(Unix/Linux使いだけど、MacOS Xは初心者って方向けの方法)

| | | コメント (2) | トラックバック (0)

2009年4月 2日 (木)

ちょいと迷惑だったストアドファンクション(おまけ)

〜〜〜〜し〜〜し〜〜でぃ〜〜っ。む、ぶん!
やけに耳の残っちゃうんだよな〜。あの声とアクセント

なんて関係ない話はおいといて。。と



以前、
ちょいと迷惑だったストアドファンクション
ちょいと迷惑だったストアドファンクション(つづき)

ということを書いていたが、ちょいと迷惑だったストアドファンクションの例外ハンドラーで問題解決の手がかりとするためログ出力が実装されていたらどうするの? っていうこともあるのでおまけのおまけということで書いておきます。

以前の例では例外ハンドラーをバッサリ切り捨ててしまったのですが、例外ハンドラーで問題解決の手がかりとするためのログ出力が実装されていて例外ハンドラーを取り除くことが出来ない場合、例外名無しのRAISE文を使えば解決できますよ。:)

ちょいと迷惑だったストアドファンクションの例外ハンドラーで問題解決の手がかりとするためのログ出力を実装していたらという例・・・・(赤太字部分)

尚、この例ではオープンソースのPL/SQL向けロギングフレームワークLog4PLSQLを利用している。

CREATE OR REPLACE
PACKAGE inconvenient_package
AS
TYPE employeesType IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
TYPE membersType IS RECORD (
deptNo dept.deptNo%TYPE
,deptName dept.dName%TYPE
,employees employeesType
);
FUNCTION inconvenient_function(iDeptNo IN dept.deptNo%TYPE)
RETURN membersType;
END inconvenient_package;
/
show errors


CREATE OR REPLACE
PACKAGE BODY inconvenient_package
AS
FUNCTION inconvenient_function(iDeptNo IN dept.deptNo%TYPE)
RETURN membersType
AS
members membersType;
Log4PlsqlCtx plogparam.log_ctx := plog.init(pDBMS_OUTPUT => TRUE); -- Log4PLSQL setting
BEGIN
SELECT
deptNo
,dName
INTO
members.deptNo
,members.deptName
FROM
dept
WHERE
deptNo = iDeptNo
;
--
SELECT *
BULK COLLECT INTO
members.employees
FROM
emp
WHERE
deptno = iDeptNo
;
RETURN members;
EXCEPTION
WHEN NO_DATA_FOUND THEN
plog.warn(Log4PlsqlCtx, sqlerrm()); -- Log4PLSQL warnning
RETURN NULL;
END inconvenient_function;
END inconvenient_package;
/
show errors


変更前ファンクションの例外ハンドラー部分にある"RETURN NULL;"を"RAISE;"(例外名無しのRAISE文)に変更するだけ。

CREATE OR REPLACE
PACKAGE BODY inconvenient_package
AS
FUNCTION inconvenient_function(iDeptNo IN dept.deptNo%TYPE)
RETURN membersType
AS
members membersType;
Log4PlsqlCtx plogparam.log_ctx := plog.init(pDBMS_OUTPUT => TRUE); -- Log4PLSQL setting
BEGIN
SELECT
deptNo
,dName
INTO
members.deptNo
,members.deptName
FROM
dept
WHERE
deptNo = iDeptNo
;
--
SELECT *
BULK COLLECT INTO
members.employees
FROM
emp
WHERE
deptno = iDeptNo
;
RETURN members;
EXCEPTION
WHEN NO_DATA_FOUND THEN
plog.warn(Log4PlsqlCtx, sqlerrm()); -- Log4PLSQL warnning
RAISE;
END inconvenient_function;
END inconvenient_package;
/


例外ハンドラー内で例外名を省略すると該当する例外がさらに外側のブロックへスローされます。詳細はマニュアル(Oracle Database PL/SQL Language Reference 11g Release 1 : RIASE statement)及び、Oracle Database PL/SQL Language Reference 11g Release 1 : How PL/SQL Exceptions Propagate参照のこと。

SCOTT> set linesize 132
SCOTT> set serveroutput on
SCOTT> l
1 DECLARE
2 members inconvenient_package.membersType;
3 Log4PlsqlCtx plogparam.log_ctx := plog.init(pDBMS_OUTPUT => TRUE);
4 BEGIN
5 BEGIN
6 members := inconvenient_package.inconvenient_function(99);
7 DBMS_OUTPUT.PUT_LINE(
8 'Dept name/id:'
9 ||members.deptname
10 ||'/'||TO_CHAR(members.deptno)
11 );
12 FOR i IN members.employees.FIRST..members.employees.LAST LOOP
13 DBMS_OUTPUT.PUT_LINE(' -- '||members.employees(i).ename);
14 END LOOP;
15 EXCEPTION
16 WHEN NO_DATA_FOUND THEN
17 plog.warn(Log4PlsqlCtx, sqlerrm());
18 END;
19* END;
SCOTT> /
12:35:27:02-WARN-block-->SCOTT.INCONVENIENT_PACKAGE ORA-01403: データが見つかりません。
12:35:27:03-WARN-block ORA-01403: データが見つかりません。

PL/SQLプロシージャが正常に完了しました。

SCOTT> l
1 DECLARE
2 members inconvenient_package.membersType;
3 Log4PlsqlCtx plogparam.log_ctx := plog.init(pDBMS_OUTPUT => TRUE);
4 BEGIN
5 BEGIN
6 members := inconvenient_package.inconvenient_function(10);
7 DBMS_OUTPUT.PUT_LINE(
8 'Dept name/id:'
9 ||members.deptname
10 ||'/'||TO_CHAR(members.deptno)
11 );
12 FOR i IN members.employees.FIRST..members.employees.LAST LOOP
13 DBMS_OUTPUT.PUT_LINE(' -- '||members.employees(i).ename);
14 END LOOP;
15 EXCEPTION
16 WHEN NO_DATA_FOUND THEN
17 plog.warn(Log4PlsqlCtx, sqlerrm());
18 END;
19* END;
SCOTT> /
Dept name/id:ACCOUNTING/10
-- CLARK
-- KING
-- MILLER

PL/SQLプロシージャが正常に完了しました。

SCOTT>

| | | コメント (0) | トラックバック (0)

2008年4月17日 (木)

埋込みPL/SQL Gatewayで外部URLアクセス

Oracle11g R1 11.1.0.6.0 for Linux x86 and Oracle Application Expressという記事で埋込みPL/SQL GatewayをでPython Challengeを試してみたのだが・・・埋込みPL/SQL Gatewayを利用した場合、UTL_HTTPパッケージで外部のURLへアクセスすることはできないみたいなので、あきらめて通常のmod_plsqlでやってみるか・・・。Oracle11gからならACLで許可してやればアクセスできるのに、Oracle11gに内蔵されているXDB HTTP Serverではそれは効かないのね。。遊べると思ったのに。。orz.(Oracle11gの埋込みPL/SQL Gatewayの備忘録として残しておくか。。)

エラーメッセージは以下の通り。(Apexを3.1にするとできるようになったりして。。。2008/4/19追記)

ORA-31020:操作できません。 理由:For security reasons, ftp and http access over XDB repository is not allowed on server side

以下、埋込みPL/SQL Gatewayを利用したOracle Application Expressのスクリーンショット
9


4/18追記
どうもこれは埋込みPL/SQL GatewayというよりOracle Application Express側の影響のように思えてきた。10gのmod_plsql経由でもApexでは同じエラーだった。
ちなみに、mod_plsql経由で呼び出すだけならACL定義でアクセスするホストを許可してやるだけでOK。

以下にその時のスクリーンショットを載せておく。
答えにLevel17の答えに結びつく部分はぼかしてますよ〜〜。

19


でも何故、ApexからUTL_HTTPを利用するとXDB絡みのエラーになるんだ? 不思議すぎる。。。これは追々調べておくか。。何か手順から漏れていることがあったりして。。。。つづく。

さらに追記、やはり出来ない(11gからはできなくなったと言ったほうがいいだろう)というのが正しいようだ。10gではApexから直接UTL_HTTPパッケージからHTTPリクエスト出してfeedやらHTMLやらアクセスし放題だったのに〜〜〜、プンプンのプン。 ふ〜普通のmod_plsqlでやりますよ〜〜〜〜だ。

| | | コメント (0) | トラックバック (0)

2008年4月14日 (月)

Java AppletがWindowsのJavaSE6.0 Update5で動作しない件

megawattさんのブログは見ていたものここ数日コメント欄までは見ていなかったよ。megawattさんが忙しいのは知ってたけどメールくれれば調べといたのに〜。 :)
megawattさんのブログの日本の借金時計は私のブログ内にあるアプレットを呼び出しているだけですから。

ということで調べてみた。

おかげで、何ヶ月か振りでWindowsを起動しちゃったよ。(笑

結論から言うと、Java SE6.0 update 5のバグっぽい。

試してみたところWindows XP SP1(32bit)にJ2SE5.0のJREのみインストールしてある場合にはIE6、FIrefox2、Safari3.1とも特に問題もなくapplet動作する

次に、J2SE5.0をアンインストールした後にJavaSE6.0のJREのみインストールし、IE6、FIrefox2、Safari3.1でappletを起動してみると、IE6以外はappletが正しく動作せず、Firefox2やSafari3.1ではその後挙動があやしくなりHang upしたり、強制終了すると起動しなくなったりと散々な結果になった。IEはやはり動きが特殊なのなかぁ。
以下、その時のスクリーンショット。

Javase6_applet_win01

Javase6_applet_win02 Javase6_applet_win03

Javase6_applet_win04


Javase6_applet_win06


Javase6_applet_win07


Javase6_applet_win08


それは置いといて、JavaSE6.0でappletを動作させる方法は、このブログに書かれていた。(ありがとうございます)ので早速試してみると、お〜〜お見事! 回避できた。
Javase6_applet_win09

Javase6_applet_win10

Javase6_applet_win11


ちなみに、MacOSXではJavaSE6.0はPreview ReleaseのままですがWindowsのコントロールパネルと違い、Appletを実行するjavaのversionを簡単に切り替えられる。
とは言ってもMacOSX版では今のところ今回のような問題は起きていない(ようだ)。

Javase6_applet_mac_preference


ちなみに、MacOSX 10.4.11 PowerPC G5ではOpera9.25/Safari3.1/Firefox2.0.0.13ともOKだった。

| | | コメント (7) | トラックバック (0)

2008年2月 4日 (月)

Oracle SQL Developer for MacOSX で Oracle Instant Clientを使ってみる

前回、Oracle Instant Client 10g R1 for MacOSX(PPC)がMacOSX LeopardのRosettaによって動作するところまでは確認できたということを書いたが、今日は、Oracle SQL Developer 1.2.1 for MacOSX(現時点での最新版)で Oracle Instant Client 10g R1のTNS接続する方法を書いておくことにする。

SQL*PlusをTerminalやxtermから起動するのであれば .bashrc 等で環境変数を設定すればよいですが、Oracle SQL DeveloperのようなGUIツールはどうすればいいのか?
MacOSXのGUI環境では、.bashrc等は利用できない。その変わりにenvironment.plistが利用される。environment.plistの説明は、Apple Technical Q&A QA1067 : Setting environment variables for user processesを見てもらうとして早速試してみます。

Oracle_sql_developer_sprash Oracle_sql_developer_1_2_1

まず、最初は、environment.plistが存在しない状態でOracle SQL Developerを起動し、前回設定したOracle Instant Client 10g R1 for MacOSX(PPC)のtnsnames.oraを利用したTNS接続が行えるか確認してみる。
Oracle_sql_developer_create_conne_2


tnsnames.oraの定義してある接続文字列がドロップダウンメニューに現れないですね。TNS_ADMIN環境変数などが効いていません。
Oracle_sql_developer_not_appear_con


次にApple Technical Q&A QA1067 : Setting environment variables for user processesの説明通りにenvironment.plistを作成後、一旦ログオフする。
(尚、environment.plistに設定する環境変数に関しては、前回のエントリを参照してください。

Last login: Mon Feb  4 18:58:45 on console
Macintosh:˜ discus$ mkdir .MacOSX
Macintosh:˜ discus$ cat environment.plust
cat: environment.plust: No such file or directory
Macintosh:˜ discus$ cat environment.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>DYLD_LIBRARY_PATH</key>
<string>/Users/Shared/oracleInstantClient</string>
<key>NLS_LANG</key>
<string>Japanese_Japan.AL32UTF8</string>
<key>ORACLE_HOME</key>
<string>/Users/Shared/oracleInstantClient</string>
<key>TNS_ADMIN</key>
<string>/Users/Shared/oracleInstantClient</string>
</dict>
</plist>
Macintosh:˜ discus$ mv environment.plist .MacOSX
Macintosh:˜ discus$

再度、ログインして、Oracle SQL Developerを起動し同じ手順でTNS接続できるか確認する。
お〜〜、こんどは、tnsnames.oraに定義してある接続文字列がドロップダウンメニューにリストされています。
Oracle_sql_developer_appear_connect


接続テストも上手く行きました。
Oracle_sql_developer_test_session_s


最後に、SQL WorkSheetなどでEMP表にデータを登録したり問い合わせたりしてみました。これまた問題なく使えます。
Oracle_sql_developer_insert_data_to

Oracle_sql_developer_query_data_fro


この例では、MacOSX Leopard(Intel Mac)のRosetta配下で動作しているOracle Instant Clientを利用しましたが、MacOSX Tiger(PPC)版でも同様の手順で動作します。
興味のある方はお試しあれ。。..

| | | コメント (0) | トラックバック (0)

2008年1月27日 (日)

MacOSX で Eclipse の日本語化(備忘録)

Eclipseの日本語化プラグインのインストール手順について以下を参考にしたのだが、MacOSX以外の記述もあり少々分かりにくかったので自分なりの備忘録として書いておく。
とはいってもMacOSX上で利用する可能性は低いのだけれど・・・
Eclipse Wiki - 日本語化に関する記述

だったら何故、EclipseをMacOSXで使おうと思ったのか?・・・・

先日、OTN-Jのエントリに回答したのがきっかけで・・・、仕事ではEclipseを利用する事が多いウチの奥さんと

私     :「MacOSXでEclipseを日本語化する手順ってブログで書いてたっけ?
ウチの奥さん:「書いてないと思う」

という話になりこのエントリを書く気になったというわけ。。。(個人的には日本語化しないと使いにくいというわけでもないのですが・・・)

2008/1/28追記
奥さん曰く、Eclipseネタは沢山あるからネタとしてはつまらないし、誰かがもう書いていると思ったからだとか。。。

今回利用した Eclipse for MacOSXのバージョンは、3.3.1.1
日本語化プラグインは、pleiades-all-in-one-java-wtp_20071121.zip
MacOSX : version 10.4.11

http://www.eclipse.org/downloads/
http://mergedoc.sourceforge.jp/

※ダウンロードしたEclipseと日本語化プラグインはぞれぞれ適当なフォルダで解凍済み。

以下の例では、Eclpseと日本語化プラグインはそれぞれ

/Volumes/ExtraDisk/Application



/Volumes/DiscusWork/temp

以下に解凍した。

Last login: Wed Jan 23 17:16:05 on console
Welcome to Darwin!
G5Server:˜ discus$
G5Server:˜ discus$ cd /Volumes/ExtraDisk/Application/eclipse/
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ ls -l
total 64
drwxr-xr-x 3 discus discus 102 Nov 4 02:47 Eclipse.app
drwxr-xr-x 10 discus discus 340 Jan 23 18:06 configuration
lrwxrwxrwx 1 discus discus 34 Jan 23 17:48 eclipse -> Eclipse.app/Contents/MacOS/eclipse
-rw-r--r-- 1 discus discus 16536 Nov 4 02:47 epl-v10.html
drwxr-xr-x 104 discus discus 3536 Jan 23 17:50 features
-rw-r--r-- 1 discus discus 6506 Nov 4 02:47 notice.html
drwxr-xr-x 461 discus discus 15674 Jan 23 17:52 plugins
drwxr-xr-x 3 discus discus 102 Nov 4 02:47 readme
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$

※これが重要。
日本語化パッケージのconfiguration、features、pluginsの各ディレクトリ以下にある全ファイルをコピーする(注:ディレクトリごと上書きしないように!)

G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ cp -Rf /Volumes/DiscusWork/temp/pleiades-all-in-one-java-wtp_20071121/configuration/*.* configuration
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ cp -Rf /Volumes/DiscusWork/temp/pleiades-all-in-one-java-wtp_20071121/features/*.* features
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ cp -Rf /Volumes/DiscusWork/temp/pleiades-all-in-one-java-wtp_20071121/plugins/*.* plugins
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$

※eclipse.iniを編集。

(configuration以下のelipse.iniではないので注意。)
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ cat Eclipse.app/Contents/MacOS/eclipse.ini
-showsplash
org.eclipse.platform
-vmargs
-Xdock:icon=../Resources/Eclipse.icns
-XstartOnFirstThread
-Xms40m
-Xmx512m
-XX:MaxPermSize=256m
-javaagent:/Volumes/ExtraDisk/Application/eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar=no.mnemonic
-Dosgi.requiredJavaVersion=1.5
-Dorg.eclipse.swt.internal.carbon.smallFonts


※細かいことは考えずにとりあえず、-cleanを付けて起動しちゃう。(日本語プラグイン追加後に一度だけ。)

G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ 
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$ ./eclipse -clean &
[1] 1814
2008-01-23 18:06:41.005 eclipse[1704] [Java CocoaComponent compatibility mode]: Enabled
2008-01-23 18:06:41.005 eclipse[1704] [Java CocoaComponent compatibility mode]: Setting timeout for SWT to 0.100000

G5Server:/Volumes/ExtraDisk/Application/eclipse discus$
[1]+ Done ./eclipse -clean
G5Server:/Volumes/ExtraDisk/Application/eclipse discus$

上手くできました〜。 :)


Eclipse1 Eclipse2

Eclipse3

Eclipse4

最後の画像は、最初に

先日、OTN-Jのエントリに回答したのがきっかけで・・・、

と書いた”きっかけ”の話題に関連したおまけ・・・次回のエントリはそのネタにしようかと思う。

| | | コメント (0) | トラックバック (0)

2008年1月22日 (火)

Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #4 のおまけ

Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #4では専用サーバ接続であるためoracle.exeがユーザメモリ空間の制限である2GBを超えてスレッドを作れずにORA-12518が発生していた。
では2GBを超えない範囲で共有サーバを起動して接続した場合はどうなるだろうか?(大抵の方は共有サーバ接続であればこの問題を解決できることは認識されていると思うが共有サーバ接続にした場合、専用サーバ接続に比べデメリットもある。テスト環境でしっかりテストし条件を満たせるかしっかり見極める必要がある。共有サーバ接続のご利用は計画的に!!)

2006年のOOWで興味深い資料が公開されていたのでURLを載せておきます。
http://www.oracle.co.jp/openworld/tokyo2006/session_download/1B-8.pdf

以下に示した初期化パラメータ以外は以前のエントリで記載した値に同じ

SYS> alter system set shared_servers=5 scope=both;

システムが変更されました。

SYS> alter system set dispatchers='(PROTOCOL=TCP)(SERVICE=catfishS)(DIS=10)' scope=both;

システムが変更されました。

SYS> select name,network from v$dispatcher;

NAME NETWORK
---- --------------------------------------------------------------------------------
D000 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=3818))
D001 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1463))
D002 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1464))
D003 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1465))
D004 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1466))
D005 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1467))
D006 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1468))
D007 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1469))
D008 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1470))
D009 (ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=1471))


10行が選択されました。

SYS> $lsnrctl service

SYS> select count(*) from v$session;

COUNT(*)
----------
16


ここでJDeveloperから接続(400セッション)接続に利用したコードは以前テストで利用したものと同じ。
Shared_server_jdev

Shared_server_win_mon

SYS> select count(*) from v$session;

COUNT(*)
----------
416

SYS> $lsnrctl service

LSNRCTL for 32-bit Windows: Version 10.2.0.2.0 - Production on 20-1月 -2008 14:47:56

Copyright (c) 1991, 2005, Oracle. All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=catfish.macdeoracle.jp)(PORT=1521)))に接続中
サービスのサマリー...
サービス"catfish.macdeoracle.info"には、1件のインスタンスがあります。
インスタンス"catfish"、状態READYには、このサービスに対する1件のハンドラがあります...
ハンドラ:
"DEDICATED" 確立:0 拒否:0 状態:ready
LOCAL SERVER
サービス"catfishS.macdeoracle.info"には、1件のインスタンスがあります。
インスタンス"catfish"、状態READYには、このサービスに対する10件のハンドラがあります...
ハンドラ:
"D009" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 320>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2213))
"D008" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 2756>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2212))
"D007" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 2912>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2211))
"D006" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 856>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2210))
"D005" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 2092>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2209))
"D004" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 1256>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2208))
"D003" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 3236>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2206))
"D002" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 3248>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2205))
"D001" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 3312>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2204))
"D000" 確立:40 拒否:0 現行:40 最大:1002 状態:ready
DISPATCHER <machine: CATFISH, pid: 3288>
(ADDRESS=(PROTOCOL=tcp)(HOST=catfish.macdeoracle.jp)(PORT=2203))
コマンドは正常に終了しました。

SYS>
SYS>

| | | コメント (0) | トラックバック (0)

2007年12月21日 (金)

太った空白

書こうか迷っていたのだが、意外とハマっている方が多いらしいので書いておく事にした。
まあ、エラーメッセージがわかりずらいのでしょうがないとは思うが。

PL/SQLで開発する際に出くわす事がある。
(私もたまにやっちゃいますけどハマることはないです。こればかりは慣れるしかないですね。)

ということで太った空白のお話。

PL/SQLではマルチバイト文字の空白(全角の空白と言ったほうがわかりやすいでしょうか?)が原因で意味不明のコンパイルエラーを引き起こす。
そう、”太った空白”と書いたのはマルチバイト文字の空白。半角の空白が2つあるように見えるが全角の空白が1文字。

まずは、以下の例を見てください。

Last login: Fri Dec 21 08:25:31 on console
Welcome to Darwin!
G5Server:˜ discus$ su - oracle
Password:
G5Server:˜ oracle$ ssh oracle@corydoras
oracle@corydoras's password:
Last login: Sun Dec 16 18:02:52 2007 from 192.168.1.19
[oracle@corydoras ˜]$ sqlplus /nolog

SQL*Plus: Release 11.1.0.6.0 - Production on 金 12月 21 19:50:10 2007

Copyright (c) 1982, 2007, Oracle. All rights reserved.

> conn scott/tiger
接続されました。
SCOTT> create or replace function now
2 return timestamp
3 is
4 begin
5  return systimestamp;
6 end;
7 /

警告: ファンクションが作成されましたが、コンパイル・エラーがあります。

SCOTT> show errors
FUNCTION NOWのエラーです。

LINE/COL ERROR
-------- -----------------------------------------------------------------
5/3 PLS-00103: 記号""が見つかりました。 次のうちの1つが入るとき:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
記号"" は無視されました。

上記では、now()というストアドファンクションを作成しようとして5行目の3カラム目でコンパイルエラーが発生している。
(ストアドファンクション自体は意味の無いものなので気にしないでね。コンパイルエラーに注目してください。)

5行目の3カラム目は、"r"だなと思ったあなたは、もうすでにハマっていますよ。

実は、5行目の先頭の空白は半角の空白2文字ではなく”全角の空白1文字”なんです。

もうお分かりですよね。では、全角空白1文字を半角空白2文字に置換して再実行してみましょう。

SCOTT> list 5
5*  return systimestamp;
SCOTT> c / return/ return
5* return systimestamp;
SCOTT> l
1 create or replace function now
2 return timestamp
3 is
4 begin
5 return systimestamp;
6* end;
SCOTT> /

ファンクションが作成されました。

SCOTT> select now() from dual;

NOW()
---------------------------------------------------------------------------
07-12-21 19:56:19.046993000

SCOTT> /

| | | コメント (0) | トラックバック (0)

2007年11月14日 (水)

Mac De Oracle なんですが、Windows(32bit)でのOracleな話 #4

久々のオラクルネタですがWindows版のOracleネタです。随分前にこんなこと書いてました。

で、それ以後、見た目にはどんなエラーがでるの? とか、最近の検索フレーズ/ワードに、"Windows oracle 最大 session数"のようなフレーズを多く目にするようになったので、超簡単な再現例を一つ。


尚、Windows 32ビット環境で同時接続可能なセッション数(processes、sessions初期化パラメータに設定している値を超えた場合を除く。)は、SGAのサイズや各セッションのPGAサイズ、それに専用サーバーであるか、共有サーバーであるかという点にも影響される。お使いのシステムで、どの程度まで同時に接続できるかはその環境でしか確認できないということを付け加えておく。

また、必要最小限のPGAサイズ(単に接続しているだけなので)でどれだけ同時に接続できるのかを試している点にもご注意願いたい。(ストアドやDML文を実行する際に必要なPGAサイズはもっと大きくなる場合がほとんどなので実際に同時接続できるセッション数はもっと少なくなることの方が多い。)

●準備

この例ではすぐに接続数の上限に達するようSGAサイズはOracle10g R2 EE for Windows(32bit)が起動できる限界付近のサイズまで増加させた。

注)この方法でテストするのであれば、変更前のspfileをpfileへ退避して置く事をおすすめします。SGAサイズを大きくし過ぎてOracleを起動できなくなった場合でもpfileを使って起動することで元に戻せますから。

接続は専用サーバー接続で行い、目的のエラーを発生させる以前にprocess数やsession数の上限を超えないようにprocesses初期化パラメータを調整してある。
(SGA_TARGET及び、SGA_MAX_SIZE初期化パラメータは、私の環境でOracle10g EEが起動できる最大サイズに近い値に設定してある。ここまでSGAサイズを大きくすると、SQL*Plusを複数起動して接続するという力技でも目的のエラーを再現できる。その他初期化パラメータは以下を参照のこと。)

SYS>

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Prod
PL/SQL Release 10.2.0.2.0 - Production
CORE 10.2.0.2.0 Production
TNS for 32-bit Windows: Version 10.2.0.2.0 - Production
NLSRTL Version 10.2.0.2.0 - Production

SYS> show parameter sga_

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
sga_max_size big integer 1760M
sga_target big integer 1760M
SYS>

SYS> show parameter pga

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target big integer 60M
SYS>
SYS> show parameter sessions

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
・・・・中略・・・・
sessions integer 555
・・・・中略・・・・
SYS>
SYS>
SYS> show parameter processes

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
・・・・中略・・・・
processes integer 500
SYS>

SYS> select sum(value) from v$sga;

SUM(VALUE)
----------
1845493760

SYS>
SYS> select count(*) from v$session;

COUNT(*)
----------
16


前述の状態で、ひたすら(と言っても400までだが)Oracleへ接続するだけのjava programを実行すると。。。oracle.exeのVMサイズが2GBを超えた為に、あたらなスレッドを作れないことが原因で、ORA-12518というエラーが発生する。

●以下、JDeveloper10gで試してるスナップショット

Jdev_macosx_maxconn

/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/bin/java -server -classpath /Users/discus/jdevhome/mywork/MaxConnection/
MaxConnection/classes:/Volumes/ExtraDisk/Application/JDeveloper.app/Contents/Resources/jdev/jdbc/lib/ojdbc14dms.jar:/Volumes/ExtraDisk/
Application/JDeveloper.app/Contents/Resources/jdev/jdbc/lib/orai18n.jar:/Volumes/ExtraDisk/Application/JDeveloper.app/Contents/Resources/jdev/
jdbc/lib/ocrs12.jar:/Volumes/ExtraDisk/Application/JDeveloper.app/Contents/Resources/jdev/diagnostics/lib/ojdl.jar:/
Volumes/ExtraDisk/Application/JDeveloper.app/Contents/Resources/jdev/lib/dms.jar maxconnection.MaxConnection
コネクション数:21
java.sql.SQLException: Listener refused the connection with the following error:
ORA-12518, TNS:listener could not hand off client connection
The Connection descriptor used by the client was:
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=192.168.1.2))(CONNECT_DATA=(SERVICE_NAME=catfish.macdeoracle.info)))

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:138)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:293)
at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:328)
at oracle.jdbc.driver.PhysicalConnection.(PhysicalConnection.java:430)
at oracle.jdbc.driver.T4CConnection.(T4CConnection.java:151)
at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:32)
at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:608)
at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:218)
at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:159)
at maxconnection.MaxConnection.(MaxConnection.java:26)
at maxconnection.MaxConnection.main(MaxConnection.java:41)


●21セッション増えたことの確認!

SYS> r   
1* select count(*) from v$session

COUNT(*)
----------
37

SYS>

SGAサイズをOracleが起動可能な最大値に近づけたため、たった22セッションでoracle.exeのVMサイズが2GBを超えORA-12518が発生した。

ハッキリ言って、ORA-12518 : TNS:listener could not hand off client connection というエラーメッセージを見て32ビット版Windowsの上で実行されているoracle.exeが2GBの制限を超えたため新たなスレッドを作ることができなかったエラーであると、直感的に理解出来る人は少ないんじゃないかと思う。
(他の原因でこのエラーが発生する場合もあるのかもしれないが、私がこのエラーを目にしたのはWindows環境でoracle.exeが利用できるメモリサイズを超過したため新たに専用サーバーのスレッドを作れなかった場合だけだった。
2007/11/29追記:共有サーバーモードでも試してみるか!。またWindowsでOracleを使う事もあるだろうから。。ー>TODO)

不幸にしてORA-12518に遭遇した場合は、まず最初にoracle.exeのVMサイズが2GBを超えていないか確認したほうがいいだろう。(尚、本番システムやテスト環境で発生する場合は、SGAと新たに必要とするされるPGAサイズの関係で、oracle.exeのVMサイズが1.5GBから1.6GB程度でも発生する場合もある。)

●以下、マニュアルからの引用


ORA-12518: TNS: リスナーはクライアント接続をハンドオフできませんでした
 原因: クライアント接続を別のプロセスにハンドオフするプロセスが失敗しました。
 処置: トレースをオンにして、失敗した操作を再実行してください。
    リスナーおよびデータベース・インスタンスが、

    ダイレクト・ハンドオフに対して正しく構成されていることを確認してください。
    問題が解決されない場合は、オラクル社カスタマ・サポート・センターに連絡して
    ください。



●以下、テストに利用したjavaのソース。

package jp.macdeoracle.maxconnection;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import oracle.jdbc.pool.OracleDataSource;


public class MaxConnection
{
public MaxConnection()
{
OracleDataSource ods;
ArrayList connList = new ArrayList();
try
{
ods = new OracleDataSource();
ods.setServerName( "192.168.1.2" );
ods.setServiceName( "catfish.macdeoracle.info" );
ods.setPortNumber( 1521 );
ods.setDriverType( "thin" );
ods.setUser( "scott" );
ods.setPassword( "tiger" );

for (int i=0;i<400;i++){
connList.add( ods.getConnection() );
}

} catch (SQLException e)
{
e.printStackTrace();
} finally
{
System.out.println("コネクション数:"+connList.size());
}
}

public static void main(String[] args)
{
MaxConnection maxConnection = new MaxConnection();

try
{
Thread.sleep(60000L); //sleep 60sec
}
catch (InterruptedException e)
{
// TODO
}
}
}


おまけ。
ORA-12518が発生した際、Pslist.exeを実行して取得したoracle.exeのVMサイズとスレッド一覧の例)

VMサイズ(赤字)が2GBに近い。

Thread detail for CATFISH:

Name Pid VM WS Priv Priv Pk Faults NonP Page
oracle 972 2074972 324688 1918700 1922024 123716 59 165
oracle 972:
Tid Pri Cswtch State User Time Kernel Time Elapsed Time
2832 9 314 Wait:Executive 0:00:00.000 0:00:00.250 0:37:03.156
2228 9 97 Wait:UserReq 0:00:00.000 0:00:00.031 0:37:01.281
2236 9 327 Wait:UserReq 0:00:00.000 0:00:00.015 0:37:01.265
2376 10 1082 Wait:UserReq 0:00:00.000 0:00:00.046 0:36:57.718
2380 8 1363 Wait:UserReq 0:00:00.015 0:00:00.000 0:36:57.687
2384 8 999 Wait:UserReq 0:00:00.375 0:00:00.015 0:36:57.671
・・・・中略・・・・
2484 10 45 Wait:UserReq 0:00:00.000 0:00:00.015 0:00:02.359
1512 10 48 Wait:UserReq 0:00:00.031 0:00:00.015 0:00:02.312
2316 10 45 Wait:UserReq 0:00:00.015 0:00:00.015 0:00:02.250

| | | コメント (0) | トラックバック (0)

2007年9月28日 (金)

"日本語"のデータベースオブジェクト名 #2

ということで、前回のつづきというか、予告通り、"日本語"(マルチバイト文字)をデータベースオブジェクト名として定義しているデータベースで起こりうる問題点について。

"日本語"(マルチバイト文字)で 表名、列名、いないと思うけど(笑)ストアドプロシージャ名などを定義している方々は注意しておいたほうがよいだろう。

Oracleで許可されているデータベースオブジェクト名のサイズは、マニュアルにも記載されているように、データベース名とデータベースリンクを除き、30バイトまでと規定されていることを思い出してほしい。

日本語:Oracle Database SQLリファレンス 10g リリース2(10.2) - スキーマ・オブジェクトのネーミング規則

英語:Oracle® Database SQL Reference 10g Release 2 (10.2) - Schema Object Names and Qualifiers

例えば、データベースキャラクタセットが、JA16SJISである場合、テーブル名や列名を日本語(マルチバイト文字)で定義すると利用できる文字数は最大15文字までである。(半角英数字であれば、30文字)

以下、データベースキャラクタセット:JA16SJIS環境下でマルチバイト/シングルバイトでテーブル名を定義した場合の例

SCOTT> select * from v$nls_parameters where parameter = 'NLS_CHARACTERSET';

PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET JA16SJISTILDE

SCOTT>
SCOTT>
SCOTT> create table z12345678901234567890123456789 (a number);

表が作成されました。

●マルチバイト文字で15文字(30バイト)なのでOK!

SCOTT> create table "Z12345678901234" (a number);

表が作成されました。

SCOTT> create table z12345678901234567890123456789x (a number);
create table z12345678901234567890123456789x (a number)
*
行1でエラーが発生しました。:
ORA-00972: 識別子が長すぎます。

●マルチバイト文字で16文字(32バイト)なのでNG!

SCOTT> create table "Z123456789012345" (a number);
create table "Z123456789012345" (a number)
*
行1でエラーが発生しました。:
ORA-00972: 識別子が長すぎます。


SCOTT>
SCOTT> select table_name,lengthb(table_name) from user_tables;

TABLE_NAME LENGTHB(TABLE_NAME)
------------------------------ -------------------
Z12345678901234567890123456789 30
Z12345678901234 30

SCOTT>


前述のようなJA16SJISをデータベースキャラクタセットとするデータベースを、Unicodeのキャラクタセットである AL32UTF8 をデータベースキャラクタセットとするデータベースへ移行しようとした場合、どうなるか想像できますか?

想像できますよね? 

そう、単純には移行できないのですよ。JA16SJISでは2バイトだった文字がAL32UTF8では3バイトになる訳ですから。

マルチバイト1文字のバイト数の増加によりデータ量も増加するので、DISK容量の見直しも必要になるのですが、一番問題になるのが、日本語でデータベースオブジェクト名を定義しているという点でしょう。

もし、表名や列名を日本語(マルチバイト文字)11文字以上で定義しているとしたら、データベースキャラクタセットが AL32UTF8 であるデータベース上では、DDL全てがエラーになるはずです。
それらを修正するとなると、SQL/DDL文。その他プログラムに至まで、その影響は広範囲に渡るのではないでしょうか?


将来的なことも考慮してUnicode環境へ移行したいということであれば、コストを掛けて移行する価値はあると思います。その際にはシングルバイト文字で各名称を定義したほうがよいでしょうね。日本語の表名や列名の方が、直感的にわかりやすいからと日本語でテーブル名や列名を定義し、たった10文字でわかりやすい名称を定義することはかなり難しいと思いますから。

A:「--------」
B:「え、10文字でも定義できますよって!? 」
B:「もしや、"T123456789"とかいうテーブル名にでもしようと考えていますか?」
B:「それとも、名称を省略しまくって10文字以内の略名にしようと考えているのでしょうか?」
B:「それが、直感的に見てわかりやすい名称でしょうか?」
B:「見て、直感的に理解しやすいから日本語名称でテーブル名を定義したいのではないですか?」
な〜〜〜〜んて、やりとりが聞こえてきそう。。。

私が関わったところではそんな面倒なところは無かったので幸せだったりするが・・・・・

以下、データベースキャラクタセット:AL32UTF8環境でマルチバイト/シングルバイト、それぞれでテーブル名を定義した場合の例

SCOTT> select * from v$nls_parameters where parameter = 'NLS_CHARACTERSET';

PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET AL32UTF8

SCOTT>
SCOTT> create table z12345678901234567890123456789 (a number);

表が作成されました。

●マルチバイト文字で15文字(45バイト)!、当然サイズ超過!


SCOTT> create table "Z1234567891234" (a number);
create table "Z1234567891234" (a number)
*
行1でエラーが発生しました。:
ORA-00972: 識別子が長すぎます。

●マルチバイト文字で10文字。(30バイト)

SCOTT> create table "Z123456789" (a number);

表が作成されました。

●シングルバイト文字で31文字(31バイト)なので当然エラー!

SCOTT> create table z12345678901234567890123456789x (a number);
create table z12345678901234567890123456789 (a number)
*
行1でエラーが発生しました。:
ORA-00972: 識別子が長すぎます。


SCOTT>


SCOTT> select table_name,lengthb(table_name) from user_tables;

TABLE_NAME LENGTHB(TABLE_NAME)
------------------------------------------------------------ -------------------
Z123456789 30
Z12345678901234567890123456789 30

SCOTT>

| | | コメント (0) | トラックバック (0)

2007年9月27日 (木)

"日本語"のデータベースオブジェクト名 #1

Oracle10gのグローバリゼーションガイドには、新たに構築するデータベースでは、データベースキャラクタセットとして AL32UTF8 を推奨している。
Oracle Database グローバリゼーション・サポート・ガイド
10g リリース2(10.2)- データベース・キャラクタ・セットに関する指示書

ちなみに、Oracle製品間であっても、こんな注意点もある。
Oracle Content Management SDK(Oracle CM SDK)

とにかく、データベースキャラクタセットを選択する際には、関連するプロダクトの制限などもよ〜〜く調査して、方針を決めないといけないだろうね。あとで苦労しないためにも。

次回は、データベースキャラクタセット絡みで注意しないとならない点を書こうかな。
特に、日本語(マルチバイト文字)で表名、列名、その他のデータベースオブジェクト名を定義しているようなところでは注意しておいたほうがよい点について書いてみようかと。

そういえば、"日本語" de ストアドなんてお遊びもやってましたねぇ。(^^;;

| | | コメント (0) | トラックバック (0)

2007年9月23日 (日)

SQL*Loader で BLOBロード

少々前に、大量のBLOBデータをデータベースへロードするためのプログラムをコーディングしている方がいたのだが、そのプログラムの仕様を聞いてみたら、SQL*Loaderでも対応可能な内容だった。SQL*LoaderでBLOBデータをローディングできることは知らなかったのだ。知っていたらもっと楽だったろうに。


今回の例ではOracle10g R1を利用しているのだが、Oracle10g R2でも使い方は同じ。

● 参考:
・Oracle Database ユーティリティ 10g リリース1(10.1)
・Oracle Database ユーティリティ 10g リリース2(10.2)LOBFILEからのLOBデータロード
・Oracle Database ユーティリティ 10g リリース2(10.2)EXPRESSIONパラメータ

ということで、前述の話に比べると少々単純なのだがロードの例を一つ。

● まずは準備。

BLOBを格納する表のキーはユニークな数値なので、シーケンスを利用する。え!? SQL*Loaderでシーケンスが利用できるのか?
できるんです。EXPRESSIONパラメータを利用すれば!(EXPRESSIONパラメータの利用例は随分前にOTN-JのCode Tipsへも投稿してあったっけ。OTN-JのCode Tipsを参照するにはOTN-Jへのユーザ登録(無料)必要です。)

> conn scott/tiger
接続されました。
SCOTT>
SCOTT>
SCOTT> create sequence sq_blob_key
2 start with 1
3 increment by 1
4 maxvalue 999999
5 nocycle
6 nocache;

順序が作成されました。

BLOBデータを登録する表の定義は以下。
(blob用表領域は他のデーブルデータとは別表領域にしている)

SCOTT> l
1 create table blobtab
2 (
3 blob_id number primary key,
4 blob_type varchar(4),
5 content blob
6 )
7 lob(content) store as
8 (
9 tablespace blobspace
10 nocache logging
11* )
SCOTT> /

表が作成されました。

SCOTT>


● SQL*Loaderの制御ファイルとBLOBとして登録するファイルは同一ディレクトリ上へ配置。

ちなみに、Linux/Unix系OSでは、hostコマンドまたは、! を利用すればSQL*Plusからshellコマンドなとを直接実行できる。
SCOTT>  
SCOTT> !ls | grep -E '¥.(ctl|jpg|zip|png|gif)$'
blobload.ctl
discus.gif
generic_connectivity.zip
image001.png
neontetra.jpg

SCOTT>


● SQL*Loaderの制御ファイルの内容

ポイントは、シーケンスを利用するために、expressionパラメータで sq_blob_key.nextval と設定している点と、ex_fname FILLER以降の定義(赤字部分)。
SCOTT> !cat blobload.ctl
LOAD DATA
INFILE 'blobload.dat'
INTO TABLE blobtab
FIELDS TERMINATED BY ','
(
blob_id expression "sq_blob_key.nextval",
blob_type,
ext_fname FILLER CHAR(80),
content LOBFILE(ext_fname) TERMINATED BY EOF
)


● SQL*Loaderの制御ファイルのINFILEパラメータで指定するデータファイルの内容

この例では同一ディレクトリに配置した gif/zip/png/jpgを登録対象とした。
拡張子、ファイル名の順にカンマ区切りでデータファイルを作成。(awkを使えば簡単ですしね)

SCOTT> !ls | grep -E '¥.(zip|jpg|png|gif)$' | awk '{print substr($1,index($1,".")+1)","$1","}' > blobload.dat
SCOTT> !cat blobload.dat
gif,discus.gif,
zip,generic_connectivity.zip,
png,image001.png,
jpg,neontetra.jpg,

SCOTT>
SCOTT>


● 準備完了!

準備が整った。今回はSQL*Loaderの制御ファイル、データファイル、BLOBとして登録するファイルを同一ディレクトリに配置した。
SCOTT> !ls | grep -E '¥.(ctl|dat|jpg|zip|png|gif)$'
blobload.ctl
blobload.dat
discus.gif
generic_connectivity.zip
image001.png
neontetra.jpg

SCOTT>


● SQL*Loaderの実行

SCOTT> !sqlldr userid=scott/tiger@xtiger control=blobload.ctl log=blobload.log

SQL*Loader: Release 10.1.0.3.0 - Production on 土 9月 22 17:12:16 2007

Copyright (c) 1982, 2004, Oracle. All rights reserved.

コミット・ポイントに達しました。 - 論理レコード件数4

SCOTT>


● SQL*Loaderのログを確認!

SCOTT> !cat blobload.log

SQL*Loader: Release 10.1.0.3.0 - Production on 土 9月 22 17:12:16 2007

Copyright (c) 1982, 2004, Oracle. All rights reserved.

制御ファイル: blobload.ctl
データ・ファイルblobload.dat
不良ファイル: blobload.bad
廃棄ファイル: 指定なし

(すべて廃棄できます)

ロード数: ALL
スキップ数: 0
許容エラー数: 50
バインド配列: 64行、最大256000バイト
継続文字: 指定なし
使用パス: 従来型

表BLOBTAB、 ロード済 すべての論理レコードから
この表に対する有効な挿入オプション: INSERT

Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
BLOB_ID EXPRESSION
列のSQL文字列 : "sq_blob_key.nextval"
BLOB_TYPE FIRST * , CHARACTER
EXT_FNAME NEXT 80 , CHARACTER
(FILLERフィールド)
CONTENT DERIVED * EOF CHARACTER
動的LOBFILE - ファイル名EXT_FNAME


表BLOBTAB:
4行のロードに成功しました。
0行はデータ・エラーのためロードされませんでした。
0行はWHEN句のエラーのためロードされませんでした。
0行はすべてのフィールドがNULLのためロードされませんでした。

バインド配列に割り当てられた領域: 21760バイト(64行)
読取りバッファのバイト数: 1048576

スキップされた論理レコードの合計: 0
読み込まれた論理レコードの合計: 4
拒否された論理レコードの合計: 0
廃棄された論理レコードの合計: 0

実行開始土 9月 22 17:12:16 2007
実行終了土 9月 22 17:12:17 2007

実行時間: 00: 00: 00.80
CPUタイム : 00: 00: 00.06

SCOTT>


● 内容確認。(登録ファイルのサイズとBLOBサイズが同じであることを確認)

SCOTT> select
2 blob_id,
3 blob_type,
4 lengthb(content)
5 from
6 blobtab
7 order by
8 blob_id;

BLOB_ID BLOB LENGTHB(CONTENT)
---------- ---- ----------------
1 gif 9675
2 zip 20070
3 png 2327573
4 jpg 6492

SCOTT>
SCOTT> !ls -l | grep -E '¥.(jpg|zip|png|gif)$'
-rw-r--r-- 1 oracle oinstall 9675 Dec 31 1997 discus.gif
-rw-r--r-- 1 oracle oinstall 20070 Nov 17 2005 generic_connectivity.zip
-rw-r--r-- 1 oracle oinstall 2327573 Jul 21 16:21 image001.png
-rw-r--r-- 1 oracle oinstall 6492 May 4 1999 neontetra.jpg

SCOTT>

| | | コメント (0) | トラックバック (0)

2007年8月 4日 (土)

Shutdown Timeout

今回は、Oracle10g R2から実装された停止タイムアウトネタです。よく、shutdown immediateを実行したのに、”すぐにshutdownしない”。なんて話を聞きますが、
当然ですよ、immediateという言葉とは裏腹に、shutdown immediateにより強制的にrollbackされたトランザクションが、大量にundoブロックを保持していたらどうなると思いますか?

トランザクションのロールバックに時間がかかり、shutdown処理が待たされる、という状況が発生します!

これが、shutdown immediateを実行したのに、”すぐには停止しない”理由です。(これ以外の理由で停止しない場合もあるにはあるようだが・・・、大抵の場合はこれが原因。)

Oracle10g R2 より前のリリースでは、shutdown immeidateが終了せず、遠くを見つめて、ただ、ひたすら終了を待ち続けた方は、意外と多いかもしれません。。
しかも、Oracleさん、な〜〜〜んにもメッセージ出してくれないので何を待機しているのか、何をしているのか分かりずらいし。
で、そのガス抜き策? なのか、 Oracle10g R2から、停止タイムアウトという機能が追加されています。
これは、文字通り、shutdownが一定時間内に終了しないと、shutdown自体がキャンセルされてしまうという機能です。
正常に復帰しても、本当に実行したかったのは、shutdownなのですから、それはそれで欲求不満状態ではあるのですが・・・(笑)


SYS> l
1 create smallfile tablespace test datafile E:¥ORACLE¥ORADATA¥CATFISH¥CATFISH¥test.dbf' size 10g
2 autoextend on
3 extent management local uniform size 1m
4* segment space management auto
SYS> /

表領域が作成されました。

SYS>
SYS> create table scott.test (data varchar2(4000)) tablespace test;

表が作成されました。

いうことで、準備完了。(Windows版のOracle Database 10g R2を利用しています。)

次に、以下のように、大量のデータを登録し、1時間以上実行させておく。
(当然、ストレージの性能などにより、どの程度のデータを登録すれば、ロールバックが1時間以上になるのか異なってくることをお忘れなく。今回テストに利用したものは、HDDが2本、OSとは別ドライブのDISKにOracleのバイナリ及びデータファイルを配置。)

では、SCOTTユーザに接続して、大量データを登録!

SCOTT> 
SCOTT> l
1 begin
2 for i in 1..2000000 loop
3 insert into test values(lpad('x',4000,'x'));
4 end loop;
5* end;
SCOTT> /


おまけ。
別途、SQL*Plusを起動し、SYSDBAで接続して、UNDOの量をモニタしているところ。

SYS> r
1 select
2 s.username,
3 t.used_ublk,
4 to_char(systimestamp,'hh24:mi:ss.ff') as time
5 from
6 v$session s join v$transaction t
7* on t.addr = s.taddr

USERNAME USED_UBLK TIME
---------- ---------- ------------------------------------
SCOTT 2618 17:29:43.890000


大量データ登録処理を実行したまま、一時間以上経過したところで、shutdown immediateを実行!!
すると、一時間以上実行していたトランザクションのロールバック処理のため、shutdownが待機させられる。

そう、これが、shutdown immediateで、shutdownしても言葉通り、即刻には停止しない状況の一例なのである。
Oracle10g R2より前のリリースであれば、延々と待たなければならない。それが、2時間や3時間だとキツいですよね。
しかも、shutdownを実行してしまったので、その後は何もできないし。

そこで、Oracle10g R2から実装されたのが、shutdownが自動的にcancelされるという機能。
(便利なだと感じるかどうかは。。。いろいろ・・・)


では、ロールバックに1時間以上かかる状態になったところで、shutdown immeidateを実行!!!

SYS> shutdown immediate;
・・・・・ここでおおよそ1時間ほど待たされます。コーヒーでも飲んで、ゆったりと・・・・お待ちください。

ほぼ1時間経過しても、shutdownが待機させられていると、以下のようなメッセージとともに
SQL*plusへ制御が返されます。

ORA-01013: ユーザーによって現行の操作の取消しがリクエストされました。

SYS>

この時点で、shutdownがキャンセルされ、通常の状態に戻っています。再度コマンドを投入できる状態に戻っています。(Oracle11gのマニュアルにtimeout時の注意が追加されています。10gのマニュアルにはキャンセルされるという事以外書かれていいませんが。2010/1/12追記)


alert_<SID>.logには以下のように記録されます。
以前のリリースに比べると、shutdownが待機させられているというメッセージや、shutdownがキャンセルされたというメッセージなど、意外と分かり易いメッセージが記録されている事に気付くはずです。ず〜〜〜っと、なんのメッセージもなく待たされるよりはましといったところです。

Tue Jul 31 23:03:16 2007
Shutting down instance: further logons disabled
Tue Jul 31 23:03:24 2007
Stopping background process QMNC
Tue Jul 31 23:03:24 2007
Stopping background process CJQ0
Tue Jul 31 23:03:25 2007
Stopping background process MMNL
Tue Jul 31 23:03:26 2007
Stopping background process MMON
Tue Jul 31 23:03:26 2007v
Thread 1 advanced to log sequence 289
Current log# 1 seq# 289 mem# 0: E:¥ORACLE¥ORADATA¥CATFISH¥CATFISH¥REDO01.LOG
・・・・中略・・・・
Tue Jul 31 23:03:27 2007
Shutting down instance (immediate)
License high water mark = 14
Tue Jul 31 23:03:27 2007
Stopping Job queue slave processes
Tue Jul 31 23:03:27 2007
Job queue slave processes stopped
・・・・中略・・・・
Tue Jul 31 23:08:26 2007
Active call for process 884 user 'SYSTEM' program 'ORACLE.EXE (SHAD)'
SHUTDOWN: waiting for active calls to complete.

Tue Jul 31 23:22:39 2007
MMNL absent for 1203 secs; Foregrounds taking over
・・・・中略・・・・
Wed Aug 01 00:03:26 2007
SHUTDOWN: Active sessions prevent database close operation

まあ、shutdown immediateする場合には事前に、rollbackに時間を要するトランザクションが実行中でないことをよ〜〜〜〜く確認するか、運用ルールで実行しない/できないように制限したりすることが重要だと思いますね。

次回は、ロールバックの終了時刻を大雑把に見積もるファンクションでも作成してみるか・・・

参考:「Oracle Database 管理者ガイド 10g リリース 2(10.2)- 停止タイムアウト」

2010/1/12追記.
Oracle11g R2のマニュアルの停止タイムアウトの解説に停止タイムアウトが発生した場合、再度、shutdown immediateを実行して、それでもなお、timeoutするようならshutdown abort行う。という記述が追加されています。ご注意ください。Oracle® Database Administrator's Guide 11g Release 2 (11.2) - Shutdown Timeout
shutdown timeout

| | | コメント (0) | トラックバック (0)

2007年6月 8日 (金)

Mac De Oracle : PL/SQL de Collection #5



Marcus Miller - Silver Rain - It'll Come Back to YouMarcus Miller - Silver Rain - It'll Come Back to You

ちょいと間が空いたが、PL/SQLのコレクションでのお遊び。その5回目。

検索ワードで結構目にするのが、"N次元 結合配列" というキーワード。

ということで、今回は、多次元のコレクションの例を。

N次元のコレクションってどうやって作るの? ってことで情報を検索しているのだと思いますが、しっかりマニュアルにも記載されているので、そちらもしっかり読んでおくことをおすすめしますよ!。

Associative Array(以下、結合配列)、VARRAYやNested Table(以下、ネスト表)どれでも、考え方は同じ。
コレクション自体を別コレクションの要素にすればできるんですよ。 コレクションを入れ子にする と言えばイメージしやすい? ですかね。


以下のコードで赤太字で示しているところが多次元コレクション定義とアクセスのポイント。
まず、empType型を要素とするNestedTableType型(ネスト表)を定義し、次にNestedTalbType型を要素とするStrAssociativeArrayType型(結合配列)を定義している。
以下の例は多少、ひねくれた例? として結合配列とネスト表という異なるコレクションを利用して多次元コレクションを定義している。その影響でコレクションの操作は多少面倒になるのだが..

尚、empType型は、事前に作成しておいたオブジェクト型。

create or replace
PROCEDURE multiLevelCollections
AS
TYPE NestedTableType IS TABLE OF empType;
TYPE StrAssociativeArrayType IS TABLE OF NestedTableType INDEX BY VARCHAR2(50);

myNestedTable NestedTableType := NestedTableType();
myStrAssociativeArray StrAssociativeArrayType;


claerMyStrAssociateArray StrAssociativeArrayType;

myEmp empType;
j dept.dname%TYPE;
vDeptName dept.dname%TYPE := NULL;

CURSOR csrEmp IS
SELECT
emp.job,
emp.deptno,
dept.dname,
emp.empno,
emp.ename,
emp.sal,
emp.hiredate
FROM
emp JOIN dept
ON emp.deptno = dept.deptno
ORDER BY
emp.deptno,
emp.ename;

PROCEDURE printArray
IS
BEGIN
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('**** コレクションに要素が存在している状態 ****');
DBMS_OUTPUT.PUT_LINE(
'myStrAssociativeArray('
|| myStrAssociativeArray.COUNT
|| ')'
);

j := myStrAssociativeArray.FIRST;
WHILE j IS NOT NULL LOOP
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE(
'myStrAssociativeArray('
|| j || ')(' || myStrAssociativeArray(j).COUNT || ')'
);

FOR k IN
myStrAssociativeArray(j).FIRST..myStrAssociativeArray(j).LAST
LOOP
DBMS_OUTPUT.PUT_LINE(
'myStrAssociativeArray(' || j || ').'
|| 'myNestedTable(' || TO_CHAR(k) || ') = '
|| myStrAssociativeArray(j)(k).TO_STRING()
);
END LOOP;
j := myStrAssociativeArray.NEXT(j);
END LOOP;
END;

BEGIN
FOR emp_rec IN csrEmp LOOP
IF csrEmp%ROWCOUNT = 1 THEN
vDeptName := emp_rec.dname;
END IF;

myEmp := empType (
emp_rec.empno,
emp_rec.ename,
emp_rec.job,
emp_rec.hiredate,
emp_rec.sal,
emp_rec.deptno
);

IF vDeptName <> emp_rec.dname THEN
myStrAssociativeArray(vDeptName) := myNestedTable;
vDeptName := emp_rec.dname;
myNestedTable := NestedTableType();
END IF;

myNestedTable.EXTEND(1);
myNestedTable(myNestedTable.COUNT) := myEmp;
END LOOP;

IF myNestedTable.COUNT > 0 THEN
myStrAssociativeArray(vDeptName) := myNestedTable;
END IF;

printArray();

-- 結合配列を空にする!
myStrAssociativeArray := claerMyStrAssociateArray;

DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('**** コレクションが空の状態 ****');
DBMS_OUTPUT.PUT_LINE(
'myStrAssociativeArray('
|| myStrAssociativeArray.COUNT
|| ')'
);


END;
/

SCOTT> l
1 select
2 emp.deptno,
3 dept.dname,
4 emp.empno,
5 emp.ename,
6 emp.sal,
7 emp.hiredate
8 from
9 emp join dept
10 on emp.deptno = dept.deptno
11 order by
12 emp.deptno,
13* emp.ename
SCOTT> /

DEPTNO DNAME EMPNO ENAME SAL HIREDATE
---------- -------------- ---------- ---------- ---------- --------
10 ACCOUNTING 7782 CLARK 2450 81-06-09
10 ACCOUNTING 7839 KING 5000 81-11-17
20 RESEARCH 7876 ADAMS 1100 87-05-23
20 RESEARCH 7566 JONES 2975 81-04-02
20 RESEARCH 7788 SCOTT 3000 87-04-19
20 RESEARCH 7369 SMITH 800 80-12-17
30 SALES 7499 ALLEN 1600 81-02-20
30 SALES 7698 BLAKE 2850 81-05-01
30 SALES 7900 JAMES 950 81-12-03
30 SALES 7654 MARTIN 1250 81-09-28
30 SALES 7844 TURNER 1500 81-09-08
30 SALES 7521 WARD 1250 81-02-22

12行が選択されました。

SCOTT>

実行結果は次のようになる。(Oracle SQL Developer 1.1 for MacOSX) 
1.2がリリースされたようなので後でダウンロードしとくか・・> TODO

Oracleplsqldev11

Mlevelcollresults


ということで、今日はここまで。

Have a good Week end!

| | | コメント (0) | トラックバック (0)

2007年5月29日 (火)

Mac De Oracle : PL/SQL de Collection #4

さて、PL/SQLのコレクションでのお遊び。その4回目。

今回は、既に要素を持っているコレクションを空にしてみましょう。

第二回目にも似たようなことをやっているので、VarrayとNested Tableについては想像できると思いますが、Associative Arrayにいてはどうすればよいでしょう?

ということで、答えは以下のコードを読めば分かりますよ!

create or replace
PROCEDURE COLLECTIONS AS
TYPE VarrayType IS VARRAY(100) OF empType;
TYPE NestedTableType IS TABLE OF empType;
TYPE StrAssociativeArrayType IS TABLE OF empType INDEX BY VARCHAR2(50);
TYPE NumAssociativeArrayType IS TABLE OF empType INDEX BY BINARY_INTEGER;

myVarray VarrayType := VarrayType();
myNestedTable NestedTableType := NestedTableType();
myStrAssociativeArray StrAssociativeArrayType;
myNumAssociativeArray NumAssociativeArrayType;

claerMyStrAssociateArray StrAssociativeArrayType;
clearMyNumAssociateArray NumAssociativeArrayType;

myEmp empType;

CURSOR csrEmp IS
SELECT
job,
empno,
ename,
hiredate,
sal,
deptno
FROM
emp
ORDER BY
empno;

PROCEDURE printCount
IS
BEGIN
DBMS_OUTPUT.PUT_LINE(
'Varray : '
|| myVarray.COUNT
|| ' 件の要素が存在します。'
);
DBMS_OUTPUT.PUT_LINE(
'Nested Table : '
|| myNestedTable.COUNT
|| ' 件の要素が存在します。'
);
DBMS_OUTPUT.PUT_LINE(
'Associative Array(String) :'
|| myStrAssociativeArray.COUNT
|| ' 件の要素が存在します。'
);
DBMS_OUTPUT.PUT_LINE(
'Associative Array(Number) :'
|| myNumAssociativeArray.COUNT
|| ' 件の要素が存在します。'
);
END;

BEGIN
FOR emp_rec IN csrEmp LOOP
myEmp := empType (
emp_rec.empno,
emp_rec.ename,
emp_rec.job,
emp_rec.hiredate,
emp_rec.sal,
emp_rec.deptno
);
myVarray.EXTEND(1);
myVarray(myVarray.COUNT) := myEmp;
myNestedTable.EXTEND(1);
myNestedTable(myNestedTable.COUNT) := myEmp;
myStrAssociativeArray(TO_CHAR(myEmp.empno) || ':' || myEmp.ename) := myEmp;
myNumAssociativeArray(myEmp.empno) := myEmp;
END LOOP;


DBMS_OUTPUT.PUT_LINE('**** コレクションに要素が存在している状態 ****');
printCount;


myVarray := VarrayType();
myNestedTable := NestedTableType();
myStrAssociativeArray := claerMyStrAssociateArray;
myNumAssociativeArray := clearMyNumAssociateArray;

DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('**** コレクションが空の状態 ****');
printCount;


END;
/


上記のコードの通り、Assciative Array(結合配列)を空にする方法は、同じ型の”空”結合配列をセットしてやればいいわけです。
今回の例では1次元の結合配列ですが、N次元の結合配列でも考え方は同じです。(VarrayやNested Tableとは異なります。)
ちなみに、第二回目にも書きましたが、VarrayやNexted Tableは、NULLにすることができます。その場合、VarrayやNexted Tableは、atomic nullであるためコンストラクタによる初期化が行われるまで一切の操作は行えないということもお忘れなく。

最後に、上記プロシージャの実行結果。

SCOTT> set serveroutput on size 10000 format wrapped
SCOTT> exec collections;
**** コレクションに要素が存在している状態 ****
Varray : 29 件の要素が存在します。
Nested Table : 29 件の要素が存在します。
Associative Array(String) :29 件の要素が存在します。
Associative Array(Number) :29 件の要素が存在します。

**** コレクションが空の状態 ****
Varray : 0 件の要素が存在します。
Nested Table : 0 件の要素が存在します。
Associative Array(String) :0 件の要素が存在します。
Associative Array(Number) :0 件の要素が存在します。

PL/SQLプロシージャが正常に完了しました。

SCOTT>

Collection4

今日はこのへんで。

| | | コメント (0) | トラックバック (0)

2007年5月17日 (木)

Mac De Oracle : Oracleのバージョン確認(FAQ) #9

OCFS2 のバージョン確認方法は?

10万円 de RAC #22の最後にも記載してありますが、ググれば沢山ヒットすると思いますよ。   


Googleで検索する例も載せておきましょう!

how to determine ocfs2 version で検索すれば英語のページは沢山ヒットする。
ocfs2のバージョン確認 と入力して検索すれば、私のBlogも含んだ検索結果が返ってきますよ。

以下のスクリーンショットは、MacOSXのTerminalからCentOSへssh接続したもの
Mac_linux_ssh


[root@discus1 ˜]# cat /proc/fs/ocfs2/version
OCFS2 1.2.3 Thu Aug 10 18:16:03 PDT 2006 (build 6b798aaadf626d3b137c3952809b2f38)
[root@discus1 ˜]#

| | | コメント (0) | トラックバック (0)

2007年5月16日 (水)

Mac De Oracle : Oracleのバージョン確認(FAQ) #8

Oracle JDBC Driverのバージョンは確認方法は?

マニュアルにしっかり載ってます。 
今回は、先日公開された、Oracle JDeveloper11g Technology Previewで試してみた。

ちなみに、接続先は、Oracle10g R2 EE 10.2.0.2.0
Jdev11g_jdbc11

よく見えないかもしれないので、ログペインを拡大したスクリーンショットも載せておく。
Jdbcversionresult

Oracle JDeveloper11g Technology Previewでは、Oracle JDBC Driver 11.1.0.0.0-Alpha が使われているんですね!。

Determining the Version of the JDBC Driverを見れば分かるし、マニュアルからコピペすればよいので、書くまでもないのだが。。。。。。。

読んでないのか!?、見つけられないのか?  よく聞かれるのでコードも載せておく。

package jp.macdeoracle.discus;

import oracle.jdbc.pool.OracleDataSource;
import java.sql.DatabaseMetaData;
import java.sql.Connection;
import java.sql.SQLException;

public class JdbcVersion
{
public static void main(String[] args)
{
OracleDataSource ods = null;
Connection conn = null;
try {
ods = new OracleDataSource();
ods.setURL("jdbc:oracle:thin:scott/tiger@192.168.1.2:1521:catfish");
conn = ods.getConnection();
DatabaseMetaData meta = conn.getMetaData();
System.out.println(meta.getDriverName() + "," + meta.getDriverVersion());
}
catch (SQLException se) {
System.out.println(se.getSQLState());
System.out.println(se.getErrorCode());
}
finally
{
try {
if (ods != null && conn != null && !conn.isClosed()) {
conn.close();
ods.close();
}
}
catch (SQLException se){
se.printStackTrace();
}
}
}
}

| | | コメント (0) | トラックバック (0)

2007年5月14日 (月)

Mac De Oracle : Oracleのバージョン確認(FAQ) #7

Oracle Enterprise Manager 10g Database Controlのバージョンは以下のようにしても確認できる。ついでに、URLの確認にもなるから便利。

ちなみに、EM10g R1 と R2では、Port番号が異なっているので要注意。

G5Server:˜ oracle$ emctl status dbconsole
Oracle Enterprise Manager 10g Database Control Release 10.1.0.3.0
Copyright (c) 1996, 2004 Oracle Corporation. All rights reserved.
http://192.168.1.19:5500/em/console/aboutApplication
Oracle Enterprise Manager 10g is not running.
G5Server:˜ oracle$

[oracle@discus1 ˜]$ emctl status dbconsole
TZ set to Japan
Oracle Enterprise Manager 10g Database Control Release 10.2.0.1.0
Copyright (c) 1996, 2005 Oracle Corporation. All rights reserved.
http://192.168.1.100:1158/em/console/aboutApplication
Oracle Enterprise Manager 10g is not running.
[oracle@discus1 ˜]$

| | | コメント (0) | トラックバック (0)

2007年4月27日 (金)

Mac De Oracle : Oracleのバージョン確認(FAQ) #6

リスナーのバージョン確認方法は?

注意)この方法でバージョンを確認するには、リスナーが起動している必要があります。

[oracle@discus1 ˜]$ lsnrctl version

LSNRCTL for Linux: Version 10.2.0.1.0 - Production on 26-4月 -2007 22:44:06

Copyright (c) 1991, 2005, Oracle. All rights reserved.

(ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))に接続中
TNSLSNR for Linux: Version 10.2.0.1.0 - Production
TNS for Linux: Version 10.2.0.1.0 - Production
Unix Domain Socket IPC NT Protocol Adaptor for Linux: Version 10.2.0.1.0 - Production
Oracle Bequeath NT Protocol Adapter for Linux: Version 10.2.0.1.0 - Production
TCP/IP NT Protocol Adapter for Linux: Version 10.2.0.1.0 - Production,,
コマンドは正常に終了しました。
[oracle@discus1 ˜]$

| | | コメント (0) | トラックバック (0)

2007年4月26日 (木)

Mac De Oracle : Ask Discus : 問い合わせた結果、ある列(文字列)の右端2文字でソートするには?

Ask Tomのパクリ! なんてことはやりませんが、ウチの奥さんからヘルプメールが来たので。

今、Oracle10g R2 + Seaser2 + Hibernate だっけ? を使ったプロジェクトで奮闘しているウチの奥さんから SQL文に関するヘルプメールが来たので、 Ask Discus という Ask Tomのパクリ風? ヘルプを一つ。 
FAQだと思うのでFAQカテゴリにしておきます。


You Asked...
ウチの奥さんからのヘルプ内容:

問い合わせた結果をソートする必要がある。
そして、ソート条件は、varchar2型として定義されている列で、右端から2文字で昇順にソートする。
この場合、order by句はどのように書けばいいの?  
おしえて Discus! (ダーリン!。。。かも。。。笑い)


という質問。

and I said...

簡単なんだけど。。(知らない人には簡単では無いかも。。。。)

order by句でもスカラ関数が利用できるので、 substr()を利用して可変長文字列の右端2文字を取り出せば OK.

では、お約束のscottユーザに接続し、emp表の ename列の右端2文字で昇順にソートしてみよう!

length()を利用しなくても substr()の第二引数に負の値を指定すれば簡単にできるということを忘れていた!!
こちらの方がスマートです。コメントくださった方に感謝します。

SCOTT> l
1 select
2 emp.*,
3 substr(ename,-2) as right2chars
4 from
5 emp
6 where
7 empno < 8000
8 order by
9* substr(ename,-2)
SCOTT> /

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO RIGHT2CHARS
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----------
7905 JEAN 7904 AN
7901 SADE 7876 DE
7499 ALLEN SALESMAN 7698 81-02-20 1600 300 30 EN
7844 TURNER SALESMAN 7698 81-09-08 1500 0 30 ER
7934 MILLER CLERK 7782 82-01-23 1300 10 ER
7566 JONES MANAGER 7839 81-04-02 2975 20 ES
7900 JAMES CLERK 7698 81-12-03 950 30 ES
7903 ALEX 7901 EX
7654 MARTIN SALESMAN 7698 81-09-28 1250 1400 30 IN
7698 BLAKE MANAGER 7839 81-05-01 2850 30 KE
7876 ADAMS CLERK 7788 87-05-23 1100 20 MS
7839 KING PRESIDENT 81-11-17 5000 10 NG
7521 WARD SALESMAN 7698 81-02-22 1250 500 30 RD
7902 FORD ANALYST 7566 81-12-03 3000 20 RD
7782 CLARK MANAGER 7839 81-06-09 2450 10 RK
7369 SMITH CLERK 7902 80-12-17 800 20 TH
7788 SCOTT ANALYST 7566 87-04-19 3000 20 TT
7904 DISCUS 7903 US
7907 Wendy 7906 dy
7906 Duke 7905 ke

20行が選択されました。

この方法でもできるんだが、length()関数を使わないので上記の方法がいいですね。

SCOTT> l
1 select
2 emp.*,
3 substr(ename,length(ename)-1,2) as right2chars
4 from
5 emp
6 order by
7* substr(ename,length(ename)-1,2)
SCOTT> /


ちなみに、NLSSORTを利用すれば、言語ソートも可能。 
length()関数を利用しないよう修正。

SCOTT> select 
2 emp.*,
3 substr(ename,-2) as right2chars
4 from
5 emp
6 where
7 empno between 8000 and 8010
8 order by
9 substr(ename,-2)
10 /

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO RIGH
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----
8002 かきくあい あい
8001 おえういあ いあ
8000 あいうえお えお
8008 カキクアイ アイ
8007 オエウイア イア
8006 アイウエオ エオ
8004 カキクアイ アイ
8005 オエウイア イア
8003 アイウエオ エオ

9行が選択されました。

SCOTT> l
1 select
2 emp.*,
3 substr(ename,-2) as right2chars
4 from
5 emp
6 where
7 empno between 8000 and 8010
8 order by
9 nlssort(
10 substr(ename,-2),
11 'NLS_SORT=JAPANESE_M'
12* )
SCOTT> /

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO RIGH
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----
8002 かきくあい あい
8008 カキクアイ アイ
8004 カキクアイ アイ
8001 おえういあ いあ
8007 オエウイア イア
8005 オエウイア イア
8000 あいうえお えお
8006 アイウエオ エオ
8003 アイウエオ エオ

9行が選択されました。


NLSSORT()関数や言語ソートの詳細はマニュアルを参照していろいろと試してみてくださいね。。
Oracle Database グローバリゼーション・サポート・ガイド 10g リリース2(10.2) 言語ソート
Oracle Database グローバリゼーション・サポート・ガイド 10g リリース2(10.2) 言語ソートパラメータ
Oracle Database グローバリゼーション・サポート・ガイド 10g リリース2(10.2)言語ソートの使用
Oracle Database グローバリゼーション・サポート・ガイド 10g リリース2(10.2) NLSSORT関数

| | | コメント (2) | トラックバック (0)

2007年4月25日 (水)

Mac De Oracle : Oracleのバージョン確認(FAQ) #5

APEXのバージョンを確認するには?

以下は、APEX3.0でのバージョン確認方法だが、APEX2.2の場合には、flows_020200.wwv_flows_release()ファンクションを実行する必要がある。この方法はバージョン毎にスキーマ異なるので少々面倒だ。(APEX上で表示するのならば話は別だが。。)

SYS> select flows_030000.wwv_flows_release from dual;

WWV_FLOWS_RELEASE
--------------------------------------------------------
3.0.0.00.20

SYS>

そこで、スキーマに関係なく確認する方法として、以前にも紹介した、DBA_REGISTRYビューを問い合わせる方法がある。

| | | コメント (0) | トラックバック (0)

2007年4月24日 (火)

Mac De Oracle : Oracleのバージョン確認(FAQ) #4

Oracle WebTool kitのバージョンを確認するには?

OWA_UTILパッケージの get_version関数を使う!


ちなみに、SQL*Plusから接続するユーザは、SYSユーザである必要はないが、Web toolkit関連パッケージの実行を制限している場合には権限が付与されているユーザで接続すれはOK。


OWA_UTILパッケージにアクセスできるか簡単に確認するには、

SQL> desc owa_util

としてパッケージに定義されているプロシージャやファンクションの一覧がでてくれば実行可能だ。


SYS> select owa_util.get_version from dual;

GET_VERSION
--------------------------------------------------------------------------------
10.1.2.0.6

SYS>

| | | コメント (0) | トラックバック (0)

2007年4月23日 (月)

Mac De Oracle : Oracleのバージョン確認(FAQ) #3

Oracleデータベースにロードされている各コンポーネントのバージョンを知りたいときは、dba_registryビューを問い合わせる。

マニュアル「Oracle® Database Reference 10g Release 2 (10.2)」 
普段参照することはなくても、こんなビューがあるということを思えておくだけでも、いざという時には役に立つんじゃないかなぁ。

SYS> select comp_name,version,status from dba_registry;

COMP_NAME VERSION STATUS
---------------------------------------- ------------------------------ -----------
Oracle Application Express 3.0.0.00.20 VALID
Oracle Ultra Search 10.1.0.3.0 VALID
Oracle Enterprise Manager 10.1.0.3.0 VALID
Oracle XML Database 10.1.0.3.0 VALID
OLAP Catalog 10.1.0.3.0 VALID
Oracle Text 10.1.0.3.0 INVALID
Spatial 10.1.0.3.0 VALID
Oracle interMedia 10.1.0.3.0 VALID
Oracle Expression Filter 10.1.0.3.0 VALID
Oracle Workspace Manager 10.1.0.2.0 VALID
Oracle Data Mining 10.1.0.3.0 VALID
Oracle Database Catalog Views 10.1.0.3.0 VALID
Oracle Database Packages and Types 10.1.0.3.0 INVALID
JServer JAVA Virtual Machine 10.1.0.3.0 VALID
Oracle XDK 10.1.0.3.0 VALID
Oracle Database Java Packages 10.1.0.3.0 VALID
OLAP Analytic Workspace 10.1.0.3.0 VALID
Oracle OLAP API 10.1.0.3.0 VALID

18行が選択されました。

SYS>

注意)この静的ディクショナリービューは、Oracle9i R2以降で利用可能。

| | | コメント (0) | トラックバック (0)

Mac De Oracle : Oracleのバージョン確認(FAQ) #2

Oracleのバージョン確認 その2は、SQL*Plusのバージョン確認

特に解説することも無いが、SQL*Plusのバージョンを確認は以下の方法もある。
(たまに、Oracle Universal Installerを起動して確認している方もいるが、以下の方法で確認するほうが簡単。)

discus1:˜ oracle$ sqlplus -version

SQL*Plus: Release 10.2.0.2.0 - Production

discus1:&tilde oracle$

| | | コメント (0) | トラックバック (0)

2007年4月21日 (土)

Mac De Oracle : Oracleのバージョン確認(FAQ) #1

検索ワードを見ていると、ORA-20000から ORA-20999までのエラーコードに次いで、バージョン確認というキーワードが目立つ。。。。。

基本はマニュアルを見ることですよ!

該当部分を発見しにくいマニュアルにも原因はあるかもしれないけど。。。(^^;;;;;

では、よく言聞かれるであろう、Oracle のバージョンや、Oracle のエディションの確認方法。

サポートに確認する場合、その他いろいろなフォーラムでオラクルに関する質問する際、バージョンやエティション、それにプラットフォームやそのバージョンを提示するのは当たり前のことなのだが、、、

最近、そのような基本的な事を提示しない方も多いなぁ、と感じる場面も少なくない。
しかも、検索ワードで、"Oracle Version確認" の検索数が目立つ状況だと、Blogのネタとしては面白くないのだが、確認方法を載せたほうがよいだろうと思い、FAQとして載せておく事にした。
(バージョンやエディションを提示するということ以前に、バージョンの確認方法を知らないという可能性もあるので。。。)

以下は、MacOSX Server版 Oracle10g R1 EEの例

v$version動的パフォーマンスビューを問い合わせれば確認することができる。
但し、Oracle9i 以降では、v$系のビューはselect any dictionaryシステム権限が無いと参照できないため、sysユーザか、systemユーザ、又は前述の権限を付与されたユーザで確認する。
SYS> conn / as sysdba
接続されました。
SYS>
SYS> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.1.0.3.0 - Prod
PL/SQL Release 10.1.0.3.0 - Production
CORE 10.1.0.3.0 Production
TNS for MacOS X Server: Version 10.1.0.3.0 - Production
NLSRTL Version 10.1.0.3.0 - Production

SYS>

v$version動的パフォーマンスビューを問い合わせれば、 Oracle のバージョン と エディション以外に、SQL*net や PL/SQLのバージョンも確認することができる。

尚、v$パフォーマンスビューや その他のビューからは有益な情報を問い合わせることが可能なので、暇のある時にでも眺めてみるだけでも見てみるといいだろう。 
「きっと、後々役に立つと思うよ。 あ、そういえば、あんなのがあったなぁ。とか。。。ね。」
Oracle Database リファレンス (10.2)

また、次のような確認方法もある。(但し、SQL*Plusがインストールされているという前提である。)

以下の例は、Oracle9i R2 9.2.0.1.0 のOracle Clientから、Oracle10g R2 10.2.0.2.0 EE へ接続した際のログであるが、接続後のバナーと、 exitコマンド入力後のバナーに、接続先データベースのバージョンとエディションが表示されるので、v$version動的パフォーマンスビューを問い合わせる権限の無いユーザで接続している場合でも、Oracleのversionとeditionを容易に確認することができる。 知っていて損はないだろう。
discus1:˜ oracle$ sqlplus scott@catfish

SQL*Plus: Release 9.2.0.1.0 - Production on 土 4月 21 12:49:12 2007

Copyright (c) 1982, 2002, Oracle. All rights reserved.

パスワードを入力してください:

Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Production
With the Partitioning, OLAP and Data Mining options
に接続されました。
SQL> exit
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Production
With the Partitioning, OLAP and Data Mining optionsとの接続が切断されました。
discus1:˜ oracle$

| | | コメント (0) | トラックバック (0)

2007年4月 9日 (月)

ORA-20001

マニュアルにもしっかり記載されているが、意外に多いなぁと感じた検索ワードの ”ORA-20001”を取り上げておこうと思います。

ORA-20001 を エラーメッセージマニュアルで確認してみると、

ORA-20000: %s
原因:
このメッセージが発生する原因となったストアド・プロシージャraise_application_errorが呼び出されました。
処置:
エラー・メッセージの記述どおりに問題を修正するか、詳細をアプリケーション管理者またはDBAに問い合せてください。

と、少々、不親切な書き方になっています。

上記、エラーメッセージの解説からは、ORA-20001が、ORA-20000と同じ、ユーザ定義エラーとして予約されているエラーコードであるとは読み取れません。
(Oracleさん、もうちょっとだけ、親切なドキュメントに修正してくれるとありがたいのですが・・・・・)


PL/SQLに慣れた方なら、最初に、冒頭でリンクしているこのマニュアルを見る確立は高いでしょうが、そうで無い方は、ORA-20001というエラーコードを見て、最初に参照するマニュアルはエラーメッセージマニュアルであることの方が多いはず!。 

で、「マニュアルに記載されていない!」 ということになり、ハマるわけです。。。。ね。

前置きはこれくらいにして、本題。

Oracle Database PL/SQLユーザーズ・ガイドおよびリファレンス 10g リリース2(10.2)に書かれているように、ORA-20000からORA-20999は、ユーザが自由にメッセージを割り当てる事ができるように予約されているエラーコードだ。

つまり、ORA-20000 というエラーコードには、Oracle側では対応するエラーメッセージが割り当てられていない。
ユーザが自由にエラーメッセージを割当て意味を持たせることができるエラーコードなんですよ。

たとえば、プロシージャAでは、ORA-20000は、「URLが指定されていません。」というエラーメッセージを割り当てたり、ファンクションBでは、ORA-20001には「指定された予約がありません。」というメッセージを割り当てることができるわけです。


尚、ORA-20000からORA-20999までのエラーコードはユーザが自由に設定できるエラーコードであるため、ほとんどの場合、プロジェクト毎に標準化されることが多いと思います。理由は、ORA-20000からORA-20999のエラーコードに対して一意にエラーメッセージを割り当て、1つのエラーコードが複数の意味を持たないようにするためです。
私が関わったプロジェクトでは、台帳で一元管理し、リリース後は該当システム独自のエラーメッセージマニュアルとしても利用させました。

サンプルコードは、Oracle Database PL/SQLユーザーズ・ガイドおよびリファレンス 10g リリース2(10.2)にもあるのでそちらも参照してください。

以下、簡単なサンプル。。。と行きたいところだがここまで。
(後日、このエントリを更新してサンプルを載せるかも。。。マニュアルに記載されているから追記しないかも。。。。気分次第です。)

| | | コメント (0) | トラックバック (0)

2006年10月15日 (日)

アクセスログを見ていて #3 - 全角半角入り乱れ

ココログのアクセスログには、サーチエンジンで検索された検索ワード/フレーズが記録されている。
その検索ワード/フレーズを見ていると、英数字及び半角カナの全角半角入り乱れが目につくことも少なくない。

その中から、半角/全角が ”細かく”切り替えられているものをビックアップし、入力の器用度等も含め、10点満点で評価してみた。(笑)


ブラウザや、ご使用のフォントによっては全角/半角の区別しずらい状況があるかもしれないので、半角文字は、”赤”にしてあります。



この検索ワードでは、 PLとSQLが全角英字で、なぜか、"/"が半角。DIRECTORYは半角英字。、なかなか半角/全角英数字の切り替えが器用ですね。8点
一文字単位で切り替えてくれたら、10点だった。(笑)
作成 DIRECTRY PL/SQL



半角英字から全角英数字、そして又、半角英字。3点。
ORACLE10g USER_TAB_COLUMNS



"型"の勢いで、全角英字、途中からなぜか、半角英字。3点。
型 datetime オラクル



途中から全角文字。1点。
oraー12560 



そして、全角カナから、半角カナになり、また全角。 ネット上で半角カナを使うという”勇気”ある行動 (笑) だね。 10点。
oracle ストアド char型パラメータバイト上限



これも、半角英数字->半角カナ、途中から全角カナ。前の検索ワードに比べるとパターンが単純なので、9点。
Oracle10g データベース リスナー 停止

ところで、MacOSX で、"ことえり"や"ATOK for MacOSX"を利用している場合、デフォルトでは、半角カナは入力できない。(入力できるように設定することも可能だが使うこともないので入力できるようにしたことはないが。。)

Img2 Img3

ちなみに、アクセスログを見てみると、意外に半角カナを検索文字列として入力しているものが多いのだが、100% Windowsユーザであった。(Mac De Oracleのアクセスログより)ついでなので調べてみたのだが、Windowsでは、半角カナの入力制限及び、半角カナへの変換を制限する設定は無いようですね、今でも。

最後に、
レガシーなシステムやラインプリンタだと、半角カナが無くちゃ困るなんてところもまだ残っているかもしれませんが、ネット上では困った問題を起こす事はあっても、半角カナが無くて困ることは無いだろうと思う。したがって、MacOSXの半角カナ入力制限がデフォルト設定となっていることに対しては大賛成なのである。Windowsもそうなってほしいものだ。

| | | コメント (0) | トラックバック (0)

2006年3月30日 (木)

"日本語" de ストアド

少なくなったとはいえ、今でも、日本語(マルチバイト文字でと言ったほうがいいかもしれない)で表名や列名などを定義したがる方達がいるのですよ。
Oracleでは、""(引用識別子)で囲めば正式にサポートされているわけですから、”利用できるか” といえば、「はい」というのは正しい答えであるわけです。

日本語を用いる理由としては、日本人なら直感的に理解し易い、論理名と同じで理解し易いからだとか。(それ以外の理由を聞いたことがない。。)
本当にそうでしょうか? 日本語で書こうが、英語で書こうが、意味が分かりづらい名称を付けてしまえば同じ事だと思うのですが。。。

マルチバイトで表名や列名を定義した場合の利点と欠点を比べた場合、欠点のほうが多いという事実があるにも関わらず、頑にマルチバイトのオブジェクト名に固執する方には出会ったことはないですが、既存システムで、数百もある表名と列名全てがマルチバイトで定義されているというシステムに不運にも遭遇したことはあります。

その事実を知らされたとき、「そこまで、日本語を使いたいんだったら、スドアドプロシージャ名も日本語で書けや〜〜〜っ!」、と怒鳴りたい気持ちと、暗い穴の中に落ちていく感覚が・・・・・「あ〜〜〜〜〜〜〜っ!」。 orz。
さらに、部屋の反対側で、ソースコードを編集している姿が目に映る。DISPLAYを遠くから眺めると、DISPLAYには以下のような模様が(間違いなくプログラムだと思うが模様に見えた)・・・・・・。そして、その模様はWINDOWをかなりスクロールしても終わらないようだった・・・・・・・模様としては美しいかな。(かなり引きつりならが、苦笑い。)

**********************
*********************
********************
********************
*******************
*******************
******************
*******************
********************
*******************
******************
*******************
*******************
********************
********************
*********************
**********************
**********************
*********************
********************
********************
*******************
*******************
******************
*******************
********************
*******************
******************
*******************
*******************
********************
********************
*********************
**********************

という思い出話とともに、”日本語" de スドアド をやってみましょう。
こんなことやらないとは思いますが、良い子の皆さんは、危険ですので、絶対にやらないでくださいね。念のため。(笑)
> conn / as sysdba
接続されました。

SYS> create user pleco identified by zebra
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users;

ユーザーが作成されました。

SYS> grant connect,create procedure to pleco;

権限付与が成功しました。

SYS> conn pleco
パスワードを入力してください:
接続されました。
PLECO>

PLECO> CREATE OR REPLACE FUNCTION "おりゃ"
2 RETURN VARCHAR2 AS
3 BEGIN
4 RETURN 'マルチバイトのプロシージャ名';
5 END;
6 /

ファンクションが作成されました。

PLECO>
PLECO> col "おりゃ" for a40
PLECO> select "おりゃ"() as "おりゃ" from dual;

おりゃ
----------------------------------------
マルチバイトのプロシージャ名

PLECO>
PLECO> CREATE OR REPLACE PROCEDURE "おりゃおりゃ"
2 AS
3 BEGIN
4 DBMS_OUTPUT.PUT_LINE('マルチバイトのプロシージャ名');
5 END;
6 /

プロシージャが作成されました。

PLECO> set serveroutput on
PLECO> execute "おりゃおりゃ";
マルチバイトのプロシージャ名

PL/SQLプロシージャが正常に完了しました。

PLECO>

さらに、Oracle SQL Developer 1.0 for MacOSXでもやってみた。そこまでしなくても・・・・。
(Oracle SQL Developer 1.0 for MacOSXで、どこまで出来るのか試したかったというのが本音だったりして。。)

ところで、Oracle SQL Developer 1.0 for MacOSX では、スプラッシュを表示する設定になっていてもスプラッシュがでません。(Windowsや、Linuxでもそうなんだろうか? --> 自分で試せば早いのだが。。)








それにしても、ココログの管理画面が重たい。。バージョンアップの影響とのことだが。。これまたクレーム処理が大変だろうなぁ。大きなトラブルのあとだけに。。。

| | | コメント (0) | トラックバック (0)