Introduction

Database cursors are database objects that allow applications to retrieve query results row by row rather than loading the entire result set into memory. They are essential for processing large datasets, implementing pagination, and streaming data. However, cursors are tied to specific database connections and transactions, making them vulnerable to invalidation when those underlying resources are closed, timed out, or committed.

When an application attempts to use a cursor after its associated connection has been closed, its transaction has been committed, or the cursor has timed out, the database returns a "cursor not found" or similar error. This issue commonly appears in applications that use connection pooling, have long-running queries, or don't properly manage transaction and cursor lifecycles.

Understanding the relationship between cursors, connections, and transactions is essential for resolving these errors. A cursor is only valid within the context of its creating connection and transaction. Any disruption to either invalidates the cursor, making further fetch operations impossible.

Symptoms

When database cursor errors occur, you will observe these symptoms:

  • Application errors with "cursor not found" or "invalid cursor" messages
  • Query processing fails mid-stream when fetching rows
  • Pagination operations fail after the first page
  • Batch processing jobs crash when processing large result sets
  • Error messages mention transaction ID, cursor ID, or connection state
  • The issue appears intermittently, often under load or with slow queries

Common error messages from different databases:

``` # PostgreSQL ERROR: cursor "my_cursor" does not exist ERROR: invalid cursor name

# Oracle ORA-01001: invalid cursor ORA-01002: fetch out of sequence

# SQL Server Cursor not found or has been closed Invalid cursor state

# MongoDB Cursor not found, cursor id: 123456789

# General ODBC/JDBC Invalid cursor position Cursor is closed ```

Application-level exceptions:

```python # Python psycopg2.errors.InvalidCursorName: cursor "c_123" does not exist

# Java java.sql.SQLException: Invalid cursor state java.sql.SQLException: Cursor is closed

# Node.js Error: Cursor not found ```

Common Causes

Several factors cause database cursor errors:

  1. 1.Transaction committed before cursor closed: In databases like PostgreSQL and Oracle, committing a transaction automatically closes all cursors created within that transaction. Attempting to fetch from a cursor after commit results in a "cursor not found" error.
  2. 2.Connection closed or lost: If the database connection is closed (explicitly, due to timeout, or because of a network issue), all cursors associated with that connection become invalid.
  3. 3.Cursor timeout: Many databases have server-side timeouts for idle cursors. If too much time passes between fetch operations, the server may close the cursor automatically.
  4. 4.Connection pool recycling: Connection pools may return connections to the pool (and close them) while cursors created on those connections are still in use. This is common in web applications with request-scoped connections.
  5. 5.Improper resource cleanup: Applications that don't properly close cursors, connections, or transactions in the correct order may encounter cursor invalidation errors.
  6. 6.Database server restart or failover: If the database server restarts or a failover occurs, server-side cursors are destroyed.
  7. 7.Out-of-order fetch operations: Some databases require fetching rows in sequence. Attempting to fetch out of order or after reaching the end of the result set can cause cursor errors.

Step-by-Step Fix

Follow these steps to diagnose and resolve cursor errors:

Step 1: Identify the failing operation

Determine exactly where the cursor error occurs:

```python # Add detailed logging around cursor operations try: cursor = conn.cursor(name='my_cursor') cursor.execute("SELECT * FROM large_table")

while True: rows = cursor.fetchmany(1000) if not rows: break process_rows(rows) except Exception as e: print(f"Cursor error at fetch: {e}") print(f"Connection status: {conn.closed}") raise ```

Step 2: Check transaction and connection management

Verify cursors are used within the correct transaction scope:

```python # WRONG: Committing before cursor is fully consumed conn = get_connection() cursor = conn.cursor(name='my_cursor') cursor.execute("SELECT * FROM table") rows = cursor.fetchmany(100) conn.commit() # This closes the cursor! rows = cursor.fetchmany(100) # ERROR: cursor not found

# CORRECT: Consume cursor before commit conn = get_connection() cursor = conn.cursor(name='my_cursor') cursor.execute("SELECT * FROM table") all_rows = [] while True: rows = cursor.fetchmany(1000) if not rows: break all_rows.extend(rows) cursor.close() # Explicitly close cursor conn.commit() # Now safe to commit ```

Step 3: Fix connection pooling issues

Ensure cursors don't outlive their connections:

```python # WRONG: Using connection from pool across multiple operations def process_data(): conn = connection_pool.getconn() cursor = conn.cursor(name='my_cursor') cursor.execute("SELECT * FROM large_table") rows = cursor.fetchmany(100) connection_pool.putconn(conn) # Returns connection to pool # Later... more_rows = cursor.fetchmany(100) # ERROR: connection may be reused

# CORRECT: Use connection for full cursor lifecycle def process_data(): conn = connection_pool.getconn() try: cursor = conn.cursor(name='my_cursor') cursor.execute("SELECT * FROM large_table") while True: rows = cursor.fetchmany(1000) if not rows: break process_rows(rows) cursor.close() finally: connection_pool.putconn(conn) ```

Step 4: Configure appropriate cursor timeouts

Set cursor timeouts to match your workload:

