【完全ガイド】ORA-01000: maximum open cursors exceeded の原因と解決方法|カーソルリーク・JDBC・SESSION_CACHED_CURSORS 徹底解説

【完全ガイド】ORA-01000: maximum open cursors exceeded の原因と解決方法|カーソルリーク・JDBC・SESSION_CACHED_CURSORS 徹底解説

Oracle DBA・開発者が本番で最も緊急対応を迫られるリソース枯渇エラー:

java.sql.SQLException: ORA-01000: maximum open cursors exceeded
  at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:450)
  at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:399)
  ...

「オープンカーソル数の上限に達した」というシンプルなメッセージ。しかし、これは本番システムの停止を意味します:

  • 全ての新規 SQL 実行が失敗
  • 既存アプリからの DB アクセス不可
  • バッチ処理の中断
  • 緊急対応が必要
  • DB / アプリ再起動を迫られることも

このエラーの本質的な原因は、ほぼ100%カーソルリーク:

1. JDBC / PL/SQL / アプリコードで PreparedStatement / Cursor 作成
2. close() し忘れ or 例外で close 到達せず
3. カーソルが解放されない → 累積
4. OPEN_CURSORS 上限(デフォルト 300)到達
5. ORA-01000 発生

現場で最も典型的なのは Java JDBC でのリーク:

// ❌ カーソルリーク
public void badMethod() {
    for (int i = 0; i < 1000; i++) {
        PreparedStatement ps = conn.prepareStatement("SELECT ...");
        ResultSet rs = ps.executeQuery();
        // 使用...
        // close 忘れ or 例外で到達せず → リーク
    }
}

1000 回ループで 1000 カーソル、すぐに OPEN_CURSORS 上限突破。

多くの日本語記事が「OPEN_CURSORS を増やせ」で終わりますが、実務では:

  • OPEN_CURSORS vs SESSION_CACHED_CURSORS の違い
  • JDBC の try-with-resources による自動クローズ
  • V$OPEN_CURSOR / V$SESSTAT でのリーク箇所特定
  • HikariCP / Connection Pool との相互作用
  • REF CURSOR の返却後のクローズ責任
  • MyBatis / Hibernate のカーソル管理
  • PL/SQL のカーソルの明示的 CLOSE
  • バッチ処理のループ内でのカーソル作成
  • Rails ActiveRecord の稀な発生パターン
  • Confluence / Jira など有名アプリでの対処

さらに、Oracle はリーク箇所の特定が難しく、DBA が**「特定のセッション」を疑って追跡**する必要があります:

-- どのセッションがカーソルを使いすぎているか
SELECT s.username, s.sid, s.serial#, a.value AS opened_cursors
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
ORDER BY a.value DESC;

本記事では、ORA-01000: maximum open cursors exceeded完全な原因と解決方法を、リファレンスとして実用的に整理します。10大発生パターン、リーク箇所の特定、5つの解決策、SESSION_CACHED_CURSORS、Rails/Java/Python/PL/SQL 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-01000 を根本から解決できるようになります。


目次

結論:カーソルリーク箇所を特定

時間がない方向けに、最速の対処を先に示します。

エラーメッセージの読み方

ORA-01000: maximum open cursors exceeded
                                ↑
                        オープンカーソル数上限
                        = OPEN_CURSORS パラメータ超過

最速の緊急対応

STEP 1: OPEN_CURSORS を確認・増加(一時対応)
STEP 2: リーク疑わしいセッション特定
STEP 3: そのセッションを KILL
STEP 4: アプリ再起動 or コード修正

診断クエリ

-- ① 現在の OPEN_CURSORS 設定
SHOW PARAMETER open_cursors;

-- ② セッションごとのオープンカーソル数
SELECT s.username, s.sid, s.serial#, a.value AS opened_cursors,
       s.machine, s.program
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
ORDER BY a.value DESC;

-- ③ 特定セッションのカーソル詳細
SELECT sql_text, count(*)
FROM v$open_cursor
WHERE sid = <SID>
GROUP BY sql_text
ORDER BY 2 DESC;

