【Oracle】ORA-01002の原因と解決方法|fetch out of sequence の直し方

【Oracle】ORA-01002の原因と解決方法|fetch out of sequence の直し方 Oracle

ORA-01002: fetch out of sequence は、Oracleでカーソルの FETCH 順序や状態が崩れた時に発生するエラーです。代表例は、FOR UPDATE カーソルで COMMIT した後にFETCHを続けるケース、カーソルの状態を変えた後に古い順序のまま読み続けるケース、アプリ側でResultSetやREF CURSORの読み取り順序が崩れるケースです。

Oracle公式のORA-01002説明でも、FETCHが正しい順序で実行されていないこと、またはカーソルの状態とFETCH操作が合っていないことが原因として示されています。そのため、対処では OPENFETCHCOMMITCLOSE の順序を確認します。

先に結論
ORA-01002が出たら、まず FOR UPDATE カーソル中に COMMIT していないか確認します。次に、同じカーソルを閉じた後・再OPEN後・アプリ側でclose後にFETCHしていないかを確認します。無効カーソル操作なら ORA-01001、二重OPENなら ORA-06511 側です。
スポンサーリンク

ORA-01002とは

ORA-01002は、カーソルのFETCHが想定された順序で実行できない状態を示します。カーソル自体が存在しないというより、FETCHを続ける前提が崩れていると考えると切り分けやすいです。

状態 操作 起きやすいエラー
未OPENまたはCLOSE済み FETCH ORA-01001
OPEN済み 順番にFETCH 正常
FOR UPDATE中にCOMMIT後 さらにFETCH ORA-01002
OPEN済みを再OPEN OPEN ORA-06511

FOR UPDATEカーソル中にCOMMITする例

もっとも実務で見かけるのが、FOR UPDATE カーソルで行をロックしながら処理し、ループ中で COMMIT してしまうケースです。COMMITによりカーソルの前提が崩れ、その後のFETCHでORA-01002になります。

ora01002-for-update-commit.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees
    WHERE department_id = 10
    FOR UPDATE;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp INTO l_id;
    EXIT WHEN c_emp%NOTFOUND;

    UPDATE employees
    SET last_update_date = SYSDATE
    WHERE CURRENT OF c_emp;

    COMMIT;
  END LOOP;
  CLOSE c_emp;
END;
/

-- COMMIT後の次FETCHでORA-01002になりやすい

この場合は、ループ中でCOMMITせず、処理単位の最後でまとめてCOMMITするか、処理設計を分割します。

基本の直し方

まずは、FETCH中にカーソル状態を変える操作をしていないか確認します。特に FOR UPDATECOMMITROLLBACKCLOSE、再OPENの順序が重要です。

commit-after-fetch-loop.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees
    WHERE department_id = 10
    FOR UPDATE;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp INTO l_id;
    EXIT WHEN c_emp%NOTFOUND;

    UPDATE employees
    SET last_update_date = SYSDATE
    WHERE CURRENT OF c_emp;
  END LOOP;
  CLOSE c_emp;

  COMMIT;
END;
/
確認すること NG例 修正方針
FOR UPDATE中のCOMMIT ループ内COMMIT ループ後にCOMMIT
CLOSE後のFETCH CLOSE後に続けてFETCH 処理を終了、または再OPENからやり直す
再OPEN後の古いFETCH 状態をリセットしたのに古い前提で読む OPEN/FETCH/CLOSEの単位を分ける
アプリ側の読み取り ResultSet close後にnext close順序を見直す

症状別の最短修正

ORA-01002は、どの操作の後にFETCHしているかを見れば原因を絞れます。次の表で、該当する症状から確認します。

症状 原因になりやすい箇所 最短修正
ループ途中で突然落ちる FOR UPDATE中にCOMMIT COMMITをループ外へ移す
FETCH直後ではなく次のFETCHで落ちる 直前でカーソル状態を変えた CLOSE/COMMIT/ROLLBACKを確認
アプリ側の読み取りで落ちる ResultSetやCursorを閉じた後に読む close順序と接続寿命を見直す
REF CURSORで落ちる 返却後の読み取り順序や接続管理が崩れている 読み終わるまでカーソルと接続を維持
ORA-01001と迷う カーソルが無効か、FETCH順序が崩れたか OPEN状態と直前操作を確認

ORA-01001との違い

ORA-01001とORA-01002はどちらもカーソル系ですが、少し違います。ORA-01001は無効なカーソルを操作した状態、ORA-01002はFETCHの順序や前提が崩れた状態です。

エラー 主な意味 関連記事
ORA-01001 無効なカーソルを操作 OPEN前/CLOSE後にFETCH ORA-01001
ORA-01002 FETCH順序が不正 FOR UPDATE中にCOMMIT後FETCH この記事
ORA-06511 OPEN済みカーソルを再OPEN OPEN c; OPEN c; ORA-06511
ORA-01000 開いているカーソル数が多すぎる カーソル閉じ忘れが蓄積 ORA-01000

カーソルFORループの場合

単純な全件処理なら、カーソルFORループにすることでOPEN/FETCH/CLOSEの順序ミスを減らせます。ただし、FOR UPDATE とトランザクション制御を組み合わせる場合は、COMMIT位置に注意します。

cursor-for-loop-update.sql
BEGIN
  FOR r IN (
    SELECT employee_id
    FROM employees
    WHERE department_id = 10
    FOR UPDATE
  ) LOOP
    UPDATE employees
    SET last_update_date = SYSDATE
    WHERE employee_id = r.employee_id;
  END LOOP;

  COMMIT;
