Go中需区分SQL查询执行失败与结果为空:Exec错误表操作失败;QueryRow.Scan遇sql.ErrNoRows表无匹配行;Query需遍历rows.Next()后检查rows.Err()。

在 Go 中处理 SQL 查询错误,关键在于区分两类问题:查询执行失败(如连接中断、语法错误)和查询成功但结果为空(如 WHERE 条件不匹配)。这两者需要不同的判断逻辑和响应方式。
区分 Exec / Query 的错误类型
Go 的 database/sql 包中,Exec 用于无返回结果的操作(INSERT/UPDATE/DELETE),Query 或 QueryRow 用于 SELECT。它们的错误含义不同:
-
Exec返回的error表示操作未成功执行(例如主键冲突、权限不足、网络断开) -
QueryRow.Scan()的错误通常表示“查到了行但字段类型不匹配”,而QueryRow.Err()才能告诉你是否真的没查到数据 -
Query返回*Rows,需用rows.Next()遍历;若rows.Next()返回false且rows.Err() == nil,说明查询成功但无结果
正确判断“查不到数据”而不是“查询失败”
常见错误是把 sql.ErrNoRows 当作通用错误直接 panic 或 log,但它只适用于 QueryRow 场景。正确做法:
- 用
QueryRow(...).Scan(&v)时,检查 error 是否等于sql.ErrNoRows—— 这代表“有查询、无匹配行” - 用
Query(...)时,不要依赖rows.Err()判断空结果;先循环rows.Next(),结束后再看rows.Err()是否非 nil - 示例:
row := db.QueryRow("SELECT name FROM users WHERE id = ?", 999)
var name string
if err := row.Scan(&name); err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 正常业务逻辑:用户不存在
} else {
// 真正的异常:比如 name 字段是 []byte 却 Scan 到 string
}
}
统一处理执行类错误(INSERT/UPDATE/DELETE)
Exec 和 ExecContext 的 error 是运行时异常信号,必须显式检查:
立即学习“go语言免费学习笔记(深入)”;
- 数据库约束错误(如唯一键冲突)会返回具体 driver 错误,可用
errors.As提取底层错误码(如 MySQL 的mysql.MySQLError) - 连接类错误(如 timeout、connection refused)通常包含在
net.OpError或 driver 自定义错误中,适合重试或降级 - 建议封装一个辅助函数判断常见错误类型,例如:
func IsDuplicateKey(err error) bool {
var mysqlErr *mysql.MySQLError
return errors.As(err, &mysqlErr) && mysqlErr.Number == 1062
}
避免忽略 Rows.Close() 和 Scan 后的 Err()
资源泄漏和静默失败常源于这两个细节:
- 所有
*Rows必须调用rows.Close()(可用defer rows.Close()),否则连接不会释放 -
rows.Scan()可能成功,但后续rows.Next()失败(如网络中断),所以每次循环后应检查rows.Err() - 反例:
rows, _ := db.Query("SELECT ...")
for rows.Next() {
var x int
rows.Scan(&x) // ❌ 忽略 Scan 错误
}
// ❌ 忘记 rows.Close()
// ❌ 没检查 rows.Err() 是否发生读取中断










