ora2pg エラーの原因切り分けと対処手順
目次
OracleからPostgreSQLへの移行でora2pgを使い始めると、スキーマ変換の中身を検討する前に「そもそも動かない」段階で止まることが多いです。install_driver(Oracle) failed、ORA-12154、SHOW_TABLEを叩いても無反応、出力したDDLが文字化け。いずれも原因の層が違うだけで、確認する順番を決めておけば短時間で切り分けられます。
以下では「接続」「文字コード」「変換差異」の3層に分けて、確認しやすい順に解消していきます。コマンドと設定値はそのまま試せる形で載せました。
結論:ora2pg のトラブルは「接続」「文字コード」「変換差異」の3つに分かれる
ora2pgはPerl製のツールで、DBI → DBD::Oracle → Oracleクライアントライブラリ(libclntsh)という経路でOracleに接続し、取得したメタデータとPL/SQLをPostgreSQL向けに書き換えて出力します。この構造を知っていると、エラーがどの層で起きているか見当がつきます。
| 層 | 症状の例 | 主な原因 |
|---|---|---|
| 接続(Perl / クライアント / ネットワーク / 認証) | install_driver(Oracle) failed、ORA-12154、ORA-01017、ORA-12541 |
DBD::Oracle 未導入、LD_LIBRARY_PATH 未設定、DSN 書式ミス、権限不足 |
| 文字コード | 出力 SQL が文字化け、? や 繧 のような化け方 |
NLS_LANG と DB キャラクタセットの不一致、CLIENT_ENCODING、BINMODE |
| 変換差異 | 型が意図と違う、関数が PostgreSQL で構文エラー、件数や値が合わない | 型マッピングの既定値、PL/SQL の非対応構文、Oracle 固有のセマンティクス |
この順番を守ってください。接続が怪しいまま型マッピングを調整しても、SHOW_TABLEの結果が空だった原因は権限不足、ということが起こります。接続が通っていれば-t SHOW_REPORTでスキーマ全体の構成が見え、以降の判断材料がそろいます。
原因の切り分け手順(確認しやすい順)
まず ora2pg -t SHOW_VERSION で Oracle に到達できているか確認する
最初に投げるのはSHOW_VERSIONです。接続してv$version相当の情報を取るだけなので、ここが通ればPerlモジュール、クライアントライブラリ、ネットワーク、認証がすべてクリアできていると判断できます。
ora2pg -c /etc/ora2pg/ora2pg.conf -t SHOW_VERSION
Oracleのバージョン文字列が返れば接続層は問題なし。失敗したらメッセージを見て次のどれかに分類します。
install_driver(Oracle) failed→ Perl / クライアントライブラリの構成問題ORA-12154(接続識別子を解決できない)、ORA-12541(リスナーなし)、ORA-12514(サービス名を知らない)→ DSN・tnsnames・リスナー側ORA-01017(ユーザー名/パスワード不正)、ORA-28000(アカウントロック)→ 認証- 接続は通るが権限エラー → データディクショナリへの参照権限
デバッグ出力は-dで増やせます。ora2pgが発行しているSQLやディレクティブの解釈結果が見えます。
ora2pg -c /etc/ora2pg/ora2pg.conf -t SHOW_VERSION -d
ora2pgを疑う前に、同じホスト・同じOSユーザーからsqlplusで接続できるかを確認しておくと切り分けが速くなります。
sqlplus 'MIGUSER/password@//10.0.0.10:1521/ORCLPDB1'
sqlplusで通るのにora2pgで通らないならora2pg.confのDSN書式、sqlplusでも通らないならネットワーク・リスナー・認証側の問題です。
次に Oracle クライアントと DBD::Oracle の構成を確認する
install_driver(Oracle) failed系は、ほぼこの層で決まります。まずDBD::OracleがPerlから見えているかを確認します。
perl -e 'use DBI; print join("\n", DBI->available_drivers), "\n"'
perl -MDBD::Oracle -e 'print "$DBD::Oracle::VERSION\n"'
Oracleが一覧に出ない、または2行目でエラーになるなら、DBD::Oracle自体が未導入か、共有ライブラリの解決に失敗しています。後者かどうかはOracle.soの依存関係を見ればわかります。
find $(perl -e 'print join " ", @INC') -name 'Oracle.so' 2>/dev/null
ldd /usr/lib64/perl5/vendor_perl/auto/DBD/Oracle/Oracle.so | grep -i clntsh
libclntsh.so.19.1 => not foundのように出たら、Instant Clientのパスが実行時に解決されていません。環境変数も併せて確認します。
echo "ORACLE_HOME=$ORACLE_HOME"
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"
echo "TNS_ADMIN=$TNS_ADMIN"
echo "NLS_LANG=$NLS_LANG"
見落としやすいのは実行コンテキストによって環境変数が変わる点です。対話シェルでは.bash_profileのLD_LIBRARY_PATHが効いていても、cronやsystemdのサービス、CIのジョブには渡りません。手で叩くと動くのに自動実行で落ちるときは、まずここを疑ってください。
最後に ora2pg.conf の接続・スキーマ・文字コード設定を確認する
接続層が通ったら、ora2pg.confの記述を点検します。最小構成は次のとおりです。
# TNS を使わない書式(推奨:環境依存が少ない)
ORACLE_DSN dbi:Oracle:host=10.0.0.10;service_name=ORCLPDB1;port=1521
ORACLE_USER MIGUSER
ORACLE_PWD password
# DBA_* ビューに権限がない場合は 1(USER_* ビューを使う=自分のオブジェクトのみ)
USER_GRANTS 0
# Oracle 側の対象スキーマ。Oracle はディクショナリに大文字で格納される
SCHEMA APPDATA
# 出力側のスキーマ名
PG_SCHEMA appdata
EXPORT_SCHEMA 1
NLS_LANG AMERICAN_AMERICA.AL32UTF8
CLIENT_ENCODING UTF8
BINMODE utf8
TYPE TABLE
OUTPUT schema.sql
ポイントは3つです。
- DSNの書式。
dbi:Oracle:ORCLのようなTNSエイリアス形式はtnsnames.oraとTNS_ADMINに依存します。ORA-12154が出るならホスト指定形式(host=...;service_name=...)に切り替えると原因を切り離せます。マルチテナント環境ではPDBに接続するのでservice_name=を使います。 - SCHEMAの大文字小文字。Oracleはクォートなしで作成したオブジェクト名を大文字でディクショナリに持ちます。
SCHEMA appdataと小文字で書くと一致せず、結果が空になります。 - パスワードの扱い。
ORACLE_PWDの値に#が含まれるとコメントとして解釈される可能性があります。記号を含むパスワードは変更するか、ORA2PG_PASSWD環境変数で渡す方法を公式ドキュメントで確認してください。
この記事のテーマをさらに学べる本
移行後を見据えて、PostgreSQLの内部構造やテーブル設計、バックアップ・監視を体系的に解説しています。
楽天ブックスで詳しく見るエラーメッセージ別の対処
install_driver(Oracle) failed / libclntsh.so が見つからない
典型的なメッセージはこの形です。
install_driver(Oracle) failed: Can't load '/usr/lib64/perl5/vendor_perl/auto/DBD/Oracle/Oracle.so'
for module DBD::Oracle: libclntsh.so.19.1: cannot open shared object file:
No such file or directory at /usr/share/perl5/DBI.pm line 1234.
原因は大きく3パターンです。
(1) Instant Client が入っていない/devel パッケージがない
DBD::OracleのビルドにはBasicに加えてSDK(oracle-instantclient-devel、zip版ならsdk)が必要です。両方入れてください。
# RPM 版の例(バージョンは環境に合わせる)
rpm -qa | grep -i instantclient
(2) 実行時に共有ライブラリのパスが解決されていない
LD_LIBRARY_PATHで都度渡すより、ldconfigに登録したほうがcronやsystemdでも安定します。
echo /usr/lib/oracle/19.19/client64/lib | sudo tee /etc/ld.so.conf.d/oracle-instantclient.conf
sudo ldconfig
ldconfig -p | grep clntsh
(3) ビルド時と実行時のバージョン不一致
Instant Clientを19cから21cに入れ替えるとlibclntsh.so.19.1が無くなります。DBD::Oracleはビルド時のライブラリ名を参照するため、クライアントを入れ替えたらDBD::Oracleも再ビルドするのが確実です。
export ORACLE_HOME=/usr/lib/oracle/19.19/client64
export LD_LIBRARY_PATH=$ORACLE_HOME/lib
cpan -i DBD::Oracle
ora2pg自体はソースからperl Makefile.PL && make && make installで入れますが、libclntsh.soからlibclntsh.so.19.1へのシンボリックリンクが無いとビルド段階で失敗することがあります。SDKパッケージが作らない場合は手動で張ります。
ORA-12154・ORA-01017・SHOW_TABLE の結果が空になる
ORA-12154 / ORA-12514 / ORA-12541
接続文字列とリスナー側の不整合です。確認は上から順に。
# リスナーに到達できるか
nc -zv 10.0.0.10 1521
# TNS エイリアスを使う構成なら
tnsping ORCLPDB1
ORA-12514はリスナーには届いているがサービス名が違うケースです。DB側でlsnrctl servicesを確認し、PDBのサービス名を使います。TNS利用時だけORA-12154が出るなら、TNS_ADMINがora2pgの実行プロセスに渡っていない可能性が高いです。
ORA-01017
11g以降はパスワードの大文字小文字が区別されます(SEC_CASE_SENSITIVE_LOGONの既定値)。設定ファイルにコピーしたときの末尾空白や全角文字も疑ってください。sqlplusで同じ文字列が通るかを先に確認するのが早道です。
接続は成功するが SHOW_TABLE が空
ora2pg -c ora2pg.conf -t SHOW_TABLE
何も出力されない場合、順に確認します。
SCHEMAの値が大文字で、実際のオーナー名と一致しているか- ディクショナリ参照権限があるか。ora2pgは既定で
ALL_*/DBA_*ビューを参照します。DBA_*が見えない移行用ユーザーならUSER_GRANTS 1でUSER_*ビューに切り替えます(対象は自分が所有するオブジェクトのみ) - 他スキーマのオブジェクトを吸い出すなら、DB側で
SELECT_CATALOG_ROLE(またはSELECT ANY DICTIONARY)相当の権限が必要です
Oracle側で直接確認すると切り分けが確定します。
SELECT owner, COUNT(*) FROM all_tables GROUP BY owner ORDER BY 2 DESC;
SELECT * FROM session_privs WHERE privilege LIKE '%DICTIONARY%';
テーブルは出るのにビューやプロシージャだけ出ない場合は、ALL_VIEWS / ALL_SOURCEへの権限と、TYPEディレクティブの指定漏れを確認します。
Oracle側のディクショナリや権限まわりの基礎は、Oracle Database 運用・トラブル解決ガイドで整理しています。
変換結果が合わないとき:文字化け・型マッピング・PL/SQL の見直し
接続が通ってからの問題は、原因がora2pgの設定か、OracleとPostgreSQLのセマンティクス差かを切り分けます。
文字化け
まずOracle側のキャラクタセットを確認します。
SELECT parameter, value FROM nls_database_parameters
WHERE parameter IN ('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET');
AL32UTF8ならNLS_LANG AMERICAN_AMERICA.AL32UTF8、JA16SJISTILDEなどならDB側の値に合わせます。ここをDB実体と違う値にすると、Oracleクライアントが余計な変換をかけて化けます。出力ファイル側はCLIENT_ENCODING UTF8とBINMODE utf8でUTF-8に統一すると扱いやすいです。
注意したいのは、Oracle側にDBのキャラクタセットと中身が食い違ったデータが入っている場合です。WE8MSWIN1252のDBにUTF-8のバイト列を押し込んだ運用をしていたなら、クライアント側の設定をどういじっても正しく読めません。ora2pg側では解決できないため、移行前にデータを正規化するか、バイト列として取り出して変換する方針を決めます。
型マッピング
ora2pgの既定値は使いやすさ寄りに調整されているため、業務要件と合わないことがあります。特に数値型に注意してください。
# NUMBER(p,0) を smallint/integer/bigint に寄せる
PG_INTEGER_TYPE 1
# NUMBER(p,s) を浮動小数(real/double precision)にするか
# 金額など丸め誤差が許されない列がある場合は 0 にして numeric を使う
PG_NUMERIC_TYPE 0
# 型を明示的に指定する
DATA_TYPE NUMBER(3,0):smallint,NUMBER(10,0):bigint,DATE:timestamp(0),CLOB:text,BLOB:bytea
# 列単位の上書き(表名:列名:型)
MODIFY_TYPE ORDERS:STATUS:text,ORDERS:AMOUNT:numeric(12,2)
# NUMBER(1) を boolean にする場合
REPLACE_AS_BOOLEAN ORDERS:IS_ACTIVE
NUMBERを精度指定なしで使っている列はnumericになりがちで、整数演算やインデックス検索の効率に影響します。移行後に性能が出ない原因になるので、DEFAULT_NUMERICやMODIFY_TYPEの扱いは公式ドキュメントで現行バージョンの既定値を確認したうえで決めてください。
データ移行時にORA-24345: A truncation or null fetch error occurredが出る場合は、LOB列のサイズがLONGREADLENの既定値を超えています。該当列の最大長を調べて余裕のある値に上げます。
LONGREADLEN 104857600
LONGTRUNCOK 0
PL/SQL
PLSQL_PGSQL 1でora2pgはPL/SQLをPL/pgSQLに書き換えますが、構文の置換が中心です。DECODE→CASE、NVL→COALESCE、ROWNUM→LIMIT、(+)の外部結合といった定型は変換されます。一方、次は人手の設計変更が必要です。
- 自律トランザクション(
PRAGMA AUTONOMOUS_TRANSACTION):PostgreSQLに等価機能はなく、dblinkや別接続での実装に置き換える DBMS_OUTPUT、DBMS_LOCK、UTL_FILEなどの組み込みパッケージ:orafce拡張で代替できるものと、独自実装が必要なものがあるCONNECT BY:再帰CTEへの書き換えが必要で、LEVELやSYS_CONNECT_BY_PATHは個別に手当てする- 日時関数:
SYSDATEの置き換え先がLOCALTIMESTAMP系かclock_timestamp()かで、トランザクション内の値が固定されるかどうかの挙動差が出る
変換後のファイルにはora2pgが注記コメントを残すので、まとめて拾って残件リストにします。
grep -rn -E 'TODO|not supported|ORA2PG' output/ | tee migration_todo.txt
構文が通っても結果が変わるのがOracle固有のセマンティクスです。空文字とNULLの同一視、VARCHAR2の比較時の空白扱い、ORDER BYにおけるNULLの位置(Oracleは既定でNULLS LAST、PostgreSQLは昇順でNULLS LAST、降順ではNULLS FIRST)など、ora2pgで直せない差分はアプリ側の検証で洗い出します。この種の症状別の切り分けはOracle から PostgreSQL 移行でハマるポイントにまとめてあります。
よくある質問
ora2pg は Oracle クライアントなしで使える?
DBに接続してメタデータやデータを取り出す用途では、DBD::OracleとOracleクライアントライブラリが必要です。PerlからOracleに接続する仕組み上の要件です。
すでに手元にOracleのDDL / PL/SQLをテキストで持っている場合は、-i(INPUT_FILE)でファイルを入力として変換できます。この経路ならOracleへの接続は不要です。
ora2pg -c ora2pg.conf -t PACKAGE -i package_body.sql -o package_pg.sql
クライアントの導入が難しい環境では、Instant ClientとDBD::Oracleを同梱したコンテナイメージを用意し、そこから接続する構成も現実的です。
ESTIMATE_COST の移行工数見積もりはどこまで参考にできる?
SHOW_REPORTにコスト見積もりオプションを付けると、オブジェクト数とPL/SQLの難所キーワードを集計して人日換算のレポートを出します。
ora2pg -c ora2pg.conf -t SHOW_REPORT --estimate_cost --dump_as_html -o report.html
内訳はCOST_UNIT_VALUE(1単位あたりの分数)で換算されるため、この値を自分のチームの実績に合わせて調整しないと絶対値としては使えません。次のコストは集計に含まれません。
- データ移行の実行時間と、再実行・切り戻しの運用設計
- アプリケーション側のSQL改修(Oracle固有関数、暗黙型変換に依存したSQL)
- テスト(結合テスト、性能テスト、本番相当データでの差分検証)
- 移行後の実行計画チューニングとVACUUM / autovacuum設計
使いどころは、スキーマ間・システム間の相対的な難易度比較と、自動変換できない箇所のあたり付けです。レポートに挙がるキーワード別の件数は、人手対応の見積もり根拠として実用的です。
パッケージやトリガーはそのまま PostgreSQL で動く?
そのままでは動きません。構造が変わります。
パッケージはPostgreSQLに同じ概念がないため、ora2pgは「パッケージ名のスキーマ+その中の関数」という形に展開します。問題になりやすいのはパッケージ変数です。Oracleではセッション中に状態を保持しますが、展開後の関数群では保持されません。カスタムGUC(SET/current_setting())や一時テーブルでの代替を設計してください。
トリガーは、Oracleが本体をCREATE TRIGGER内に直接書けるのに対し、PostgreSQLではトリガー関数とCREATE TRIGGERに分かれます。ora2pgはこの分割と:NEW / :OLDからNEW / OLDへの置換を行いますが、以下は確認が必要です。
- BEFORE行トリガーの関数は
RETURN NEW(またはRETURN NULLで処理中止)を返す必要がある - 同一イベントに複数トリガーがある場合、PostgreSQLではトリガー名のアルファベット順に起動する。Oracleで
FOLLOWSを使って順序制御していたなら、命名で順序を再現する - 複合トリガー(
COMPOUND TRIGGER)は分解設計が必要
OSS-DB Silver 対策テキスト(OSS教科書)
PostgreSQLの仕組みを体系的に学ぶなら、OSS-DB Silverの公式対応テキストが近道です。資格を取らなくても基礎固めに役立ちます。
再発防止:移行検証を繰り返せる状態にしておく
ora2pgのトラブル対応で役に立つのは、同じ手順を何度でも流せる状態にしておくことです。移行はスキーマ修正と再検証の反復になります。
プロジェクトの雛形はora2pg自体が作れます。
ora2pg --project_base /opt/migration --init_project myapp
設定ファイル、出力先ディレクトリ(schema / data / report)、エクスポート用スクリプトを含むツリーが生成されます。この構成に乗せたうえで、次を習慣にしておくと再現性が確保できます。
ora2pg.confをGit管理する。パスワードは環境変数や外部ファイルに分離する- 型マッピングの上書き(
MODIFY_TYPE/DATA_TYPE)は、そうした理由をコメントで残す - データ投入は
DROP_INDEXES、DEFER_FKEY、ORACLE_COPIES/JOBSなどの並列・制約遅延オプションを使い分け、流し直しの時間を短縮する - 検証は「件数」「主キーの集合差」「数値列の合計」「文字列のハッシュ」の4点を自動化する。目視確認では反復できない
- 残件リスト(
migration_todo.txt)をチケット化し、スキーマ再生成のたびに未対応が増えていないか確認する
移行後に「動くが遅い」フェーズへ移ると、問題の性質はツールの話からPostgreSQLの実行計画・VACUUM・ロックの話に変わります。その段階の切り分けはPostgreSQL 運用・トラブル解決ガイドが入口になります。