SQLITE_LOCKED_SHAREDCACHE occurs when multiple connections to the same database in shared-cache mode attempt conflicting operations simultaneously. This error indicates a table-level lock conflict and typically appears in multi-threaded applications. The recommended solution is to disable shared-cache mode or migrate to WAL mode, which handles concurrent access more efficiently.
The SQLITE_LOCKED_SHAREDCACHE error is a result code that indicates a write operation could not continue because of a locking conflict within a shared database cache. This occurs when shared-cache mode is enabled and multiple connections to the same database attempt to access the same table concurrently. In shared-cache mode, SQLite uses table-level locking instead of database-level locking. When one connection modifies data in a table, SQLite locks that table at a fine-grained level. If another connection tries to access the same locked table simultaneously, SQLite returns the SQLITE_LOCKED_SHAREDCACHE error rather than blocking and waiting. This is fundamentally different from normal SQLite locking behavior, where separate connections would lock the entire database file and typically receive SQLITE_BUSY instead of SQLITE_LOCKED.
Check if shared-cache mode is being used in your connection string or SQLite configuration. Look for:
// In-memory database with shared cache
DataSource=file::memory:?cache=shared
// Or using URI format
DataSource=:memory:?cache=shared
// Or the SQLITE_OPEN_SHAREDCACHE flag in your sqlite3_open_v2() callIf you're using Microsoft.Data.Sqlite or another SQLite client, check the connection string for Cache=Shared.
The simplest solution is to disable shared-cache mode. Change your connection string:
// Before (with shared cache - causes SQLITE_LOCKED_SHAREDCACHE)
var connectionString = "DataSource=file::memory:?cache=shared";
// After (private cache - default)
var connectionString = "DataSource=:memory:?cache=private";
// Or simply
var connectionString = "DataSource=:memory:";
// Or remove cache parameter entirely
var connectionString = "Data Source=mydata.db";The default behavior is private-cache mode, which uses database-level locking and is more suitable for most applications.
If you need better concurrent access, migrate to Write-Ahead Logging (WAL) mode. WAL mode eliminates the need for shared-cache and provides significantly better concurrency:
// When creating your connection
var connectionString = "Data Source=mydata.db";
using (var connection = new SqliteConnection(connectionString))
{
connection.Open();
// Enable WAL mode
using (var command = connection.CreateCommand())
{
command.CommandText = "PRAGMA journal_mode = WAL";
command.ExecuteNonQuery();
}
}WAL mode (introduced in SQLite 3.7.0 around 2010) is the recommended solution for concurrent access. It allows readers and writers to coexist without blocking each other.
If you must use shared-cache mode (rare), implement exponential backoff retry logic since busy timeouts don't work with SQLITE_LOCKED:
const int maxRetries = 5;
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
// Execute your database operation
connection.Execute(sql);
break; // Success
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 6) // SQLITE_LOCKED
{
retryCount++;
if (retryCount >= maxRetries) throw;
// Exponential backoff
int delayMs = (int)Math.Pow(2, retryCount) * 10;
await Task.Delay(delayMs);
}
}Note: This is not ideal and should only be used as a temporary workaround.
After making changes:
1. Remove or comment out any code that explicitly enables shared-cache mode
2. Test concurrent database access from multiple threads
3. Monitor for SQLITE_LOCKED_SHAREDCACHE errors in logs
4. If using WAL mode, verify the -wal and -shm files are being created alongside your database file
// Test concurrent access
var tasks = Enumerable.Range(0, 10)
.Select(i => Task.Run(() => ExecuteDatabaseOperation(i)))
.ToArray();
await Task.WhenAll(tasks);
// Should complete without SQLITE_LOCKED_SHAREDCACHE errorsShared-cache mode is deprecated and discouraged by SQLite developers. It was originally designed for embedded systems (like Symbian phones) where database file locking was problematic. Modern applications should use WAL mode instead.
Key differences between shared-cache and normal locking:
- Normal mode: Multiple connections lock the entire database file; conflicts use SQLITE_BUSY with timeout support
- Shared-cache mode: Multiple connections lock individual tables; conflicts use SQLITE_LOCKED with no timeout support
- WAL mode: Uses a write-ahead log for concurrent access; supports multiple readers with one writer without file-level locking
Shared-cache mode also does not work well with WAL mode. If you're mixing these, remove the shared-cache mode.
For Node.js SQLite drivers like sqlite3 or better-sqlite3, check the documentation for equivalent configuration options. The principle remains the same: disable shared-cache mode or migrate to WAL.
SQLITE_BUSY: The database file is locked
How to fix 'SQLITE_BUSY: The database file is locked' in SQLite
better-sqlite3: This statement has already been finalized
How to fix "better-sqlite3: This statement has already been finalized" in SQLite
SQLITE_AUTH: Authorization denied
SQLITE_AUTH: Authorization denied
SQLITE_CONSTRAINT_CHECK: CHECK constraint failed
CHECK constraint failed in SQLite
SQLITE_READONLY_RECOVERY: WAL mode database cannot be modified
How to fix "SQLITE_READONLY_RECOVERY: WAL mode database cannot be modified" in SQLite