QueryRow查不到数据时必须显式检查Scan()返回的err,用errors.Is(err, sql.ErrNoRows)区分“无记录”与其他错误;字段类型需严格匹配,NULL列用指针或sql.NullType,结构体字段须导出且Scan传地址。

QueryRow 返回 sql.ErrNoRows 怎么安全处理
查不到数据时,QueryRow 不会返回 nil,而是把错误塞进 Scan() 的返回值里。很多人直接忽略这个 error,结果程序 panic 或逻辑错乱。
- 必须对
Scan()的 error 做显式判断,不能只检查QueryRow本身是否为 nil - 常见写法是:先调用
QueryRow(...).Scan(&v),再用errors.Is(err, sql.ErrNoRows)区分「没找到」和「其他错误」 - 别用
if err != nil一刀切——数据库连接失败、字段类型不匹配、NULL 值未处理等也会触发 err,它们和「无记录」性质完全不同
示例:
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 真的没有这条记录
log.Println("user not found")
return
}
// 其他错误:比如 name 列是 TEXT 但 &name 是 int,或连接中断
log.Fatal(err)
}
Scan 时字段类型不匹配导致 panic 的原因
Go 的 Scan 不做运行时类型转换,它严格按 SQL 列类型往 Go 变量里填。一旦不匹配(比如数据库是 INT,你传 *string),就会 panic,而不是返回 error。
- 整数列优先用
*int64(哪怕建表是INT或TINYINT),避免溢出或类型错位 - 字符串列统一用
*string,别用*[]byte除非你明确要原始字节 - 时间列用
*time.Time,确保 DSN 加了parseTime=true参数,否则会扫成字符串再转失败 - 允许 NULL 的列,必须用指针类型(如
*string)或sql.NullString等,不能用普通string
QueryRow 和 Query + Next 的性能与语义差别
QueryRow 本质是 Query + 自动调一次 Next + 强制只取一行。它不是语法糖,底层行为有实际约束。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
QueryRow会自动关闭内部的*sql.Rows,你没法手动Close();而Query返回的*sql.Rows必须显式Close(),否则可能泄漏连接 - 如果 SQL 实际返回多行,
QueryRow只取第一行,其余被静默丢弃——这容易掩盖逻辑错误(比如本该唯一却重复) - 性能上几乎没差别,但语义更清晰:
QueryRow表达「我预期且只接受一行」,数据库约束或业务逻辑应保证这点 - 别为了“省一行代码”在循环里反复调
QueryRow,批量查请用Query+for rows.Next()
为什么用 QueryRow 却拿到空 struct 字段
常见于结构体字段未导出(小写开头)或 Scan 时没传地址,导致字段没被赋值,看起来像“查到了但全是零值”。
立即学习“go语言免费学习笔记(深入)”;
- struct 字段必须首字母大写(即 exported),否则
database/sql反射时无法写入 - Scan 参数必须是变量地址,写成
Scan(&u)而不是Scan(u);如果 u 是 struct 指针,也要确认是不是传了&u还是u - 字段名和列名不匹配时不会报错,只是跳过赋值——建议用
AS显式别名,或用sql.Scanner接口自定义映射 - 使用
Scan到 struct 时,字段顺序必须和 SELECT 列顺序严格一致,不能靠名字匹配

















