

新闻资讯
技术教程Go中database/sql错误判断需区分sql.ErrNoRows等预期错误与其他系统错误,事务Rollback()必须检查返回值,context超时错误应归类为临时故障,自定义错误类型比字符串匹配更可靠。
database/sql 的错误判断不能只看 err != nil
很多刚写 Go 数据库逻辑的人会直接写 if err != nil 就 panic 或返回,但这样会漏掉关键状态:比如查询无结果、连接断开、事务已提交失败等。Go 的 database/sql 包把“业务无数据”和“系统出错”都塞进 error 接口,必须区分。
sql.ErrNoRows 是唯一可预期的“非错误错误”,表示 QueryRow 没查到任何行 —— 它不是 bug,通常该走空值逻辑(如赋默认值或返回零结构)driver: bad connection、context deadline exceeded)才需要重试、记录或中断流程errors.Is(err, sql.ErrNoRows) 判断后还继续用扫描变量,因为 Scan() 本身已失败,变量未被赋值tx.Rollback() 必须检查其返回值tx.Rollback() 不是“一定会成功”的兜底操作 —— 如果事务早已因网络中断、数据库崩溃或超时被服务端自动清理,再次调用 Rollback() 可能返回新错误(如 sql: transaction has already been committed or rolled back)。忽略它会导致误判事务状态。
Rollback() 的 error:if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
log.Printf("rollback failed: %v", err)
}sql.ErrTxDone 是唯一可安全忽略的 rollback 错误,表示事务确实已经结束(无论 commit 还是 rollback)tx.Rollback(),除非你明确知道事务还没 commit,否则可能覆盖真正的 commit 结果context.Context 控制查询生命周期,但注意驱动兼容性传入 ctx 是防止查询卡死的最有效方式,但不同驱动对 context 的支持程度不一。比如 mysql 驱动从 v1.7+ 才完整支持 cancel;postgres 的 pgx 默认支持,但原生 lib/pq 已归档且部分 context 行为不一致。
db.QueryContext(ctx, ...) 替代 db.Query(...),并确保 ctx 带 timeout:ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id)
context deadline exceeded 或驱动特有提示(如 MySQL: driver: query canceled due to context deadline),这类错误应归类为临时故障,适合重试mattn/go-sqlite3)不响应 cancel,context 在那里只是摆设,需靠语句级 timeout(如 PRAGMA busy_timeout)补位用 strings.Contains(err.Error(), "duplicate key") 判断唯一约束冲突,既脆弱又难维护 —— 字段名、驱动版本、语言环境都可能导致错误信息变化。应该把数据库错误映射到应用层语义错误。
立即学习“go语言免费学习笔记(深入)”;
23505)稳定,可用 pgx 的 pgconn.PgError 提取:var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return ErrDuplicateEmail
}1062)也可通过 mysql.MySQLError 类型断言获取,比字符串安全得多ErrNotFound、ErrConflict),上层不用关心底层驱动细节实际项目里最容易被跳过的,是 rollback 后对错误的再判断,以及把 SQL 层错误硬编码进业务分支。这两处一旦出问题,日志看不出原因,监控也抓不到异常路径。