结论:Gin + SQLite 离线同步可行,但必须主动触发、显式控制——SQLite 无监听机制,WAL 文件仅为事务日志而非变更流,不支持 binlog/CDC;所有同步需轮询或事件驱动拉取,且须手动处理事务、锁、连接池与文件路径等并发安全细节。

直接说结论:Gin + SQLite 做离线同步可行,但必须绕开“自动同步”幻觉——SQLite 没有监听机制,所有同步动作都得由你主动触发、显式控制。
为什么不能像 MySQL 那样监听 binlog 或用 CDC?
SQLite 是文件型数据库,不运行服务进程,也没有变更日志流(如 WAL 文件不是为同步设计的,只是事务日志)。mattn/go-sqlite3 和 glebarez/go-sqlite 都不提供「数据变更通知」API。所谓“实时同步”,在 SQLite 场景下本质是轮询或事件驱动的主动拉取。
- 别指望 Gin 的 HTTP handler 里写个
db.Query("SELECT * FROM changes WHERE synced = 0")就算同步——这只能查,不能保证并发安全 - WAL 模式开启后,
PRAGMA journal_mode = WAL只提升并发读写能力,不暴露变更记录 - 想靠文件系统监控
db.sqlite文件修改时间来触发同步?不可靠:写入可能只改了页缓存,磁盘未落盘;且 SQLite 写入不总是立即 flush
离线场景下真正可用的同步模式
离线环境(如信创终端、现场设备)常见的是“单向主→备”或“本地编辑→中心回传”。推荐以下三种落地方式,按复杂度递增:
-
手动提交模式:用户点击“同步”按钮,Gin 启动一个
tx, err := db.Begin(),先查出所有status = 'pending'的记录,批量 POST 到中心 API,成功后更新本地status = 'synced',最后tx.Commit() -
定时拉取模式:用
github.com/robfig/cron/v3在 Gin 启动时注册定时任务,比如每 5 分钟http.Get("https://api.example.com/updates?since=1723490182"),拿到增量 JSON 后用tx.Exec("INSERT OR REPLACE INTO ...")落库 - 双写队列模式:所有写操作(INSERT/UPDATE/DELETE)不直接操作表,而是先写入一张
sync_queue表(含 op_type、table_name、payload_json、created_at),再由后台 goroutine 消费该队列并调用远程 API;失败则重试 + 限流,避免阻塞主流程
事务与并发必须亲手兜底
SQLite 默认允许并发读,但写入是全局锁。Gin 多请求同时触发同步逻辑时,容易因事务冲突导致 database is locked 错误。
立即学习“go语言免费学习笔记(深入)”;
- 不要在 HTTP handler 里直接
db.Exec修改同步状态——用tx.QueryRow("SELECT ... FOR UPDATE")显式加行锁 - 避免长事务:同步过程中不要做耗时 HTTP 请求后再 Commit;应先
tx.Commit()更新本地状态,再异步发请求 - 连接池要设紧:Gin 默认用
db.SetMaxOpenConns(1),因为 SQLite 不支持多连接并发写;设 >1 反而增加锁争用 - 别在事务里调
db.QueryRow—— 必须用tx.QueryRow,否则会脱离事务上下文,导致部分写入成功、部分失败
Gin 中 SQLite 同步的典型错误路径
很多项目卡在第一步:HTTP 接口返回 200,但数据库没变化,也不报错。
-
sql.Open("sqlite3", "data.db")没跟db.Ping()—— 路径错、权限不足、磁盘满,全在第一次Exec时静默失败 - 用相对路径
"./data.db",但 Gin 服务以 systemd 启动时工作目录是/,实际生成了/data.db,和你预期的./data.db不是同一个文件 - 同步逻辑里混用
db和tx:比如用tx.QueryRow查数据,却用db.Exec更新状态,后者不在事务中,无法回滚 - 忘记处理 WAL 模式下的
SHARED锁:多个进程(如 Gin + 后台同步 goroutine)同时打开同一 SQLite 文件,需确保都启用 WAL 并设置_ "github.com/glebarez/go-sqlite"
最易被忽略的点:SQLite 的 INSERT OR REPLACE 在有唯一约束时会隐式删除再插入,触发 ON DELETE CASCADE,但如果你没建外键或没开 PRAGMA foreign_keys = ON,这种级联就静默失效——同步看起来成功了,关联数据却丢了。


















