【完全ガイド】ORA-01000: maximum open cursors exceeded の原因と解決方法|カーソルリーク・JDBC・SESSION_CACHED_CURSORS 徹底解説
- 作成日 2026.08.17
- その他
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_CURSORSvsSESSION_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 を根本から解決できるようになります。
- 1. 結論:カーソルリーク箇所を特定
- 2. Oracle のカーソル仕組み
- 3. 【原因①】JDBC PreparedStatement 未クローズ(最頻出)
- 4. 【原因②】ResultSet 未クローズ
- 5. 【原因③】ループ内のカーソル作成
- 6. 【原因④】PL/SQL カーソル CLOSE 忘れ
- 7. 【原因⑤】REF CURSOR の返却後
- 8. 【原因⑥】HikariCP との相互作用
- 9. 【原因⑦】MyBatis / Hibernate 設定ミス
- 10. 【原因⑧】バッチ処理の設計ミス
- 11. 【原因⑨】トリガー内のカーソル
- 12. 【原因⑩】JDBC ドライバのバグ
- 13. 診断ツール完全リファレンス
- 14. 5つの解決策 完全リファレンス
- 15. Rails / Java / Python 対応
- 16. 実践シナリオ
- 17. トラブルシューティング
- 18. よくある質問(FAQ)
- 18.1. Q1. OPEN_CURSORS のデフォルト
- 18.2. Q2. OPEN_CURSORS vs SESSION_CACHED_CURSORS
- 18.3. Q3. リーク箇所の特定
- 18.4. Q4. Java での対処
- 18.5. Q5. Rails での対処
- 18.6. Q6. PL/SQL での対処
- 18.7. Q7. OPEN_CURSORS 変更に再起動
- 18.8. Q8. HikariCP の LeakDetectionThreshold
- 18.9. Q9. JDBC ドライバのバージョン
- 18.10. Q10. Autonomous DB での挙動
- 18.11. Q11. パフォーマンスへの影響
- 18.12. Q12. E-Business Suite 推奨
- 19. 参考リンク
- 20. まとめ
結論:カーソルリーク箇所を特定
時間がない方向けに、最速の対処を先に示します。
エラーメッセージの読み方
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-resources | Java コード修正 |
| ③ | 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 の順序: ResultSet → PreparedStatement(依存関係)。
【原因②】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 公式
- Oracle Database Error Messages: ORA-01000
- Oracle Database Reference: OPEN_CURSORS
- Oracle Database Reference: SESSION_CACHED_CURSORS
- Oracle JDBC Downloads
まとめ
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)もあわせてご確認ください。
-
前の記事
【完全ガイド】ORA-06550 line/column: PLS-00320 の原因と解決方法|エラースタック・%TYPE・カスケードエラー 徹底解説 2026.08.15
-
次の記事
【完全ガイド】ORA-01034: ORACLE not available の原因と解決方法|DB 未起動・ORACLE_SID・ORA-27101・FRA フル 徹底解説 2026.08.17
コメントを書く