SQLite 适合在 VPS 上做生产数据库吗?
了解 SQLite 在单台 VPS 上用于生产的边界,配置 WAL 和 busy_timeout,使用 Litestream 复制,并识别何时单写入者和单机限制会迫使您迁移。
在 VPS 上何时应将 SQLite 用作生产数据库
对于大多数小型应用,在 VPS 上的生产环境中运行 SQLite 是合适的选择。原因很简单:单台机器上的一个进程向一个文件写入数据,不需要数据库服务器。无需监控 daemon,无需在防火墙中开放端口,无需轮换密码,也无需维持第二台机器运行。查询是函数调用,而不是网络往返,因此一个页面执行 40 次查询时,成本就是 40 次函数调用。
它的限制明确而且确实存在。整个数据库文件同一时间只能有一个写入者,而且该文件不能在两台机器之间共享。对于运行单个应用的单台 VPS,这两个限制都没有问题。一旦超出这种部署形态,这两个限制就会立即成为阻碍。本指南介绍如何配置 SQLite 以确保其在服务器上安全运行、如何使用 Litestream 持续备份,以及何时应停止继续使用 SQLite。
首先安装命令行工具。以下操作均在 Ubuntu 24.04 上执行。
sudo apt update
sudo apt install -y sqlite3
sqlite3 --version该命令会输出一个以 3. 开头的版本号,后面跟着构建日期和源代码哈希。以 2026 年 7 月为准,Ubuntu 24.04 发布的 SQLite 版本为 3.45.1。您的应用可能不会使用这个二进制文件:大多数语言运行时会自带 SQLite 库副本,而且通常版本更新。因此,在依赖较新的功能之前,请先检查数据库驱动报告的版本。
为什么首先要修改 WAL 模式
SQLite 默认使用回滚日志。修改页面前,它会将原始页面复制到 -journal 文件中,然后直接修改数据库。为确保操作安全,SQLite 会锁定整个文件并获取排他锁。因此,只要有写操作正在进行,所有读取操作都必须等待。在笔记本电脑上通常不会察觉这一点。在 Web 服务器上,一次缓慢的写操作就可能阻塞所有访问该数据库的请求。
WAL(预写日志)模式会反转这一顺序。写入者将新页面追加到单独的 -wal 文件中,而不修改主数据库。读取者继续从启动时对应的快照读取主文件,因此读取者不会阻塞写入者,写入者也不会阻塞读取者。之后,检查点操作会将 WAL 中累积的页面复制回主数据库。正是这一项修改,让 SQLite 基本可以用于 Web 应用后端。
启用 WAL 模式并确认设置已生效
mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"该命令会输出 wal。这不是无关紧要的信息。PRAGMA journal_mode 会返回数据库实际使用的模式,因此如果返回 delete,说明更改失败,数据库仍在使用回滚日志。
WAL 模式具有持久性。它是数据库头中的标志,而不是连接设置。因此,每个数据库文件只需设置一次,之后所有连接都会继承该设置,包括重启后建立的连接。通过新连接验证这一点。
sqlite3 ~/app/app.db "PRAGMA journal_mode;"现在创建一个表,然后查看磁盘上出现了什么。
sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/现在共有 3 个文件:app.db、app.db-wal 和 app.db-shm。-wal 文件保存尚未完成检查点的已提交页面。-shm 文件是共享内存索引,所有连接都会映射它,以便对 WAL 中的内容保持一致。这两个文件都属于数据库,不是临时文件。应用运行期间单独复制 app.db,得到的文件会缺少所有最近的提交。删除 app.db 而保留另外两个文件,SQLite 就会将这些过时的 WAL 页面应用到之后以该名称创建的新文件中。尝试重置数据库时,这会导致新数据库损坏。
生产环境中的每个应用都需要的连接设置
只有 journal_mode 会存储在数据库中。下面的其他设置都针对单个连接。这意味着应用必须在每个打开的连接上执行这些设置,包括连接池在后台创建的每个连接。
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;busy_timeout = 5000 告诉 SQLite:数据库被锁定时,最多重试 5000 毫秒,然后返回 database is locked。默认值为 0,因此默认情况下,SQLite 在两个写入者首次发生重叠时会立即失败。设置这一个值,就能消除大多数被归咎于 SQLite 本身的锁错误。
synchronous = NORMAL 是 WAL 模式下的正确设置,但需要了解其中的取舍。在 FULL 下,SQLite 会在每次提交时对 WAL 调用 fsync。在 NORMAL 下,它会改为在检查点执行同步。SQLite 文档明确说明了需要放弃什么:发生断电或硬重置后,事务不再具有持久性。数据库不会因断电而损坏,但尚未写入磁盘的最后几次提交会丢失。对于 VPS,这通常是正确的取舍,因为这样可以避免每次写入都执行一次 fsync。
foreign_keys = ON 默认关闭,以保持向后兼容,并且它针对单个连接。包含大量 REFERENCES 子句的架构,在每个连接启用此设置之前,完全不会强制执行这些约束。
还有一个设置只会在后续产生影响。WAL 增长超过 1000 个页面后,SQLite 会自动执行检查点;具体由当时恰好完成事务的连接执行。这本身没有问题。运行 Litestream 时,情况就需要考虑,因为 Litestream 需要控制检查点的执行时机。
设置 busy_timeout 后为什么仍然会发生 database is locked
这是导致人们重新使用 Postgres 的故障,而且原因很明确。
busy timeout 会安装 busy handler,但 SQLite 不保证一定会调用它。
如果 SQLite 判断调用 busy handler 可能导致死锁,就会直接向应用返回 SQLITE_BUSY,而不会调用 busy handler。
它要避免的死锁发生在事务升级时。在 SQLite 中,单独使用 BEGIN 表示 BEGIN DEFERRED。如果它之后的第一条语句是 SELECT,则当前处于读事务中。当同一事务中后续的 UPDATE 需要变为写事务,而另一个连接自读取开始后已经执行过写操作时,SQLite 无法让当前连接等待。因为当前快照已经过期,继续等待只会导致两个连接相互死锁。文档直接说明了结果:
后续的写语句会在可能的情况下将事务升级为写事务,否则返回 SQLITE_BUSY。
您的 5000 毫秒超时设置根本不会生效。错误会立即返回,因此看起来就像设置没有任何作用。
修复方法只有一个词。
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;BEGIN IMMEDIATE 会在事务开始时立即获取写锁,然后才读取数据。不存在事务升级,也就没有需要避免的死锁,因此 busy handler 会生效,连接会等待轮到自己,而不是直接失败。只读事务应继续使用 deferred 模式。包含写操作的事务都应使用 immediate 模式。
锁错误的第二个原因更难发现:写事务跨越耗时操作而一直保持打开状态。SQLite 会串行执行写入,因此,如果一个事务打开后通过网络调用外部 API,最后才提交,那么该调用持续期间,其他所有写入都会被阻塞。先读取所需数据,关闭事务,执行耗时操作,然后重新打开一个简短的写事务保存结果。
使用 Litestream 持续备份
每晚复制一次最多会丢失一天的写入记录,而对正在使用的 SQLite 数据库运行 cp,可能生成无法打开的副本。以下两种方式是安全的。sqlite3 app.db ".backup /path/to/backup.db" 使用 SQLite 的在线备份接口,可以处理正在使用的数据库。Litestream 更进一步:它持续监控 WAL,并将变更发送到对象存储,从而将最坏情况下的数据丢失时间从一天缩短到约 1 秒。
Litestream 是一个与应用并行运行的 Go 二进制程序。它不位于应用与数据库之间。应用仍按原方式写入 SQLite,Litestream 读取 WAL 并上传发生变化的内容。
cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream version截至 July 2026,官方 Linux 安装页面记录的版本是 v0.5.14,v0.5.15 于 21 July 2026 发布。将两行中的版本改为 releases 页面上的当前 tag。如果 VPS 使用 arm64,则改用匹配的 arm64 软件包。
配置文件位于 /etc/litestream.yml。先配置本地文件副本,因为这样无需云凭据即可验证整个流程。
dbs:
- path: /home/appuser/app/app.db
replica:
type: file
path: /var/backups/litestream/app注意,该字段是单数形式的 replica。Litestream 0.5 将 0.3 系列中的 replicas 数组替换为单个副本块,包含两个条目的配置现在会在启动时失败。许多第三方指南仍显示旧数组,因此应按照上面的结构配置,不要直接采用搜索结果中的第一个示例。0.5 系列还将 litestream wal 子命令重命名为 litestream ltx,因为磁盘上的备份格式发生了变化。
启用任何功能前,先检查配置是否能正确解析。
sudo litestream databases -config /etc/litestream.yml然后手动验证整个往返流程。此形式跳过配置文件,将一个数据库复制到一个路径。
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app该命令会在前台运行并持续执行。在另一个 shell 中写入一行数据,再将副本恢复到新文件。
sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"计数中应包含新写入的行。如果不包含,说明变更尚未同步:Litestream 按 sync-interval 推送,默认间隔为 1 秒,因此请等待后再次恢复。这个 1 秒也是恢复点间隔。发生崩溃时,最多会丢失上一个同步间隔内的写入记录,任何配置都无法将其降为 0。
用于生产环境存储时,将副本块替换为 S3 URL。该配置适用于 Amazon S3,也适用于其他提供商提供的 S3 兼容对象存储。
dbs:
- path: /home/appuser/app/app.db
replica:
url: s3://your-bucket-name/app
region: us-east-1
snapshot:
interval: 24h
retention: 24h不要将凭据写入该文件。Litestream 从环境变量中读取 LITESTREAM_ACCESS_KEY_ID 和 LITESTREAM_SECRET_ACCESS_KEY,因此应将它们放入由 root 所有、权限为 600 的 systemd drop-in 文件中。
上面的快照参数使用默认值,但保留期限的默认值经常令人意外。保留期限决定 Litestream 保留快照及其相关文件的时间,也决定可以恢复到多早之前。24 小时意味着,如果你在 Wednesday morning 发现迁移错误,那么 Monday 的状态已经无法从中恢复。将 retention: 168h 设置为一周,并承担额外的存储成本。
在需要恢复前先验证恢复流程
litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"给定数据库路径,litestream restore 会在 /etc/litestream.yml 中查找匹配的副本并将其下载。对于正常的文件,PRAGMA integrity_check 会输出 ok;任何其他输出都表示恢复的副本不可用。使用systemd 服务和计时器按计划运行此检查,并读取输出。在至少成功恢复过一次备份之前,您无法确认备份可用。
在 systemd 下运行 Litestream
Debian 软件包会安装一个读取 /etc/litestream.yml 的 litestream 单元。
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f正常输出会列出配置中的每个数据库,之后除定期同步行外保持安静。针对数据库路径出现 no such file or directory 错误,表示配置中的路径不正确,或者进程无法读取该路径。该单元默认以 root 运行,权限高于此任务所需权限。Litestream 必须能够读写数据库及其所在目录,因为它会处理数据库旁边的 -wal 和 -shm 文件。因此,应将其配置为使用应用程序已有的账户。
# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuser使用 sudo systemctl daemon-reload 和 sudo systemctl restart litestream 应用配置。设置权限最小化的专用服务账户只需几分钟,但这决定了它是备份代理,还是服务器上的第二个 root 进程。
如果要从零重建机器,有一个启动顺序细节很重要。应用程序启动前,必须先恢复数据库。litestream restore 接受 -if-db-not-exists;如果文件已经存在,该命令会以退出码 0 结束,因此可以在每次启动时安全运行。将它放在应用程序单元的 ExecStartPre 行中。这样,新的 VPS 会下载数据库,已有数据库的 VPS 则不会执行任何操作。litestream replicate 提供了对应的 -restore-if-db-not-exists 标志;如果希望集中配置,也可以使用该标志。
VPS 上 SQLite 不适用的场景
网络文件系统。 这是无法通过配置规避的限制。WAL 模式要求使用数据库的每个进程共享一小段内存区域,-shm 文件正是用于提供该区域。SQLite 文档明确规定:
使用数据库的所有进程必须位于同一台主机上;WAL 不能通过网络文件系统工作。
因此,位于已挂载 NFS(网络文件系统)或 SMB 共享上的数据库可能发生损坏,任何 pragma 都无法阻止这一点。这里有一个容易被忽略的区别。网络块设备是大多数 VPS 提供商附加存储所使用的形式。它在 Linux 中表现为带有普通文件系统的普通磁盘,这没有问题。已挂载的文件共享则不同。
第二台应用服务器。 没有任何设置可以让这种方式正常工作。一旦需要两台机器提供同一份数据,就需要使用支持网络通信的数据库。应在仍有时间规划时决定是否进行迁移。
写入密集型负载。 同一时间只能有一个写入者,这是文件格式的特性,无法通过参数调节。短写入的成本较低,因为每次提交都是追加到 WAL,因此吞吐量更接近磁盘的小写入延迟,而不是 CPU 性能。有关这种差异的具体表现,请参阅 VPS 上的 NVMe 与 SATA SSD 存储对比。真正的问题是长事务,因为它们会让其他所有写入者排队等待。
分析查询。 SQLite 是为事务处理设计的行存储数据库。仪表板扫描一亿行数据属于另一类任务,应使用其他工具;服务器场景下 DuckDB 与 SQLite 的对比介绍了两者的适用边界。
复制场景下的 VACUUM。 完整的 VACUUM 会重写整个数据库文件,因此 Litestream 必须重新上传全部文件。Litestream 文档建议在复制处于活动状态时不要直接执行该操作。请先停止复制器,再执行 vacuum,然后重新启动复制器,并预期会生成新的完整快照。
同一数据库运行两个复制器。 不要让两个 Litestream 进程同时处理同一个数据库或使用同一个副本目标。文档明确说明,防止这种情况是您的责任;否则生成的副本将无法恢复。
Litestream 不负责的内容
Litestream 只保护数据库文件,不负责其他内容。上传的文件、应用配置、TLS(传输层安全)证书和 unit 文件仍需您自行处理。按计划将其与使用 restic 加密到异机的备份结合使用,即可覆盖这两部分内容。如果服务器是新建的,请参阅新 VPS 上的前 10 分钟,其中介绍了本指南默认已经完成的用户账户和防火墙配置。
FAQ
生产环境中的应用使用 SQLite 足够吗?
对于一台服务器上的单个应用,足够,前提是启用 WAL 模式、设置 busy timeout,并持续执行备份。需要关注的限制是结构性的:同一时间只能有一个写入者,并且只能使用一台主机。符合这些限制的应用可以使用无需网络跳转、无需单独进程监控的数据库。不符合这些限制的应用需要使用客户端-服务器数据库,任何调优都无法改变这一点。
设置 busy_timeout 后,为什么仍然会收到 database is locked?
因为等待可能导致死锁时,SQLite 会跳过 busy handler。以裸 BEGIN 开始的事务是延迟事务:开始执行的 SELECT 会使其进入读事务,后续写入则必须执行事务升级。如果期间另一个连接完成了写入,SQLite 会立即返回 SQLITE_BUSY,而不是调用 busy handler,因为当前读快照已经过期。所有会执行写入的事务都应使用 BEGIN IMMEDIATE 开始,这样可以预先获取写锁,超时设置才能生效。
可以将 SQLite 数据库放在网络存储上吗?
不能放在 NFS 或 SMB 等网络文件系统上。WAL 模式需要所有进程通过 -shm 文件共享内存,SQLite 文档规定,使用该数据库的每个进程都必须位于同一台主机上。云服务商提供的网络块设备则不同:Linux 会将其视为带有普通文件系统的普通磁盘,SQLite 可以在其上正常工作。
如果已经每天晚上执行备份,还需要 Litestream 吗?
这取决于您最多能承受丢失多少数据。每天执行一次备份意味着最多可能丢失 24 小时的写入数据。Litestream 大约每秒同步一次,因此崩溃时大约只会丢失最后 1 秒的数据。使用 cp 直接复制数据库文件可能在数据库写入过程中复制到不完整状态,因此 Litestream 也更安全。Litestream 只负责数据库,因此仍应同时运行通用文件备份。