OCIを使ってOracleに高速にデータをロードする
最近こればっかりだが…、現在embulk-output-oracleの高速化に励んでいる。
どうやったら大量データをOracleに高速に突っ込めるか?というのをいろいろ試してみた。
SQL*Loader
その目的に作られているだけあって、さすがに速い。
JDBCからダイレクト・パス・ロード
SQL*Loaderでは、普通のINSERTではなく、ダイレクト・パス・ロードという特別な方法で高速にデータをロードしている。
実はこれ、JDBC経由でも使える。
APPEND_VALUESヒントを付けるのだ。
INSERT /*+ APPEND_VALUES */ INTO <テーブル名> ...
普通のINSERTに比べるとだいぶ速い。
とは言え、SQL*Loaderにはまだまだ敵わない。
OCI(Oracle Call Interface)でダイレクト・パス・ロード
速度を追求するなら、やはりJavaよりC。
OracleはCで使えるライブラリを提供していて、そこにダイレクト・パス・ロード用のAPIも含まれている。
OracleのInstant Client SDKとかいうやつをダウンロードすればよい。
ドキュメントはこれ。自分が使ったのは12cだが、なぜか11gのドキュメントしか見つからなかった。
書いてある通りなんだけど、少し補足してみる。
環境の準備
自分の場合はWindowsでVisual Studio利用だが、どの環境でも同じようなものだと思う。
- コンパイル時のインクルードパスに"oci.h"のパスを追加
- リンク時のライブラリパスに"oci.lib"のパスを追加
- リンク対象の"oci.lib"を追加
データベースへの接続
データベースへの接続には、OCILogonとかOCILogon2を使えばよい。
でもこいつの引数にはデータベース名、ユーザ名、パスワードしかない。
サーバ名はどうやって指定するの?と思ったが、2通りあるようだ。
- tnsname.oraでマッピングを定義する
- データベース名に「<サーバ名>:<ポート番号>/データベース名」の形式で渡す
後者が使えるのが嬉しい。
ロードの準備
OCIDirPathPrepareによりロードの準備をするのだが、その前にいろいろ属性を設定しなくてはいけない。
ドキュメントには「操作するオブジェクトの名前、列データの外部属性、およびすべてのロード・オプション」とあるが、具体的には最低限何を設定すればよいのか調べてみた。
- テーブル名 (OCI_ATTR_NAME)
- 列数 (OCI_ATTR_NUM_COLS)
- 各列の列名 (OCI_ATTR_NAME)
- 各列の型 (OCI_ATTR_DATA_TYPE)
- 各列のサイズ (OCI_ATTR_DATA_SIZE)
ロード処理の流れ
基本的には、
- OCIDirPathColArrayEntrySetにより配列にデータを設定する
- OCIDirPathColArrayToStreamにより配列をストリームに変換する
- OCIDirPathLoadStreamによりストリームをロードする
- まだデータがある場合はOCIDirPathStreamResetによりストリームをリセットし、1に戻る
- OCIDirPathFinishにより終了する
という流れになる。
配列とストリーム、と2段階になるのがちょっとややこしい。
配列のサイズは、OCIAttrGet関数で取得できる(属性の名前はOCI_ATTR_NUM_ROWS)。
実は、配列のキャパシティとストリームのキャパシティは異なり、2で入り切らない場合がある(OCI_CONTINUEが返される)。
その場合は、3、4と進んだ後、2に戻って残りの配列をストリームに変換する。
何行変換されたかは、OCIAttrGet関数で取得できる(属性の名前はOCI_ATTR_ROW_COUNT)。
OCIDirPathColArrayToStreamにはrowcnt(配列内の行数)とrowoff(配列内の開始インデックス)を渡すことができるので、rowoffに足してやればよい。
rowcntの方は、残りの行数ではなくて配列全体の行数なので、変える必要はない(ここを間違えてはまってしまった)。
さすがに速い!!!
で、実際にロードしてみたら、さすがに速かった。
SQL*Loaderよりちょっと遅いくらい。
まとめ
OCIでのダイレクト・パス・ロードはかなり速いので、スピード狂の人はお試しあれ。
OS | Windows 7 (64bit) |
Oracle | 12c |
Instant Client SDK | 12.1.0.2.0 (Windows x64) |
Visual Studio | 2010 |