正确做法是在启动时初始化 sqlx.db 并通过依赖注入(如结构体字段或闭包)传给 handler 所需的 service 层,避免在 handler 中直接操作 sqlx.db 或滥用 context.keys;务必配置连接池、传递 context、区分 sql.errnorows 与真实错误。

如何用 sqlx 连上数据库并注入 Gin 的 handler
不能在每个 handler 里 sqlx.Connect,也不能把 *sqlx.DB 直接塞进 gin.Context。正确做法是:在启动时初始化 *sqlx.DB,通过依赖注入(比如全局变量、配置结构体字段或 DI 工具)传给 handler 所需的 service 或 repository 层。
常见错误现象:panic: sql: database is closed,多因连接被提前 Close() 或未复用单例;context canceled 错误常出现在超时未传递到 sqlx 查询中。
- 初始化时务必调用
db.SetMaxOpenConns和db.SetMaxIdleConns,尤其在容器环境,默认值(0/2)极易打满连接池 - MySQL 驱动必须显式导入:
_ "github.com/go-sql-driver/mysql",否则sqlx.Connect报sql: unknown driver "mysql" - Gin handler 中不要直接操作
*sqlx.DB,应封装到repository包里,handler 只调repo.GetUserByID(ctx, id)
Query 参数绑定与 SQL 拼接的安全边界
sqlx.Named 和 sqlx.In 是唯二推荐的参数化方式,绝不用字符串拼接构建 WHERE 条件——哪怕你“确定输入是数字”。Gin 的 c.Query 返回的是 string,不校验类型也不防注入。
使用场景:按姓名模糊查、按多个 ID 批量获取、动态排序字段(需白名单校验)。
-
sqlx.Named适用于结构体或 map 绑定命名参数,例如SELECT * FROM users WHERE name LIKE :name+map[string]interface{}{"name": "%"+name+"%"} -
sqlx.In处理IN (?)场景,必须配合sqlx.In+sqlx.Rebind,例如ids := []int{1,2,3}; query, args, _ := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids) - 排序字段(如
ORDER BY ?)不能参数化,必须硬编码白名单:if !slices.Contains([]string{"created_at", "name"}, sortField) { sortField = "id" }
事务控制必须显式管理上下文生命周期
sqlx 的事务对象 *sqlx.Tx 不支持 Gin 的 c.Request.Context() 自动传播,必须手动把 context 传入 tx.QueryxContext 等方法,否则超时或取消无法中断正在执行的 SQL。
容易踩的坑:用 tx.Queryx(无 context 版)导致 goroutine 卡死;在 defer 中只写 tx.Rollback() 而不判断 tx != nil,panic 时 panic 嵌套。
- 开启事务:用
db.Beginx(),不是db.MustBeginx()—— 后者 panic 会绕过 defer - 提交或回滚前先检查
err:只有err == nil才tx.Commit(),否则tx.Rollback() - 所有查询/执行必须带
Context:例如tx.GetContext(c.Request.Context(), &user, query, args...)
错误处理别忽略 sql.ErrNoRows 和驱动特有错误码
sqlx 不会把 NoRows 当成 error 向上抛,但业务逻辑常需要区分「没找到」和「查失败」。MySQL 驱动还可能返回 mysql.MySQLError,比如唯一键冲突(errno 1062),这类错误必须显式断言才能做重试或提示。
性能影响:每次 errors.Is(err, sql.ErrNoRows) 开销极小,但漏判会导致 500 错误掩盖本该是 404 的场景。
- 查单条用
db.Get/tx.Get,查列表用db.Select,别反着用(Get对空结果集直接返回sql.ErrNoRows) - 捕获 MySQL 错误:
if err != nil { if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1062 { /* 处理重复插入 */ } } - 自定义错误包装建议用
fmt.Errorf("get user %d: %w", id, err),保留原始 error 链供errors.Is判断
Gin 和 sqlx 的交界处最易出错的不是语法,而是上下文传递、连接生命周期和错误分类——这三个点一旦松动,问题会藏得深、复现难、日志少。











