【Oracle】ORA-06511の原因と解決方法|cursor already open の直し方

【Oracle】ORA-06511の原因と解決方法|cursor already open の直し方 Oracle

ORA-06511: PL/SQL: cursor already open は、Oracle PL/SQLで、すでに開いている明示的カーソルをもう一度 OPEN しようとした時に発生します。カーソルを開いたまま同じ処理を再実行した、例外発生時に CLOSE されなかった、ループ内で同じカーソルを何度も OPEN した、といったケースで起きやすいです。

Oracle公式のORA-06511説明でも、原因は「すでにOPEN済みのカーソルをOPENしようとした」ことで、対処は再オープン前にカーソルを閉じることです。明示的カーソル全体の基本は 明示的カーソル完全ガイド も参考になります。

先に結論
ORA-06511が出たら、OPEN cursor_name; の直前で cursor_name%ISOPEN を確認します。開いているなら CLOSE してから OPEN するか、そもそもカーソルFORループに置き換えます。閉じたカーソルをFETCHするエラーは、後で扱う ORA-01001: invalid cursor 側です。
スポンサーリンク

ORA-06511とは

ORA-06511は、PL/SQLの事前定義例外 CURSOR_ALREADY_OPEN に対応するエラーです。明示的カーソルは OPENFETCHCLOSE の状態を持つため、OPEN済みのまま再度OPENするとエラーになります。

状態 操作 結果
未OPEN OPEN c; 正常
OPEN済み OPEN c; ORA-06511
OPEN済み FETCH c INTO ... 正常
CLOSE済み FETCH c INTO ... ORA-01001になりやすい

つまり、明示的カーソルは「未OPEN → OPEN済み → CLOSE済み」という状態を意識して扱う必要があります。状態管理が面倒な処理では、カーソルFORループへ寄せる方が保守しやすいです。

発生する典型例

次の例では、同じ明示的カーソル c_emp を2回OPENしています。1回目のOPEN後にCLOSEしていないため、2回目のOPENでORA-06511になります。

ora06511-open-twice.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
BEGIN
  OPEN c_emp;
  OPEN c_emp;
END;
/

-- ORA-06511: PL/SQL: cursor already open
-- ORA-06512: at line 7

発生行は ORA-06512 で確認します。行番号の読み方は ORA-06512の原因と読み方 で詳しく整理しています。

基本の直し方

再度OPENする必要がある場合は、先に %ISOPEN で状態を確認し、開いていれば閉じます。ただし、毎回この形を書くより、処理構造を見直した方が良いことも多いです。

close-before-open.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
BEGIN
  IF c_emp%ISOPEN THEN
    CLOSE c_emp;
  END IF;

  OPEN c_emp;
  CLOSE c_emp;
END;
/
やりたいこと 安全な書き方 注意点
再OPENしたい %ISOPEN確認後にCLOSE 状態が残っている前提で防御する
1回だけ全件処理したい カーソルFORループを使う OPEN/CLOSEを自分で書かなくてよい
例外時も閉じたい 例外ハンドラで%ISOPEN確認してCLOSE 握りつぶさず再送出する
何度も同じカーソルを使う 処理単位ごとにOPEN/CLOSEを明確にする パッケージ変数カーソルは特に注意

安全なOPEN/FETCH/CLOSEテンプレート

明示的カーソルを使う必要がある場合は、正常系と例外系の両方で閉じる形をテンプレート化しておくと安全です。処理途中で例外が起きても、カーソルを開いたまま残しにくくなります。

safe-explicit-cursor-template.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;

  LOOP
    FETCH c_emp INTO l_id;
    EXIT WHEN c_emp%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE(l_id);
  END LOOP;

  CLOSE c_emp;
EXCEPTION
  WHEN OTHERS THEN
    IF c_emp%ISOPEN THEN
      CLOSE c_emp;
    END IF;
    RAISE;
END;
/

手動カーソルを使うべき場面

ORA-06511を避けるだけならカーソルFORループが簡単ですが、明示的カーソルが必要な場面もあります。必要性が薄いなら、手動OPEN/FETCH/CLOSEを減らす方が安全です。

処理 おすすめ 理由
単純に全件を順番に処理 カーソルFORループ OPEN/CLOSEを自動管理できる
途中でFETCHを細かく制御 明示的カーソル フェッチ単位や終了条件を制御しやすい
BULK COLLECT LIMITで分割取得 明示的カーソル 大量データの分割処理に向く
例外時の復旧が複雑 設計見直し CLOSE漏れが起きやすい

症状別の最短修正

ORA-06511は、カーソルを開いたままにした場所を見つけるのが近道です。発生箇所に応じて、次のように切り分けます。

症状 原因になりやすい箇所 最短修正
同じ行で毎回落ちる 直前で同じカーソルをOPEN済み OPEN前に%ISOPENを確認
2回目の実行だけ落ちる 前回例外時にCLOSEされていない 例外ハンドラでCLOSEしてからRAISE
ループ内で落ちる ループのたびに同じカーソルをOPEN OPEN位置をループ外に出すかカーソルFORループへ
パッケージ処理で落ちる パッケージ状態としてカーソルが開いたまま 処理終了時に必ずCLOSE
動的に条件を変えたい 同じカーソルを再利用しようとしている 一度CLOSEしてからOPENし直す

例外発生時にCLOSEされない場合

実務で多いのは、FETCH中や処理中に例外が発生し、CLOSE まで到達しないケースです。その後に同じセッションや同じパッケージ状態で再実行すると、カーソルが開いたままになっていてORA-06511につながります。