```sql -- PostgreSQL: Configure cursor_tuple_fraction SET cursor_tuple_fraction = 1.0; -- Fetch all rows

-- Check for server-side timeouts SHOW statement_timeout; SHOW idle_in_transaction_session_timeout;

-- Set appropriate timeouts SET statement_timeout = '300s'; SET idle_in_transaction_session_timeout = '300s'; ```

python
# Configure timeouts in connection string
conn = psycopg2.connect(
    "host=localhost dbname=mydb user=myuser password=mypass "
    "options='-c statement_timeout=300000'"
)

Step 5: Use client-side cursors for connection pooling

When using connection pools, prefer client-side cursors:

```python # PostgreSQL: Use client-side cursor (default) cursor = conn.cursor() # Client-side, stores all results

# For large results, iterate without named cursor cursor = conn.cursor() cursor.execute("SELECT * FROM large_table") for row in cursor: # Streams from server without named cursor process_row(row)

# Alternative: Use server-side cursor with hold # (Keeps cursor open across transaction commits) cursor = conn.cursor(name='my_cursor', withhold=True) ```

Step 6: Implement proper resource cleanup with context managers

Use context managers to ensure proper cleanup:

```python from contextlib import contextmanager

@contextmanager def db_cursor(conn): cursor = None try: cursor = conn.cursor() yield cursor finally: if cursor: cursor.close()

# Usage with db_cursor(conn) as cursor: cursor.execute("SELECT * FROM table") rows = cursor.fetchall() # Cursor automatically closed ```

For full connection management:

```python @contextmanager def db_transaction(pool): conn = pool.getconn() try: yield conn conn.commit() except Exception: conn.rollback() raise finally: pool.putconn(conn)

# Usage with db_transaction(pool) as conn: with conn.cursor() as cursor: cursor.execute("SELECT * FROM table") rows = cursor.fetchall() # Cursor and transaction properly managed ```

Step 7: Handle failover and reconnection

Implement retry logic for transient failures:

```python import time from functools import wraps

def retry_on_cursor_error(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except (psycopg2.errors.InvalidCursorName, psycopg2.OperationalError) as e: if attempt == max_retries - 1: raise print(f"Cursor error, retrying ({attempt + 1}/{max_retries})") time.sleep(delay * (attempt + 1)) # Re-establish connection/transaction kwargs['conn'] = get_new_connection() return wrapper return decorator ```

Verification

After fixing cursor issues, verify the solution works:

```python # Test cursor over large result set def test_cursor_processing(): conn = get_connection() try: cursor = conn.cursor(name='test_cursor') cursor.execute("SELECT * FROM large_table")

total_rows = 0 while True: rows = cursor.fetchmany(1000) if not rows: break total_rows += len(rows)

cursor.close() print(f"Successfully processed {total_rows} rows") return True except Exception as e: print(f"Test failed: {e}") return False finally: conn.close()

# Run test assert test_cursor_processing() ```

```sql -- Verify database cursor configuration SHOW statement_timeout; SHOW idle_in_transaction_session_timeout; SHOW cursor_tuple_fraction;

-- Check for open cursors (PostgreSQL) SELECT * FROM pg_cursors; ```

Prevention

To prevent cursor errors:

  1. 1.Use context managers: Always use with statements or context managers for cursors and connections to ensure proper cleanup.
python
# Best practice
with conn.cursor() as cursor:
    cursor.execute("SELECT * FROM table")
    for row in cursor:
        process(row)
  1. 1.Keep cursors within transaction scope: Don't commit transactions until all cursor operations are complete, or use WITH HOLD cursors for cross-transaction usage.
  2. 2.Configure appropriate timeouts: Set statement and connection timeouts to accommodate your longest expected cursor operations.
  3. 3.Avoid mixing connection pool and cursor lifecycles: Ensure cursors are fully consumed before returning connections to pools.
  4. 4.Use client-side cursors when appropriate: For most use cases with connection pooling, client-side cursors are more reliable than server-side named cursors.
  5. 5.Add monitoring for cursor errors: Log and alert on cursor-related errors to catch issues early.

```python import logging logger = logging.getLogger(__name__)

try: # cursor operations except Exception as e: if 'cursor' in str(e).lower(): logger.error(f"Cursor error: {e}", exc_info=True) raise ```

  1. 1.Document cursor usage patterns: Clearly document when and how cursors should be used in your application to prevent misuse by other developers.
  • [Database troubleshooting: Fix Backup Exclusive Lock Table Production Writes ](backup-exclusive-lock-table-production-writes)
  • [Fix Connection Pool Leak Application Not Closing Issue in Database](connection-pool-leak-application-not-closing)
  • [Fix Connection Reset Idle Timeout Firewall Issue in Database](connection-reset-idle-timeout-firewall)
  • [Fix Connection Reset Idle Timeout Serverless Database Issue in Database](connection-reset-idle-timeout-serverless-database)
  • [Fix Connection String Encoding Special Characters Issue in Database](connection-string-encoding-special-characters)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Database Cursor Not Found", "description": "Resolve database cursor not found errors caused by transaction timeouts, connection issues, or improper cursor management.", "url": "https://www.fixwikihub.com/fix-database-cursor-not-found", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-23T06:32:21.400Z", "dateModified": "2026-01-23T06:32:21.400Z" } </script>