sqlx.queryrowcontext返回sql.errnorows时不panic,须用errors.is(err, sql.errnorows)显式判断并按业务逻辑处理(如返回404),其他错误才视为异常;结构体字段必须加db tag才能正确映射,漏写则值为零值。

sqlx.QueryRowContext 返回 sql.ErrNoRows 时别 panic,要显式判断
很多刚用 sqlx 的人看到文档里 QueryRowContext 返回 error 就直接 if err != nil { panic(err) },结果查不到数据时整个服务崩了。这不是 bug,是设计——sql.ErrNoRows 是合法的、预期中的错误,不是异常。
正确做法是单独判断:
var name string
err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", userID).Scan(&name)
if err == sql.ErrNoRows {
// 处理“没找到”的业务逻辑,比如返回 404 或默认值
return "", nil
}
if err != nil {
// 其他真实错误,比如连接断开、语法错
return "", err
}
-
sql.ErrNoRows不等于nil,但也不该被当作系统级故障处理 - 用
errors.Is(err, sql.ErrNoRows)比err == sql.ErrNoRows更安全(兼容包装 error) - 如果业务上“必须存在”,才考虑 panic 或提前 abort;否则一律按正常控制流分支处理
结构体字段名不加 db 标签就扫不出数据
sqlx.Get、sqlx.Select 这类结构体映射操作,默认靠 struct tag db 找列名。没写 tag,哪怕字段名和数据库列名完全一致,sqlx 也扫不到值——它不会 fallback 到字段名自动匹配。
例如:
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
- 漏写
`db:"xxx"`,对应字段永远为零值(0、""、nil) - 列名含下划线(如
created_at)时,tag 必须显式写成`db:"created_at"`,不能指望自动转驼峰 - 想全局启用“无 tag 默认映射字段名”,得用
sqlx.NewDb+ 自定义Mapper,但不推荐:易和大小写、SQL 别名冲突,调试困难
批量插入慎用 sqlx.NamedExec,大数量时性能掉得厉害
sqlx.NamedExec 支持 map 或 struct 参数,写法清爽,但底层是把每个参数展开成独立占位符(?, ?, ?),再拼 SQL。100 条记录插入,就会生成带 300 个 ? 的语句——MySQL 有 max_allowed_packet 限制,PostgreSQL 有参数数量上限(默认 65535),超了直接报错 too many arguments。
真正大批量插入该用原生 sql 的 Prepare + Exec 循环,或数据库特有语法:
- PostgreSQL:用
INSERT INTO ... VALUES (...), (...), (...)一次性插多行 - MySQL:同上,或开启
multiStatements=true(需驱动支持且注意安全) - 无论如何,避免在循环里反复调
sqlx.NamedExec——每次都要解析 SQL、重建 stmt,开销远大于复用 prepared statement
事务中混用 sqlx.DB 和 sqlx.Tx 会导致连接泄漏
常见错误:在 tx, _ := db.Beginx() 后,仍用 db.Queryx(...) 而非 tx.Queryx(...)。表面看能跑通,但 db 会从连接池拿新连接,而 tx 占着另一条连接不释放——尤其高并发时,连接池迅速耗尽,后续请求卡死在 acquireConn。
所有操作必须统一走 tx 对象:
tx, err := db.Beginx()
if err != nil {
return err
}
defer tx.Close() // 注意:不是 tx.Rollback(),那是回滚逻辑里的
<p>_, err = tx.Exec("INSERT INTO orders ...", orderID)
if err != nil {
tx.Rollback()
return err
}</p><p>// 别写成 db.Get(...),必须用 tx.Get(...)
var user User
err = tx.Get(&user, "SELECT * FROM users WHERE id = ?", userID)
</p>
-
sqlx.Tx不是装饰器,它持有专属连接,和sqlx.DB的连接池隔离 - 忘记
tx.Commit()或tx.Rollback()也会导致连接不归还,但更隐蔽——连接一直被占用,直到超时关闭 - 用
defer确保Close()不够,必须显式 commit/rollback,Close()只是清理资源引用
实际项目里最常踩的坑不在语法,而在连接生命周期和错误分类——sql.ErrNoRows 被 panic、struct tag 漏写、事务里误用 db 实例,这三个加起来占了 80% 的线上数据库相关故障。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











