kagamihogeの日記

kagamihogeの日記です。

SQL*Loaderで大きい表に少量データロードするときの速度

Oracle Databaseユーティリティ11g リリース1(11.1)11 従来型パス・ロードおよびダイレクト・パス・ロードの「従来型パスを使用する場合」には、ダイレクトパスロードよりも従来型INSERTを用いた方がよいケースについての記述がある。

従来型パスを使用する場合
比較的少数の行を索引付きの大きな表にロードする場合。

ダイレクト・パス・ロードでは、既存の索引を新しい索引キーとマージするために、既存の索引をコピーします。既存の索引が非常に大きく、新しいキーの数が非常に少ない場合は、索引をコピーする時間が、ダイレクト・パス・ロードで節約できる時間を相殺してしまうことがあります。

Oracle Databaseユーティリティ11g リリース1(11.1)11 従来型パス・ロードおよびダイレクト・パス・ロード より抜粋

というわけで、実際にやってみることにする。具体的には、インデックスが有効になっているテーブルに百万件入れたあと、SQL*Loaderから千件データをロードする。そのとき、ダイレクトパスロードと従来型INSERTにどのような差が出るのかを確認する。

環境

準備

データを投入していくテーブルはこんな感じ。データロードを一回やるたびにDROP&CREATEして百万件データをロードしなおす。

DROP TABLE DEST PURGE;
CREATE TABLE DEST (COLUMN1 VARCHAR2(16) , CONSTRAINT DEST_PK PRIMARY KEY(COLUMN1));

事前に投入しておくデータはこんな感じ。ゼロ埋め16バイトで数値が昇順に並んだものを百万件。

0000000000000001
0000000000000002
0000000000000003
(以下略)

下記のデータを投入したときの速度を計測する。ゼロ埋め16バイトで百万飛んで一から昇順に並んだものを千件。

0000000001000001
0000000001000002
0000000001000003
(以下略。実際には改行無し)

SQL*Loaderの制御ファイル。下記は従来型INSERTの場合。コミット間隔は1000固定。

OPTIONS (ROWS=1000)
load data 
infile 'data_asc_1000.dat' "fix 16"
APPEND
into table DEST
(COLUMN1 CHAR(16))

下記はダイレクトパスインサートの場合。SINGLEROWは有りと無しとで速度を計測する。

OPTIONS (DIRECT=TRUE)
load data 
infile 'data_asc_1000.dat' "fix 16"
APPEND
into table DEST
SINGLEROW
(COLUMN1 CHAR(16))

実行結果

従来型INSERT 00.19 00.16 00.30
DIRECT=TRUE 05.64 04.41 04.62
DIRECT=TRUE, SINGLEROW 00.37 00.23 00.31

ぶっちゃけ千件程度なんざどんな方法でロードしようが大した差は生じないのだが、そこは本論ではないので置いておく。

この条件下では、従来型INSERTの方がダイレクトパスロードよりもかなり速い。先に示したOracleのリファレンスの記述通り、INSERTを千件発行する方が、ダイレクトパスロードのインデックスのマージ作業よりも安く済む、ということを示している。

また、インデックスの修正コストが、インデックスに追加するデータをリーフブロック末尾に追加するだけと、安いことも影響している。ダイレクトパスロードでSINGLEROWを付与すると、マージはせず一件ずつインデックスに挿入する動作になるため、実行速度は改善される。最も、逐次動作にしてしまったらダイレクトパスロードのメリットを一つ損なうようなものなのだが……

このことから読み取れるのは、空でない表にデータを追加する場合は必ずしもダイレクトパスロードが優位とは限らない。では、常に従来型INSERTが優位かというとそうとも言えない。その理由は、ダイレクトパスロードでもSINGLEROWの余地があること、インデックスに逐次データ追加することとまとめてマージを行うことのどちらがより速度的に優れるかは環境に依存すること。この場合の環境とは、表のレコード数・総レコード数に対するロード件数の割合・メンテナンスされるインデックスの構造と量、など。