When using SQLite's shared-cache mode, connections within the same process experience table-level locking conflicts that return SQLITE_LOCKED_SHAREDCACHE (error code 6). Unlike SQLITE_BUSY (which occurs between separate processes), this error indicates a locking conflict between connections sharing the same cache and cannot be resolved with timeout settings.
The SQLITE_LOCKED_SHAREDCACHE error is specific to SQLite's shared-cache mode, an optional feature that allows multiple connections within the same process to share a memory cache. In shared-cache mode, SQLite enforces table-level locking instead of database-level locking. When two or more connections attempt conflicting operations on the same table (or when a schema modification is pending), one connection receives SQLITE_LOCKED_SHAREDCACHE. This error differs fundamentally from SQLITE_BUSY. The SQLITE_BUSY error (code 5) occurs when separate database processes conflict over the database file. In contrast, SQLITE_LOCKED (code 6) occurs between connections in the same process that share a cache. The error cannot be resolved with busy timeouts or retry loops—if a lock conflict occurs in shared-cache mode, the operation has immediately failed. Shared-cache mode was designed in 2006 to save memory on embedded platforms but has been largely superseded by WAL (Write-Ahead Logging) mode, which provides better concurrency without the same locking limitations.
Shared-cache mode is obsolete and causes more problems than it solves. Disable it in your connection string or initialization code:
# Python - sqlite3
import sqlite3
conn = sqlite3.connect('database.db') # Shared cache disabled by default
# If it was enabled, disable it
sqlite3.enable_shared_cache(False)// Node.js - better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('database.db');
// better-sqlite3 does not use shared cache mode by default
// JavaScript - sqlite3
const sqlite3 = require('sqlite3');
const db = new sqlite3.Database('database.db');
// sqlite3 does not use shared cache mode by default// Go - go-sqlite3
import _ "github.com/mattn/go-sqlite3"
// Shared cache can be enabled via URI parameter
// To disable: do NOT use ?cache=shared in the connection string
db, err := sql.Open("sqlite3", "file:database.db")WAL (Write-Ahead Logging) mode provides superior concurrent access without the locking problems of shared-cache mode. Enable WAL mode to allow readers and writers to operate simultaneously:
import sqlite3
conn = sqlite3.connect('database.db')
conn.execute('PRAGMA journal_mode=WAL')
conn.close()// Node.js - better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('database.db');
db.pragma('journal_mode = WAL');-- In SQLite directly
PRAGMA journal_mode=WAL;WAL mode allows readers to continue accessing the database while writers commit changes, eliminating the need for shared-cache mode's table-level locks.
If you cannot immediately disable shared-cache mode, enable read_uncommitted to allow readers to bypass read locks. Note this only helps readers, not writers:
import sqlite3
conn = sqlite3.connect('database.db')
conn.execute('PRAGMA read_uncommitted=1')
# Now SELECT queries will not attempt to obtain read locks// Node.js - better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('database.db');
db.pragma('read_uncommitted = 1');PRAGMA read_uncommitted=1;This changes the isolation level from serialized (default) to read-uncommitted, allowing readers to access tables even when writers hold table locks. However, this is not a complete solution for write-write conflicts.
As a temporary measure, reduce concurrency by serializing database access through a single connection or queue:
import sqlite3
from threading import Lock
class DatabaseQueue:
def __init__(self, db_path):
self.conn = sqlite3.connect(db_path)
self.lock = Lock()
def execute(self, query, params=()):
with self.lock:
return self.conn.execute(query, params).fetchall()
db = DatabaseQueue('database.db')
# All operations are serialized through the lock
result = db.execute('SELECT * FROM users WHERE id = ?', (1,))// Node.js - using a mutex or queue
const Database = require('better-sqlite3');
const Mutex = require('async-lock');
class DatabaseQueue {
constructor(dbPath) {
this.db = new Database(dbPath);
this.lock = new Mutex();
}
async execute(query, params = []) {
return this.lock.acquire('db', () => {
return this.db.prepare(query).all(...params);
});
}
}
const db = new DatabaseQueue('database.db');
// All operations serialize through the lockEnsure all transactions (especially those modifying the schema) are committed or rolled back immediately. Long-running or uncommitted transactions block other connections:
import sqlite3
conn = sqlite3.connect('database.db')
# GOOD: Transaction is short and committed immediately
try:
conn.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
conn.commit()
except Exception as e:
conn.rollback()
print(f"Error: {e}")
# BAD: Transaction left open indefinitely
cursor = conn.cursor()
cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
# If this transaction is not committed, other connections will be blocked// Node.js - better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('database.db');
// GOOD: Use transactions and commit immediately
const transaction = db.transaction(() => {
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
});
transaction();
// BAD: Manual transaction left open
db.exec('BEGIN');
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
// If COMMIT is not called, schema changes block all other connectionsVerify that shared-cache mode is not accidentally enabled in your application:
- Python sqlite3: Shared cache is disabled by default. Only enabled via sqlite3.enable_shared_cache(True)
- Node.js better-sqlite3: Does not support shared-cache mode
- Node.js sqlite3: Does not enable shared-cache by default
- Go go-sqlite3: Only enabled if ?cache=shared is in the connection URI
- Rust rusqlite: Shared cache disabled by default
- Java JDBC: Not applicable; each JVM process gets its own SQLite instance
If you see shared-cache enabled in your configuration, remove it immediately.
Schema-Level Locking Details: In shared-cache mode, any connection modifying the schema (CREATE TABLE, DROP TABLE, ALTER TABLE, etc.) holds a write-lock on sqlite_schema. All other connections attempting to read or modify any table will receive SQLITE_LOCKED_SHAREDCACHE until that schema transaction commits. Even connections using PRAGMA read_uncommitted=1 cannot bypass this.
Shared-Cache History: Shared-cache mode was introduced in SQLite 3.3.0 to save memory on embedded systems (specifically Symbian phones). However, WAL mode (introduced in SQLite 3.7.0, circa 2010) provides a superior solution for the same problem—allowing concurrent reads and writes without the sharp locking semantics of shared-cache.
Why Timeout Does Not Work: SQLITE_BUSY can be managed with sqlite3_busy_timeout() because it represents a transient file lock that will eventually be released. SQLITE_LOCKED_SHAREDCACHE, however, represents an immediate application-level conflict that will never resolve through waiting—the lock is permanent until one of the conflicting operations completes.
Unlock Notify API: Some SQLite bindings (like go-sqlite3) mention the unlock_notify API as a solution, but this is rarely implemented and requires careful multi-threaded code. Most applications benefit more from simply disabling shared-cache or using WAL mode.
Migration Path: If your application currently relies on shared-cache for memory efficiency or concurrency, migrate to:
1. WAL mode for improved concurrency (primary recommendation)
2. A client-server database (PostgreSQL, MySQL) if you need true multi-process concurrent access
SQLITE_CORRUPT_VTAB: Content in virtual table is corrupt
Content in virtual table is corrupt
SQLITE_IOERR_WRITE: Disk I/O error during write
Disk I/O error during write operation
SQLITE_READONLY: Attempt to write a readonly database
How to fix "SQLITE_READONLY: Attempt to write a readonly database" in SQLite
SQLITE_CONSTRAINT_PRIMARYKEY: PRIMARY KEY constraint failed
How to fix "SQLITE_CONSTRAINT_PRIMARYKEY" in SQLite
SQLITE_READONLY_DBMOVED: Database file has been moved since opened
How to fix 'SQLITE_READONLY_DBMOVED: Database file has been moved since opened'