应使用 errors.Is(err, sql.ErrNoRows) 判断,因驱动常包装错误导致 == 失效;事务需显式 Rollback/Commit 且仅一次;QueryRow.Scan 错误语义不同于 Exec,前者空结果返回 ErrNoRows,后者非 nil 错误表示执行失败。

判断 sql.ErrNoRows 要用 errors.Is,不是 ==
直接用 err == sql.ErrNoRows 在大多数情况下会失效,因为数据库驱动(如 pgx、mysql)返回的错误常是包装后的类型。Go 1.13+ 的标准做法是用 errors.Is 判断底层是否为该错误:
err := db.QueryRow("SELECT name FROM users WHERE id = $1", 123).Scan(&name)
if errors.Is(err, sql.ErrNoRows) {
// 处理未找到
} else if err != nil {
// 其他错误,如连接失败、语法错等
}
-
sql.ErrNoRows是哨兵错误(sentinel error),但仅当驱动未包装时才可直接比较 - 使用
errors.As可提取具体驱动错误(比如*pq.Error或*mysql.MySQLError)来获取 SQLState 或 errno - 别在日志里只打
err.Error()就完事——它可能不包含原始 SQL,建议同时记录查询语句片段和参数
事务中遇到错误必须显式 tx.Rollback(),不能依赖 defer
常见写法 defer tx.Rollback() 看似安全,但容易掩盖成功提交后的 panic,或在已 Commit() 后重复调用导致 panic("sql: transaction has already been committed or rolled back")。
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
_, err = tx.Exec("INSERT INTO orders (...) VALUES (...)", ...)
if err != nil {
tx.Rollback() // 显式回滚
return err
}
return tx.Commit()
- 所有分支路径(包括
return前)都必须确保Rollback()或Commit()被调用且仅调用一次 - 不要把
tx传给其他函数再让它决定是否提交——事务生命周期应由入口函数严格控制 - 若需嵌套逻辑,用函数返回
error并由外层统一处理回滚,而非让子函数调用tx.Rollback()
db.QueryRow().Scan() 和 db.Exec() 的错误语义完全不同
QueryRow 类操作的错误只表示“查询执行失败”,不表示“没查到数据”;而 Exec 的错误才真正代表语句未生效。混淆这两者会导致误判业务状态。
使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888
-
db.QueryRow(...).Scan(&v):返回sql.ErrNoRows表示查询成功但结果集为空;返回其他错误(如"pq: invalid input syntax")才是执行失败 -
db.Exec(...):只要返回非nil错误,就代表该语句**完全未被数据库执行**(例如约束冲突、权限不足、SQL 语法错) -
db.Prepare()错误发生在预编译阶段,与参数无关;而stmt.Exec(args...)的错误才和参数及运行时状态相关 - 批量插入用
db.Exec("INSERT ... VALUES (...), (...), ...")比循环调用Exec更高效,但出错时无法定位哪一行失败
事务隔离级别和上下文超时要按需设置,不是越严越高
默认事务隔离级别因驱动而异(pgx 默认 ReadCommitted,MySQL 默认 RepeatableRead),盲目设成 Serializable 会显著降低并发度,甚至引发死锁。
立即学习“go语言免费学习笔记(深入)”;
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
ReadOnly: false,
})
- 读多写少场景(如报表生成)可用
ReadOnly: true提示驱动优化 - 超时必须设在
BeginTx的context中,而不是靠后续每个Query单独设——事务级锁可能卡住整个流程 - PostgreSQL 的
serializable事务失败时会返回"ERROR: could not serialize access due to read/write dependencies",需捕获并重试,Go 标准库不自动重试
"i/o timeout" 可能需要重试,而主键冲突则应直接返回用户。错误分类得靠 errors.As 提取驱动原生错误类型,再结合业务语义判断。









