How to fix TypeError: This database connection is busy executing a query in SQLite

SQLiteINTERMEDIATEMEDIUMUpdated Dec 22, 2025· 175 views

This error occurs when better-sqlite3 tries to execute a query while the database connection is already processing another query. It commonly happens when attempting to run an UPDATE or DELETE while iterating through a SELECT result set, or when trying to close the database while a query is still running.

Quick fix

Fixes ERR_DATABASE_BUSY

const Database = require('better-sqlite3');
const db = new Database('database.db');

// Fetch all results first
const users = db.prepare('SELECT * FROM users').all();

// Then iterate and modify
for (const user of users) {
  db.prepare('UPDATE users SET active = 1 WHERE id = ?').run(user.id);
}
  1. Understand the execution model of better-sqlite3
  2. Replace .iterate() with .all() for simple cases
  3. Use transactions for batch operations
  4. Refactor .iterate() usage for streaming operations
sqlite — error
const Database = require('better-sqlite3');
const db = new Database('database.db');
// Fetch all results first
const users = db.prepare('SELECT * FROM users').all();
// Then iterate and modify
for (const user of users) {
db.prepare('UPDATE users SET active = 1 WHERE id = ?').run(user.id);
}
TypeError: This database connection is busy executing a query

What this error means

In better-sqlite3, each database connection can only process one query at a time. Unlike some database drivers that handle concurrent operations automatically, better-sqlite3 is a synchronous SQLite wrapper that maintains strict sequential execution. The error "This database connection is busy executing a query" occurs when: 1. **Attempting nested queries** while iterating through results with `.iterate()` 2. **Calling methods** on the database while a statement is still executing 3. **Closing the connection** while an active iterator or query is still in progress 4. **Multiple statement execution** on the same connection without completing the previous operation This is fundamentally different from SQLITE_BUSY (database file locked). This error is a programming issue - you're trying to do multiple things with one connection simultaneously.

Typical symptoms

  • TypeError: This database connection is busy executing a query appears when running certain operations
  • Error occurs when trying to UPDATE or DELETE rows while iterating through a SELECT
  • Database close fails with this error when an iterator is still active
  • Nested or dependent queries fail if you try to run them on the same connection during iteration
  • The error happens intermittently or under specific code patterns
  • Error appears specifically when using .iterate() to loop through large result sets

Common causes

  1. Attempting to run a write query (INSERT, UPDATE, DELETE) while iterating through SELECT results with .iterate()
  2. Closing the database connection without finishing an active iterator
  3. Trying to execute another statement on the same connection before the current operation completes
  4. Using .iterate() and then attempting to access the connection from a callback or async function
  5. Not properly managing the lifecycle of prepared statements and iterators
  6. Running queries within a loop that's already processing query results

How to fix "TypeError: This database connection is busy executing a query"

1Understand the execution model of better-sqlite3

better-sqlite3 is a synchronous library that executes queries one at a time per connection. Unlike async database drivers:

- Each connection has a single execution queue
- Operations block until completion
- No concurrent queries on one connection
- Iterators must be fully consumed before running other queries

Good pattern:

javascript
const Database = require('better-sqlite3');
const db = new Database('database.db');

// Fetch all results first
const users = db.prepare('SELECT * FROM users').all();

// Then iterate and modify
for (const user of users) {
  db.prepare('UPDATE users SET active = 1 WHERE id = ?').run(user.id);
}

Bad pattern:

javascript
// Don't try to iterate and modify simultaneously
for (const row of db.prepare('SELECT * FROM users').iterate()) {
  // ERROR: connection is busy with the SELECT iterator
  db.prepare('UPDATE users SET status = ? WHERE id = ?')
    .run('active', row.id);
}
2Replace .iterate() with .all() for simple cases

The simplest solution is to use .all() instead of .iterate(). This fetches all results into memory at once:

javascript
const Database = require('better-sqlite3');
const db = new Database('database.db');

// Use .all() instead of .iterate()
const rows = db.prepare('SELECT id, name FROM large_table').all();

// Now you can safely run other queries
for (const row of rows) {
  db.prepare('UPDATE large_table SET processed = 1 WHERE id = ?').run(row.id);
}

db.close();

When to use each method:
- .all(): All results fit in memory, want simplicity
- .get(): Single row, immediate operation
- .iterate(): Memory-constrained, processing doesn't need the connection
- .raw(): Performance-critical, need raw arrays

Example with all() and transactions:

javascript
const db = new Database('database.db');

const allUsers = db.prepare('SELECT id FROM users WHERE inactive = 1').all();