5つの解決策

#手法使う場面
OPEN_CURSORS 増加緊急対応
try-with-resourcesJava コード修正
SESSION_CACHED_CURSORS予防・最適化
ORM 設定調整Rails/MyBatis
PL/SQL 明示 CLOSEストアド修正

OPEN_CURSORS のデフォルト値

Oracle 12c+: 300
Oracle 11g:  300
一般的な推奨: 500-1000(負荷に応じて)

詳細は以下で解説します。


Oracle のカーソル仕組み

カーソルとは

Oracle のカーソル = メモリ内の SQL 領域:

SQL 実行時に確保される作業領域
- SQL テキスト
- 解析済みプラン
- 結果セット
- ロック情報

3種類のカーソル

明示的カーソル(PL/SQL):

DECLARE
  CURSOR c IS SELECT * FROM emp;
BEGIN
  OPEN c;
  ...
  CLOSE c;   -- 必須
END;
/

暗黙的カーソル(PL/SQL SELECT INTO 等):

DECLARE
  v_name VARCHAR2(100);
BEGIN
  SELECT name INTO v_name FROM emp WHERE id = 100;
  -- 内部的にカーソルが開いて閉じる
END;
/

JDBC カーソル:

PreparedStatement ps = conn.prepareStatement("SELECT ...");
ResultSet rs = ps.executeQuery();
// ps.close() で解放

OPEN_CURSORS vs SESSION_CACHED_CURSORS

OPEN_CURSORS:

  • 1セッションが同時に開ける最大カーソル数
  • これを超えると ORA-01000

SESSION_CACHED_CURSORS:

  • キャッシュされるカーソル数
  • 繰り返し実行を高速化
  • 通常 50 が推奨
-- 確認
SHOW PARAMETER open_cursors;
SHOW PARAMETER session_cached_cursors;

【原因①】JDBC PreparedStatement 未クローズ(最頻出)

症状(超典型)

// ❌ カーソルリーク
public void findAll() {
    try {
        PreparedStatement ps = conn.prepareStatement("SELECT * FROM users");
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
            // 処理
        }
        // close 忘れ!
    } catch (SQLException e) {
        // 例外で close 到達せず
    }
}
// 呼ぶたびにカーソル 1 つずつリーク

解決A: try-with-resources(Java 7+ 推奨)

public void findAll() {
    try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users");
         ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            // 処理
        }
    } catch (SQLException e) {
        // 自動 close 保証
    }
}

解決B: finally ブロック

PreparedStatement ps = null;
ResultSet rs = null;
try {
    ps = conn.prepareStatement("SELECT * FROM users");
    rs = ps.executeQuery();
    // 処理
} finally {
    if (rs != null) try { rs.close(); } catch (Exception e) {}
    if (ps != null) try { ps.close(); } catch (Exception e) {}
}

close の順序: ResultSetPreparedStatement(依存関係)。


【原因②】ResultSet 未クローズ

症状

// ResultSet を独立して使う場合
ResultSet rs = ps.executeQuery();
List<String> results = new ArrayList<>();
while (rs.next()) {
    results.add(rs.getString(1));
}
ps.close();
// rs.close() 忘れ → リーク可能性

解決

try-with-resources:

try (ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
        // 処理
    }
}

または明示的:

rs.close();   // ps.close() の前に
ps.close();

【原因③】ループ内のカーソル作成

症状

// ❌ ループ内で PreparedStatement 作成
for (int i = 0; i < 10000; i++) {
    PreparedStatement ps = conn.prepareStatement("SELECT * FROM t WHERE id = ?");
    ps.setInt(1, i);
    ResultSet rs = ps.executeQuery();
    // ... 
    // close 忘れ or 例外
}
// カーソル 10000 個

解決

ループ外で作成:

try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM t WHERE id = ?")) {
    for (int i = 0; i < 10000; i++) {
        ps.setInt(1, i);
        try (ResultSet rs = ps.executeQuery()) {
            // 処理
        }
    }
}

PreparedStatement は再利用、ResultSet だけループ内で close。


【原因④】PL/SQL カーソル CLOSE 忘れ

症状

DECLARE
  CURSOR c IS SELECT * FROM emp;
  r emp%ROWTYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO r;
    EXIT WHEN c%NOTFOUND;
    -- 処理
  END LOOP;
  -- CLOSE c 忘れ → リーク
END;
/

セッション終了までカーソル残留

解決

DECLARE
  CURSOR c IS SELECT * FROM emp;
  r emp%ROWTYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO r;
    EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;   -- 必須
EXCEPTION
  WHEN OTHERS THEN
    IF c%ISOPEN THEN CLOSE c; END IF;
    RAISE;
END;
/

FOR ループは自動 CLOSE:

BEGIN
  FOR r IN (SELECT * FROM emp) LOOP
    -- カーソルは自動管理
  END LOOP;
END;
/

【原因⑤】REF CURSOR の返却後

症状

-- PL/SQL 側
CREATE PROCEDURE get_data(rc OUT SYS_REFCURSOR) IS
BEGIN
  OPEN rc FOR SELECT * FROM emp;
END;
/
// Java 側で受け取る
CallableStatement cs = conn.prepareCall("{call get_data(?)}");
cs.registerOutParameter(1, OracleTypes.CURSOR);
cs.execute();
ResultSet rs = (ResultSet) cs.getObject(1);
// rs.close() 忘れ → リーク

解決

try (CallableStatement cs = conn.prepareCall("{call get_data(?)}")) {
    cs.registerOutParameter(1, OracleTypes.CURSOR);
    cs.execute();
    try (ResultSet rs = (ResultSet) cs.getObject(1)) {
        while (rs.next()) {
            // 処理
        }
    }
}

【原因⑥】HikariCP との相互作用

症状

HikariCP のような Connection Pool:

1. Pool から Connection 取得
2. カーソル作成、close 忘れ
3. Pool に返却
4. 別スレッドが同じ Connection 取得
5. 前のカーソル残留状態でさらに作成
6. すぐに ORA-01000

解決

HikariCP の設定:

HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMaxLifetime(1800000);   // 30分で接続再作成
config.setLeakDetectionThreshold(60000);   // 60秒でリーク検出
config.setConnectionTestQuery("SELECT 1 FROM DUAL");

leakDetectionThreshold で開放されない接続を検出。

コネクションプール関連は ORA-00020: maximum processes の記事も参照してください。


【原因⑦】MyBatis / Hibernate 設定ミス

MyBatis での対処

<!-- mybatis-config.xml -->
<setting name="defaultStatementTimeout" value="30"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>

PreparedStatement は自動管理、通常リークしない。ただし ResultHandler で長時間保持するとリーク。

Hibernate

// StatelessSession は自動 close なし
StatelessSession session = sessionFactory.openStatelessSession();
try {
    ScrollableResults sr = session.createQuery("...").scroll();
    // 使用...
    sr.close();   // 必須
} finally {
    session.close();
}

【原因⑧】バッチ処理の設計ミス

症状

// ❌ 1レコード1接続、1接続1カーソル
public void processBatch(List<Long> ids) {
    for (Long id : ids) {
        Connection conn = dataSource.getConnection();
        PreparedStatement ps = conn.prepareStatement("...");
        // 処理...
        // close 忘れ
    }
}

解決

接続とカーソルを外で管理:

public void processBatch(List<Long> ids) {
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement("UPDATE ... WHERE id = ?")) {
        for (Long id : ids) {
            ps.setLong(1, id);
            ps.addBatch();
        }
        ps.executeBatch();
    }
}

バッチ処理でカーソル数を最小化。


【原因⑨】トリガー内のカーソル

症状

CREATE TRIGGER trg_before_insert 
BEFORE INSERT ON orders
FOR EACH ROW
DECLARE
  CURSOR c IS SELECT * FROM customers WHERE id = :NEW.customer_id;
  v_cust customers%ROWTYPE;
BEGIN
  OPEN c;
  FETCH c INTO v_cust;
  -- CLOSE 忘れ
