【PL/SQL】エラーログを自動収集するDBMS_ERRLOGの使い方

【PL/SQL】エラーログを自動収集するDBMS_ERRLOGの使い方 PL/SQL

データ移行やバルク更新の最中に一部の行だけが制約違反で落ちてしまい、処理全体が中断してやり直しになる――そんな無駄を避けるために用意されているのがDBMS_ERRLOGとLOG ERRORS句です。挿入や更新、削除、MERGEといったDMLを実行しながら、失敗した行の情報を専用テーブルへ自動退避し、成功分はそのまま進めることができます。ここでは準備から基本の使い方、INSERT/UPDATE/MERGEへの適用、エラーテーブルの読み方、ERRTAGの活用、運用時の勘所までを、実務でそのまま使える形で解説します。

エラーログ用テーブルの作成と仕組み

DBMS_ERRLOG.CREATE_ERROR_LOG手続きで、対象表ごとにERR$_プレフィックスのエラーログ表を作成します。中にはエラーの種類や元行のROWID、失敗時に評価しようとした列値などが格納され、後から原因を精密に突き止められます。既定の列としてORA_ERR_NUMBER$(エラー番号)、ORA_ERR_MESG$(メッセージ)、ORA_ERR_ROWID$(元表のROWID)、ORA_ERR_TAG$(任意のタグ)、さらに対象表の列名に対応する縦持ちの値が入ります。

BEGIN
  DBMS_ERRLOG.CREATE_ERROR_LOG(dml_table_name => 'SALES');     -- ERR$_SALES を生成
  DBMS_ERRLOG.CREATE_ERROR_LOG(dml_table_name => 'CUSTOMERS'); -- 必要な表ごとに作成
END;
/
-- 既に存在する場合は ORA-00955 になるため、EXCEPTIONで握るか事前に存在確認を行う

CREATE_ERROR_LOGは列名の長さやデータ型に応じて最適なログ表を自動生成します。追加カラムが欲しいときは生成後にALTER TABLEで拡張して構いません。

基本構文:LOG ERRORS INTO … REJECT LIMIT …

通常のDML文の末尾にLOG ERRORS INTO err$_対象表 REJECT LIMIT n を付けるだけで、失敗行がエラーテーブルへ退避されます。REJECT LIMITで許容する失敗件数を指定し、UNLIMITEDにすると全件退避が可能です。

-- 例:ステージングから本表へ一括挿入。制約に抵触した行はログへ退避し処理継続
INSERT /*+ APPEND */ INTO sales(id, cust_id, amount, sale_dt)
SELECT id, cust_id, amount, sale_dt
  FROM sales_stg
LOG ERRORS INTO err$_sales ('LOAD_20251004') REJECT LIMIT UNLIMITED;

COMMIT;

括弧内の文字列はORA_ERR_TAG$に入る任意タグで、ジョブ名やバッチIDを入れておくと後続分析が容易になります。REJECT LIMITに整数を指定すると、その件数を超えた時点で文全体がエラー終了します。

UPDATEやDELETE、MERGEでも有効に使える

INSERT以外のDMLにも同じ要領で適用できます。たとえば更新先で一意制約に触れる可能性がある場合でも、衝突した行だけをログへ逃し、他の行は進められます。

-- UPDATEへの適用
UPDATE customers c
   SET c.rank = 'GOLD'
 WHERE c.total_amount >= 100000
LOG ERRORS INTO err$_customers('PROMO_202510') REJECT LIMIT UNLIMITED;

-- MERGE(UPSERT)への適用
MERGE INTO customers d
USING (SELECT cust_id, name, email FROM customers_delta) s
   ON (d.cust_id = s.cust_id)
 WHEN MATCHED THEN UPDATE SET d.name=s.name, d.email=s.email
 WHEN NOT MATCHED THEN INSERT (cust_id, name, email) VALUES (s.cust_id, s.name, s.email)
LOG ERRORS INTO err$_customers('DELTA_20251004') REJECT LIMIT UNLIMITED;

COMMIT;

MERGEでは更新側・挿入側どちらで起きた制約違反もログへ記録され、どの句で落ちたかはORA_ERR_MESG$や列値から特定できます。

エラーテーブルの中身を読み解く

実行後はERR$_表を参照することで、どの行がどの制約で失敗したかを追跡できます。タグやタイムスタンプで切り出すと、特定ジョブの失敗だけを迅速に抽出できます。

