Go中db.Exec和tx.Commit的错误必须显式检查,因database/sql不自动panic,忽略error会导致事务未回滚、数据不一致;每步操作及Commit均需判错,Rollback须在出错时调用且仅一次。

Go 中 db.Exec 和 tx.Commit 的错误必须显式检查
Go 的 database/sql 不会自动 panic 或中断执行,所有数据库操作返回的 error 都必须手动判断。忽略 err 是导致事务未回滚、数据不一致的最常见原因。
典型错误写法:_, _ = db.Exec("INSERT ...") —— 这等于主动丢弃错误,后续也无法回滚。
正确做法是逐层检查,尤其在事务中:
-
tx, err := db.Begin()后必须检查err != nil - 每条
tx.QueryRow/tx.Exec后都要检查err -
tx.Commit()本身也可能失败(如连接断开、约束冲突),也得检查
事务回滚必须调用 tx.Rollback(),且只应在出错时调用一次
tx.Rollback() 不是“撤销已执行语句”,而是向数据库发送 ROLLBACK 命令,终止当前事务并释放锁。它本身也会返回 error(例如连接已关闭),但通常可忽略——只要确保它被调用了。
立即学习“go语言免费学习笔记(深入)”;
常见误区:
使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888
- 在
defer tx.Rollback()后忘记在成功路径上return,导致无论成败都回滚 - 多次调用
tx.Rollback(),第二次会返回"sql: transaction has already been committed or rolled back" - 用
recover()捕获 panic 后未检查事务状态,直接继续 Commit
推荐结构:
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
if _, err := tx.Exec("INSERT ..."); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
PostgreSQL 和 MySQL 对事务失败的响应差异影响错误处理逻辑
不同驱动对同一类错误的封装不同,比如唯一约束冲突:
- MySQL 驱动(
github.com/go-sql-driver/mysql)通常返回*mysql.MySQLError,可用err.(*mysql.MySQLError).Number判断错误码(如 1062) - PostgreSQL 驱动(
github.com/lib/pq)返回*pq.Error,需检查err.(*pq.Error).Code(如"23505"表示唯一违反) - SQLite 驱动(
github.com/mattn/go-sqlite3)错误码为整数,可通过err.(sqlite3.Error).Code获取
跨数据库兼容性差,建议按驱动类型做类型断言,而不是依赖错误字符串匹配。
使用 sql.Tx 时不要混用 db 和 tx 的查询
一旦开始事务,所有相关操作必须走 tx 对象。混用 db.Query 和 tx.Exec 会导致:
- 读取不到未提交的变更(脏读被隔离)
- 更新丢失(
db.Exec在另一个事务中提交,覆盖了tx中的意图) - 死锁风险升高(两个事务交叉持有不同行锁)
尤其注意日志、审计或中间件中隐式调用 db 查询的情况。事务边界要清晰,避免在 tx 执行中途切到全局 db。
error 类型和含义都不一样,不能靠一个 if err != nil 统一处理。