bad-exception-no-close.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  FETCH c_emp INTO l_id;
  RAISE_APPLICATION_ERROR(-20001, '途中でエラー');
  CLOSE c_emp;
END;
/

-- CLOSEまで到達しない
good-exception-close.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  FETCH c_emp INTO l_id;
  RAISE_APPLICATION_ERROR(-20001, '途中でエラー');
  CLOSE c_emp;
EXCEPTION
  WHEN OTHERS THEN
    IF c_emp%ISOPEN THEN
      CLOSE c_emp;
    END IF;
    RAISE;
END;
/

例外処理全体の考え方は PL/SQL例外処理ガイド も参考になります。

カーソルFORループに置き換える

明示的にOPEN/FETCH/CLOSEを書く必要がない処理なら、カーソルFORループを使うとORA-06511を避けやすくなります。カーソルFORループでは、PL/SQLがOPEN、FETCH、CLOSEを管理します。

cursor-for-loop.sql
BEGIN
  FOR r IN (
    SELECT employee_id
    FROM employees
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.employee_id);
  END LOOP;
END;
/

明示的カーソルとカーソルFORループの使い分けは PL/SQLカーソル完全ガイド でも整理しています。

パッケージ変数のカーソルで起きる場合

パッケージ内にカーソル状態を持つ処理では、呼び出し間で状態が残ることがあります。処理が途中で止まると、次回呼び出し時に同じカーソルが開いたままになっていることがあります。

package-cursor-safe-open.sql
CREATE OR REPLACE PACKAGE BODY pkg_emp AS
  CURSOR c_emp IS
    SELECT employee_id FROM employees;

  PROCEDURE open_cursor IS
  BEGIN
    IF c_emp%ISOPEN THEN
      CLOSE c_emp;
    END IF;

    OPEN c_emp;
  END;
END;
/

ORA-01001との違い

ORA-06511と混同しやすいのが ORA-01001: invalid cursor です。ざっくり言えば、ORA-06511は「開いているのにまた開いた」、ORA-01001は「使えないカーソルを使った」と考えると切り分けやすいです。

エラー 状態 見る場所
ORA-06511 OPEN済みカーソルを再OPEN OPEN c; OPEN c; %ISOPENとCLOSE漏れ
ORA-01001 無効または閉じたカーソルを操作 CLOSE後にFETCH FETCH/CLOSEの順序
ORA-01000 開きすぎ アプリやPL/SQLでカーソルを閉じ忘れる ORA-01000を確認

調査手順

実際にORA-06511が出たら、ORA-06512の行番号から USER_SOURCE で該当の OPEN 行を確認します。そのカーソルが直前、前回処理、例外経路のどこで開いたままになっているかを追います。

check-user-source-ora06511.sql
SELECT line,
       text
FROM user_source
WHERE name = 'PKG_EMP'
  AND type = 'PACKAGE BODY'
  AND line BETWEEN 60 AND 90
ORDER BY line;

本番障害では、DBMS_UTILITY.FORMAT_ERROR_BACKTRACE をログに残すと、どのOPEN行で落ちたかを追いやすくなります。

よくある原因と対処一覧

原因 症状 対処
同じカーソルを2回OPEN 即座にORA-06511 OPEN前に%ISOPEN確認
例外でCLOSE漏れ 2回目以降の実行で落ちる 例外ハンドラでCLOSEしてRAISE
ループ内OPEN ループ2周目で落ちる OPENをループ外へ移動
パッケージ状態が残る 同一セッションで再実行時に落ちる 処理終了時に必ずCLOSE
明示的カーソルが不要 OPEN/CLOSEの管理が複雑 カーソルFORループへ置換

チェックリスト

項目 OKの状態
OPEN前に状態を見た %ISOPENで開いていないことを確認
処理終了時にCLOSEした 正常系で必ず閉じている
例外時にもCLOSEした 例外ハンドラで後始末している
カーソルFORループを検討した 手動OPENが本当に必要か確認済み
ORA-06512の行を確認した どのOPENで落ちたか分かる

よくある質問

OPEN前に毎回%ISOPENを見ればよいですか?

防御策として有効ですが、そもそもOPEN/CLOSEの構造が複雑になっていないかも見直します。単純な全件処理ならカーソルFORループが向いています。

例外時にCLOSEしたあとRAISEは必要ですか?

多くの場合は必要です。CLOSEだけして例外を握りつぶすと、呼び出し元が失敗を検知できません。

カーソルFORループならORA-06511は起きませんか?

手動で同じカーソルを二重OPENする問題は避けやすくなります。PL/SQLがOPEN/FETCH/CLOSEを管理するためです。

ORA-01000との関係はありますか?

どちらもカーソルの閉じ忘れと関係することがあります。ORA-06511は同じカーソルの再OPEN、ORA-01000は開いているカーソル数の上限超過です。

まとめ

ORA-06511は、OPEN済みの明示的カーソルを再度OPENした時に発生します。対処は、再OPEN前に %ISOPEN を確認して閉じる、正常系と例外系の両方で CLOSE する、カーソルFORループへ置き換える、という流れです。

発生行はORA-06512で確認し、該当の OPEN がなぜ二重実行されたかを追います。特に例外時のCLOSE漏れとパッケージ状態に注意すると、再発を防ぎやすくなります。

参考

ORA-06511 – Oracle Database Error Help

Cursors Overview – Oracle Database PL/SQL Language Reference

Cursor Attributes – Oracle PL/SQL User's Guide and Reference