-- 直近ロードの失敗内容を要約
SELECT ORA_ERR_NUMBER$       AS err_code,
       SUBSTR(ORA_ERR_MESG$,1,200) AS err_msg,
       ORA_ERR_ROWID$        AS bad_rowid,
       ORA_ERR_TAG$          AS tag,
       id, cust_id, amount, sale_dt
  FROM err$_sales
 WHERE ORA_ERR_TAG$ = 'LOAD_20251004'
 ORDER BY bad_rowid;

-- 失敗行だけを再処理したい場合のROWID回収
SELECT ORA_ERR_ROWID$ FROM err$_customers WHERE ORA_ERR_TAG$ = 'DELTA_20251004';

ORA_ERR_ROWID$がNULLになるケース(挿入失敗など)では、ERR$_表側に保持された列値から再構築や再投入の材料を得ます。エラーメッセージは制約名を含むため、原因の特定と対処(例:参照整合の順序入れ替えや無効なメール形式の除外)に直結します。

FORALLやパーティション限定DMLとの組み合わせ

PL/SQLのFORALLで個別DMLを投げる設計よりも、集合志向の1文+LOG ERRORSのほうがシンプルで速い場面が多くあります。どうしても行前処理が必要でFORALLを使うなら、各DMLにLOG ERRORS句を付けることも可能です。また、パーティションが明確な場合は挿入先・更新先をPARTITION句で限定し、失敗の切り分けとリカバリを簡素化します。

FORALL i IN 1..v_rows.COUNT
  INSERT INTO sales PARTITION(p202510) (id, cust_id, amount, sale_dt)
  VALUES (v_rows(i).id, v_rows(i).cust_id, v_rows(i).amount, v_rows(i).sale_dt)
  LOG ERRORS INTO err$_sales('PART_202510') REJECT LIMIT UNLIMITED;
COMMIT;

どんなエラーが拾えるのかと拾えないもの

LOG ERRORSで拾えるのは行単位で起こる例外、たとえば一意・外部キー・チェック制約違反、NOT NULL違反、数値変換や日付変換の失敗などです。文全体に関わるエラー(文法エラー、権限不足、テーブルや列の不存在)や、トリガ内での意図的なアプリケーションエラーの一部はLOG ERRORSでは拾えず、文全体が失敗します。想定外の全体失敗に備えて、実行前後で件数検証やサマリログを残すのが現実的です。

再処理フローとERRTAGの設計

ERRTAGにはロードIDやサブステップ名、対象期間などを埋め込み、同じERR$_表を複数ジョブで共有しても衝突しないようにします。一般的な再処理フローは、DML+LOG ERRORSで成功分を確定させた後、ERR$_表から原因別に分岐し、修正可能なものは補正して再投入、残るものは別テーブルへ隔離保存、最後にERR$_表をクリーンアップという手順です。

-- 例:形式不正のメールアドレスだけ抽出し、検証用に別表へ
INSERT INTO bad_email_queue(cust_id, email, reason, tag, logged_at)
SELECT cust_id, email, 'invalid format', ORA_ERR_TAG$, SYSTIMESTAMP
  FROM err$_customers
 WHERE ORA_ERR_TAG$ = 'DELTA_20251004'
   AND ORA_ERR_MESG$ LIKE '%ORA-06502%'  -- 変換エラー等を例示
;
-- 処理後に対象タグのログを削除(保持ポリシーに応じて実施)
DELETE FROM err$_customers WHERE ORA_ERR_TAG$ = 'DELTA_20251004';
COMMIT;

パフォーマンスと運用上の注意

LOG ERRORSは失敗行のみを別表に挿入するため、通常の成功パスの性能劣化は限定的です。一方でエラーログ表の行は増え続けるため、タグや日付でのローテーション、一定期間での削除やアーカイブを必ず運用に組み込みます。多量の失敗が出る想定でREJECT LIMITをUNLIMITEDにする場合は、ERR$_表の表領域やインデックスの設計にも注意を払い、不要な二次的エラー(表領域不足)でロード全体を止めないようにします。NULL可能な列が多いテーブルでは、ERR$_側の該当列も可変長で作られるため、極端に長い文字列やLOB列の扱いは設計段階で確認しておくと安全です。

まとめ

DBMS_ERRLOGとLOG ERRORSは「成功は確定、失敗は記録して後で対処」という堅牢なDML実行モデルをPL/SQLに持ち込みます。CREATE_ERROR_LOGで準備し、DML末尾にLOG ERRORSとREJECT LIMIT、必要ならERRETAGを添えるだけで運用負荷を大幅に下げられます。エラーテーブルの読み解きと再処理の型をあらかじめ決め、件数検証とローテーションの仕組みを合わせて用意しておけば、データ品質のばらつきに強い取り込み・更新基盤をシンプルに実現できます。