June 4, 2020·5 min read·engineeringsqlitetransactionscorrectness

SQLite fixes a false success result from COMMIT

SQLite 3.32.2 corrects a bytecode-engine defect that could report a successful COMMIT after failure. The transaction rules explain why the returned outcome matters.

A successful write statement inside a transaction does not establish that the transaction committed. Applications depend on the result of COMMIT to tell them whether the database accepted that final operation. SQLite 3.32.2 fixes a defect that could make that distinction unreliable. The June 4 release notes describe a long-standing bytecode-engine bug that could report success when a COMMIT had failed.

The description is specific about the incorrect outcome, but it does not say that every commit is affected. It also does not describe the defect as having been introduced by the preceding feature release. Calling it long-standing matters when interpreting the patch. The scope is a correction to an existing transaction failure path, with consequences for how an application understands its database state.

I value SQLite because an application can obtain transactional behavior without managing a separate database server. That preference still depends on a reliable answer at transaction completion. A false success result matters even when ordinary reads and writes continue to behave as expected.

Statements and transactions finish separately

The transaction documentation as of June 4 explains that SQLite performs database reads and writes within transactions. A command that accesses the database starts a transaction automatically if necessary. An automatically started transaction commits when its last active statement finishes. Explicit BEGIN changes the lifetime, keeping the transaction open until a subsequent completion operation or an applicable error response.

That distinction lets an application group several statements into one transaction. A statement can complete before the transaction's final result is known. The following illustrative SQL makes the stages visible, although it is not a reproducer for the defect.

CREATE TABLE settings (
    name TEXT PRIMARY KEY,
    value TEXT
);

BEGIN;
INSERT INTO settings (name, value)
VALUES ('display', 'compact');
COMMIT;

The insert's result concerns execution of that statement. The result of the final command concerns completion of the explicit transaction. Treating those outcomes as interchangeable would lose information even with a correct engine. This patch concerns the engine supplying an incorrect success outcome at that final stage.

The documentation also says that an explicit COMMIT runs immediately even with pending SELECT statements. Pending write operations are different, since they can make COMMIT fail with SQLITE_BUSY. The existence of an explicit completion command therefore does not guarantee that completion succeeds. Its result remains necessary information for the caller.

Failure can leave a transaction active

The documented COMMIT behavior includes a busy result when another thread or process has an open read connection that prevents completion. In that case the transaction remains active, and the caller can retry COMMIT after the reader clears. This is a documented transaction rule, not a claim about the exact conditions that trigger the newly fixed bytecode defect.

It nevertheless shows why failure reporting has operational meaning. A caller that receives a failure can distinguish an outstanding transaction from one that has completed. A caller that receives false success can make subsequent decisions under an incorrect assumption. The release note identifies that outcome mismatch as the reason for the correction.

The appropriate response to a genuine busy commit is not established by pretending that all errors mean the same thing. The documented retry possibility applies while the transaction remains active. Other errors have their own effects. An application's error handling needs the actual result and the connection's actual transaction state to choose a valid continuation.

SQLite supports several ways to begin an explicit transaction. The default deferred form waits until database access to establish the transaction's work. BEGIN IMMEDIATE starts a write transaction immediately and can fail if another writer is active. Selecting that form changes when contention may be encountered, but the release notes do not offer it as a workaround for the false-success defect.

An error does not always roll back the same amount

The historical transaction documentation lists several errors that can cause automatic rollback, including a full database or disk, an I/O error, a busy database and allocation failure. SQLite first attempts to undo the statement that encountered the problem while leaving earlier work in the transaction intact. Depending on the statement and failure point, it may need to roll back the whole transaction instead.

That means transaction state after an error is part of the interface. The documentation names sqlite3_get_autocommit() as the C interface for determining which course SQLite took. It also recommends an explicit rollback in response to the listed errors. If the engine has already rolled the transaction back, the extra rollback fails without causing harm.

Those rules describe recovery from an error the caller knows occurred. False success interferes with the earlier step of recognizing that recovery is necessary. An application cannot apply a documented error policy to an error outcome it never receives. The patch restores that distinction in the affected COMMIT path.

I regard the correction as more important than the size of a patch release suggests. A transaction boundary coordinates the database's changes with the application's next decision. The reliability of the reported boundary affects whether the caller continues as though its intended state exists.

The patch note has a narrower claim than an incident report

The release notes do not establish how often applications encountered the defect or quantify any resulting data loss. They identify the bytecode engine, the failed COMMIT and its incorrect success report. That is enough to explain the correctness problem without inventing a production incident or attributing every unexpected transaction outcome to this bug.

The release page also repeats changes inherited from earlier versions. Those include the approximate analysis support introduced in 3.32.0 and the fixes carried in 3.32.1. Its separate heading for changes in this specific patch release identifies the COMMIT correction as the new item. Reading that organization matters, because the inherited list is not evidence that each entry first shipped on June 4.

For an embedded application, the relevant version is the SQLite engine it actually links or bundles. Updating an unrelated database utility does not establish that the application's own engine includes the correction. The substantive change in 3.32.2 is the restored accuracy of the COMMIT result in the affected failure case, which lets the application use the documented transaction rules with the result the operation actually produced.