应使用 errors.Is(err, sql.ErrNoRows) 判断未查到记录,因 Go 1.13+ 错误可能被包装,== 不可靠;QueryRow().Scan() 返回 sql.ErrNoRows 表示无结果,其他错误需单独处理。

判断 sql.ErrNoRows 要用 errors.Is,别用 ==
查一条记录时,Row.Scan() 失败很常见,但只有 sql.ErrNoRows 表示“没查到”,其他错误(如连接断开、类型不匹配)必须单独处理。直接用 err == sql.ErrNoRows 在 Go 1.13+ 会失效,因为底层可能返回包装后的错误。
- ✅ 正确写法:
if errors.Is(err, sql.ErrNoRows) { // 记录未找到,不是异常 } - ❌ 错误写法:
if err == sql.ErrNoRows { /* 不可靠 */ } -
errors.Is能穿透fmt.Errorf("failed to query: %w", err)这类包装,==不能 - 如果只关心“是否为 no rows”,不要用
errors.As——它用于提取具体错误类型,这里不需要
DB.QueryRow() 和 DB.Query() 的错误时机完全不同
QueryRow() 的 Err() 只在扫描阶段触发(比如 Scan() 时字段数不匹配),而 Query() 的错误在调用时就返回(比如 SQL 语法错、表不存在)。很多人误以为两者都“执行完才报错”,结果漏掉早期失败。
-
QueryRow().Scan():SQL 执行成功但结果为空 →sql.ErrNoRows;扫描失败(如int字段扫进string)→ 具体类型错误 -
Query():SQL 解析/执行失败 → 立即返回非nilerr;否则返回*Rows,需手动rows.Close() - 忘记
rows.Close()会导致连接泄漏,尤其在循环中调用Query()时极易出问题 - 建议用
defer rows.Close(),但注意:如果rows是nil(Query()已返回错误),直接 defer 会 panic,要先判空
事务中错误未回滚就 return,连接会卡住
用 tx, err := db.Begin() 启动事务后,只要没显式 tx.Commit() 或 tx.Rollback(),该连接就会一直被占用,直到超时或进程退出。常见错误是某个分支忘了 Rollback() 就直接 return。
- ✅ 推荐模式:
tx, err := db.Begin() if err != nil { return err } defer func() { if r := recover(); r != nil { tx.Rollback() panic(r) } }() if err := doSomething(tx); err != nil { tx.Rollback() return err } return tx.Commit() - ⚠️ 注意:不能只靠
defer tx.Rollback(),否则成功时也会回滚。必须在 commit 前取消 defer,或用标记变量控制 - 如果事务里调用了多个函数,每个函数内部都不该自行
Commit/Rollback,责任必须集中在外层 -
tx.QueryRow()等方法返回的错误不会自动回滚事务,必须手动处理
驱动特定错误码(如 MySQL 1062)要用 errors.As 提取
唯一键冲突、锁等待超时这类数据库原生错误,在不同驱动中类型不同(*mysql.MySQLError、*pq.Error),无法用通用 error 值判断。硬写字符串匹配(如检查 err.Error() 是否含 "Duplicate entry")脆弱且不可靠。
立即学习“go语言免费学习笔记(深入)”;
- MySQL 场景下提取错误码:
var myErr *mysql.MySQLError if errors.As(err, &myErr) && myErr.Number == 1062 { // 处理重复键 } - PostgreSQL 对应:
var pgErr *pq.Error if errors.As(err, &pgErr) && pgErr.Code == "23505" { // unique_violation } - 这些驱动类型需显式导入:
github.com/go-sql-driver/mysql或github.com/lib/pq - 不要试图把所有数据库错误统一成一个 switch,驱动差异太大,按需适配更稳
实际项目里最常踩的坑不是语法写错,而是把“错误发生位置”和“错误该由谁处理”搞混:查询逻辑认为“没数据=正常”,事务外围却没兜住执行失败;或者日志里只打 err.Error(),丢掉了原始错误链里的上下文。留心错误传播路径比记住所有 error 类型更重要。










