Comparing Lock Strategies
BEGIN IMMEDIATE provides a more predictable latency profile than a global busy timeout because it resolves lock contention at the start of the transaction. While sqlite3_busy_timeout() is a reactive mechanism that retries operations after a conflict occurs, BEGIN IMMEDIATE is a proactive strategy that prevents the most common cause of SQLite deadlocks: the lock upgrade conflict.
The Mechanism Difference
In standard BEGIN transactions, SQLite starts with a shared lock. If the transaction later attempts a write, it must upgrade to a reserved lock. If two connections both hold shared locks and both attempt to upgrade simultaneously, a deadlock occurs, and one must return SQLITE_BUSY.
- sqlite3_busy_timeout(): Tells SQLite to sleep and retry for a specified duration when it encounters a lock. It handles the
SQLITE_BUSY error internally, but it cannot resolve a deadlock where two connections are waiting on each other to release shared locks.
- BEGIN IMMEDIATE: Acquires a reserved lock immediately. This ensures that if the command succeeds, no other connection can start a write transaction, guaranteeing that the subsequent commit will not fail due to lock contention.
Interaction and Redundancy
Combining both mechanisms is not redundant; rather, they serve different purposes. sqlite3_busy_timeout() acts as the safety net for BEGIN IMMEDIATE. If you call BEGIN IMMEDIATE while another connection already holds a reserved lock, SQLite will use the busy timeout to wait for that lock to be released before failing with SQLITE_BUSY.
Thread Starvation Risks: Starvation typically occurs when transactions are long-lived. Because BEGIN IMMEDIATE blocks other writers from even starting, a single slow transaction can queue all other write attempts for the duration of the busy timeout, potentially exhausting application thread pools.
Implementation Steps
- Verify WAL Mode: Ensure the database is in Write-Ahead Logging mode to allow concurrent readers and writers.
PRAGMA journal_mode=WAL;
- Set a Global Timeout: Configure a reasonable timeout (e.g., 5000ms) to handle transient locks.
sqlite3_busy_timeout(db, 5000);
- Use Immediate Transactions: Replace
BEGIN with BEGIN IMMEDIATE for any transaction intended to perform writes.
Diagnostic Requirement: To refine this recommendation, please specify the average duration of your write transactions. If transactions exceed several hundred milliseconds, BEGIN IMMEDIATE may significantly increase the frequency of timeouts for concurrent writers.