
把一个 SQLite 数据库文件原样丢进 Syncthing 同步目录理论上听起来很方便多台设备都有一份数据库数据不就用上了吗实际做过的人基本都会在一段时间后收到一个database disk image is malformed或者“同步出一堆 conflict 副本”的惊喜。这就是社区里常说的 Syncthing and SQLite Gotcha。Syncthing 负责在设备之间同步文件内容SQLite 负责在小文件里帮你做事务、索引、崩溃恢复。两个工具本身都没有问题问题出在它们对“数据一致”的理解完全不同。Syncthing 看到的是文件块SQLite 维护的是事务日志和锁。把 SQLite 的活动数据库当成普通文档搬运到另一台设备继续写等于在没有任何分布式共识机制的情况下搞多副本损坏只是时间问题。本文会从底层的文件结构讲起说明为什么复制单个app.db文件并不能得到一份一致的数据库然后给出一套可复现的验证流程用来确认损坏路径再整理损坏后的恢复顺序和命令最后给出几套替换方案包括.stignore忽略规则、定时一致性备份、以及单写者加 Litestream 之类的演进路线。适合读者用 Syncthing 同步笔记或网盘目录的 NAS 用户桌面端或移动端以 SQLite 做本地存储的开发者以及在 TrueNAS、QNAP 上部署过 Syncthing、准备“顺手”同步数据库文件的朋友。1. 核心能力速览先把这篇文章涉及的两个组件和它们“组合之后”的能力边界放一张表里。后面所有分析都围绕这张表展开。维度SyncthingSQLite组合使用时的 Gotcha项目类型开源文件同步工具嵌入式关系型数据库文件同步与数据库一致性的冲突主要功能跨设备增量同步、版本控制、REST API事务、索引、SQL 查询、崩溃恢复直接同步活动数据库文件属于高风险操作平台支持Windows / macOS / Linux / Android / BSD常见 NAS 可运行几乎所有操作系统取决于客户端平台启动方式常驻服务Web 管理界面或命令行嵌入到应用中或命令行sqlite3无资源占用轻量级但取决于文件数量与版本控制策略极小可忽略无API提供 REST API可管理设备与文件夹提供编程接口支持多种语言绑定无批量任务支持文件夹批量同步支持批量 SQL 操作批量复制数据库文件属于危险操作适合场景文档、媒体、代码目录多设备同步本地应用数据存储只建议单写者加一致性导出备份不建议多设备写同一个数据库关于 Syncthing 的硬件门槛它本身是轻量级常驻服务常见 NAS、树莓派级别设备都能跑。但具体内存和磁盘占用取决于同步目录文件数量和版本控制策略不要用“很省资源”作为唯一判断标准。Web 管理界面默认端口常见为 8384本地观察时注意端口是否被占用实际端口以你的配置文件为准。关于 SQLite 的能力边界它在单机本地文件系统上表现优秀官方文档也不建议把数据库文件直接放在 NFS、SMB 这类网络文件系统上运行。Syncthing 同步目录本质上会把数据库文件变得“不再本地”这已经踩进了 SQLite 的边界之外。多设备同时写同一个 SQLite 文件时SQLite 自带的文件锁机制并不能跨设备生效这就是后续所有问题的根因。2. 适用场景与使用边界2.1 谁适合继续用这套组合只想做“单向备份”的开发者本地 SQLite 数据库通过 Syncthing 同步到另一台设备作为冷备且原数据库所在设备是唯一写入者。此时只要保证同步期间应用处于停止或 checkpoint 后的状态仍然可以接受。以文档、图片、视频等非数据库文件为主的同步用户Syncthing 对这类内容完全适用只需要把数据库文件排除在外。应用不支持服务器数据库、又需要多设备只读副本的场景可以接受“同步完成后必须断开写入”这种弱一致性。2.2 谁不适合需要在多台设备上同时打开同一个 SQLite 数据库并写入的团队或开发者。这不是 Syncthing 的 bug而是文件同步工具无法等价于数据库复制协议。对数据零丢失要求较高的生产业务。即使使用忽略规则也只能避免问题不能解决多写者需求。移动端 App 自动备份数据目录的玩家用户。Android 和 iOS 的应用沙箱数据库往往包含未 checkpoint 的 WAL 内容直接同步非常容易得到不可用文件。2.3 使用边界与合规提醒任何同步方案都会涉及数据拷贝。这里的“数据拷贝”不仅仅是文件复制还可能包含用户隐私、业务数据、版权素材。Syncthing 默认使用 TLS 加密传输并要求设备之间通过证书完成身份认证在同类工具里属于基础配置偏严格。但加密传输不等于授权合规数据到达另一端之后的存储、备份、再分发仍然需要你自己控制。如果你是开发者在设计同步功能前要确认数据授权范围如果你用 Syncthing 同步办公文档或代码也要保证设备和链路的访问控制。涉及人脸、声音、隐私字段、受版权保护的素材时务必确认你有权复制和存储这些数据再决定是否进入同步链路。3. 理解 SQLite 的持久化机制3.1 SQLite 不是一个文件很多人以为 SQLite 数据库就是单个.db文件实际上它可能是一组文件。最常见的是主数据库文件app.db但在不同的日志模式下还会出现陪伴文件回滚日志模式写事务开始前会生成app.db-journal文件用于崩溃回滚。WAL 模式数据先写入app.db-wal同时生成app.db-shm作为 WAL 索引的共享内存映射文件。主数据库文件内部由数据库头、B-tree 页面、页面结构组成页面大小通常是 4KB 或更大。数据库头里包含 page size、change counter、schema version 等字段这些字段必须和文件内容保持一致。如果你只复制了部分页面或者复制时主文件正处于写入中间状态文件头和数据页就会错位。3.2 事务、锁与崩溃恢复SQLite 使用操作系统文件锁来实现并发控制。同一台机器上的多个进程可以通过文件锁协调读写读锁可以共享写锁互斥。但锁信息只存在于本机操作系统Syncthing 把文件内容推送到另一台设备时对端进程并不感知“这个文件刚刚被替换”。原子性方面SQLite 通过日志重放或回滚来保证崩溃恢复。回滚日志模式会在修改前写旧页面WAL 模式会在提交后把新页面追加到 WAL。重新打开数据库时SQLite 会根据日志内容决定是重放还是回滚。关键点是日志文件必须和主库文件配套日志里的页面变更必须按顺序应用到同一个主库文件上。3.3 一句话结论SQLite 的一致性等于主库文件加日志文件加操作系统的锁三者必须在一个本地文件系统上同时生效。缺少任何一个SQLite 都无法按预期保证一致性。Syncthing 的同步目录无法提供这个前提所以用它同步活动数据库就是踩入 gotcha。4. 为什么用 Syncthing 同步 SQLite 会翻车4.1 文件级同步不等于数据库复制Syncthing 检测到文件块变化后会把变化块增量传输给对端。它并不理解“这一批变化是整个事务的一部分”。当应用在同一时刻写入了 5 个页面Syncthing 可能只同步了其中 2 个对端就临时存储了一个半新半旧的主文件。如果这个半成品被另一个进程加载SQLite 会把它判定为损坏。4.2 WAL 文件与主库文件存在时间差WAL 模式下提交的数据先进入app.db-wal主库文件的 change counter 可能很久都不变。Syncthing 同步时可能只同步了 WAL 文件而主库文件还是旧版本或者反过来。目标设备上的 SQLite 拿到一对不一致的主库和 WAL 时只能报错或选择回滚回滚意味着丢失刚刚在其他设备提交的事务。4.3-shm文件不能当作数据复制app.db-shm是 WAL 索引的共享内存映射文件由进程间共享内存机制管理它应该在每台设备上由各自 SQLite 进程重新创建。如果 Syncthing 把这个文件也从一台设备复制到另一台反而可能导致对端 SQLite 读到错误索引位置。最稳妥的做法是把它排除掉不要同步。4.4 多设备并发写的锁协调问题SQLite 的文件锁基于本机操作系统。设备 A 和设备 B 各自认为自己拿到了写锁两个进程在不同机器上写同一个文件最后落盘结果取决于 Syncthing 最后一次合并覆盖了谁。轻则丢更新重则产生混合页面连完整性检查都过不了。4.5 “看起来同步成功了”的假象