应使用 errors.Is(err, sql.ErrNoRows) 判断查询无结果,而非字符串匹配;区分数据库连接错误与业务错误需类型断言;事务中 Rollback() 和 Commit() 的返回值均须检查;日志中禁止打印原始 SQL 和敏感参数。
判断 sql.ErrNoRows 而不是用字符串匹配错误信息
很多初学者看到查询无结果时 panic 或返回空结构体,就去检查 err.Error() 是否包含 “no rows” —— 这是脆弱且不可靠的。Go 标准库明确提供了导出变量 sql.ErrNoRows 用于判等,它在任何驱动(mysql、postgres、sqlite)下行为一致。
正确做法是直接比较错误值:
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 处理未找到
return nil, fmt.Errorf("user not found")
}
return nil, err // 其他错误(如连接中断、类型不匹配)需向上抛
}
- 永远别用
strings.Contains(err.Error(), "no rows")—— 驱动可能本地化错误文本,或未来版本调整措辞 -
errors.Is()比==更安全,能穿透包装错误(比如fmt.Errorf("query failed: %w", err)) -
sql.ErrNoRows仅适用于QueryRow和Scan场景;Query返回*sql.Rows,需调用rows.Next()判断是否为空
区分数据库连接错误和业务逻辑错误
数据库操作失败原因差异极大:网络抖动、认证失败、语句语法错、约束冲突(如唯一键重复)、超时 —— 它们应对策略完全不同。Golang 的 driver.ErrBadConn 和 pgconn.PgError(PostgreSQL)等类型提供了分类依据,但标准 error 接口本身不暴露这些细节。
实操建议:
- 对 MySQL,用
mysql.MySQLError类型断言获取Number字段(如 1062 是唯一键冲突) - 对 PostgreSQL,用
pgconn.PgError断言,检查Code(如"23505"表示唯一约束违规) - 通用兜底:用
errors.Is(err, driver.ErrBadConn)判断是否需要重试(注意:该错误在 Go 1.19+ 已被弃用,应改用driver.IsConnectionReset或驱动专属判断) - 不要把约束错误(如插入重复邮箱)当作系统故障记录 ERROR 日志 —— 它属于用户输入问题,应返回 400 和明确提示
使用 tx.Rollback() 后必须检查其返回错误
事务中发生错误后调用 tx.Rollback() 看似保险,但它的返回值可能非 nil:比如事务已提交成功、连接已断开、或底层驱动在回滚阶段又出错。忽略这个错误会导致误判事务状态。