tursodatabase/turso · error · SqliteException
5
5
Error message
SQLite Error 5: 'database is locked'.
What it means
The synchronous batch execution path simulates SQLite's SQLITE_BUSY (error code 5, 'database is locked'): the connection already has an open data reader and the batch contains a write statement, so executing would mutate the database while a reader holds it. The implementation waits (Thread.Sleep for the command timeout) and then throws SqliteException with code 5.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteBatch.cs:418
}
mapping.Command.SetRecordsAffected(commandTotal);
total = checked(total + commandTotal);
}
return total;
}
private SqliteCommand CreateReaderOwner()
=> new(_connection) { Transaction = _transaction };
private void WaitForOpenReader(IReadOnlyList<ManagedSqliteStatement> statements)
{
if (_connection?.HasOpenReader != true || !statements.Any(SqliteCommand.IsWriteStatement))
return;
Thread.Sleep(TimeSpan.FromSeconds(Timeout));
throw new SqliteException(Properties.Resources.SqliteNativeError(5, "database is locked"), 5);
}
private async Task WaitForOpenReaderAsync(
IReadOnlyList<ManagedSqliteStatement> statements,
CancellationToken cancellationToken)
{
if (_connection?.HasOpenReader != true || !statements.Any(SqliteCommand.IsWriteStatement))
return;
await Task.Delay(TimeSpan.FromSeconds(Timeout), cancellationToken).ConfigureAwait(false);
throw new SqliteException(Properties.Resources.SqliteNativeError(5, "database is locked"), 5);
}
private static object? ReadScalar(DbDataReader reader)
{
do
{
if (reader.FieldCount > 0 && reader.Read())View on GitHub (pinned to 6c72522679)
Solutions
- Close/dispose the open DbDataReader before executing the batch
- Buffer the reader results into a list, close the reader, then execute the write batch
- Use a separate connection for the write if concurrent read/write is required
- Reduce CommandTimeout is irrelevant - the wait is fixed; the fix is not holding the reader
Example fix
// before
using var reader = cmd.ExecuteReader();
while (reader.Read())
batch.ExecuteNonQuery(); // throws: reader open
// after
var rows = new List<object[]>();
using (var reader = cmd.ExecuteReader())
while (reader.Read()) { /* buffer rows */ }
foreach (var _ in rows)
batch.ExecuteNonQuery(); Defensive patterns
Strategy: try-catch
Validate before calling
if (connection.HasOpenReader && batch.BatchCommands.Any(c => /* IsWriteStatement */ c.CommandText.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase)))
throw new InvalidOperationException("Close the open reader before executing a write batch."); Try / catch
try { batch.ExecuteNonQuery(); }
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
// close readers, then retry once
batch.ExecuteNonQuery();
} Prevention
- Always wrap readers in using statements
- Never execute writes on a connection while a reader is open; buffer reads first
- Use separate connections for concurrent read/write patterns
When it happens
Trigger: Executing SqliteBatch.ExecuteNonQuery while another SqliteCommand/reader on the same managed connection still has open results (HasOpenReader == true) and the batch contains INSERT/UPDATE/DELETE/DDL (IsWriteStatement).
Common situations: Iterating a DataReader from one command and executing a batch write on the same connection inside the loop; forgetting to close/dispose a reader before writing; draining a SELECT into memory first is required in single-connection managed mode.
Related errors
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-09-06).
Data as JSON: /api/errors/e085af5604a0c0c2.
Report an issue: GitHub.