sql.errnorows仅在queryrow执行的sql查不到任何行时由scan()返回,而非queryrow本身返回;其他错误如语法错误、连接失败等返回不同错误类型,须用errors.is(err, sql.errnorows)安全判断。

QueryRow 返回的 error 什么时候是 ErrNoRows
只有当 QueryRow 执行的 SQL 确实没查到任何行时,它返回的 error 才可能是 sql.ErrNoRows。注意:这不是“所有查询失败都走这里”,而是特指“结果集为空”这一种情况。比如 SELECT id FROM users WHERE id = ? 查不到匹配记录,才会触发;而语法错误、连接断开、权限不足等,返回的是其他具体错误(如 driver: bad connection 或 pq: syntax error)。
常见误判点:
- 把
QueryRow.Scan()的 panic 当作ErrNoRows处理 —— 实际上如果QueryRow返回了非nilerror,Scan()不会执行,更不会 panic - 用
== nil判断 error 后直接调用Scan(),却不检查 error 类型 —— 这样漏掉ErrNoRows就等于把“查无此数据”当成“系统异常”
如何正确判断并区分 ErrNoRows 和其他错误
必须显式用 errors.Is(err, sql.ErrNoRows)(Go 1.13+)或 err == sql.ErrNoRows(兼容旧版本)做判断。不能靠字符串匹配或 err != nil 一概而论。
典型写法:
var id int
err := db.QueryRow("SELECT id FROM users WHERE email = ?", email).Scan(&id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 用户不存在,业务逻辑按预期处理
return nil, ErrUserNotFound
}
// 其他错误:数据库故障、类型不匹配等,需记录日志并返回
log.Printf("query user failed: %v", err)
return nil, err
}
注意:errors.Is 比 == 更安全,因为某些驱动(如 pgx)可能包装了原始 error。
Scan 调用前未检查 error 导致 panic 的真实原因
panic 不来自 ErrNoRows,而是来自对 nil *sql.Row 的 Scan() 调用 —— 但 QueryRow 永远不会返回 nil,所以真正出问题的是:你忽略了 QueryRow 返回的 error,直接传给 Scan(),而该 error 又不是 ErrNoRows(比如是类型转换失败),这时 Scan() 内部发现底层 rows 已关闭或未初始化,就 panic。
关键点:
-
QueryRow总是返回一个非 nil 的*sql.Row,哪怕 SQL 有语法错误 - 只有调用
Scan()时,才真正触发查询执行和结果读取 - 如果
Scan()前没检查QueryRow的 error,且该 error 是非ErrNoRows类型(如列数不匹配),Scan()会 panic:「sql: Scan on zero columns」或「sql: expected 1 destination argument」
为什么不能用 Query + Next 替代 QueryRow 处理单行场景
可以,但没必要,而且容易引入冗余逻辑。用 Query + Next 需手动控制迭代、检查多行、处理 NoRows 和 EOF,而 QueryRow 专为“至多一行”设计,语义清晰、代码简洁。
但要注意边界:
- 若 SQL 实际可能返回多行(比如漏写了
LIMIT 1或WHERE条件不唯一),QueryRow.Scan()只取第一行,其余被静默丢弃 —— 这不是 bug,是设计行为 - 若业务要求“必须且仅有一行”,应加数据库约束(如 UNIQUE 索引)+ 应用层校验,而不是依赖
QueryRow的行为 - 性能上无差异,两者底层都复用相同的 query 执行路径
真正容易被忽略的是:ErrNoRows 是业务正常流的一部分,不是异常;而把它和其他 error 混为一谈,会让监控误报、日志淹没真问题,甚至导致前端反复重试空查询。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











