java.sql.SQLException: ORA-01000: maximum open cursors exceeded
應用拋出此異常,表示資料庫會話開啟的游標數超過參數 open_cursors 限制(預設 300)。
SQL> SHOW PARAMETER open_cursors;
NAME TYPE VALUE
-------------------- ------- -----
open_cursors integer 300
SELECT o.sid, s.osuser, s.machine, COUNT(*) AS num_curs
FROM v$open_cursor o
JOIN v$session s ON o.sid = s.sid
WHERE s.username = 'YOUR_APP_USER'
GROUP BY o.sid, s.osuser, s.machine
ORDER BY num_curs DESC;
鎖定 SID、OSUSER、MACHINE,反查應用伺服器節點。
SELECT o.sid, q.sql_text
FROM v$open_cursor o
JOIN v$sql q ON q.hash_value = o.hash_value
WHERE o.sid = :target_sid;
重點關注:是否有大量相似 SQL(未使用綁定變數)、未關閉的 ResultSet/Statement。
| 常見原因 | Java 代碼反模式 | 修正方案 |
|---|---|---|
| 循環中建立 Statement | for(...) { stmt = conn.createStatement(); ... } | 移出循環,使用 PreparedStatement 並重用 |
| 未關閉 ResultSet/Statement | 執行 executeQuery() 後未調用 close() | try-with-resources 或 finally 區塊確保關閉 |
| 連線池洩漏 | 借用連線後未歸還,導致會話累積 | 檢查連線池配置 maxOpenPreparedStatements、removeAbandoned |
| 游標參數過小 | 應用確實需要大量並發游標 | ALTER SYSTEM SET open_cursors=2000 SCOPE=BOTH; |
// ✅ 正確:PreparedStatement 重用 + try-with-resources 自動關閉
String sql = "SELECT * FROM orders WHERE user_id = ? AND status = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (UserTask task : tasks) {
ps.setLong(1, task.getUserId());
ps.setString(2, task.getStatus());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) { /* process */ }
} // rs 自動關閉
}
} // ps 自動關閉
// ❌ 錯誤:循環內新建 Statement,無關閉
for (UserTask task : tasks) {
Statement stmt = conn.createStatement(); // 每次開新游標
ResultSet rs = stmt.executeQuery("SELECT * FROM orders WHERE user_id = " + task.getUserId());
// 缺少 rs.close(), stmt.close()
}
ALTER SYSTEM SET open_cursors=2000 SCOPE=MEMORY; 立即生效,重啟失效。ALTER SYSTEM SET open_cursors=2000 SCOPE=SPFILE; 需重啟生效,或 SCOPE=BOTH 兩者兼顧。「游標洩漏是 Java 應用連接 Oracle 的頭號殺手。」建立代碼審查清單:每個
createStatement/prepareStatement必須有對應close,循環外建立、循環內重用、try-with-resources統管生命週期。