PRAGMA 是你和引擎对话的方式
PRAGMA 是 SQLite 特有的语句,用来读取或修改引擎的行为。你可以像执行其他 SQL 一样执行它们,只不过它们操作的不是你的数据,而是数据库的配置。
当作查询执行时,PRAGMA 返回当前值;当作赋值执行时,它会修改这个值:
可以这样理解:大多数 PRAGMA 都是按连接生效的。打开一个新连接,默认值就又回来了。这就是为什么生产代码里通常会有一小段 PRAGMA 语句,在每次建立连接后立刻执行。
生产环境基线
如果你只记得住五条 PRAGMA,就记住这几条:
对几乎所有把 SQLite 当作主存储的应用来说,这都是一套合理的默认配置。每一条都值得单独弄懂,这一页剩下的部分会逐一介绍。
journal_mode = WAL
日志模式决定了 SQLite 如何让写入变得持久。默认的 DELETE 模式使用回滚日志:写操作会阻塞读操作,读操作也会阻塞写操作。对命令行工具来说没问题,对 Web 应用来说就很痛苦了。
WAL(预写日志)把这一点反了过来。读和写互不阻塞:在写操作提交期间,读操作看到的是一个一致的快照。同一时间仍然只能有一个写操作,但在高负载下读取依然很快。
有几点需要知道:
journal_mode是持久生效的:一旦设置,就会一直保留在这个数据库文件上。你不需要在每个连接上都设置,不过设置了也无妨。- WAL 会在你的
.db旁边创建两个额外的文件:一个-wal和一个-shm。数据库打开期间不要删除它们。 - WAL 在网络文件系统(NFS、SMB)上效果不好。请把数据库放在本地磁盘上。
另有一篇关于 WAL 模式与并发的文档讲得更深入。目前你只需要:把它打开。
synchronous = NORMAL
synchronous 控制 SQLite 把数据刷到磁盘的积极程度。这是持久性和速度之间的取舍。
FULL(默认):每次提交后都刷盘。持久性最高,速度较慢。NORMAL:在安全的检查点刷盘。配合 WAL 时是安全的,速度更快。OFF:交给操作系统决定。很快,但断电时有损坏数据库的风险。
结果中的整数(1)对应 NORMAL。在 WAL 模式下,推荐使用 NORMAL:程序崩溃时你不会丢失已提交的事务,只有在断电时才可能丢失最近的几个。对大多数应用来说,这是恰当的平衡。
除非你是在填充一个可以随时从头重建的临时数据库,否则不要用 OFF。
foreign_keys = ON
这一条经常让人栽跟头。SQLite 支持外键,但外键约束检查默认是关闭的,而且这是一个按连接生效的设置:
在 foreign_keys = ON 时,最后那条插入会失败,因为不存在 id 为 999 的作者。没有这条 PRAGMA 的话,SQLite 会若无其事地写入这条孤儿记录,几个月后你才会发现这摊烂摊子。
在每个新连接上,把 PRAGMA foreign_keys = ON; 作为第一条语句执行。大多数 ORM 会自动这么做;如果你直接用底层驱动,这件事就得你自己负责。
busy_timeout = 5000
SQLite 同一时间只允许一个写操作。默认情况下,如果第二个连接在第一个连接的事务进行到一半时尝试写入,它会收到 SQLITE_BUSY 并立即放弃。
busy_timeout 会让 SQLite 改为等待并重试:
单位是毫秒。5000 的意思是“最多等 5 秒拿到锁,然后才放弃”。配合 WAL,这能消除并发应用中大多数莫名其妙的 database is locked 错误。
如果你发现自己要把它调到 30 秒以上,真正的解决办法可能是缩短事务,而不是延长超时时间。
cache_size
cache_size 设置 SQLite 在内存中保留多少个数据库页。缓存越大,磁盘读取就越少,热点数据的查询也就越快。
这个值有两种形式:
- 正数:表示页数。默认页大小为 4 KB 时,
2000就是 8 MB。 - 负数:表示 KiB。不管页大小是多少,
-20000都是 20 MB。
负数形式更容易理解:你说的是“给我 20 MB 缓存”,而不用拿页大小去换算。对小型应用来说,20 到 50 MB 就足够了。对于数据库较大、读多写少的场景,可以设得更高。和 synchronous 一样,cache_size 也是按连接生效的。
mmap_size
内存映射 I/O 让 SQLite 直接从操作系统的页缓存中读取数据库文件的部分内容,省掉一次复制。它可以加快大型数据库的读取速度:
这是 256 MB。只要有空间,SQLite 最多会把数据库的这么多内容映射到内存中。分页由操作系统负责,所以你并不是一开始就分配了 256 MB,而只是允许它最多映射这么多。
mmap_size 在读多写少的场景下效果最好。对小数据库也没有坏处。默认值比较保守,所以调大它通常都有收益。
PRAGMA optimize
查询规划器根据统计信息来选择索引。统计信息过时,就会得到糟糕的查询计划。PRAGMA optimize 能以很小的代价更新这些统计信息:
推荐的做法是在关闭长时间存活的连接之前执行它,比如应用关闭时,或者在一个长时间持有连接的请求处理结束时。它很快(通常只要几毫秒),而且只在确实有东西需要更新时才会干活。
它和 ANALYZE 不一样,后者会完整重建统计信息。optimize 是它的轻量版亲戚,可以经常执行。
查看所有设置
如果你想知道一个连接当前的配置,就不带赋值地查询这些 PRAGMA:
调试时很有用:从另一个驱动连接后发现行为变了,原因几乎总是 PRAGMA 不同。
还有一条 PRAGMA pragma_list;,可以列出当前构建支持的所有 PRAGMA:
PRAGMA pragma_list;
不需要背下来,但需要时很方便。
应该在建库时设置、而不是运行时设置的选项
有几条 PRAGMA 配置的是数据库文件本身,只有在创建任何表之前设置才会生效:
PRAGMA page_size = 8192;:磁盘上的页大小。默认是 4096,对大多数场景都合适。更大的页对较大的行有帮助。PRAGMA encoding = 'UTF-8';:文本编码。
PRAGMA page_size = 8192;
PRAGMA encoding = 'UTF-8';
CREATE TABLE ...
如果你在已有的数据库上修改 page_size,需要执行 VACUUM 才会生效。在建库时设置一次,然后就可以忘掉它们了。
一段真实的连接初始化代码
在应用代码里,这段代码通常放在负责打开连接的地方。概念上是这样的:
-- Run once on every new connection:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA cache_size = -20000;
PRAGMA temp_store = MEMORY;
-- Run periodically, or before close:
PRAGMA optimize;
temp_store = MEMORY 会把临时表和临时索引放在内存中,能加快那些需要在没有索引的情况下排序或聚合的查询。
这就是完整的生产环境检查清单。短短六行,SQLite 就从“开发时够用”变成了“能扛住真实负载”。
下一篇:常见错误
即使 PRAGMA 设置得当,你还是会碰到 SQLite 那几个常见的错误:database is locked、disk I/O error、constraint failed。下一页会逐一讲解它们的真正含义以及如何修复。
常见问题
SQLite 中的 PRAGMA 语句是什么?
PRAGMA 是 SQLite 特有的命令,用来读取或修改数据库引擎的行为。你可以像执行 SQL 一样执行它们:PRAGMA journal_mode = WAL; 切换日志模式,PRAGMA foreign_keys; 读取当前值。大多数 PRAGMA 只对当前连接生效,所以通常在打开数据库后立刻执行。
生产环境中应该用哪些 PRAGMA 设置?
对大多数应用来说,一个稳妥的基线是:journal_mode = WAL、synchronous = NORMAL、foreign_keys = ON、busy_timeout = 5000,再加上一个充足的 cache_size。在关闭长时间存活的连接之前执行 PRAGMA optimize。这些设置能让你轻松获得并发读取、持久的写入和参照完整性。
为什么 PRAGMA foreign_keys 默认是关闭的?
为了向后兼容。SQLite 在 3.6.19 版本加入了外键约束检查,并默认保持关闭,以免旧数据库突然开始拒绝写入。你必须在每个新连接上用 PRAGMA foreign_keys = ON; 打开它:这不是数据库级别的设置,而是连接级别的。
PRAGMA optimize 有什么作用?
PRAGMA optimize 执行轻量的维护工作,主要是更新查询规划器选择索引时使用的统计信息。它开销很小,定期执行也很安全。推荐的做法是在关闭长时间存活的连接之前调用它,这样下次应用启动时,规划器就有新的统计信息可用。