// Wrap updates in a transaction
const updateUser = db.prepare('UPDATE users SET active = 1 WHERE id = ?');
const transaction = db.transaction((ids) => {
  for (const id of ids) {
    updateUser.run(id);
  }
});

transaction(allUsers.map(u => u.id));
db.close();
3Use transactions for batch operations

When modifying multiple rows, wrap operations in a transaction. This is both faster and cleaner:

javascript
const Database = require('better-sqlite3');
const db = new Database('database.db');

// Define your prepared statements
const getRows = db.prepare('SELECT id, value FROM data WHERE processed = 0');
const updateRow = db.prepare('UPDATE data SET processed = 1 WHERE id = ?');

// Create a transaction function
const processRows = db.transaction(() => {
  const rows = getRows.all();
  for (const row of rows) {
    updateRow.run(row.id);
  }
  return rows.length;
});

// Execute the transaction
const count = processRows();
console.log(`Processed ${count} rows`);

db.close();

Transaction benefits:
- All operations succeed or all fail (atomicity)
- Faster than individual transactions
- Cleaner code structure
- Automatic rollback on error

Example with error handling:

javascript
const processRowsSafe = db.transaction(() => {
  const rows = getRows.all();
  for (const row of rows) {
    try {
      updateRow.run(row.id);
    } catch (error) {
      console.error(`Failed to process row ${row.id}: ${error.message}`);
      throw error; // Rollback the transaction
    }
  }
  return rows.length;
});

try {
  const count = processRowsSafe();
  console.log(`Success: ${count} rows`);
} catch (error) {
  console.error('Transaction failed, all changes rolled back');
}
4Refactor .iterate() usage for streaming operations

If you're using .iterate() for memory efficiency, refactor to avoid running queries during iteration:

javascript
const Database = require('better-sqlite3');
const db = new Database('database.db');

// BAD: Trying to run queries while iterating
// const iterator = db.prepare('SELECT id FROM huge_table').iterate();
// for (const row of iterator) {
//   db.prepare('PROCESS ...').run(row.id); // ERROR: connection busy
// }

// GOOD: Collect what you need, then process
const batchSize = 1000;
let offset = 0;

while (true) {
  const batch = db
    .prepare('SELECT id FROM huge_table LIMIT ? OFFSET ?')
    .all(batchSize, offset);

  if (batch.length === 0) break;

  // Process this batch (now safe to run other queries)
  const update = db.prepare('UPDATE huge_table SET processed = 1 WHERE id = ?');
  for (const row of batch) {
    update.run(row.id);
  }

  offset += batchSize;
  console.log(`Processed: ${offset}`);
}

db.close();

Alternative with separate connections:

javascript
const Database = require('better-sqlite3');

// Use one connection for reading
const readDb = new Database('database.db');
const writeDb = new Database('database.db');

// Now you can safely iterate on one and write on another
for (const row of readDb.prepare('SELECT id FROM users').iterate()) {
  writeDb.prepare('UPDATE users SET status = 1 WHERE id = ?').run(row.id);
}

readDb.close();
writeDb.close();
5Fix improper connection closure

Ensure you're not closing the connection while iterators are still active:

javascript
const Database = require('better-sqlite3');
const db = new Database('database.db');

// BAD: Trying to close while iterator is active
// const iterator = db.prepare('SELECT * FROM users').iterate();
// setTimeout(() => db.close(), 100); // ERROR: may close while iterating

// GOOD: Finish iteration before closing
async function processUsers() {
  const iterator = db.prepare('SELECT * FROM users').iterate();
  const allUsers = [...iterator]; // Consume the entire iterator

  db.close(); // Safe to close now
  return allUsers;
}

// Or explicitly complete the iterator
const users = [];
const iterator = db.prepare('SELECT * FROM users').iterate();
for (const user of iterator) {
  users.push(user);
}
// Iterator is now complete

db.close();

Proper resource management:

javascript
const Database = require('better-sqlite3');

function withDatabase(callback) {
  const db = new Database('database.db');
  try {
    return callback(db);
  } finally {
    db.close();
  }
}

// Usage
const users = withDatabase((db) => {
  return db.prepare('SELECT * FROM users').all();
});

console.log(users);
6Use multiple connections for concurrent-like behavior

If you absolutely need to read and write simultaneously, use separate database connections:

javascript
const Database = require('better-sqlite3');

const readDb = new Database('database.db');
const writeDb = new Database('database.db');

try {
  // Safe to iterate on readDb while using writeDb
  const selectStmt = readDb.prepare('SELECT id FROM items WHERE active = 0');
  const updateStmt = writeDb.prepare('UPDATE items SET active = 1 WHERE id = ?');

  for (const row of selectStmt.iterate()) {
    updateStmt.run(row.id);
  }
} finally {
  readDb.close();
  writeDb.close();
}