END;
/
-- 大量 INSERT でリーク

解決

明示的な CLOSE + 例外処理:

CREATE TRIGGER trg_before_insert 
BEFORE INSERT ON orders
FOR EACH ROW
DECLARE
  CURSOR c IS SELECT * FROM customers WHERE id = :NEW.customer_id;
  v_cust customers%ROWTYPE;
BEGIN
  OPEN c;
  FETCH c INTO v_cust;
  CLOSE c;
EXCEPTION
  WHEN OTHERS THEN
    IF c%ISOPEN THEN CLOSE c; END IF;
    RAISE;
END;
/

FOR ループ推奨:

FOR r IN (SELECT * FROM customers WHERE id = :NEW.customer_id) LOOP
  -- 自動管理
END LOOP;

トリガー関連は ORA-04091: mutating table の記事も参照してください。


【原因⑩】JDBC ドライバのバグ

症状

古い JDBC ドライバにリークバグが存在:

Oracle JDBC 12.1 で PreparedStatement のリークバグ報告

解決

JDBC ドライバをアップグレード:

<!-- Maven -->
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc11</artifactId>
    <version>23.3.0.23.09</version>
</dependency>

推奨: DB バージョン以上の JDBC ドライバ使用。


診断ツール完全リファレンス

V$OPEN_CURSOR

-- セッションごとのオープンカーソル一覧
SELECT sid, user_name, sql_text, sql_id
FROM v$open_cursor
WHERE sid = <SID>
ORDER BY sql_text;

-- 特定 SQL の重複カウント
SELECT sql_text, count(*) AS dup_count
FROM v$open_cursor
WHERE sid = <SID>
GROUP BY sql_text
HAVING count(*) > 10
ORDER BY 2 DESC;

重複カウントが多い SQL がリーク箇所

V$SESSTAT + V$STATNAME

-- セッションごとのオープンカーソル数
SELECT s.username, s.sid, s.serial#, 
       a.value AS opened_cursors,
       s.machine, s.program, s.status
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
  AND a.value > 100
ORDER BY a.value DESC;

V$SYSSTAT

-- システム全体の統計
SELECT name, value FROM v$sysstat 
WHERE name IN (
  'opened cursors cumulative',
  'session cursor cache hits',
  'session cursor cache count',
  'parse count (hard)',
  'parse count (soft)'
);

AWR レポート

-- カーソル系メトリクス確認
SELECT snap_id, opened_cursors_current, opened_cursors_cumulative
FROM dba_hist_sysstat
WHERE stat_name = 'opened cursors current';

5つの解決策 完全リファレンス

解決策① OPEN_CURSORS 増加(緊急対応)

-- 現在確認
SHOW PARAMETER open_cursors;

-- 変更(動的、即時反映)
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH;

-- 推奨値
-- 開発/小規模: 300-500
-- 中規模: 500-1000
-- 大規模: 1000-3000

注意: 増やしすぎるとメモリ消費増加、根本解決にならない。

解決策② try-with-resources(Java)

try (PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
        // 処理
    }
}
// 自動 close 保証

解決策③ SESSION_CACHED_CURSORS

-- 確認
SHOW PARAMETER session_cached_cursors;

-- 変更
ALTER SYSTEM SET session_cached_cursors = 100 SCOPE = SPFILE;
-- 再起動必要(静的パラメータ)

動作: 頻繁に実行される SQL カーソルをキャッシュ、再解析回避。

解決策④ ORM 設定調整

Rails ActiveRecord:

production:
  adapter: oracle_enhanced
  cursor_sharing: force
  statement_limit: 1000

Hibernate:

hibernate.jdbc.batch_size=50
hibernate.jdbc.fetch_size=100
hibernate.max_fetch_depth=3

解決策⑤ PL/SQL 明示 CLOSE

BEGIN
  OPEN c;
  ...
  CLOSE c;
EXCEPTION
  WHEN OTHERS THEN
    IF c%ISOPEN THEN CLOSE c; END IF;
    RAISE;
END;

または FOR ループ(自動管理):

FOR r IN (SELECT ...) LOOP
  ...
END LOOP;

Rails / Java / Python 対応

Rails ActiveRecord

通常はリークしない(自動管理)。稀に発生する場合:

# ❌ Rails でカーソルリークになりうる
ActiveRecord::Base.connection.raw_connection.exec_query("SELECT ...")
# 生カーソル操作、要注意

# ✅ 通常の使い方
User.where(status: 'active').each do |user|
  # 自動 close
end

# エラーハンドリング
begin
  # 処理
rescue ActiveRecord::StatementInvalid => e
  if e.message.include?("ORA-01000")
    Rails.logger.error "カーソルリーク検出"
    # 接続プールリセット
    ActiveRecord::Base.connection_pool.disconnect!
  end
end

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事、Solid Queue 使い方の記事も参照してください。

Java (JDBC / HikariCP)

HikariCP 完全設定:

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:oracle:thin:@//host:1521/orcl");
config.setUsername("app");
config.setPassword("pw");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setIdleTimeout(300000);
config.setMaxLifetime(1800000);       // 30分でリセット
config.setLeakDetectionThreshold(60000);  // 60秒でリーク検出
config.setConnectionTestQuery("SELECT 1 FROM DUAL");

// カーソル設定
Properties props = new Properties();
props.setProperty("oracle.jdbc.implicitStatementCacheSize", "50");
config.setDataSourceProperties(props);

try-with-resources 徹底:

try (Connection conn = ds.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // 処理
}

Python (oracledb)

import oracledb

# ✅ with 文で自動 close
with oracledb.connect(user, pw, dsn) as conn:
    with conn.cursor() as cursor:
        cursor.execute("SELECT * FROM users")
        rows = cursor.fetchall()

# エラーハンドリング
try:
    cursor.execute(sql)
except oracledb.DatabaseError as e:
    error_obj, = e.args
    if error_obj.code == 1000:
        print(f"カーソルリーク: {error_obj.message}")
        # 接続再作成

実践シナリオ

シナリオ1:本番緊急対応

# 1. サーバーへログイン
ssh dbserver

# 2. SYSDBA 接続
sqlplus / as sysdba

# 3. 現状把握
SHOW PARAMETER open_cursors;

SELECT s.username, s.sid, s.serial#, a.value AS opened_cursors, s.program
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
ORDER BY a.value DESC;

# 4. 疑わしいセッション特定 → 特定 SQL 確認
SELECT sql_text, count(*) 
FROM v$open_cursor 
WHERE sid = <SID> 
GROUP BY sql_text 
ORDER BY 2 DESC;

# 5. 一時的 OPEN_CURSORS 増加
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH;

# 6. リーク元セッション KILL
ALTER SYSTEM KILL SESSION '<SID>,<SERIAL#>' IMMEDIATE;

シナリオ2:Java コードのリーク検出

// HikariCP のリーク検出設定
HikariConfig config = new HikariConfig();
config.setLeakDetectionThreshold(60000);   // 60秒
// 開放されない接続をログ出力

シナリオ3:Rails での予防

# config/database.yml
production:
  adapter: oracle_enhanced
  pool: 10
  reaping_frequency: 60      # 未使用接続の自動回収
  idle_timeout: 300          # 5分でアイドル切断

シナリオ4:Docker Compose での設定

services:
  db:
    image: gvenzl/oracle-xe:21
    environment:
      ORACLE_OPEN_CURSORS: 1000

Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。

シナリオ5:PL/SQL の堅牢なカーソル

CREATE OR REPLACE PROCEDURE safe_cursor_proc IS
  CURSOR c IS SELECT * FROM emp;
  r emp%ROWTYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO r;
    EXIT WHEN c%NOTFOUND;
    -- 処理
  END LOOP;
  CLOSE c;
EXCEPTION
  WHEN OTHERS THEN
    IF c%ISOPEN THEN CLOSE c; END IF;
    RAISE;
END;
/

シナリオ6:MyBatis での対処

<!-- mapper.xml -->
<select id="findAll" resultType="User" fetchSize="100">
  SELECT * FROM users
</select>

<!-- ResultHandler 使用時 -->
<select id="processLarge" resultType="User" fetchSize="1000" resultOrdered="true">
  SELECT * FROM users
</select>

シナリオ7:Autonomous DB での対応

-- Autonomous DB では OPEN_CURSORS 自動チューニング
-- ただしアプリ側リークは要修正
SELECT s.username, s.sid, a.value AS opened_cursors
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current';

シナリオ8:Java Spring での対応

@Configuration
public class OracleConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(user);
        config.setPassword(pw);
        config.setMaximumPoolSize(20);
        config.setLeakDetectionThreshold(60000);
        
        // カーソルキャッシュ有効化
        Properties props = new Properties();
        props.setProperty("oracle.jdbc.implicitStatementCacheSize", "50");
        config.setDataSourceProperties(props);
        
        return new HikariDataSource(config);
    }
}

シナリオ9:Kamal デプロイ後のカーソル監視

docker exec -it db-container sqlplus / as sysdba <<EOF
SELECT s.username, s.sid, a.value AS opened_cursors
FROM v\$sesstat a, v\$statname b, v\$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
ORDER BY a.value DESC
FETCH FIRST 10 ROWS ONLY;
EOF

Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事、systemctl の詳細は systemctl vs service の記事も参照してください。

シナリオ10:定期監視スクリプト

#!/bin/bash
# monitor_cursors.sh

THRESHOLD=200
sqlplus -s / as sysdba <<EOF
SET LINESIZE 200
SELECT CASE 
  WHEN MAX(a.value) > $THRESHOLD THEN 'WARNING'
  ELSE 'OK' 
END AS status, MAX(a.value) AS max_cursors
FROM v\$sesstat a, v\$statname b
WHERE a.statistic# = b.statistic#
  AND b.name = 'opened cursors current';
EOF

crontab の詳細は crontab 使い方の記事も参照してください。


トラブルシューティング

リーク箇所が特定できない

-- 特定セッションの SQL 一覧
SELECT sql_text, count(*)
FROM v$open_cursor
WHERE sid = <SID>
GROUP BY sql_text
HAVING count(*) > 5
ORDER BY 2 DESC;
-- 最頻出 SQL がリーク箇所

OPEN_CURSORS 増やしても収まらない

**根本原因(リーク)**が解決されていない。コード修正必須。

JDBC ドライババグの疑い

Oracle JDBC ドライバをアップグレード
最新版で修正されている可能性

HikariCP LeakDetectionThreshold

setLeakDetectionThreshold(60000);
// 60秒以上開放されない接続をログ
// リーク元スタックトレース出力

監視ジョブでリーク検出

-- 定期的にセッション監視
BEGIN
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'MONITOR_CURSORS',
    job_type => 'PLSQL_BLOCK',
    job_action => q'[
      DECLARE
        v_max NUMBER;
      BEGIN
        SELECT MAX(a.value) INTO v_max
        FROM v$sesstat a, v$statname b
        WHERE a.statistic# = b.statistic#
          AND b.name = 'opened cursors current';
        
        IF v_max > 200 THEN
          -- アラート送信
          UTL_MAIL.SEND(...);
        END IF;
      END;
    ]',
    repeat_interval => 'FREQ=MINUTELY; INTERVAL=5',
    enabled => TRUE
  );
END;
/

E-Business Suite での対処

Oracle 推奨: OPEN_CURSORS = 1000-2000


よくある質問(FAQ)

Q1. OPEN_CURSORS のデフォルト

Oracle 12c+: 300、推奨: 500-1000。

Q2. OPEN_CURSORS vs SESSION_CACHED_CURSORS

  • OPEN_CURSORS: 同時に開ける最大数
  • SESSION_CACHED_CURSORS: キャッシュ数

Q3. リーク箇所の特定

V$OPEN_CURSOR で SQL テキスト重複カウント。

Q4. Java での対処

try-with-resources 徹底、HikariCP leakDetectionThreshold

Q5. Rails での対処

通常は不要、connection_pool.disconnect! で緊急対応。

