应优先选用 Get;它自动处理 Scan 和 sql.ErrNoRows 错误,避免因漏判导致 panic,而 QueryRowx 需手动 Scan 且易忽略错误判断,仅在需复用 Row 或调用 ColumnTypes 等场景才适用。

Sqlx 的 QueryRowx 和 Get 到底该选哪个?
查单行数据时,QueryRowx 和 Get 表面行为相似,但底层处理逻辑不同:前者返回 *sqlx.Row,后者直接填充结构体并检查 sql.ErrNoRows。实际用 Get 更安全——它自动调用 Scan 并统一错误判断,避免漏掉 ErrNoRows 导致 panic。
常见错误是写成:err := db.QueryRowx("SELECT name FROM users WHERE id=$1", 1).Scan(&name),但没检查 err == sql.ErrNoRows,结果一查不到就 panic。换成 err := db.Get(&user, "SELECT * FROM users WHERE id=$1", 1),错误直接包含语义(比如查不到就是 sql.ErrNoRows),不用额外 if 判断。
-
Get要求目标变量地址可寻址(不能传字面量或只读字段) -
QueryRowx更灵活,适合需要手动控制 Scan 时机或复用 Row 的场景(比如后续调用ColumnTypes()) - PostgreSQL 下若字段名含大小写,确保 struct tag 用
db:"field_name"显式映射,否则Get可能静默失败(字段不匹配就不赋值,也不报错)
Struct tag 里 db 和 json 能共用吗?
可以共用,但要注意优先级和覆盖风险。sqlx 默认读取 db tag,没定义时才 fallback 到 json tag。这意味着如果只写 json:"user_id",sqlx 会尝试按 user_id 去匹配数据库列名——但多数数据库用下划线命名(user_id),而 Go 结构体习惯驼峰(UserID),所以必须显式写 db:"user_id"。
容易踩的坑是误以为加了 json tag 就够了,结果查询时字段全为零值,日志也无报错。sqlx 不会报“字段未映射”,而是跳过赋值。
立即学习“go语言免费学习笔记(深入)”;
- 推荐写法:
UserID int `db:"user_id" json:"user_id"` - 别依赖 fallback:一旦结构体字段多、列名不规则(比如含数字或特殊符号),
jsontag 完全不可靠 - 用
sqlx.DB.Rebind()前先确认 driver 是否支持(如postgres用$1,mysql用?),否则 tag 写对了,占位符类型不匹配照样查不出数据
为什么 NamedExec 插入后拿不到自增 ID?
PostgreSQL 和 MySQL 处理方式不同:NamedExec 本身不返回 last insert id,必须用 QueryRowx + RETURNING(PG)或 LAST_INSERT_ID()(MySQL)显式获取。常见错误是以为 res, _ := db.NamedExec("INSERT INTO users(name) VALUES (:name)", arg) 后调 res.LastInsertId() 就能拿到——MySQL 可行,但 PostgreSQL 返回 0, sql.ErrNoRows。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
正确做法是把插入和取 ID 合成一条语句:
err := db.QueryRowx("INSERT INTO users(name) VALUES ($1) RETURNING id", name).Scan(&id)注意点:
- SQLite 用
RETURNING语法不兼容,得改用db.QueryRow("INSERT ...; SELECT last_insert_rowid()") - struct 中 ID 字段必须导出且有
db:"id"tag,否则Scan失败不报错,id保持零值 - 事务中慎用
RETURNING:某些 PG 驱动在事务未提交时返回临时 ID,提交后可能变化(极少见,但高并发写入时需验证)
封装 Query 方法时,为什么泛型 func Get[T any](...) 容易出问题?
sqlx 本身不支持泛型,硬套 any 类型会导致反射开销上升、错误定位变难。最典型的问题是:泛型函数里调 db.Get(&v, query, args...),但 v 是值而非指针,运行时报 reflect.Set: value of type T is not addressable。
根本原因是 Go 泛型擦除后,T 在运行时无法保证是指针类型,而 sqlx 所有 Scan 类方法都要求接收方为地址。
- 安全封装方式:只接受
interface{}并强制断言为指针,或者用具体类型重载(如GetUser、GetOrder) - 别在泛型里调
db.Select:它内部用reflect.Append,若切片元素类型与扫描目标不一致(比如 DB 返回 int64,结构体字段是 int),会静默失败 - 真正省事的封装不是泛型,而是复用
sqlx.DB实例 + 统一 error wrap(例如用fmt.Errorf("get user: %w", err)),而不是抽象掉 sqlx 原生行为
复杂点在于:sqlx 的便利性来自贴近原生 sql 的控制力,过度封装反而掩盖了 driver 差异、字段映射细节和错误语义——这些恰恰是调试时最需要的信息。

















