6月1日 00:42

SQLite WAL 模式是什么?为什么能提升并发读写?

SQLite 的 WAL,全称 Write-Ahead Logging,意思是写操作先追加到 WAL 日志文件,而不是立刻修改主数据库文件。默认回滚日志模式更像“先准备撤销方案,再改原文件”;WAL 模式则是“先把变更写到旁边日志,合适时再合并”。这个变化让读写冲突少很多:读连接继续看主库和某个时间点之前的 WAL,写连接把新内容追加到 -wal 文件。

sql
PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA wal_autocheckpoint = 1000; PRAGMA wal_checkpoint(TRUNCATE);

启用 WAL 后,同目录通常会出现 database.db-waldatabase.db-shm-wal 保存尚未合并回主库的页面,-shm 用来协调连接之间的共享状态。checkpoint 会把 WAL 中的有效页面写回主数据库,自动 checkpoint 默认大约在 WAL 达到 1000 页时触发,也可以手动执行。

WAL 的优势主要有三点。第一,读不会轻易阻塞写,写也不会轻易阻塞读,适合读多写少但偶尔写入的 Web、小服务、桌面应用和移动应用。第二,写入通常是追加写,比频繁改主库页面更顺。第三,崩溃恢复路径清晰,已提交事务在 WAL 里,SQLite 可以据此恢复到一致状态。

但 WAL 不是“打开就永远更快”。SQLite 同一时间仍然只有一个写事务,WAL 提升的是读写并发,不是把单写者变成多写者。长时间未结束的读事务可能让 checkpoint 无法推进,-wal 文件越长越大,最后读性能和磁盘占用都会受影响。

追问

WAL 和默认回滚日志模式怎么取舍?

如果应用存在并发读取,同时又有持续写入,WAL 通常更合适。默认回滚日志模式更保守,文件形态简单,对一次性脚本、低并发工具或旧环境也够用。取舍关键不是哪个更高级,而是连接数、读事务时长和文件系统是否适配。踩坑点是把数据库放在 NFS、SMB 这类网络盘上再开 WAL,锁行为异常时很难复现。

checkpoint 是什么?为什么 WAL 文件会变大?

checkpoint 会把 WAL 里已经提交的页面合并回主数据库,然后让 WAL 可以复用或截断。WAL 变大通常不是写入异常,而是有读事务一直占着旧快照,SQLite 不能清掉它还可能需要的页面。边界在于 checkpoint 不是越频繁越好,太频繁增加 I/O,太少又会让 WAL 膨胀。实际项目可以保留自动 checkpoint,再在低峰期执行 PRAGMA wal_checkpoint(TRUNCATE);

synchronous=NORMAL 安全吗?

在 WAL 模式下,synchronous=NORMAL 是常见折中,它减少同步刷盘次数,换来更好的写入性能。代价是极端断电或系统崩溃时,最近事务的持久性保证弱于 FULL。金融记账、不可丢事件日志更适合 FULL,普通缓存、内容索引、本地配置库可以考虑 NORMAL。不要直接用 OFF 追性能,除非这些数据完全可重建。

WAL 能解决 database is locked 吗?

它能减少读写互相阻塞导致的锁等待,但不能消灭所有 database is locked。SQLite 仍然只有一个写者,如果某个写事务做大量计算、网络请求或批量更新,其他写入照样会等。正确做法是缩短事务时间,批量写入时快速完成,并设置合理的 busy timeout。常见坑是在事务里调用外部接口,锁被白白占住,WAL 也救不了。

移动端和桌面端默认都应该开 WAL 吗?

多数本地应用可以优先考虑 WAL,因为它能改善界面读取和后台写入同时发生时的卡顿。边界是数据库文件必须和 -wal-shm 一起被正确备份、迁移或上传,否则只复制主库可能拿不到最新数据。若应用需要频繁把单个 db 文件发给别人,回滚日志模式反而省心。落地时要把备份流程、关闭连接和 checkpoint 策略一起设计,而不是只改一行 PRAGMA。

标签:Sqlite