Considerations for multiple connections:
- Each connection to the same database file is independent
- Writes are still serialized at the SQLite level (only one writer)
- Reads can happen concurrently
- WAL mode (Write-Ahead Logging) improves concurrency
- Remember to close all connections

With WAL mode:

javascript
const Database = require('better-sqlite3');

const readDb = new Database('database.db');
const writeDb = new Database('database.db');

// Enable WAL mode for better concurrency
readDb.pragma('journal_mode = WAL');

try {
  // Better concurrency between read and write
  const selectStmt = readDb.prepare('SELECT id FROM items LIMIT 100');
  const updateStmt = writeDb.prepare('UPDATE items SET status = ? WHERE id = ?');

  for (const row of selectStmt.iterate()) {
    updateStmt.run('processed', row.id);
  }
} finally {
  readDb.close();
  writeDb.close();
}

Advanced notes

### Better-sqlite3 Execution Model

Unlike most database libraries, better-sqlite3 is synchronous and single-threaded:

1. Synchronous execution: Each query blocks until complete
2. Single queue per connection: Only one operation at a time
3. No implicit async/await: No hidden concurrency
4. Iterator lifecycle: Iterators must be fully consumed before other operations

This design choice makes:
- Performance predictable
- Code easier to reason about
- Memory usage explicit (you choose .all() vs .iterate())
- Concurrency explicit (use multiple connections if needed)

### Prepared Statements and Iterators

Once you call .iterate() on a prepared statement, the connection is "busy":

javascript
const stmt = db.prepare('SELECT * FROM users');

// Starting iteration
const iter = stmt.iterate();

// Connection is now BUSY until iteration completes
// ❌ Can't run this: db.prepare('...').run()
// ❌ Can't run this: db.close()

// Must consume the iterator
for (const row of iter) { /* ... */ }

// NOW connection is free

### Transaction Semantics

better-sqlite3 transactions are powerful:

javascript
// Manual transaction
db.exec('BEGIN');
try {
  // Your operations
  db.exec('COMMIT');
} catch {
  db.exec('ROLLBACK');
}

// Automatic transaction (recommended)
const myTransaction = db.transaction(() => {
  // Automatic BEGIN/COMMIT/ROLLBACK
  db.prepare('INSERT ...').run(...);
  db.prepare('UPDATE ...').run(...);
});

myTransaction();

### Performance Implications

javascript
// Slow: Many small transactions
for (const id of ids) {
  db.prepare('UPDATE users SET active = 1 WHERE id = ?').run(id);
}
// Each run is a separate transaction

// Fast: One transaction with many operations
const update = db.transaction((ids) => {
  const stmt = db.prepare('UPDATE users SET active = 1 WHERE id = ?');
  for (const id of ids) {
    stmt.run(id);
  }
});
update(ids);

### Memory vs. Speed Trade-offs

| Method | Memory | Speed | Use Case |
|--------|--------|-------|----------|
| .all() | High (all rows) | Fast (cached) | Small-medium result sets |
| .iterate() | Low (one row) | Slower (per-row overhead) | Large result sets, memory-constrained |
| .get() | Very low (one row) | Fastest | Single row queries |
| .exec() | N/A | Fastest | Multiple statements, no results |

### Connection Management Best Practices

1. Keep connections open for the lifetime of your app
2. Reuse prepared statements (they're cached after first prepare)
3. Use transactions for multiple related operations
4. Don't share connections between threads (sync-only)
5. Always close on app exit or error
6. Enable WAL mode for better concurrency with multiple connections

Frequently asked questions

What does "ERR_DATABASE_BUSY" mean?
In better-sqlite3, each database connection can only process one query at a time. Unlike some database drivers that handle concurrent operations automatically, better-sqlite3 is a synchronous SQLite wrapper that maintains strict sequential execution. The error "This database connection is busy executing a query" occurs when: 1.
How do I fix "ERR_DATABASE_BUSY"?
This error occurs when better-sqlite3 tries to execute a query while the database connection is already processing another query. It commonly happens when attempting to run an UPDATE or DELETE while iterating through a SELECT result set, or when trying to close the database while a query is still running.
What causes "ERR_DATABASE_BUSY"?
Attempting to run a write query (INSERT, UPDATE, DELETE) while iterating through SELECT results with .iterate() Closing the database connection without finishing an active iterator Trying to execute another statement on the same connection before the current operation completes
Is the "ERR_DATABASE_BUSY" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading