DBMS_OUTPUT は PL/SQL の実行中に文字列をバッファに書き込み、実行終了後にクライアント(SQL*Plus・SQL Developer・DBeaver 等)に出力するパッケージです。デバッグ用の出力・進捗確認・変数値のトレースに広く使われます。
この記事でわかること
- DBMS_OUTPUT.PUT_LINE の基本と SET SERVEROUTPUT ON の設定
- PUT・NEW_LINE・PUT_LINE の違い
- バッファサイズの変更(DBMS_OUTPUT.ENABLE)
- ループ進捗・変数値・例外情報を出力するデバッグパターン
- 本番環境で使えるログテーブルへの書き込みパターン
- DBMS_OUTPUT の制限と代替手段(UTL_FILE・DBMS_APPLICATION_INFO)
DBMS_OUTPUT の基本:PUT_LINE で出力する
SET SERVEROUTPUT ON と基本的な PUT_LINE
-- SQL*Plus / SQL Developer で出力を有効化する(実行前に1回だけ必要)
SET SERVEROUTPUT ON
-- 基本的な出力
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, Oracle!');
DBMS_OUTPUT.PUT_LINE('今日の日付: ' || TO_CHAR(SYSDATE, 'YYYY/MM/DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('1 + 1 = ' || TO_CHAR(1 + 1));
END;
/
-- 出力:
-- Hello, Oracle!
-- 今日の日付: 2024/03/15 10:30:00
-- 1 + 1 = 2
-- PUT と NEW_LINE の使い方(PUT_LINE = PUT + NEW_LINE)
BEGIN
DBMS_OUTPUT.PUT('A'); -- 改行なしで "A" をバッファに追加
DBMS_OUTPUT.PUT('B'); -- 改行なしで "B" を追加
DBMS_OUTPUT.NEW_LINE; -- 改行を追加 → "AB" が1行として出力される
DBMS_OUTPUT.PUT_LINE('CD'); -- "CD" を1行出力(PUT + NEW_LINE と同等)
END;
/
-- 出力:
-- AB
-- CD
バッファサイズの設定(DBMS_OUTPUT.ENABLE)
DBMS_OUTPUT はバッファにすべての出力を蓄積し、PL/SQL ブロック終了後にクライアントへ送ります。デフォルトのバッファサイズは 20000 バイトで、大量の出力が必要な場合は DBMS_OUTPUT.ENABLE でサイズを変更します。
DBMS_OUTPUT.ENABLE でバッファサイズを設定する
-- バッファサイズを 1MB に設定(UNLIMITED も指定可能)
BEGIN
DBMS_OUTPUT.ENABLE(buffer_size => 1000000); -- 1MB
END;
/
-- または SQL*Plus の SET SERVEROUTPUT コマンドでサイズ指定
SET SERVEROUTPUT ON SIZE 1000000 -- 1MB
-- バッファが溢れると ORU-10027: buffer overflow エラーになる
-- → DBMS_OUTPUT.ENABLE(NULL) で UNLIMITED に設定すると溢れない
BEGIN
DBMS_OUTPUT.ENABLE(NULL); -- バッファ無制限
FOR i IN 1 .. 100000 LOOP
DBMS_OUTPUT.PUT_LINE('行 ' || i);
END LOOP;
END;
/
-- 大量の出力でも ORU-10027 が発生しない
デバッグに役立つ出力パターン
変数値・ループ進捗のトレース
DECLARE
v_count NUMBER := 0;
v_dept_id employees.department_id%TYPE;
v_salary employees.salary%TYPE;
BEGIN
-- 変数の値を出力
SELECT MAX(salary) INTO v_salary FROM employees;
DBMS_OUTPUT.PUT_LINE('最大給与: ' || v_salary);
-- ループの進捗を確認(大量データ処理時の途中経過確認)
FOR rec IN (SELECT department_id, COUNT(*) AS cnt
FROM employees GROUP BY department_id
ORDER BY department_id)
LOOP
v_count := v_count + rec.cnt;
DBMS_OUTPUT.PUT_LINE(
'部門 ' || LPAD(rec.department_id, 5) ||
' : ' || LPAD(rec.cnt, 4) || '名' ||
'(累計: ' || v_count || ')'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE('--- 処理完了: 合計 ' || v_count || '件 ---');
END;
/
例外発生時の詳細情報を出力する
BEGIN
-- 意図的に例外を発生させる
DECLARE
v_val NUMBER;
BEGIN
SELECT employee_id INTO v_val FROM employees WHERE 1=0; -- データなし
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('[ERROR] NO_DATA_FOUND 発生');
DBMS_OUTPUT.PUT_LINE(' SQLCODE: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE(' SQLERRM: ' || SQLERRM);
-- エラーのスタックトレースも確認できる(Oracle 12c 以降)
DBMS_OUTPUT.PUT_LINE(' BACKTRACE: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('[ERROR] 予期しない例外: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE(' BACKTRACE: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
RAISE; -- 例外を再発生させる
END;
END;
/
本番環境で使えるログテーブルへの書き込みパターン
本番環境では DBMS_OUTPUT で出力してもアプリケーション側に表示されません。長時間バッチや自動実行のジョブでは、ログをテーブルに書き込む方法が実用的です。
ログテーブルの作成
-- 汎用ログテーブル
CREATE TABLE app_log (
log_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
log_time TIMESTAMP(3) DEFAULT SYSTIMESTAMP NOT NULL,
log_level VARCHAR2(10) DEFAULT 'INFO'
CHECK (log_level IN ('DEBUG', 'INFO', 'WARN', 'ERROR')),
proc_name VARCHAR2(100),
message VARCHAR2(4000),
err_code NUMBER,
err_msg VARCHAR2(4000)
);
ログパッケージの実装例
-- ログ書き込みプロシージャ
CREATE OR REPLACE PROCEDURE write_log(
p_level VARCHAR2,
p_proc_name VARCHAR2,
p_message VARCHAR2,
p_err_code NUMBER DEFAULT NULL,
p_err_msg VARCHAR2 DEFAULT NULL
)
AS
PRAGMA AUTONOMOUS_TRANSACTION; -- 親トランザクションから独立してコミット
BEGIN
INSERT INTO app_log (log_level, proc_name, message, err_code, err_msg)
VALUES (p_level, p_proc_name, SUBSTR(p_message, 1, 4000),
p_err_code, SUBSTR(p_err_msg, 1, 4000));
COMMIT; -- 親トランザクションが ROLLBACK しても、ログは残る
END write_log;
/
-- 使い方: バッチ処理のログ記録
CREATE OR REPLACE PROCEDURE batch_update_salary AS
v_updated NUMBER := 0;
BEGIN
write_log('INFO', 'batch_update_salary', '処理開始');
UPDATE employees SET salary = salary * 1.05
WHERE department_id = 50;
v_updated := SQL%ROWCOUNT;
write_log('INFO', 'batch_update_salary',
v_updated || '件の給与を更新しました');
COMMIT;
EXCEPTION
WHEN OTHERS THEN
write_log('ERROR', 'batch_update_salary', '給与更新エラー',
SQLCODE, SQLERRM || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
ROLLBACK;
RAISE;
END;
/
PRAGMA AUTONOMOUS_TRANSACTION でログを独立してコミット
ログ書き込みプロシージャに
ログ書き込みプロシージャに
PRAGMA AUTONOMOUS_TRANSACTION を付けると、呼び出し元のトランザクションが ROLLBACK されてもログレコードはコミットされたまま残ります。バッチ処理でエラーが発生して ROLLBACK した場合でも、どこまで処理が進んでいたか確認できます。
DBMS_OUTPUT の制限と代替手段
| 制限・問題点 | 対策・代替手段 |
|---|---|
| PL/SQL 終了後にまとめて出力(リアルタイムではない) | DBMS_APPLICATION_INFO でリアルタイム進捗を V$SESSION に記録 |
| バッファが溢れると ORU-10027 エラー | DBMS_OUTPUT.ENABLE(NULL) または UTL_FILE でファイルに書き出し |
| 本番の自動実行(DBMS_SCHEDULER 等)では出力されない | ログテーブル(AUTONOMOUS_TRANSACTION)または UTL_FILE を使う |
| VARCHAR2 の最大長(32767バイト)を超える出力は1行に収まらない | 長いメッセージは複数回に分けて PUT_LINE するか CLOB でログテーブルに書く |
DBMS_APPLICATION_INFO でリアルタイム進捗を記録する
-- DBMS_APPLICATION_INFO: V$SESSION の MODULE/ACTION/CLIENT_INFO に書き込む
-- → 別セッションから V$SESSION をポーリングしてリアルタイムで進捗確認できる
BEGIN
-- ジョブ開始時にモジュール名を設定
DBMS_APPLICATION_INFO.SET_MODULE('batch_update_salary', '初期化');
FOR i IN 1 .. 1000 LOOP
-- ループ内で進捗を更新
DBMS_APPLICATION_INFO.SET_ACTION('処理中: ' || i || '/1000');
-- ... 実際の処理 ...
END LOOP;
DBMS_APPLICATION_INFO.SET_MODULE(NULL, NULL); -- クリア
END;
/
-- 別セッションから確認
SELECT module, action, client_info
FROM v$session
WHERE module = 'batch_update_salary';
-- ACTION: 処理中: 350/1000(リアルタイムで更新されている)
まとめ
- SET SERVEROUTPUT ON:SQL*Plus・SQL Developer でまず実行。サイズ指定で大量出力に対応
- DBMS_OUTPUT.PUT_LINE:変数の値・ループ進捗・例外情報のデバッグに便利
- DBMS_UTILITY.FORMAT_ERROR_BACKTRACE:例外発生箇所のスタックトレースを取得
- PRAGMA AUTONOMOUS_TRANSACTION:ログを親トランザクションから独立してコミット
- DBMS_APPLICATION_INFO:リアルタイムで V$SESSION に進捗を記録。長時間バッチの監視に有効
PL/SQL の例外処理の詳細はPL/SQL 例外処理(Exception Handling)完全ガイドを、ファイルへの出力が必要な場合はUTL_FILE完全ガイドも参照してください。
