Syncthing 同步 SQLite 为何损坏?解决方案与数据恢复指南
发布时间:2026/8/27 5:40:01 作者:尧图编辑部 阅读量:1,286

我见过不少人在本地文件同步上栽跟头最典型的场景就是在一台电脑上自托管了一个使用 SQLite 的应用比如笔记系统、资料库、记账工具然后把整个数据目录放进 Syncthing让两台甚至更多设备同步这个文件夹。刚开始一切正常直到某一天应用打不开了报错是database disk image is malformed。这时候很多人第一反应是硬盘坏了或者应用本身有 bug很少有人会往 Syncthing 方向想。但这个问题的根源往往就是 Syncthing 和 SQLite 在“什么是可信文件”这件事上出现了根本分歧。Syncthing 是一个优秀的文件同步工具但它不是数据库同步工具。数据文件夹里一旦有 SQLite 文件被实时同步就要接受损坏风险。这篇文章想把这个坑讲透为什么 Syncthing 会损害 SQLite遇到这种情况怎么抢救以及真正适合你的数据库多设备方案到底是什么。1. 为什么 Syncthing 和 SQLite 放在一起会出事1.1 两者对文件的假设完全不同Syncthing 的同步方式本质上是在不同设备之间复制文件块。它会把文件划分成块计算哈希然后传输两台设备之间不同的块。这套机制的前提是文件是一个相对静态的对象你可以读取它、复制它、替换它并且文件在某一时刻是完整的。但 SQLite 对这个前提完全不认同。SQLite 是一个关系型数据库它的文件在运行时是持续变化的。每一条写入都可能修改数据库文件的多个页面并且为了保证事务的原子性SQLite 不会直接在原文件上乱写而是借助 WALWrite-Ahead Logging机制先把变更写到单独的-wal文件里然后在合适的检查点合并回主数据库文件。可以说SQLite 数据库文件不是一个“静止文件”而是一套由主文件、-wal文件、-shm文件配合组成的有状态系统。Syncthing 同步这些文件时不会知道 SQLite 的事务边界也不了解 WAL 和主文件之间的一致性关系。它只会各管各地复制也许数据库主文件被复制过去了但对应的-wal文件没有复制完或者主文件复制时文件内容正在被 SQLite 写入。最终在另一台设备上拼出来的就是一个逻辑上互相矛盾的混合状态。这就是最核心的冲突Syncthing 认为文件同步只要做到“内容最终一致”就够了但 SQLite 还需要“事务边界也一致”。这个额外的要求Syncthing 并没有能力满足。1.2 一个看似稳定的同步状态可能是损坏前的最后瞬间有很多人一开始用得很顺手觉得“我同步了一年都没问题”。这并不奇怪因为 SQLite 本身有不错的容错能力只要你没有在两个设备上并发写同一个库同步过程也没有刚好碰到写库的瞬间那么复制过去的主文件可能是完整的。但坑就藏在“刚好”两个字里。只要你的应用自动写入数据库无论是一天十次还是每小时一次总有一个同步扫描的时间窗口会撞上写入过程。Syncthing 可能先把主数据库文件复制走然后在下一个同步循环里把-wal文件复制过去。如果顺序不对对端拿到的数据库就是坏的。而且 Syncthing 默认按文件变更时间扫描并不能保证传输顺序能保护数据库一致性。另外还有一个很常见的隐藏问题如果两台设备都打开了同一个应用并且应用都会尝试写入同一个 SQLite 数据库文件那么 Syncthing 会产生冲突副本比如notes.db.sync-conflict-20250912-123456-ABCDE。这个文件可能是某一台设备上的完整数据库但它和主数据库已经分叉了。你在界面上看到的是“同步完成”实际上数据已经被悄悄拆成了多份。所以“看起来一切正常”并不能说明方案正确只能说明你还没撞到概率窗口。2. 常见症状与误判不是“同步没同步上”的问题2.1 典型的损坏报错当 SQLite 数据库文件因为同步而损坏时应用通常会有几个典型反应启动时直接报错提示database disk image is malformed。应用能打开但查询某张表时提示database or disk is full实际上磁盘并没有满。执行VACUUM失败或者备份恢复时提示file is not a database。部分页面能读部分页面读不了比如文章列表正常打开某一条详情时报错。这些报错很容易让人误以为是应用版本升级、硬盘坏道、系统权限等问题。排查半天最后才想到去看看同步目录里有没有sync-conflict文件或者去对比两台设备上的数据库文件大小和哈希。2.2 最容易被忽略的 sync-conflict 文件Syncthing 检测到同一个文件在两台设备上几乎同时被修改时并不会删掉某一方的数据而是保留其中一方为主文件把另一方重命名为sync-conflict文件。对普通文档来说这是个安全机制保留冲突副本让你自己选。但对 SQLite 数据库来说sync-conflict文件意味着两件事第一至少有一台设备上的数据库已经和另一台不一致了而且这个不一致不是“多了一条记录”那么简单可能是两张表指向了同一页日志中不同的变更记录。第二如果应用只会按固定路径打开数据库那么sync-conflict文件里的数据对你来说基本等于丢失了因为你不知道要如何处理它也不知道该拿它合并回主库。当你看到一个目录里有notes.db和notes.db.sync-conflict-2025...时基本可以断定这个目录不适合用 Syncthing 直接同步。2.3 多设备并发写入的破坏力如果只是单设备写入、另一台设备只读那么损坏的概率可能被压缩到“同步刚好撞上写入”的窗口。但如果你在两台电脑上同时打开应用并且都往同一个数据库写数据情况就会急剧恶化。SQLite 是为“本地文件锁”设计的。它通过操作系统的文件锁机制保证同一个进程内的并发安全但 Syncthing 并不参与锁协商。两台设备上的 SQLite 进程各拿各的锁在各自本地都以为自己是唯一写入者。Syncthing 会把这两台设备的变更互相传播最终结果往往不是合并而是互相覆盖或生成冲突副本。这种场景下数据库的损坏更像是一个逻辑层面的撕裂。你拿到的那份文件里可能有 A 设备的账号记录也有 B 设备写入的分类数据但这两部分引用的外键关系已经对不上了。即使文件没有损坏到打不开数据也可能出现了不可信。3. 如果已经在用 Syncthing怎么把 SQLite 同步风险降下来不想放弃 Syncthing但已经发现目录里有数据库文件有几个步骤可以立刻做。3.1 第一件事给数据库文件加上 ignore 规则在 Syncthing 的文件夹设置里添加 ignore patterns把数据库相关文件排除在同步范围之外。常见写法.db *.db *.sqlite *.sqlite3 *.db-wal *.db-shm *-wal *-shm注意如果你直接把.db忽略掉那么 Syncthing 会删除其他设备上已经同步过来的数据库文件吗这个取决于你的配置。默认情况下新加 ignore 规则后Syncthing 会把远程匹配的文件标记为“忽略”但你可能需要在两台设备上都执行“恢复默认忽略”或手动处理现有文件。建议先把现有数据库目录从同步文件夹里迁出去再添加 ignore 规则避免触发误删。注意ignore 规则只解决“以后不同步数据库文件”的问题不解决“已经被损坏的数据库怎么合并”的问题。先停止同步再做数据恢复。3.2 更好的做法先停写入再同步如果你确实需要把某台设备上的 SQLite 文件同步到另一台设备最安全的方式是先停止应用对数据库的写入等数据库完全落盘并关闭再触发 Syncthing 同步。你可以这样操作在源设备上关闭会写入数据库的应用或者先把服务停掉。等待几秒钟让 SQLite 完成 WAL 检查点数据库进入静止状态。在 Syncthing 里手动“发送”一次或者等待自动扫描。确认对端文件同步完成、哈希一致后再在目标设备上启动应用。这种方式有一个明显约束同步期间应用不能使用数据库。对个人使用场景可以接受但对一个持续运行的服务器进程来说体验很差。而且手动操作很容易忘。更好的做法是不要用 Syncthing 做实时数据库同步。3.3 用 SQLite 自身备份机制生成一致性快照SQLite 提供了内置的在线备份能力可以在不停止服务的情况下生成一个一致性快照。命令行写法是sqlite3 source.db .backup backup.db生成的backup.db是一个独立的一致性的数据库文件不会包含正在写入的中间状态。然后你可以把这个backup.db放到 Syncthing 同步目录里或者用其他方式传输。这样同步的目标文件不是“正在被使用的数据库”而是一个固定的备份快照。如果你用的是应用层语言也可以用 SQLite 官方备份 API比如 Python 的sqlite3无法直接调用备份 API但可以通过backup方法实现类似效果。或者更好一点使用sqlite3CLI 配合 cron 定时生成快照。这种方案适合“多设备只读消费”的场景。比如一台电脑负责录入其他设备只需要查看最新的数据快照那么每小时生成一次备份并同步过去是可接受的。3.4 一个更稳妥的文件夹结构如果你既要同步文档、图片等常规文件又要使用本地 SQLite 数据库建议把数据库和普通文件分到不同的 Syncthing 文件夹里。不要把它们放在同一个同步目录里避免后续误操作。推荐结构~/sync-data/ notes/ # 纯 Markdown 文件、图片交给 Syncthing app-data/ # 只有一个 .backup.db 快照交给 Syncthing db/ # 本地数据库目录不同步只做快照备份这样即使 Syncthing 偶尔异常同步最多影响备份快照不会直接伤害正在使用的数据库。4. 面向不同写入场景的方案选型Syncthing 是否适合某个数据库同步需求取决于你的写入模型。我建议先判断自己的场景属于下面哪一类再做选择。4.1 单设备写、其他设备只读可以用快照同步如果是只有一台设备会写入数据库其他设备只是读取数据那么你可以用“数据库备份 Syncthing 同步备份文件”的方式。数据库源文件不进入同步目录定时用sqlite3 .backup生成快照然后让 Syncthing 同步快照文件。读取端打开的是快照文件虽然可能不是实时数据但一致性是有保障的。这种方案的关键点是备份快照必须由数据库自身生成不能直接复制.db文件。因为你不知道复制的那一刻数据库是否处于一致状态。4.2 多设备都写同一个库Syncthing 不适配考虑整体替换如果你需要多台设备同时往同一个数据库里写入数据并且需要实时合并那么 Syncthing 几乎一定不行。原因不仅是并发锁问题更是因为 SQLite 没有内置的网络合并协议。可选方案包括改用客户端-服务器数据库比如 PostgreSQL 或 MySQL。应用通过数据库连接访问中心服务多设备并发写由数据库事务保证。如果一定要保留 SQLite 生态可以考虑使用支持多主复制的嵌入式数据库比如 CouchDB 兼容层、PouchDB CouchDB 同步或者用 rqlite 等分布式 SQLite 方案。也可以把应用架构改成“多设备各自写本地 SQLite再通过同步 API 上传到服务端进行合并”但这是应用层的工作量不是文件同步器能代劳的。在这个场景里不要试图靠 Syncthing 的 conflict 处理机制来合并数据库。冲突副本不是合并它只是把问题从“数据库损坏”变成了“数据分叉”。4.3 只是想备份数据库推荐专用备份通道而不是实时文件同步如果只是想让数据库有异地备份那么不需要使用 Syncthing 实时同步数据库文件。SQLite 的备份工具已经足够比如litestream可以把 SQLite 持续流式复制到对象存储或远程目录或者使用sqlite3 .backup加 cron再把备份文件上传到 NAS 或云盘。Syncthing 长于同步“一组可能会变化的独立文件”比如文档目录、代码仓库、照片库。它不擅长同步“需要内部一致性保证的数据库系统”。4.4 场景与方案对照场景推荐方案不推荐方案一台电脑写另一台只读查看数据库备份快照 Syncthing 同步快照直接同步正在运行的数据库文件两台电脑同时写同一个数据库更换为中心数据库如 PostgreSQL、MySQLSyncthing 同步 SQLite 文件只想做数据库异地备份litestream或定时.backup NAS 同步用 Syncthing 实时同步.db和 WAL 文件同步纯文件应用如 Markdown 笔记Syncthing 直接同步不需要数据库化存储这个表格可以当成一个简单的选型清单。遇到问题先回答我的数据库是“写入源”还是“消费端”是否有多个写入源如果都是否备份策略就能解决如果是就要换工具。5. 如果数据库已经损坏如何最大程度恢复数据如果你的数据库已经被同步文件搞坏了先别急着删掉重来按照下面的排查顺序操作能提升恢复成功率。5.1 恢复前先做镜像备份在拿到一个疑似损坏的数据库文件时第一步不是去验证能不能打开而是先把这个文件完整复制一份保留现场。你可能会需要多次尝试不同的恢复方式每次尝试都可能改变原文件。做一个只读镜像备份能让你随时回到初始状态。cp app.db app.db.before-recovery同时检查同步文件夹里有没有*.sync-conflict-*文件、*-wal文件、*-shm文件。如果有把这些文件也单独复制出来。它们可能是另一个时间点的数据版本。5.2 用 sqlite3 命令行尝试恢复SQLite 自带.recover命令可以尽量从损坏的数据库中提取数据。常见做法sqlite3 corrupted.db .recover recovered.sql然后创建一个新的数据库导入刚才提取的 SQLsqlite3 new.db recovered.sql如果.recover也不能完整工作再尝试.dumpsqlite3 corrupted.db .dump dump.sql sqlite3 new.db dump.sql.dump通常要求数据库能够通过完整完整性检查否则可能在某个表上停下来。而.recover更适合跳过损坏页面的场景它能读取出尽可能多的数据。如果你平时习惯用图形工具可以试试 DB Browser for SQLite。它有一个“Export to SQL file”功能但如果数据库已经坏到连打开都失败图形工具同样会卡住。命令行更可控尤其是处理大量数据时。5.3 恢复后如何防止二次损坏恢复完成后会得到一个相对完整的新数据库。这时候不要立刻把它放回原来的 Syncthing 同步目录里。因为原来的同步目录还包含和旧数据库相关的-wal、-shm、sync-conflict文件一旦继续同步新数据库很可能再次被污染。正确做法是把恢复出来的new.db移动到同步目录之外先用它启动应用验证核心数据是否完整。如果数据没问题再考虑是否要把这个库作为数据源重新设计同步方案。如果仍然需要跨设备访问数据按照第 3 节和第 4 节的建议改成“备份快照同步”或“中心数据库”。注意不要在用 Syncthing 同步的目录里直接对损坏数据库执行“修复前后文件替换”操作。务必先关闭 Syncthing 对文件夹的实时同步再进行任何文件级操作。6. 从这次踩坑中提炼出来的一个判断框架6.1 判断一个文件能否被通用同步器同步以后拿到任何一个应用的数据目录先别急着丢进 Syncthing。用下面三个问题过一遍这个文件是否会在运行时被持续写入这个文件在写入过程中是否存在临时辅助文件这个文件是否依赖多个文件的协作才能保持完整如果三个问题里有两个回答“是”基本可以判断它不适合通用同步器。拿 SQLite 来说主文件、-wal、-shm是被持续写入的并且依赖多文件配合所以不适合而 Markdown 文件、PDF、图片是一次性生成的内容后续即使修改也是整文件覆写适合同步。这个判断框架同样可以扩展到其他数据库或应用只要看到目录里有*.db、*.sqlite、*.ldb、*.log就要多一层警惕。6.2 三个问题决定你该不该用 Syncthing 处理数据库如果已经明确文件是数据库继续问三个问题有多少个写入方一个还是多个写入方是否会长时间持续写入还是只在特定时间段写入对端需要实时看到最新数据还是能容忍一定时间的延迟根据答案可以快速定位方案单个写入方、能容忍延迟备份快照 Syncthing 同步快照。多个写入方、无法容忍延迟放弃 Syncthing改用中心化数据库。单个写入方、无法容忍延迟直接用数据库远程接口比如 PostgreSQL/MySQL 或 SQLite over network但要注意 SQLite 的网络文件锁非常脆弱不建议走共享文件系统。6.3 最终建议Syncthing 是我非常喜欢的开源工具在很多场景下替代网盘是完全合理的。但工具都有边界。Syncthing 的边界就是“文件级最终一致”它不处理数据库事务也不理解应用数据的一致性协议。把 SQLite 直接放进去短期可能运气好长期一定会遇到问题只是时间早晚而已。如果你想省心就记住一句话数据库文件是用来被数据库管理的不是用来被文件同步器管理的。真正的多设备数据共享要么通过数据库自身能力要么通过应用层导出的标准文件格式。Syncthing 可以同步前者生成的导出文件但不能同步前者的源文件。这次踩坑的价值不只是让你以后避开一个文件夹而是让你建立起“数据文件使用方式决定同步工具选型”的判断习惯。先搞清楚数据是怎么被写入的再决定用哪种同步方式比下载一百个同步工具都有用。