Skip to content
This repository was archived by the owner on Sep 24, 2026. It is now read-only.
This repository was archived by the owner on Sep 24, 2026. It is now read-only.

Run mutation 在等待 dae 重载期间持有数据库事务,导致并发查询 SQLITE_BUSY #204

Description

@kenzok8

现象

Run mutation 会开启一个 serializable 事务,并在这个事务内部把配置发给 dae、等待重载完成:

// graphql/mutation.go
tx := db.BeginTx(context.TODO())
ret, err := config.Run(tx, args.Dry)

config.Run 内部把配置发到 dae.ChReloadConfigs 后阻塞等待回调,也就是说整个重载期间数据库事务一直开着。重载耗时取决于节点数量与 DNS 解析,节点多时可以到几十秒。

同时 db/db.go 里 sqlite.Open(path) 没有带任何 DSN 参数,默认是 rollback journal 模式——写事务开启期间读会被阻塞。两者叠加,重载期间任何并发查询都会等到超时:

database is locked (5) (SQLITE_BUSY)
[5033.408ms] [rows:0] SELECT * FROM `subscriptions` WHERE ...

登录用的 users 表查询也在其中,表现为密码正确却提示 incorrect username or password。

建议的两处改动

1. 重载移出事务:读配置用一个事务,发起重载前先 commit;重载结束后再开一个短事务写运行状态。

2. SQLite 开启 WAL:DSN 加上 journal_mode(WAL) 与 busy_timeout(30000),读不再被写事务阻塞。

实测数据

我维护着一个 OpenWrt 打包分支,在那里订阅定时更新完成后会自动应用配置,所以这条路径每天都会走到,问题也更容易暴露。在一台 OpenWrt x86_64 测试机上,三个订阅各 300 个节点、cron 设为每分钟同时触发、订阅内容每轮变化:

改动前 改动后
5 分钟内 database is locked 52 次 0 次
登录查询耗时 > 1 秒 15 次 0 次

第 2 条与自动应用与否无关,对当前上游的手动应用流程同样有收益。

一个需要注意的副作用

启用 WAL 后,数据库的持久状态分布在 wing.db 和 wing.db-wal 两个文件里,只复制主库可能丢失已提交的数据(我们的测试机上出现过主库 4KB、WAL 1MB 的情况),备份与打包流程需要相应调整。

如果这两个方向可以接受,我可以提 PR。

Activity

  1. changed the title [-]Scheduled subscription update can hang forever and lock up the dashboard[/-] [+][Bug Report] 定时订阅更新可能永久卡死,导致面板无法登录[/+] on Aug 4, 2026
  2. changed the title [-][Bug Report] 定时订阅更新可能永久卡死,导致面板无法登录[/-] [+]Run mutation 在等待 dae 重载期间持有数据库事务,导致并发查询 SQLITE_BUSY[/+] on Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions