惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
Last Week in AI
Last Week in AI
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
博客园 - 【当耐特】
博客园 - 叶小钗
量子位
博客园 - 聂微东
S
SegmentFault 最新的问题
美团技术团队
Hugging Face - Blog
Hugging Face - Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
宝玉的分享
宝玉的分享
小众软件
小众软件
罗磊的独立博客
有赞技术团队
有赞技术团队
Stack Overflow Blog
Stack Overflow Blog

blag

SQLite prefixes its temp files with `etilqs_` - blag Setsum - order agnostic, additive, subtractive checksum - blag Oldest recorded transaction - blag Replacing a cache service with a database - blag PSA: SQLite WAL checksums fail silently and may lose data - blag Rickrolling Turso DB (SQLite rewrite in Rust) - blag Collection of insane and fun facts about SQLite - blag How bloom filters made SQLite 10x faster - blag In search of a faster SQLite - blag Galloping Search - blag Building a distributed log using S3 (under 150 lines of Go) - blag Zero Disk Architecture - blag PSA: Most databases do not do checksums by default - blag PSA: SQLite does not do checksums - blag Disaggregated Storage - a brief introduction - blag Why does SQLite (in production) have such a bad rep? - blag SQLite Slaps - blag Now - blag Learning C - blag Snapshot Testing - blag Win: contribution to libSQL (SQLite) codebase - blag Errata in Hekaton MVCC paper - blag Internet is wholesome: MVCC edition - blag It is becoming difficult for me to be productive in Python - blag MongoDB secondary only index - blag Introducing CaskDB – a project to teach you writing a key-value store - blag Recurse Center: Winter Break - blag Recurse Center Day 24: Hacking Go compiler to add a new keyword - blag Recurse Center Day 20: Django v4 upgrade (from v1) - blag Recurse Center Day 19 - blag
SQLite commits are not durable under default settings - blag
2025-08-24 · via blag

Previously, I claimed that transactions in SQLite with WAL are not durable under default settings. Turns out, I was only half wrong but technically correct; the issue is actually with SQLite in rollback journal mode (the default). This post is now amended with the changes.

Here’s what I mean by durability: when the database acknowledges that a transaction is committed, it’s ‘durably’ saved to disk. That is, neither an application crash nor an OS crash should make that transaction disappear. Imagine you make a new commit, the db acknowledges success, and suddenly your OS reboots. Do you expect your transaction changes to be persisted? For example, in Postgres you can expect your changes to be there. This is how most OLTP databases behave.

SQLite Journal Mode

Under the default settings, SQLite operates in rollback journal mode. SQLite also has a PRAGMA called synchronous which configures how fsync is called. The synchronous setting has many modes: OFF, NORMAL, FULL, EXTRA. The default is set to FULL:

$ sqlite3 test.db

SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> PRAGMA journal_mode;
delete
sqlite> PRAGMA synchronous;
2

Unfortunately, in journal mode, FULL isn’t enough to make transactions durable. Here’s what the documentation states:

EXTRA (3) EXTRA synchronous is like FULL with the addition that the directory containing a rollback journal is synced after that journal is unlinked to commit a transaction in DELETE mode. EXTRA provides additional durability if the commit is followed closely by a power loss.

FULL (2) When synchronous is FULL (2), the SQLite database engine will use the xSync method of the VFS to ensure that all content is safely written to the disk surface prior to continuing. This ensures that an operating system crash or power failure will not corrupt the database.

Notice that it says FULL ensures that the database isn’t corrupted, but NOT that the last transaction is durable. The highlighted part in EXTRA provides that durability.

SQLite with WAL

SQLite also has a WAL mode, and you’re likely using it if you want higher write throughput. The synchronous PRAGMA also applies to WAL. The default is FULL:

With synchronous=FULL in WAL mode, an additional sync operation of the WAL file happens after each transaction commit. The extra WAL sync following each transaction helps ensure that transactions are durable across a power loss. Transactions are consistent with or without the extra syncs provided by synchronous=FULL.

However, NORMAL seems misnamed, as it doesn’t seem normal to me:

[..] but WAL mode does lose durability. A transaction committed in WAL mode with synchronous=NORMAL might roll back following a power loss or system crash.

In WAL mode when synchronous is NORMAL (1), the WAL file is synchronized before each checkpoint and the database file is synchronized after each completed checkpoint and the WAL file header is synchronized when a WAL file begins to be reused after a checkpoint, but no sync operations occur during most transactions.

If durability is not a concern, then synchronous=NORMAL is normally all one needs in WAL mode.

So, if you’re using WAL, stick with FULL. If durability isn’t a concern, then NORMAL may be preferred for higher performance. While this is what the documentation says, DRH, the creator of SQLite, said the following which contradicts the documentation:

If you switch to WAL mode, the default behavior is that transactions are durable across application crashes (or SIGKILL or similar) but are not necessarily durable across OS crashes or power failures. Transactions are atomic across OS crashes and power failures. But if you commit a transaction in WAL mode and take a power loss shortly thereafter, the transaction might be rolled back after power is restored.

I’ll leave it up to you to decide which is correct 🤷‍♂️

SQLite on macOS

The situation on macOS is quite fucked up. The SQLite shipped with macOS has the following:

$ sqlite3 test.db

SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> PRAGMA journal_mode=wal;
wal
sqlite> PRAGMA synchronous;
1
sqlite> PRAGMA fullfsync;
0
sqlite>

That is, the default is NORMAL. So, commits are not durable. But even if you use FULL, it’s not enough. You’ll want to set fullfsync to true. This is false by default. Apparently, Apple has purposely fucked up fsync and you always want to use fullfsync. This setting has no effect on non-macOS machines.

Compile Time Options

SQLite decides all this config through compile-time defaults:

SQLITE_DEFAULT_SYNCHRONOUS=<0-3> This macro determines the default value of the PRAGMA synchronous setting. If not overridden at compile-time, the default setting is 2 (FULL).

SQLITE_DEFAULT_WAL_SYNCHRONOUS=<0-3> This macro determines the default value of the PRAGMA synchronous setting for database files that open in WAL mode. If not overridden at compile-time, this value is the same as SQLITE_DEFAULT_SYNCHRONOUS.

There’s no compile-time option for fullfsync, so by default it’s false.

So it’s totally possible that your distribution might be shipping SQLite with default synchronous as NORMAL.

The lesson here should be that you should always check the setting and make sure it’s what you want. Here’s a small chart to help you set:

Journal ModeSynchronousfullfsync
DELETE (rollback)EXTRA1
WALFULL1

Thanks to all the people who commented and discussed the original version of this article on Hacker News. Their comments helped me make this post better.

Someone passed me this post SurrealDB is sacrificing data durability to make benchmarks look better and asked me how SQLite works.