Home โ€บ Database โ€บ ORA-01000: Unclosed Result Sets Took Down the Report Service
Intermediate 3 min · October 29, 2026
ORA-01000: Maximum Open Cursors Exceeded

ORA-01000: Unclosed Result Sets Took Down the Report Service

ORA-01000 means one session has more cursors open than OPEN_CURSORS allows.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Written from production experience, not tutorials.

Follow
✓ Production
production tested
October 29, 2026
last updated
2,089
articles · all by Naren
Before you start⏱ 28 min
  • ✓Basic PL/SQL syntax (DECLARE/BEGIN/END blocks, explicit cursors, FOR loops)
  • ✓Familiarity with Oracle session concepts (SID, SERIAL#, V$SESSION)
  • ✓Ability to run queries against V$ views as a DBA or privileged user
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
⚡Quick Answer
  • One session has more cursors open than OPEN_CURSORS. It's almost always a leak, not real demand.
  • Find the session: V$SESSTAT 'opened cursors current'. Find the leaking SQL: GROUP BY sql_text in V$OPEN_CURSOR for that SID.
  • Most common leaks: JDBC Statements or ResultSets not closed on error paths, REF CURSORs returned to the app and never closed, DBMS_SQL cursors without CLOSE_CURSOR.
  • Fix the leak (try-with-resources, close REF CURSORs, cursor FOR loops). Raising OPEN_CURSORS only delays the failure.
โœฆ Definition~90s read
What is ORA-01000?

ORA-01000 (maximum open cursors exceeded) is raised when a single session tries to have more cursors open at once than the OPEN_CURSORS parameter allows. A cursor is the handle to a parsed SQL statement and its result. The limit is per session, so one leaking connection can fail while every other session is fine.

โ˜…
Imagine a restaurant kitchen with a fixed number of cutting boards.
Plain-English First

Imagine a restaurant kitchen with a fixed number of cutting boards. Every dish you prep takes a board. If you never wash and return a board after plating, the kitchen runs out โ€” even though you're only cooking one dish at a time. ORA-01000 is the chef yelling 'no more boards!' because someone stacked dirty ones in the corner. The fix isn't buying more boards (raising OPEN_CURSORS); it's washing them (closing cursors).

ORA-01000 doesn't mean the database is overloaded. It means one session is holding on to cursors it never closed. Usually it's application code that opens a statement or result set and loses track of it on an error path. Because the limit is per session, and connection pools keep sessions alive for hours, the leak builds up quietly. Then one pooled connection crosses the limit and starts failing while the rest look healthy.

This guide shows what actually leaks cursors (and what doesn't), how to find the leaking session and statement in two queries, and why raising OPEN_CURSORS is a delay rather than a fix.

What Actually Counts as a Cursor (It's Not Just SELECT)

Every SQL statement a session runs needs a cursor: SELECT, INSERT, UPDATE, DELETE, and PL/SQL blocks. The OPEN_CURSORS parameter caps how many a single session can have open at once. What leaks and what doesn't matters here. Static SQL inside PL/SQL, and cursor FOR loops, are managed for you. Explicit cursors declared locally in a procedure are closed when the block exits. The things that leak are the ones whose lifetime you control: JDBC Statements and ResultSets, REF CURSORs handed to the application, DBMS_SQL cursors, and cursors declared at package level, which live as long as the session. OPEN_CURSORS defaults to 50, and most database setup templates set it to 300. The limit itself reserves no memory; only cursors that are actually open use any.

cursor_count_check.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- io.thecodeforge โ€” Database tutorial
-- Check current OPEN_CURSORS setting and per-session usage
SELECT name, value
FROM v$parameter
WHERE name = 'open_cursors';

-- Find sessions with the most open cursors
SELECT s.sid,
       s.serial#,
       s.username,
       s.program,
       st.value AS open_cursors
FROM v$session s
JOIN v$sesstat st ON st.sid = s.sid
JOIN v$statname sn ON sn.statistic# = st.statistic#
WHERE sn.name = 'opened cursors current'
  AND st.value > 0
ORDER BY st.value DESC;
Output
NAME VALUE
open_cursors 300
SID SERIAL# USERNAME PROGRAM OPEN_CURSORS
142 58231 APP_USER report_generator.exe 287
89 12044 APP_USER jdbc thin client 12
โš  Production Trap:
'opened cursors current' counts cursors open right now. 'opened cursors cumulative' is the lifetime total. A healthy session's current count stays flat. A leaking session's current count climbs steadily: that's the trend to alert on.

The Classic Leak: Cursors Closed Only on the Happy Path

In PL/SQL, the cursors that truly leak are the ones that outlive a single call. DBMS_SQL is the classic: DBMS_SQL.OPEN_CURSOR returns a number, and that cursor stays open until you call CLOSE_CURSOR or the session ends. If an exception skips the close, every failing call leaks one cursor, and in a pooled session those add up across requests. Package-level cursors behave the same way, because package state lives for the whole session. Note what does not leak: reopening a local explicit cursor that is still open raises ORA-06511 'cursor already open', and a local cursor is closed automatically when its block exits. The fix is to close in the exception handler too, or to avoid hand-managed cursors entirely.

dbms_sql_leak.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- io.thecodeforge โ€” Database tutorial
-- BAD: leaks one DBMS_SQL cursor every time the parse/execute fails
CREATE OR REPLACE FUNCTION count_rows(p_table VARCHAR2) RETURN NUMBER IS
  c   INTEGER;
  n   NUMBER;
  rc  INTEGER;
BEGIN
  c := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(c, 'SELECT COUNT(*) FROM ' || DBMS_ASSERT.SQL_OBJECT_NAME(p_table), DBMS_SQL.NATIVE);
  DBMS_SQL.DEFINE_COLUMN(c, 1, n);
  rc := DBMS_SQL.EXECUTE_AND_FETCH(c);
  DBMS_SQL.COLUMN_VALUE(c, 1, n);
  DBMS_SQL.CLOSE_CURSOR(c);          -- only reached on success
  RETURN n;
END;
/

-- GOOD: close on every path
--   EXCEPTION WHEN OTHERS THEN
--     IF DBMS_SQL.IS_OPEN(c) THEN DBMS_SQL.CLOSE_CURSOR(c); END IF;
--     RAISE;
Output
-- each failed call leaves one cursor open in the session
-- in a pooled session, ~300 failures later:
ORA-01000: maximum open cursors exceeded
๐Ÿ’กSenior Shortcut:
For static queries, a cursor FOR loop removes the problem entirely: FOR r IN (SELECT ...) LOOP ... END LOOP; opens, fetches and closes for you, even when an exception leaves the loop.

REF CURSORs: Opened in the Database, Leaked in the App

A procedure that returns a SYS_REFCURSOR opens the cursor and hands responsibility to the caller. The database can't close it; only the client can, by closing the ResultSet and Statement. This is the most common source of ORA-01000 in Java and .NET applications. The code works perfectly in tests, then leaks on the one error path that skips the close. Inside PL/SQL, the same rule applies to cursor variables passed between subprograms. Whoever ends up holding a REF CURSOR must close it. Reopening the same cursor variable with OPEN ... FOR replaces its previous query, so the leak comes from cursors that are handed off and forgotten, not from reopening one variable.

refcursor_contract.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- io.thecodeforge โ€” Database tutorial
-- The procedure opens the cursor; the CALLER must close it
CREATE OR REPLACE PROCEDURE get_report(p_day DATE, p_rows OUT SYS_REFCURSOR) IS
BEGIN
  OPEN p_rows FOR
    SELECT customer_id, total FROM daily_sales WHERE sale_day = p_day;
END;
/

-- PL/SQL caller: fetch, then close on every path
DECLARE
  c   SYS_REFCURSOR;
  id  NUMBER; amt NUMBER;
BEGIN
  get_report(TRUNC(SYSDATE), c);
  LOOP
    FETCH c INTO id, amt;
    EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;
EXCEPTION
  WHEN OTHERS THEN
    IF c%ISOPEN THEN CLOSE c; END IF;
    RAISE;
END;
/
Output
Procedure created.
PL/SQL procedure successfully completed.
โš  Never Do This:
Don't return a REF CURSOR to the application without a clear owner for closing it. In Java, the ResultSet you get from getObject() on the OUT parameter must be closed, and so must the CallableStatement.

Connection Pools: Why It Looks Random

ORA-01000 is per session, and a connection pool keeps each session alive for hours or days. A leak of one cursor per failed request builds up on whichever pooled connection served that request. Eventually one connection crosses the limit. From then on, every request routed to it fails, and every other connection works. That's why the error looks random and intermittent. The fix is in the code: close Statements and ResultSets in try-with-resources, so they close even when an exception is thrown. For immediate relief, evict or recycle the bad connection. Most pools support a maximum connection lifetime, and keeping it to hours rather than days limits how far a leak can grow before it's noticed.

CursorLeakExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge โ€” Database tutorial
import java.sql.*;

public class CursorLeakExample {
    // BAD: statement never closed โ€” leaks a cursor per call
    public int countPendingInvoices(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM invoices WHERE status = 'PENDING'");
        rs.next();
        return rs.getInt(1);
        // MISSING: rs.close(); stmt.close();
    }

    // GOOD: try-with-resources closes both ResultSet and Statement
    public int countPendingInvoicesFixed(Connection conn) throws SQLException {
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM invoices WHERE status = 'PENDING'")) {
            rs.next();
            return rs.getInt(1);
        }
    }
}
Output
After 300 calls to countPendingInvoices() on the same connection:
java.sql.SQLException: ORA-01000: maximum open cursors exceeded
๐Ÿ’กInterview Gold:
The limit is per session, not per pool or per database. A pool of 20 connections can have one connection failing with ORA-01000 and 19 healthy ones. V$SESSTAT tells you which.

Diagnosing the Leak: V$OPEN_CURSOR Is Your Friend

When ORA-01000 fires, you need to know what cursors are open and why. V$OPEN_CURSOR shows every open cursor per session, including the SQL text. This is the difference between guessing and knowing.

Run this query against the leaking session (you'll need the SID from V$SESSTAT). You'll see the exact SQL statements that are still open. If you see the same statement repeated 200 times, you've found your leak.

One caveat: V$OPEN_CURSOR only shows cursors that are currently open. If the session already errored out and closed everything, you're too late. Set up a monitoring job that snapshots this view every minute when opened cursors current exceeds a threshold.

diagnose_open_cursors.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- io.thecodeforge โ€” Database tutorial
-- Step 1: find the leaking SID
SELECT sid, value
FROM v$sesstat st
JOIN v$statname sn ON sn.statistic# = st.statistic#
WHERE sn.name = 'opened cursors current'
ORDER BY value DESC
FETCH FIRST 5 ROWS ONLY;

-- Step 2: see what cursors are open for that SID
SELECT sql_text, COUNT(*) AS times_open
FROM v$open_cursor
WHERE sid = 142
GROUP BY sql_text
ORDER BY times_open DESC;
Output
SID VALUE
142 287
89 12
SQL_TEXT TIMES_OPEN
SELECT LINE_ID, AMOUNT FROM INVOICE_LINES WHERE INVOICE_ID = :B1 287
๐Ÿ”ฅProduction Trap:
V$OPEN_CURSOR also lists cursors held in the session cursor cache, so the count can exceed what the app really has open. On 12c and later, the CURSOR_TYPE column separates them. Look for the same SQL text repeated hundreds of times: that's the leak.

The Band-Aid: Raising OPEN_CURSORS (And When It's Okay)

Raising OPEN_CURSORS is the fastest way to stop the errors, and the most overused fix. If the app leaks, a higher limit only means the leak runs longer before it fails, and it's harder to diagnose when it does. There are legitimate reasons to raise it. Apps that cache many prepared statements per connection, or ORMs with statement caching, can genuinely need several hundred cursors per session. The limit doesn't reserve memory, so a higher value costs nothing until cursors are actually opened. The real risk of a very high value is losing the early warning, not the memory. The rule: confirm there's no leak (a flat 'opened cursors current' over time), then size the limit with headroom, typically 500 to 1,000.

raise_open_cursors.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- io.thecodeforge โ€” Database tutorial
-- Check the current value
SHOW PARAMETER open_cursors

-- Raise it now and persist it (all RAC instances)
ALTER SYSTEM SET open_cursors = 1000 SCOPE = BOTH SID = '*';

-- Verify
SHOW PARAMETER open_cursors

-- Note: OPEN_CURSORS is system-level only; ALTER SESSION cannot change it.
Output
NAME TYPE VALUE
open_cursors integer 300
System altered.
NAME TYPE VALUE
open_cursors integer 1000
โš  Never Do This:
Don't set OPEN_CURSORS to 50,000 to make the error go away. It hides the leak, and the leaking sessions keep holding cursors and shared pool memory until they hit a much larger, much later wall.

The Real Fix: Cursor FOR Loops and Exception-Safe Closing

The permanent fix is simple to state: close every cursor you open, on every path. In PL/SQL, prefer cursor FOR loops for static queries. They can't leak. When you need explicit control (BULK COLLECT with LIMIT, DBMS_SQL, REF CURSORs), close in the normal path and again in the exception handler, guarded by %ISOPEN or DBMS_SQL.IS_OPEN. In application code, try-with-resources (Java), using blocks (.NET) or context managers (Python) do the same job: the close happens however the block exits.

safe_cursor_patterns.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- io.thecodeforge โ€” Database tutorial
-- Pattern 1: cursor FOR loop โ€” automatically closes, even on exception
BEGIN
  FOR invoice_rec IN (SELECT invoice_id FROM invoices WHERE status = 'PENDING') LOOP
    FOR line_rec IN (SELECT line_id, amount FROM invoice_lines WHERE invoice_id = invoice_rec.invoice_id) LOOP
      -- process line...
      NULL;
    END LOOP;
  END LOOP;
END;
/

-- Pattern 2: explicit cursor with exception-safe close
DECLARE
  CURSOR line_cur IS
    SELECT line_id, amount FROM invoice_lines WHERE invoice_id = :invoice_id;
  v_line_id invoice_lines.line_id%TYPE;
  v_amount  invoice_lines.amount%TYPE;
BEGIN
  OPEN line_cur;
  LOOP
    FETCH line_cur INTO v_line_id, v_amount;
    EXIT WHEN line_cur%NOTFOUND;
    -- process line...
  END LOOP;
  CLOSE line_cur;
EXCEPTION
  WHEN OTHERS THEN
    -- Ensure cursor is closed before propagating the error
    IF line_cur%ISOPEN THEN
      CLOSE line_cur;
    END IF;
    RAISE;
END;
/
Output
PL/SQL procedure successfully completed.
PL/SQL procedure successfully completed.
๐Ÿ’กSenior Shortcut:
Guard closes in exception handlers with %ISOPEN. Closing a cursor that is already closed raises ORA-01001 'invalid cursor', which replaces the error you actually wanted to see.

When NOT to Use Explicit Cursors at All

Explicit OPEN/FETCH/CLOSE code is often unnecessary. Since Oracle 10g, a cursor FOR loop fetches 100 rows at a time behind the scenes, so it's both shorter and fast. Keep explicit cursors for the cases that need them: BULK COLLECT with a LIMIT for large batches, REF CURSORs passed to other programs, and dynamic SQL through DBMS_SQL. In code review, when you see OPEN ... LOOP ... FETCH ... EXIT WHEN ... CLOSE, ask whether it could be a FOR loop. Usually it can.

bulk_collect_example.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- io.thecodeforge โ€” Database tutorial
-- Legitimate use of explicit cursor: BULK COLLECT with LIMIT
DECLARE
  CURSOR invoice_cur IS
    SELECT invoice_id FROM invoices WHERE status = 'PENDING';
  TYPE invoice_tab IS TABLE OF invoices.invoice_id%TYPE;
  v_invoices invoice_tab;
BEGIN
  OPEN invoice_cur;
  LOOP
    FETCH invoice_cur BULK COLLECT INTO v_invoices LIMIT 100;
    EXIT WHEN v_invoices.COUNT = 0;
    FORALL i IN 1..v_invoices.COUNT
      UPDATE invoices SET status = 'PROCESSED' WHERE invoice_id = v_invoices(i);
    COMMIT;
  END LOOP;
  CLOSE invoice_cur;
EXCEPTION
  WHEN OTHERS THEN
    IF invoice_cur%ISOPEN THEN CLOSE invoice_cur; END IF;
    RAISE;
END;
/
Output
PL/SQL procedure successfully completed.
๐Ÿ”ฅProduction Trap:
BULK COLLECT without a LIMIT clause can consume gigabytes of PGA memory on large result sets. Always use LIMIT 100 or LIMIT 1000 โ€” the performance difference is negligible, but the memory difference is massive.
● Production incidentPOST-MORTEMseverity: high

Unclosed Result Sets Took Down the Report Service

Symptom
From about 3 AM, roughly one in ten report requests failed with ORA-01000. Restarting the database listener changed nothing, and the failures were spread across every report type.
Assumption
The team assumed the database was overloaded, then that a recent schema change had broken a report query.
Root cause
The Java service called a procedure that returns a SYS_REFCURSOR. It closed the ResultSet and CallableStatement only on the success path. When a row failed formatting, the exception skipped the close. Each failure leaked one cursor onto a pooled connection that lived for days. After 300 leaks, that connection hit OPEN_CURSORS and failed every request routed to it.
Fix
Wrapped every Statement and ResultSet in try-with-resources, so they close on every path. The leaking connections were evicted from the pool for immediate relief. A monitor now alerts when any session's 'opened cursors current' passes 80% of OPEN_CURSORS.
Key lesson
  • ORA-01000 is a leak report for one session, not a capacity problem.
  • Close cursors on every path, including the error path, and alert on the trend before the limit is hit.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.4 entries
Symptom · 01
ORA-01000 on some requests after the app has been up for hours
→
Fix
1. Find the session: V$SESSTAT 'opened cursors current', highest first. 2. GROUP BY sql_text in V$OPEN_CURSOR for that SID. 3. Find the code that runs that SQL and check its close on error paths. 4. Evict the connection for immediate relief.
Symptom · 02
ORA-01000 in a PL/SQL batch
→
Fix
1. Look for DBMS_SQL cursors, package-level cursors and REF CURSORs. 2. Make sure each is closed in the exception handler as well. 3. Replace static explicit cursors with cursor FOR loops.
Symptom · 03
ORA-01000 in a Java or .NET app
→
Fix
1. Search for Statements, PreparedStatements, CallableStatements and ResultSets created outside try-with-resources or using blocks. 2. Check REF CURSOR OUT parameters especially. 3. Set a maximum connection lifetime in the pool to cap leak growth.
Symptom · 04
ORA-01000 right after a deployment
→
Fix
Diff the new data-access code for statements opened without a guaranteed close, then confirm with V$OPEN_CURSOR that the new SQL is the one repeating.
★ ORA-01000 Triage Cheat SheetFirst-response commands for when things go wrong โ€” copy-paste ready.
`ORA-01000: maximum open cursors exceeded` on a specific session−
Immediate action
Find the session with the most open cursors.
Commands
SELECT sid, value FROM v$sesstat st JOIN v$statname sn ON sn.statistic# = st.statistic# WHERE sn.name = 'opened cursors current' ORDER BY value DESC FETCH FIRST 5 ROWS ONLY;
SELECT sql_text, COUNT(*) FROM v$open_cursor WHERE sid = <sid> GROUP BY sql_text ORDER BY 2 DESC;
Fix now
Evict that connection from the pool, or ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; then fix the code.
ORA-01000 in a PL/SQL batch+
Immediate action
See which statement repeats for the batch session.
Commands
SELECT sql_text, COUNT(*) FROM v$open_cursor WHERE sid = <sid> GROUP BY sql_text ORDER BY 2 DESC;
SHOW PARAMETER open_cursors;
Fix now
Close DBMS_SQL/REF CURSOR/package cursors in the exception handler, or switch to cursor FOR loops.
`ORA-01000` in a connection pool after many requests+
Immediate action
Check if JDBC Statements/ResultSets are being closed.
Commands
SELECT sid, program, value FROM v$sesstat st JOIN v$statname sn ON sn.statistic# = st.statistic# JOIN v$session s ON s.sid = st.sid WHERE sn.name = 'opened cursors current' AND s.program LIKE '%jdbc%' ORDER BY value DESC;
SELECT sql_text FROM v$open_cursor WHERE sid = <sid>;
Fix now
Use try-with-resources in Java, or close Statement/ResultSet in a finally block.
ORA-01000 came back after raising OPEN_CURSORS+
Immediate action
That's a leak. Watch the trend for one session.
Commands
SELECT s.sid, s.program, st.value FROM v$sesstat st JOIN v$statname sn ON sn.statistic# = st.statistic# JOIN v$session s ON s.sid = st.sid WHERE sn.name = 'opened cursors current' ORDER BY st.value DESC FETCH FIRST 5 ROWS ONLY;
-- rerun in 10 minutes: a steadily climbing value is the leak
Fix now
Fix the close-on-error path in the code that runs the repeated SQL.
ApproachCursor FOR LoopExplicit Cursor + CLOSE
Lines of code3โ€“510โ€“15
Auto-closes on exceptionYesNo โ€” requires manual handler
Supports BULK COLLECTNoYes
Supports REF CURSORNoYes
PerformanceArray-fetches 100 rows (10g+)Same, or better with BULK COLLECT
Leak riskNoneHigh if CLOSE is missed on an error path
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
cursor_count_check.sqlSELECT name, valueWhat Actually Counts as a Cursor (It's Not Just SELECT)
dbms_sql_leak.sqlCREATE OR REPLACE FUNCTION count_rows(p_table VARCHAR2) RETURN NUMBER ISThe Classic Leak
refcursor_contract.sqlCREATE OR REPLACE PROCEDURE get_report(p_day DATE, p_rows OUT SYS_REFCURSOR) ISREF CURSORs
CursorLeakExample.javapublic class CursorLeakExample {Connection Pools
diagnose_open_cursors.sqlSELECT sid, valueDiagnosing the Leak
raise_open_cursors.sqlSHOW PARAMETER open_cursorsThe Band-Aid
safe_cursor_patterns.sqlBEGINThe Real Fix
bulk_collect_example.sqlDECLAREWhen NOT to Use Explicit Cursors at All

Key takeaways

1
ORA-01000 is per session
one leaking connection fails while the rest are fine.
2
It's almost always a leak
JDBC Statements/ResultSets, REF CURSORs, DBMS_SQL or package cursors not closed on an error path.
3
Find it in two queries
V$SESSTAT for the session, V$OPEN_CURSOR GROUP BY sql_text for the statement.
4
Close on every path
try-with-resources, cursor FOR loops, and %ISOPEN-guarded closes in exception handlers.
5
Raising OPEN_CURSORS is a delay, not a fix, unless the trend shows there's no leak.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Oracle count cursors per session, and what happens when you hit...
Q02SENIOR
When would you choose to raise OPEN_CURSORS versus fixing the code?
Q03SENIOR
What happens if you close a cursor that's already closed, and how do you...
Q04JUNIOR
What is a cursor in Oracle, and how does it differ from a cursor variabl...
Q05SENIOR
An app fails with ORA-01000 on a few requests after running for hours. W...
Q06SENIOR
How would you design a PL/SQL batch processor that handles millions of r...
Q01 of 06SENIOR

How does Oracle count cursors per session, and what happens when you hit the limit?

ANSWER
Oracle tracks opened cursors current in V$SESSTAT per session. When a session tries to open a cursor and the count equals OPEN_CURSORS, it raises ORA-01000 and the statement fails. The session remains alive โ€” it's not killed โ€” but any further cursor opens will also fail until some cursors are closed.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I fix ORA-01000 maximum open cursors exceeded?
02
What's the difference between OPEN_CURSORS and SESSION_CACHED_CURSORS?
03
How do I find which session is leaking cursors in Oracle?
04
Does closing a JDBC ResultSet close the underlying Oracle cursor?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Written from production experience, not tutorials.

Follow
✓ Verified
production tested
October 29, 2026
last updated
2,089
articles · all by Naren
🔥

That's PL/SQL. Mark it forged?

3 min read · try the examples if you haven't

←
Previous
ORA-04068 and ORA-04063: Existing State of Packages Discarded / Package Has Errors
29 / 30 · PL/SQL
Next
ORA-06502: PL/SQL Numeric or Value Error (Character String Buffer Too Small)
→