sqlx 直连 sqlite 更稳更快,因 sqlite 无服务进程、不支持并发写、语法受限,gorm 默认按 postgresql/mysql 建模易导致迁移失败、时间错乱、外键失效等问题;推荐仅用 sqlx + sqlite3,显式设置 pragma 并复用 db 实例。

直接用 sqlx 连 SQLite,别碰 GORM
SQLite 是嵌入式数据库,没有服务进程、不支持并发写、不兼容部分 SQL 语法(比如 ALTER TABLE ... DROP COLUMN),而 GORM 默认按 PostgreSQL/MySQL 行为建模,强行套用会踩一堆坑:迁移失败、时间字段错乱、外键约束被忽略、事务行为异常。实际项目中,sqlx + 原生 SQL 更稳、更透明、启动更快。
- 只引入
github.com/jmoiron/sqlx和github.com/mattn/go-sqlite3两个依赖,无额外抽象层 - 连接字符串用文件路径(如
"./app.db")或内存模式(":memory:"),无需用户名密码 - 首次
sqlx.Connect("sqlite3", dsn)时,若文件不存在会自动创建,但不会自动建表 —— 这是预期行为,不是 bug - 记得调用
db.Ping()验证连接可用,Open只校验参数,不真正连库
sqlite3.Open 后必须手动执行 PRAGMA 设置
SQLite 默认不启用外键、不强制 WAL 模式,也不做同步写保护,开箱即用的默认配置在多协程或崩溃恢复场景下容易丢数据。Gin 启动时应立即执行初始化语句:
_, _ = db.Exec("PRAGMA foreign_keys = ON")
_, _ = db.Exec("PRAGMA journal_mode = WAL")
_, _ = db.Exec("PRAGMA synchronous = NORMAL")
-
foreign_keys = ON必须显式开启,否则FOREIGN KEY约束形同虚设 -
journal_mode = WAL提升并发读性能,避免写阻塞所有读操作 -
synchronous = NORMAL在数据安全和写入速度间取得平衡;设为OFF虽快但断电可能丢最后几条 - 这些 PRAGMA 对每个新连接都需重设,不能只在建库时执行一次
路由处理函数里别直接传 *sqlx.DB,用 context.Context 控制生命周期
Gin 的 c.Request.Context() 是请求级上下文,SQLite 连接本身是全局复用的,但查询应绑定请求生命周期,尤其涉及超时、取消或日志追踪时。错误写法是把 db 当普通变量传进 handler;正确做法是封装一层带 context 的查询:
func (h *Handler) GetUser(c *gin.Context) {
id := c.Param("id")
var user User
err := h.db.GetContext(c.Request.Context(), &user, "SELECT * FROM users WHERE id = ?", id)
if err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}
c.JSON(200, user)
}
-
GetContext/SelectContext等方法才响应 context 取消,普通Get/Select会一直卡住 - 别在 handler 里调
db.Begin()后忘Rollback()或Commit(),建议用db.TransactContext封装事务逻辑 - 如果用了
:memory:模式,每次请求新建连接就等于新建空库 —— 记住这不是“临时库”,而是“每次重启就清空”的库
测试时用 :memory:,但得注意连接隔离性
单元测试常用 ":memory:" 启动 SQLite 内存库,速度快、无副作用。但 SQLite 的内存数据库默认按连接隔离:两个 sqlx.Connect 实例对应两个完全独立的内存库,彼此看不到对方写入的数据。
- 测试多个 handler 交互时,必须复用同一个
*sqlx.DB实例,不能每个 test case 都Connect一次 - 如果用 Gin 的
httptest.NewRecorder()测试 HTTP 接口,确保 handler 使用的是测试专用的db,且该db在 test setup 阶段已执行过建表语句 - 想模拟“多个请求看到同一份内存数据”,只能用
"file::memory:?cache=shared"连接字符串,但仅限单进程内有效,跨 goroutine 安全性需自行保证
SQLite 不是“简化版 MySQL”,它是另一套设计哲学:单文件、强一致性、零运维。集成时放弃对“标准 ORM 行为”的幻想,接受它的边界,反而最省事。











