【Oracle】Standard EditionでData Pumpをパラレル実行する

## 【Oracle】Standard EditionでData Pumpをパラレル実行する

### 動作環境・使用するツールや言語

- Windows 10 Pro 22H2
- Oracle 12c Standard Edition 2

### はじめに
パラレル処理はEnterprise Editionの機能です。Standard Editionにはありません。
Data Pumpはパラレル処理以外に高速化する手段があまりないので、Standard Editionでは悩むところです。
しかし自前でパラレル処理を構築することは可能です。
オブジェクト間の依存関係が複雑に絡み合っているのでそれをクリアする必要があります。

### エクスポート・インポートの構造
まず普通にエクスポートやインポートを実行した時のログを確認してみましょう。
「オブジェクト型SCHEMA_EXPORT/TABLE/TABLE/TABLE_DATAの処理中です」
のように表示されています。
これは「SCHEMA_EXPORT」を頂点とするツリー構造のようなイメージで、スラッシュによって区切っています。

DBにどのようなオブジェクトがあるかによりますが、下記のようなものがあります。
Oracle公式サイトにも特に記載はありませんでしたが、大体どういうものかは名前で推測できます。よく分からないのもあります。

SCHEMA_EXPORT/TABLE/TABLE/TABLE_DATA
⇒テーブルのデータ
SCHEMA_EXPORT/TABLE/TABLE/TABLE
⇒テーブル定義
SCHEMA_EXPORT/TABLE/INDEX/INDEX
⇒B木インデックス
SCHEMA_EXPORT/TABLE/INDEX/FUNCTIONAL_AND_BITMAP/INDEX
⇒ファンクションインデックス、ビットマップインデックス
SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
⇒B木インデックス統計
SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/FUNCTIONAL_AND_BITMAP/INDEX_STATISTICS
⇒ファンクションインデックス、ビットマップインデックスの統計
SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
⇒制約（テーブル単体で完結）
SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT
⇒外部キー制約
SCHEMA_EXPORT/TABLE/COMMENT 
⇒テーブルのコメント
SCHEMA_EXPORT/TABLE/TRIGGER
⇒トリガー
SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
⇒テーブル統計
SCHEMA_EXPORT/USER
⇒ユーザ定義
SCHEMA_EXPORT/SYSTEM_GRANT
⇒システム権限付与の定義
SCHEMA_EXPORT/ROLE_GRANT
⇒ロール付与の定義
SCHEMA_EXPORT/DEFAULT_ROLE
⇒デフォルトロール付与の定義
SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
⇒不明
SCHEMA_EXPORT/DB_LINK
⇒データベースリンク
SCHEMA_EXPORT/SEQUENCE/SEQUENCE
⇒シーケンス
SCHEMA_EXPORT/FUNCTION/FUNCTION
⇒ファンクション
SCHEMA_EXPORT/FUNCTION/ALTER_FUNCTION
⇒不明
SCHEMA_EXPORT/VIEW/VIEW
⇒ビュー
SCHEMA_EXPORT/VIEW/GRANT/OWNER_GRANT/OBJECT_GRANT
⇒オブジェクト権限
SCHEMA_EXPORT/MATERIALIZED_VIEW
⇒マテリアライズドビュー
SCHEMA_EXPORT/JOB
⇒ジョブ
SCHEMA_EXPORT/REFRESH_GROUP
⇒リフレッシュグループ

これらのうち、ユーザ定義やロール、権限に関係するものは一番最初にインポートする必要があります。
インポートのログを見ればわかりますが、先頭に来ているはずです。
インポートのログでは大まかに以下のような順番になっています。

１．ユーザ定義、権限、ロール、シーケンスなど
２．テーブル定義、テーブルデータ
３．テーブルのコメント、ファンクション、ビュー、インデックス、制約、統計など

エクスポートに関してはバラバラでも構いませんが、インポートに関しては基本的にはこの順番を守る必要があります。
外部キー制約、ビュー、マテリアライズドビュー、リフレッシュグループなどは複数テーブル間にまたがる定義なので、全テーブルをインポートしてからでなければインポートできません。
特に外部キー制約に関しては中身のデータが関係するので、テーブルのデータをすべてインポートし終わってからインポートする必要があります。
なお参照パーティションを設定している場合はテーブル定義に外部キー制約が含まれているようなイメージなので、それらを並列にインポートするのは難しいと思います。
また、インポートに時間がかかるのは巨大なオブジェクトであるテーブルやインデックスであるため、並列処理が必要なのはそこになります。
以上を踏まえると、並列インポートの処理順としては

