Go中database/sql错误判断需区分sql.ErrNoRows等预期错误与其他系统错误,事务Rollback()必须检查返回值,context超时错误应归类为临时故障,自定义错误类型比字符串匹配更可靠。

Go 中 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) - 不要在 defer 中无条件调用
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)
- 若 ctx 超时,错误通常是
context deadline exceeded或驱动特有提示(如MySQL: driver: query canceled due to context deadline),这类错误应归类为临时故障,适合重试 - SQLite 驱动(如
mattn/go-sqlite3)不响应 cancel,context 在那里只是摆设,需靠语句级 timeout(如 PRAGMA busy_timeout)补位
自定义错误类型比字符串匹配更可靠
用 strings.Contains(err.Error(), "duplicate key") 判断唯一约束冲突,既脆弱又难维护 —— 字段名、驱动版本、语言环境都可能导致错误信息变化。应该把数据库错误映射到应用层语义错误。
使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888
立即学习“go语言免费学习笔记(深入)”;
- PostgreSQL 错误码(如
23505)稳定,可用pgx的pgconn.PgError提取:var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return ErrDuplicateEmail } - MySQL 错误号(如
1062)也可通过mysql.MySQLError类型断言获取,比字符串安全得多 - 所有数据库错误最终应转成你应用定义的错误变量(如
ErrNotFound、ErrConflict),上层不用关心底层驱动细节
实际项目里最容易被跳过的,是 rollback 后对错误的再判断,以及把 SQL 层错误硬编码进业务分支。这两处一旦出问题,日志看不出原因,监控也抓不到异常路径。









