
如果你在 NAS 和电脑之间用 Syncthing 同步文件可能已经遇到过这样的场景运行了一晚上早上打开 Web 管理界面发现某个文件夹还停在 scanning磁盘占用比想象中大得多日志里反复出现sqlite: database is locked更严重一点直接提示database disk image is malformed。这些现象拆开看很容易被误判为“Syncthing 不稳定”或者“NAS 磁盘要坏了”。但把场景拉远一点会发现一个共同线索这些设备的同步目录里出现了 Syncthing 自己生成的index-v0.14.0.db、*.db-wal、*.db-shm这类文件。换句话说Syncthing 正在把自己的 SQLite 索引数据库当成普通文件同步给了另一台设备。这不是一个偶发 Bug而是文件级实时同步与事务型数据库之间的天然矛盾。这篇文章要讲的就是 Syncthing 和 SQLite 组合出的这个 Gotcha。这里先给出一个明确结论Syncthing 的index-*.db文件永远不应该出现在任何同步文件夹里。如果你已经在同步目录中看到了它那么你要处理的不只是删文件而是先搞清楚它是怎么进去的否则删了它还会再生。1. 这篇文章真正要解决的问题Syncthing 在多设备文件同步领域口碑很好因为它开源、无中心服务器、传输加密而且天然适合内网直连。很多人把它装在威联通、TrueNAS 上一边同步办公文档一边同步照片和素材库。安装过程确实简单但真正用起来之后不少用户会被同一个问题卡住Web 管理界面一直显示scanning并且没有停止的迹象同步目录里出现很多带db后缀、体积不小的陌生文件日志里出现database disk image is malformed或sqlite: database is locked另一台设备上多出了index-v0.14.0.db的副本而且还在不断更新在 TrueNAS SCALE 上安装应用时甚至可能遇到类似failed up action for syncthing app的启动失败。这些问题表面看各不相同根因却常常指向同一个Syncthing 自己的 SQLite 数据库被放置在了一个由 Syncthing 管理的同步文件夹里。甚至有用户为了“备份 Syncthing 配置”直接把整个配置目录放进了同步盘结果引发数据库文件在多个设备之间反复覆盖最终损坏。如果你属于下面几类读者这篇文章值得收藏在 NAS、软路由或 Docker 上部署过 Syncthing 的用户想在多台设备间统一同步文件但不想踩“索引数据库被同步”坑的开发者对 Syncthing 内部机制好奇想知道它为什么用 SQLite、以及 SQLite 文件为什么不能乱同步的人。这篇文章会把机制讲清楚再给出可复制的配置和恢复步骤。整个过程不涉及任何高风险操作但删除或移动数据库文件之前请一定先确认你已经理解了其中的原理。2. 从文件同步到索引数据库两个基础概念2.1 Syncthing 是如何工作的Syncthing 并不是简简单单地把文件从一个目录复制到另一个目录。它是一个基于对等节点P2P的文件同步协议实现核心工作流程大致如下扫描本地文件夹读取文件列表、大小、修改时间和内容哈希将这些信息写入本地索引数据库与远端设备交换索引信息根据索引差异按需传输文件内容的数据块传输完成后再次更新本地索引数据库。因为有了索引数据库Syncthing 才能在文件数量很大的文件夹里快速判断哪些文件变了、哪些块需要传输。这个索引数据库默认使用 SQLite 存储在当前版本中文件名通常是index-v0.14.0.db位于 Syncthing 的数据目录下。从设计上看这个数据库更像“运行时状态”而不是“用户数据”。它服务于 Syncthing 自身的运行和普通业务数据库的地位完全不同。2.2 SQLite 是什么SQLite 是嵌入式关系型数据库没有独立服务进程数据直接保存在一个普通文件中。最常见的表现就是一个.db文件。对开发者来说SQLite 是一个很亲切的存在它支持标准 SQL、支持事务、支持增删改查也支持表结构升级时新增字段或新增表。它的字段类型非常灵活TEXT、INTEGER、REAL、BLOB都可以用其中BLOB经常用来存 GUID、二进制内容或序列化对象。日常排查 SQLite 文件常用手段包括用命令行工具sqlite3查询和验证用 DB Browser for SQLite 图形化打开.db文件在 VS Code 中安装 SQLite 插件直接浏览表结构。SQLite 最大的优点是零配置、单文件、移动方便。但这一点也带来了使用边界单文件数据库并不适合被文件级别的实时同步工具反复复制。2.3 为什么 Syncthing 选择 SQLiteSyncthing 选择 SQLite 是很务实的决定。索引数据需要支持频繁读写SQLite 可以做到崩溃恢复而且不需要额外维护一个数据库服务。相比直接在文件系统里保存 JSON 或纯文本索引SQLite 在查询效率、并发控制、数据完整性上都更可靠。问题不在 SQLite 本身而在于很多用户把这个数据库文件当成了“普通文件”放进了一个被 Syncthing 自己监控的目录里。这个组合才是真正的 Gotcha。3. 三个典型的踩坑场景3.1 场景一把整个 Syncthing 数据目录放进同步文件夹这是最常见的翻车方式。很多用户想备份 Syncthing 的配置和索引于是在 NAS 上新建了一个共享文件夹比如sync-backup然后把 Syncthing 的配置目录整个复制进去并把它纳入了某台设备的同步范围。表面上看这实现了“配置多设备备份”。但实际结果是Syncthing 运行时不断修改自己的index-v0.14.0.db这个文件被同步到另一台设备另一台设备上的 Syncthing 也在修改同一个数据库文件再把新版本同步回来。两个节点基于完全不同的运行状态写入同一个数据库最终结果就是数据库文件被拼成一种从未真实存在过的状态。3.2 场景二在 NAS 上把容器配置目录挂载到共享目录在 Docker、威联通 Container Station 或 TrueNAS SCALE 上安装 Syncthing 时需要把容器内部的 Syncthing 数据目录映射到宿主机目录。有些人为了方便直接把配置目录映射到了一个被 Syncthing 同步的共享目录中。这样做的后果是Syncthing 一边运行一边扫描自己的数据库目录发现index-v0.14.0.db发生了变化然后又把这个变化同步出去。更麻烦的是共享目录往往又通过 SMB/NFS 挂载给其他设备形成多重文件系统叠加数据库锁和文件句柄在这种环境下会更加脆弱。3.3 场景三用 Syncthing 做“数据库双机热备”有用户会想既然 Syncthing 可以在两台设备间双向同步我是不是可以用它把数据库文件实时同步到备用服务器实现“双机热备”这个想法听起来合理但忽略了数据库的一致性问题。SQLite 的写入不是一次性完成整个文件的替换而是按页写入且可能依赖 WAL 日志。如果一台设备正在写数据库另一台设备恰好把此刻的文件复制同步走那么这份副本可能处于事务中间状态既不是旧版本也不是新版本而是一份损坏的文件。所以结论很直接Syncthing 适合同步文件数据不适合同步数据库文件。4. 为什么 SQLite 文件不适合被 Syncthing 实时同步4.1 SQLite 的一致性模型是一整套状态SQLite 启用 WAL 模式后主数据库文件之外还会出现-wal和-shm文件。这三个文件之间存在严格的时序关系。主库文件里存放的是最近提交的事务数据-wal文件里存放的则是尚未合并到主库的后续事务。Syncthing 是文件级同步工具它不知道这些文件之间是什么关系。它把index-v0.14.0.db同步过去可能是一个主库文件没有包含最新 WAL 内容的版本它把index-v0.14.0.db-wal也同步过去但对端设备上的主库文件却是另一个时间点的状态。几台设备各自复制不同时间点的组合数据库基本不具备被正确恢复的可能性。4.2 数据库是“活文件”变化频率远高于普通文件普通文档可能一天改几次但 Syncthing 的索引数据库在运行时会反复写入。扫描文件、接收远端设备索引、写入同步进度每一次操作都可能造成数据库文件变化。在一个包含数万文件的同步文件夹里数据库可能几分钟就触发一次文件的元数据变动。Syncthing 检测到文件变化后会尝试把新的数据块同步给远端设备。这个过程中数据库又被再次写入再次触发扫描。这种高频写入让文件级同步变得毫无效率可言也更容易截取到数据库写入的中间状态。4.3 多设备并发写入等于互相覆盖如果是普通业务数据库多设备并发写通常由数据库自身的锁机制和事务机制来保障。但 Syncthing 的索引数据库本质上只是本机的一个状态文件它没有设计成多节点同时写入同一个数据库文件的分布式系统。当两台设备同时运行 Syncthing并且同时把对方的数据库同步过来时每一台设备都会按照自己的状态覆盖本地的数据库。A 设备新写入了一条索引记录同步给 BB 设备在收到这条记录之前可能已经写入了另一条记录然后 B 又把自己的数据库覆盖给 A。最终A 和 B 都无法保证自己拥有完整的、一致的索引。4.4 同步循环会持续消耗资源和带宽数据库文件变化 → 同步 → 对端收到数据库文件 → 对端扫描 → 对端数据库文件发生变化 → 对端同步回来 → 本机扫描 → 又触发同步。这是一个自我维持的循环。在这个循环里Syncthing 的日志会不断滚动CPU 会被反复占用磁盘空间会持续增长网络带宽被无意义地消耗。如果你在 NAS 上看到 Syncthing 进程占用持续走高而你的文件夹本身并没有多少文件变化第一步就应该检查同步目录里是不是混入了数据库文件。5. 先确认问题找到数据库并判断它是否在同步范围5.1 找到 Syncthing 的索引数据库Syncthing 的默认数据目录因操作系统而异。常见位置如下Linuxsystemd~/.local/state/syncthing/Linux部分发行版或旧版本~/.config/syncthing/Windows%LOCALAPPDATA%\SyncthingmacOS~/Library/Application Support/SyncthingDocker 容器取决于启动时挂载的路径如果你不确定数据库在哪里可以在 Linux 上执行find / -name index-v0.14.0.db 2/dev/null不同版本数据库文件名可能不同但通常以index-开头例如index-v0.14.0.db。5.2 判断数据库是否在同步范围内打开 Syncthing Web 管理界面依次查看每个文件夹的路径。如果数据库所在目录是某个同步文件夹的子目录或者反过来某个同步文件夹是数据库所在目录的子目录那么你的配置就已经踩进了这个坑。另一个判断方式是查看同步文件夹内是否出现了这些文件index-*.db*.db-wal*.db-shm*.tmpconfig.xml的副本一旦出现说明 Syncthing 正在尝试同步自己的运行时文件。5.3 用 sqlite3 检查数据库状态如果需要判断数据库是否健康可以用 SQLite 自带的完整性检查sqlite3 /path/to/index-v0.14.0.db PRAGMA integrity_check;如果输出结果是ok说明数据库本身没有结构性问题。如果输出大量错误信息说明数据库已经损坏。5.4 如果你用 DB Browser for SQLite 打开失败有些用户拿到index-*.db文件后会用 DB Browser for SQLite 打开结果提示“file is not a database”或者“encrypted”。这里要记住不是这个文件加密了而是它被同步工具在不同时间点截取并混合之后变成了损坏文件。在这种情况下最正确的动作不是尝试修复而是让 Syncthing 重新生成一份数据库具体方法见下一章。6. 解决方案从止损到根治6.1 最快止损在 .stignore 中排除数据库文件Syncthing 的每个同步文件夹都支持.stignore忽略规则。如果你只是想尽快止血可以先在同步文件夹根目录创建或修改.stignore把运行时文件排除掉。// 排除 Syncthing 自身运行时文件 index-*.db *.db-wal *.db-shm *.tmp *.tmp-*注意.stignore的修改会在几秒内生效。添加规则后Syncthing 会停止同步这些文件。但已经同步到其他设备的损坏副本不会自动删除需要手工清理。这个方案适合急救但它只是“止损”不是“根治”。如果数据库文件本身就在同步目录内Syncthing 仍然会在本地读写它只是不再向外同步。更好的做法是把它从同步目录中迁移出去。6.2 根治把数据库移出同步目录如果你能控制 Syncthing 的启动方式最可靠的方法是让 Syncthing 的数据目录和数据目录完全分离。Syncthing 运行时会根据环境变量STHOMEDIR确定主目录的位置这个目录用来存放配置、密钥和索引数据库。在启动 Syncthing 前可以指定export STHOMEDIR/volume1/docker/syncthing/config syncthing -no-browser这样数据库和配置都会写入/volume1/docker/syncthing/config目录。而你需要同步的文件则放在/volume1/share这类独立目录。如果你使用 Docker 部署更典型的做法是把配置和数据分别映射version: 3 services: syncthing: image: syncthing/syncthing container_name: syncthing hostname: my-syncthing environment: - STHOMEDIR/var/syncthing/config volumes: - /volume1/docker/syncthing/config:/var/syncthing/config - /volume1/share:/data/share ports: - 8384:8384 - 22000:22000在这个配置里/var/syncthing/config是 Syncthing 自己的运行目录/data/share才是真正对外同步的数据。两者分开后数据库文件就不会出现在同步范围内。6.3 正确备份 Syncthing 配置如果你想把 Syncthing 的配置和索引备份到另一台设备正确的做法不是实时同步而是“先停止服务再复制文件”。以 systemd 服务为例sudo systemctl stop syncthing$USER tar czf syncthing-config-backup.tar.gz ~/.local/state/syncthing sudo systemctl start syncthing$USER这个流程能保证备份文件处于一个一致的状态因为备份期间 Syncthing 没有写入操作。如果你用的是 Docker可以先把容器停止再打包配置目录。需要强调一点备份的频率不需要很高。大部分 Syncthing 配置一天备份一次就够了索引数据库即使丢失也可以通过重新扫描重建。6.4 数据库损坏后的恢复如果数据库已经损坏最直接的恢复策略是让 Syncthing 重建索引。这个过程中文件数据不会丢失因为实际文件仍然保留在磁盘上丢失的只是“哪些文件有哪些块”的索引元数据。操作步骤如下sudo systemctl stop syncthing$USER mv ~/.local/state/syncthing/index-v0.14.0.db ~/.local/state/syncthing/index-v0.14.0.db.corrupt sudo systemctl start syncthing$USER启动后Syncthing 会创建新的空数据库并开始全量扫描所有同步文件夹。文件数量越多重建索引的时间越长。对包含数万文件的目录来说这个过程可能需要几十分钟甚至更久属于正常现象。如果你在 Docker 中运行可以把STHOMEDIR指向的目录映射到宿主机然后用同样的方法重命名数据库文件。7. 验证是否恢复从日志到数据完整性7.1 判断同步循环是否停止恢复之后打开 Syncthing Web 管理界面点击“操作”菜单查看“事件”或“日志”。正常情况下日志不会像之前那样持续出现扫描和同步事件。如果文件夹状态从scanning变回idle说明同步循环已经停止。7.2 确认数据库健康重建索引之后可以再次运行完整性检查sqlite3 ~/.local/state/syncthing/index-v0.14.0.db PRAGMA integrity_check;输出ok即表示当前数据库结构正常。这一步对后续排查很有帮助因为它能排除数据库本身的问题。7.3 观察磁盘占用和网络流量如果你是因为磁盘占用异常才发现的这个问题可以在修复后观察一两天。正常情况下同步完成后的磁盘占用应该保持稳定不应该持续增长。网络流量也应该只在文件变化时出现而不是 24 小时都在传输大量数据。8. 常见问题与排查思路问题现象可能原因排查方式解决方案应用启动失败出现failed up action数据目录被占用或数据库文件损坏查看应用日志确认挂载路径停止应用重命名损坏数据库检查挂载是否指向同步目录Web 界面一直显示scanning数据库文件持续变化导致扫描循环检查日志中是否有database is locked排除数据库文件迁移数据目录日志出现database disk image is malformed数据库被文件级同步工具混写执行PRAGMA integrity_check用备份恢复或停止服务后重建索引用 DB Browser for SQLite 打开报not a database文件在传输过程中被截断或混合检查文件大小和哈希不要尝试修复改用健康副本或重建另一台设备出现index-*.db副本同步目录中包含了数据库文件搜索同步目录中的index-*删除副本在.stignore中忽略同步目录中出现.tmp文件Syncthing 传输时产生的临时文件查看文件名和大小在.stignore中忽略*.tmp磁盘占用持续上升数据库或临时文件被反复同步检查文件夹版本控制配置移出数据库关闭不必要的版本控制Windows 下无法打开.db文件缺少查看工具或文件已损坏使用 sqlite3 或 DB Browser for SQLite确认文件来源不要直接同步数据库遇到具体问题时第一件事永远是查看 Syncthing 日志。日志能告诉你错误发生在扫描、传输还是数据库写入阶段这是定位问题的起点。9. 最佳实践与工程建议9.1 目录规划从第一天就做对部署 Syncthing 之前先明确三个目录Syncthing 主目录存放配置、密钥和数据库同步数据目录真正需要和远端共享的文件临时目录用于下载或缓存不参与同步。三者在文件系统上应该彼此独立避免嵌套和软链接交叉。这样即使配置出现问题也不会影响数据。9.2 同步范围宁小勿大很多人喜欢把整个 NAS 都同步给远端设备这会让数据库体积膨胀到几个 GB也更容易碰到文件系统层面的奇怪问题。更稳妥的做法是只同步需要跨设备访问的目录比如文档、照片、常用代码仓库。系统文件、日志、数据库和配置目录不要放进同步范围。9.3 忽略规则要主动维护不要等.db文件出现了才想起.stignore。部署新同步文件夹时就应当主动加入以下规则的基础版本// Syncthing 自身运行文件 index-*.db *.db-wal *.db-shm // 临时文件和备份文件 *.tmp *.tmp-* *~同时要注意.stignore的规则是针对同步文件夹的不是全局配置。每个同步文件夹都需要独立维护自己的忽略规则。9.4 备份不等于实时同步如果你想保护重要文件的完整性实时同步是一个好方案。但数据库文件的“备份”必须遵循数据库备份的基本原则要么通过数据库自身的备份接口要么先停止服务再复制文件。用 Syncthing 实时同步 SQLite 文件不是备份而是制造损坏。9.5 在 NAS 和 Docker 上部署时注意权限在威联通、TrueNAS 或群晖上部署时挂载路径的权限会影响 Syncthing 的写入行为。如果容器以 root 运行而同步目录的属主是普通用户Syncthing 扫描文件时会遇到权限问题日志中会出现大量permission denied。建议将容器用户和同步目录属主保持一致并把配置目录与数据目录分别挂载。9.6 定期巡检即使目录规划正确也值得定期巡检。建议每月花几分钟做一次检查查看 Web 管理界面是否有文件夹处于异常状态查看同步日志中是否有database、error等关键字的错误运行一次PRAGMA integrity_check确保索引数据库健康。对规模比较大的同步节点这个巡检习惯能帮你提前发现很多隐患。10. 总结这个 Gotcha 提醒了我们什么Syncthing 本身是优秀的文件同步工具SQLite 也是可靠的单文件数据库。但它们组合起来就形成了一个隐蔽的陷阱Syncthing 的索引数据库是运行时状态文件不是普通文档。它既不应该被同步也不应该被放在一个由 Syncthing 监控的目录里。这篇文章要提醒你三件事如果同步目录中出现了index-*.db、*.db-wal、*.db-shm先把它从同步范围排除再迁移数据库目录如果日志报错提示数据库损坏停止服务、重命名索引数据库、让它重新扫描这是最可靠的恢复方式备份配置的时候先停服务再复制文件不要用实时同步代替备份。如果你在配置 Syncthing 时还没有遇到这些问题建议现在就去检查一下数据目录和同步目录是否重叠。发现问题的时间越早恢复成本越低。以后如果遇到有人把index-*.db同步到了所有设备你可以把这份排查思路转发给他。Syncthing 和 SQLite 都没错错的是让一个正在被进程写入的数据库文件出现在一个实时同步工具自己的同步范围里。