１．ユーザ定義、権限、ロール、シーケンス、テーブル定義などを通常インポート
２．テーブルデータを並列インポート
３．インデックスを並列インポート
４．テーブルのコメント、ファンクション、ビュー、制約、統計などを通常インポート

となります。

### 実際の処理
レコード数の多い巨大なテーブルをいくつかピックアップし、ある程度同じぐらいの規模のグループに分けます。
これらを並列でエクスポート・インポートするようにしましょう。
まずはそれぞれバッチを作って別々にDMPをエクスポートするようにします。
１例ですが、この時、INCLUDEやEXCLUDEの指定は次のようにします。
```
INCLUDE=REF_CONSTRAINT
INCLUDE=TABLE:\"IN (\'【テーブル１】\',\'【テーブル２】\'\)\"
INCLUDE=TABLE:\"IN \(\'【テーブル３】\',\'【テーブル４】\'\)\"
INCLUDE=TABLE:\"IN \(\'【テーブル５】\',\'【テーブル６】\'\)\"
EXCLUDE=TABLE:\"IN \(\'【テーブル１】\',\'【テーブル２】・・・テーブル６まで
```

実際のところ、上のツリー構造でも見たように、テーブルに付随するデータが多いのでテーブル単位で区切った方が楽です。
REF_CONSTRAINTは外部キー制約です。最後にインポートします。
エクスポートに関してはこれらを同時並行で流せばいいだけです。

インポートするときはまずEXCLUDEを指定して出力したDMPを最初にインポートします。
これには全体のメタデータ（＋テーブル１～６以外のテーブル）が入っているので、インポートに必要なユーザ定義などをインポートしますが、普通にインポートした時のログでテーブル定義以前に出てくるもの以外は除外します。
例えば以下のようにします。
```
EXCLUDE=MATERIALIZED_VIEW,REFRESH_GROUP,VIEW,JOB,STATISTICS,CONSTRAINT,INDEX,TABLE_DATA
```
次に並列でそれぞれインポートします。
EXCLUDEのDMPにもテーブル１～６以外のテーブルが入っているのでINCLUDE=TABLE_DATAで入れておきます。
普通にインポートした時はTABLE_DATAをインポートした後にINDEXをインポートしているので、TABLE_DATAの並列インポートとINDEXの並列インポートは分けた方がいいでしょう。

並列のインポートが終わったらEXCLUDEで除外したビュー定義などをインポートします。
最後にREF_CONSTRAINTをインポートします。
なお完了時に同期を取るには、各インポートバッチの最後にフラグファイルを作成し、全部揃っていたら次に進むなどの方法があります。
```BAT:
:TOP 
TIMEOUT 10
IF EXIST 【フラグファイル１】 IF EXIST 【フラグファイル２】・・・GOTO NEXT
GOTO TOP
:NEXT 
```
ジョブ管理系の製品で同期機能があればそれでもいいと思います。
タスクスケジューラでもできるかもしれませんが把握していません。

### 確認
定義をうまくインポートできたかを確認します。
普通にインポートした時と結果が同じになることを確認しましょう。
```SQL:
--オブジェクトのステータス確認
SELECT OBJECT_NAME,STATUS FROM DBA_OBJECTS:
```
テーブル定義、インデックス定義、ユーザ定義、シーケンス、マテリアライズドビュー、ＤＢリンク、トリガー、ファンクションなどはDBMS_METADATA.GET_DDLで取得できます。
詳細はOracle公式サイト等を確認してください。
```SQL:
--テーブル定義
SELECT TABLE_NAME AS テーブル名,TO_CHAR(DBMS_METADATA.GET_DDL('TABLE',USER_TABLES.TABLE_NAME) AS 定義
FROM USER_TABLES;
--インデックス定義
SELECT INDEX_NAME AS インデックス名,TO_CHAR(DBMS_METADATA.GET_DDL('INDEX',USER_INDEXES.INDEX_NAME) AS 定義
FROM USER_INDEXES:
--（以下略）
```
権限などは直接参照できます。
```SQL:
--オブジェクト権限
SELECT GRANTEE,TABLE_NAME FROM DBA_TAB_PRIVS;
--システム権限
SELECT PRIVILEGE FROM DBA_SYS_PRIVS;
--ロール
SELECT GRANTED_ROLE FROM DBA_ROLE_PRIVS;
```
各テーブルの行数もカウントして前後比較した方がいいでしょう。