《第一次把 SQLite 用对:WAL 模式、事务、fsync 与那些“突然失败”的提交》

一篇真正讲清楚 SQLite WAL 的工程实战博客


一、前言:为什么我第一次用 SQLite 就踩了 WAL 的坑?

很多人对 SQLite 的印象是:

“轻量、简单、嵌入式,随便用用就行。”

直到你:

  • 数据量开始变大
  • 有批量写入
  • 有恢复 / 幂等要求
  • 开始追求性能

然后你看到了一行“性能优化秘籍”:

PRAGMA journal_mode = WAL;

你一设,性能确实提升了
但随之而来的,是一堆“原来不报错、现在突然失败”的提交异常

database is locked
commit failed
busy

这篇文章,就是把这些问题从“为什么”到“怎么正确用”一次讲清楚


二、SQLite 默认模式在干什么?(DELETE 模式)

在不显式设置的情况下,SQLite 使用的是 DELETE 模式

一个事务在 DELETE 模式下的真实流程

BEGIN
→ 写 rollback journal(回滚日志)
→ fsync
→ 修改主数据库文件
→ fsync
→ 删除 journal
COMMIT

每一步用人话翻译:

  • rollback journal

    “后悔药”:先把要改的数据原样备份

  • fsync

    “我确认真的写进磁盘了”

  • 修改数据库文件

    真正改数据

  • 删除 journal

    事务成功,后悔药可以扔了

DELETE 模式的特点

特点 说明
安全 崩溃可回滚
简单 粗粒度锁
宽容 很多资源没关也能跑
每次都改主库

DELETE 模式像一个“老好人”
能忍就忍,能兜底就兜底。


三、WAL 模式是什么?(人话版)

WAL 全称:Write-Ahead Logging(写前日志)

一句话翻译 WAL

“写的时候不改账本,只写流水账;
账本什么时候抄,另说。”


WAL 模式下的三件套

文件 人话
.db 正式账本(稳定、只读)
.db-wal 流水账(所有新写都追加在这)
.db-shm 共享记事板(告诉别人记到哪了)

写数据时发生了什么?

  • ❌ 不改 .db
  • ✅ 只往 .db-wal追加写

读数据时发生了什么?

  • 先读 .db
  • 再读 .db-wal
  • 在内存里拼出一个“最新一致视图”

👉 读写可以并行


四、WAL 模式什么时候“合并”?读的时候会吗?

先给结论(非常重要)

读操作永远不会触发 WAL 合并(checkpoint)


WAL 合并(checkpoint)是什么?

把流水账里的内容,正式誊写回账本


SQLite 什么时候会合并?

  1. 自动 checkpoint

    • WAL 修改页数达到阈值(默认 1000 页)
    • 在写操作时触发
  2. 主动触发

    PRAGMA wal_checkpoint(TRUNCATE);
    
  3. 所有连接关闭时

  4. SQLite 内部策略(不保证)


关键认知

读 = 看账,不誊账
写 = 可能触发誊账


五、为什么一开 WAL,你的提交就失败了?

这是几乎所有第一次用 WAL 的人都会遇到的。

原因只有一句话:

WAL 模式不再帮你“兜底不规范用法”


DELETE 模式下能侥幸成功的行为

  • Reader 没及时关闭
  • Command 生命周期过长
  • 隐式事务
  • 写事务边界不清晰

DELETE 模式:

“算了,我忍了。”

WAL 模式:

“不行,这会破坏一致性,拒绝。”


六、WAL 模式下,事务到底该怎么用?

一个非常重要的结论

不是“每一行 SQL 都要事务”
而是“每一个有业务意义的写操作,必须有清晰事务边界”


推荐事务使用规则表

场景 是否需要事务
纯 SELECT
批量 INSERT
状态更新(提交 / 回滚)
多条写构成一个业务动作
隐式 UPDATE ❌(不推荐)

核心口诀

读随便读
写一定包
包就包清楚
一次事务 = 一次业务动作


七、两个常见 PRAGMA 的“人话解释”

1️⃣ PRAGMA synchronous = NORMAL

人话:

“写日志时,别每一步都硬等磁盘确认。”

含义:

  • 可能在极端断电时丢最后 1~2 个事务
  • 但数据库结构永远不会损坏

非常适合:

  • 初始化数据
  • 可重跑任务
  • 幂等场景

2️⃣ PRAGMA temp_store = MEMORY

人话:

“临时用的草稿,别写硬盘,放内存。”

适合:

  • 排序
  • 分组
  • 批量读取

八、工程级推荐配置(可直接用)

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA temp_store = MEMORY;
PRAGMA busy_timeout = 30000;

这是 SQLite 在工程场景下的“黄金组合”


九、WAL 模式下的最佳实践总结

你应该做的

  • ✔️ 所有写操作显式事务
  • ✔️ Reader / Command 生命周期极短
  • ✔️ 初始化完成后主动 checkpoint 一次
  • ✔️ 单 Connection 对应单执行流

你不该指望的

  • ❌ WAL 帮你兜底脏代码
  • ❌ 隐式事务永远安全
  • ❌ 读操作会帮你清理 WAL

十、最后一句话总结

DELETE 模式像“老好人”
WAL 模式像“严格的工程师”

它不是更难用,
而是让你第一次真正把 SQLite 用对


posted @ 2026-01-29 16:06  C余L小R鱼  阅读(0)  评论(0)    收藏  举报