用 Gin + SQLite 做本地服务完全可行,但必须弃用 gin.Default() 改用 gin.New() 手动配置中间件,并为 SQLite 设置 _busy_timeout=5000 和 _journal_mode=WAL,同时严格处理 QueryRow 错误、显式关闭数据库连接。

直接说结论:用 Gin + SQLite 做本地服务完全可行,但必须绕开默认的 gin.Default() 启动方式,否则会因日志写入失败或并发锁问题在 Windows/macOS 上静默崩溃。
为什么 gin.Default() 在 SQLite 场景下容易出问题
默认引擎启用了 gin.Logger() 和 gin.Recovery() 中间件,前者会尝试向 stdout/stderr 写日志——在某些嵌入式或桌面打包环境(如打包成 macOS .app 或 Windows 服务)中,标准流可能不可写;后者在 panic 恢复时若触发数据库操作(比如记录错误到 error_log 表),而 SQLite 连接未设 sqlite3.BusyTimeout,就会卡死或返回 database is locked 错误。
- 现象:服务启动后首次请求正常,后续并发请求频繁返回 500 且无日志输出
- 根本原因:
gin.Default()的日志中间件和 SQLite 的 WAL 模式/连接池不兼容 - 正确做法:用
gin.New()手动装配中间件,并为 SQLite 设置连接超时与序列化模式
sqlite3.Open 连接参数必须加 _busy_timeout=5000 和 _journal_mode=WAL
SQLite 默认是 DELETE 日志模式,高并发读写下极易锁表。不显式配置,db.Exec 或 db.QueryRow 在多 goroutine 调用时大概率触发 database is locked。
-
_busy_timeout=5000:让 SQLite 在锁住时最多等待 5 秒再返回错误,避免立即失败 -
_journal_mode=WAL:启用 Write-Ahead Logging,允许多读一写并发,显著降低锁冲突 - 完整 DSN 示例:
file:./data.db?_busy_timeout=5000&_journal_mode=WAL&_sync=OFF(_sync=OFF可提升写性能,但断电可能丢最后几条数据,本地工具类服务可接受)
路由里调用 db.QueryRow 前必须检查 err != nil,不能只看 scan 结果
SQLite 驱动(mattn/go-sqlite3)在查询无结果时,db.QueryRow().Scan() 会返回 sql.ErrNoRows;但如果 SQL 语法错、表不存在或连接已断开,QueryRow() 本身就会返回非 nil error —— 此时若跳过检查直接 Scan(),会 panic。
- 错误写法:
err := db.QueryRow("SELECT name FROM user WHERE id = ?", id).Scan(&name) - 正确写法:
row := db.QueryRow("SELECT name FROM user WHERE id = ?", id); err := row.Err(); if err != nil { /* 处理连接/SQL 错误 */ }; err = row.Scan(&name) - 更稳妥:封装一个
safeQueryRow函数,统一处理row.Err()和Scan()两层错误
关闭服务时必须显式调用 db.Close(),否则进程退出后 db 文件可能被锁
Gin 本身不管理数据库生命周期。如果用 defer db.Close() 放在 main 函数里,Ctrl+C 退出时能释放连接;但若服务跑在 systemd 或 launchd 下,进程可能被 SIGKILL 强杀,defer 不执行,SQLite 的 wal 文件残留会导致下次启动报 database is locked。
- 解决方案:监听 OS 信号,在
os.Interrupt或syscall.SIGTERM时同步关闭 db - 示例:
sigChan := make(chan os.Signal, 1); signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM); go func() { - 额外建议:启动时检查
data.db-wal和data.db-shm是否残留,存在则删掉(说明上次未正常关闭)
SQLite 文件路径权限、WAL 文件残留、连接未关闭 —— 这三个点不处理,哪怕代码逻辑再对,服务也会在真实环境里“看起来能跑,其实随时崩”。


















