SQLite 常被误认为不适合高并发场景。实际上,通过合理的架构和配置(特别是 WAL 模式),SQLite 在大多数 Web 应用的中低并发场景下(读多写少)表现优异。本文将解析 WAL 模式的工作原理及高并发架构的注意事项。
默认的 DELETE 日志模式(回滚日志)在写入前会创建日志文件,事务提交时释放锁并删除日志。这种模式在高并发写入时锁竞争激烈。
WAL 模式的核心变化:
写操作:不直接修改原数据库文件,而是将变更追加到 .wal 文件中。数据库原文件保持不变。
读操作:读取数据时,会合并读取原数据库文件 + .wal 文件中的最新记录。
Checkpoint:当 .wal 文件增长到一定阈值或满足特定条件时,执行 Checkpoint 操作,将 .wal 中的变更合并回原数据库文件。
在 WAL 模式下:
写操作:可以并发。在 WAL 模式下,通常只能有一个写操作(SQLite 本身是库级锁),但多个写操作可以同时开始,它们会按序提交。实际上,WAL 模式下写操作的锁机制比 DELETE 模式更精细。
读写关系:读操作不阻塞写操作,写操作也不阻塞读操作。这是 WAL 模式最大的优势。读操作读取的是数据库在“开始读”那个时间点的快照,不受后续写操作的干扰。
尽管 WAL 模式解决了读写互斥的问题,但 SQLite 仍然是进程/线程安全但写入串行化的数据库。
连接池策略:不要使用连接池。SQLite 基于文件锁,多线程同时使用一个连接容易出问题。推荐使用“单写多读”模式:
维护一个单一的写入连接(或者使用队列进行写入)。
每个线程维护自己的只读连接。
busy_timeout:当写入遇到锁时,设置 PRAGMA busy_timeout = 3000;(等待3秒)。这比立即返回 SQLITE_BUSY 错误更能提高写入成功率。
PRAGMA wal_autocheckpoint:控制自动 Checkpoint 的频率。默认是 1000 页。如果写操作非常频繁,适当减小该值(如 500)可以防止 .wal 文件过大导致读取时需要合并太多数据,从而提升读性能。
绝对不要将 SQLite 数据库文件放在 NFS(Network File System,网络文件系统)或 SMB(Server Message Block,服务器消息块)共享存储上。SQLite 依赖于底层文件系统的 fcntl 锁来实现并发控制,网络文件系统的锁机制通常有缺陷或性能极差,极易导致数据库损坏。SQLite 适合本地磁盘或容器持久化卷(如 Docker volumes)存储。