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 に対応するエラーです。明示的カーソルは OPEN、FETCH、CLOSE の状態を持つため、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になります。
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 で状態を確認し、開いていれば閉じます。ただし、毎回この形を書くより、処理構造を見直した方が良いことも多いです。
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テンプレート
明示的カーソルを使う必要がある場合は、正常系と例外系の両方で閉じる形をテンプレート化しておくと安全です。処理途中で例外が起きても、カーソルを開いたまま残しにくくなります。
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につながります。
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まで到達しない
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を管理します。
BEGIN
FOR r IN (
SELECT employee_id
FROM employees
) LOOP
DBMS_OUTPUT.PUT_LINE(r.employee_id);
END LOOP;
END;
/
明示的カーソルとカーソルFORループの使い分けは PL/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 行を確認します。そのカーソルが直前、前回処理、例外経路のどこで開いたままになっているかを追います。
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