Q6. PL/SQL での対処

明示的 CLOSE + 例外処理、FOR ループ推奨。

Q7. OPEN_CURSORS 変更に再起動

動的パラメータALTER SYSTEM SET open_cursors = ... で即時反映。

Q8. HikariCP の LeakDetectionThreshold

60000(60秒) が一般的、開発時は 30000。

Q9. JDBC ドライバのバージョン

DB バージョン以上推奨、最新版でバグ修正。

Q10. Autonomous DB での挙動

自動チューニング、アプリ側リークは要修正。

Q11. パフォーマンスへの影響

SESSION_CACHED_CURSORS は再解析回避、性能向上。

Q12. E-Business Suite 推奨

OPEN_CURSORS = 1000-2000


参考リンク

Oracle 公式


まとめ

ORA-01000: maximum open cursors exceeded の要点を再整理します。

エラーの本質

セッションのオープンカーソル数が OPEN_CURSORS 上限に到達
→ ほぼ100% カーソルリークが原因
→ 本番停止のリスク

最速の緊急対応

STEP 1: OPEN_CURSORS 増加(一時対応)
STEP 2: リーク疑わしいセッション特定
STEP 3: KILL SESSION
STEP 4: アプリ再起動 or コード修正

診断クエリ

-- セッションごとのオープンカーソル数
SELECT s.username, s.sid, a.value AS opened_cursors
FROM v$sesstat a, v$statname b, v$session s
WHERE a.statistic# = b.statistic#
  AND s.sid = a.sid
  AND b.name = 'opened cursors current'
ORDER BY a.value DESC;

-- リーク SQL 特定
SELECT sql_text, count(*) 
FROM v$open_cursor 
WHERE sid = <SID> 
GROUP BY sql_text 
ORDER BY 2 DESC;

10大原因

#原因対処
JDBC PreparedStatement 未クローズtry-with-resources
ResultSet 未クローズtry-with-resources
ループ内カーソル作成ループ外で作成
PL/SQL CLOSE 忘れ明示的 CLOSE
REF CURSOR 返却後Java 側で close
HikariCP との相互作用leakDetectionThreshold
MyBatis/Hibernate設定調整
バッチ処理設計バッチ処理化
トリガー内カーソル明示 CLOSE
JDBC ドライババグ最新版アップグレード

5つの解決策

-- ① OPEN_CURSORS 増加(緊急)
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH;
// ② try-with-resources
try (PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // 処理
}
-- ③ SESSION_CACHED_CURSORS
ALTER SYSTEM SET session_cached_cursors = 100 SCOPE = SPFILE;

-- ④ ORM 設定調整
-- HikariCP: leakDetectionThreshold
-- Rails: reaping_frequency

-- ⑤ PL/SQL 明示 CLOSE
CLOSE c;
IF c%ISOPEN THEN CLOSE c; END IF;

OPEN_CURSORS vs SESSION_CACHED_CURSORS

OPEN_CURSORS: 同時に開ける最大数(上限)
SESSION_CACHED_CURSORS: キャッシュ数(再利用高速化)

各言語での対応

Java:   try-with-resources + HikariCP leakDetectionThreshold
Rails:  基本自動、config.yml で reaping_frequency
Python: with 文で自動 close
PL/SQL: 明示 CLOSE + EXCEPTION で CLOSE

予防のポイント

1. try-with-resources 徹底
2. HikariCP leakDetectionThreshold 設定
3. JDBC ドライバ最新版
4. PL/SQL は FOR ループ推奨
5. トリガー内カーソルは明示 CLOSE
6. バッチ処理は addBatch/executeBatch
7. ORM の設定確認
8. 定期監視ジョブ
9. AWR でカーソル系メトリクス確認
10. コードレビューで close 忘れ検出

これらの知識は、Oracle DBA の本番運用・障害対応・Java / Rails / Python アプリ開発・カーソル管理・パフォーマンスチューニング・監視設計・Kamal デプロイ・E-Business Suite 運用など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-01000 に出会っても冷静に的確に対処できるようになります。


本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。