END;
/

カーソルFORループと明示的カーソルの使い分けは PL/SQLカーソル完全ガイド、明示的カーソルの基本は 明示的カーソル完全ガイド が参考になります。

REF CURSORやアプリ側で起きる場合

Java、C#、Python、PHPなどのアプリケーションからREF CURSORやResultSetを読む場合も、読み取り順序とclose順序が崩れるとORA-01002につながります。DB側が返したカーソルを読み終える前に接続を閉じていないか、途中でトランザクションを終了していないかを確認します。

確認項目 理由
ResultSet/Cursorを読み終える前にcloseしていないか 以降のFETCHができなくなる
接続やトランザクションを途中で終了していないか カーソル状態が変わることがある
複数スレッドで同じカーソルを読んでいないか FETCH順序が崩れる
例外時のfinally/deferで早く閉じていないか 後続処理が閉じたカーソルを読む

JDBCや接続プール経由で発生する場合は、アプリのコードだけでなく接続設定も確認します。autocommit が有効な状態で FOR UPDATE カーソルを読み続けると、アプリ側では明示的に COMMIT していないつもりでも、FETCHの前提が崩れることがあります。

アプリ側の確認点 見る場所 直し方
autocommitが有効になっていないか JDBC設定、接続プール、フレームワーク設定 読み取り中は明示的なトランザクション境界にする
ResultSetを読み切る前にcommit/closeしていないか finally、try-with-resources、defer、例外処理 読み取り完了後にclose/commitする
同じCursorを複数スレッドで読んでいないか 非同期処理、バッチ並列化 Cursorを共有せず処理単位を分ける
長時間カーソルを保持していないか バッチ処理、画面ページング IDリスト化やページング条件で再取得する

FETCH across COMMITを避ける

大量処理で「少しずつFETCHして、途中でCOMMITしたい」と考えることがあります。しかし、FOR UPDATE カーソルではCOMMIT後にFETCHを続ける設計は危険です。処理対象を一度IDリストとして確定する、処理単位を分割する、ステータス列で対象を管理するなどの設計にします。

batch-by-id-list.sql
DECLARE
  TYPE t_id_list IS TABLE OF employees.employee_id%TYPE;
  l_ids t_id_list;
BEGIN
  SELECT employee_id
  BULK COLLECT INTO l_ids
  FROM employees
  WHERE department_id = 10;

  FOR i IN 1 .. l_ids.COUNT LOOP
    UPDATE employees
    SET last_update_date = SYSDATE
    WHERE employee_id = l_ids(i);
  END LOOP;

  COMMIT;
END;
/

BULK COLLECT を使う場合は、BULK COLLECT / FORALL完全ガイド も参考になります。

調査手順

PL/SQLでORA-01002が出た場合は、ORA-06512の行番号から USER_SOURCE で該当FETCH行を確認します。そのFETCHの前に、COMMIT、ROLLBACK、CLOSE、再OPEN、例外ハンドラがないかを確認します。

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

行番号の読み方は ORA-06512の原因と読み方、バックトレースのログは DBMS_UTILITY完全ガイド が参考になります。

よくある原因と対処一覧

原因 症状 対処
FOR UPDATE中のCOMMIT 次のFETCHでORA-01002 COMMITをループ外へ移す
CLOSE後のFETCH FETCHで失敗 処理を終了、または再OPENからやり直す
ROLLBACK後のFETCH トランザクション終了後に失敗 FETCH中にトランザクションを終えない
アプリ側の読み取り順序ミス ResultSet読み取り中に失敗 close/commit/connectionの順序を確認
カーソル状態管理が複雑 ORA-01001やORA-06511も混在 カーソルFORループや設計分割を検討

チェックリスト

項目 OKの状態
FOR UPDATE中にCOMMITしていない FETCH終了後にCOMMITしている
FETCH前にカーソル状態を変えていない CLOSE/ROLLBACK/再OPENがない
アプリ側で早くcloseしていない 読み終わってから閉じている
ORA-01001と切り分けた 無効カーソルではなくFETCH順序の問題と分かる
ORA-06512の行を確認した 失敗したFETCH行が分かっている

よくある質問

ORA-01001とは何が違いますか?

ORA-01001は無効なカーソル操作、ORA-01002はFETCH順序やトランザクションによりFETCHの前提が崩れた状態です。

FOR UPDATE中にCOMMITしてはいけませんか?

カーソルでFETCHを続ける前提なら避けます。COMMITはFETCH終了後に行うか、処理設計を分割します。

アプリ側でだけORA-01002になります

ResultSet/Cursorの読み取り順序、close、commit、connection終了のタイミングを確認してください。

カーソルFORループなら防げますか?

OPEN/FETCH/CLOSEの順序ミスは減らせます。ただしFOR UPDATE中のCOMMITなど、トランザクション制御には引き続き注意が必要です。

まとめ

ORA-01002は、カーソルのFETCH順序や状態が崩れた時に発生します。特に FOR UPDATE カーソル中の COMMIT、CLOSE後のFETCH、アプリ側のResultSet/REF CURSOR読み取り順序に注意します。

対処は、FETCH中にCOMMITやCLOSEを挟まない、COMMITはループ後に行う、カーソル状態を単純に保つことです。ORA-01001やORA-06511との違いを意識して、失敗したFETCH行と直前操作を確認すると切り分けやすくなります。

参考

ORA-01002 – Oracle Database Error Help

Cursors Overview – Oracle Database PL/SQL Language Reference

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