如何在Golang中处理数据库操作错误_Golang数据库操作错误与回滚方法

7次阅读

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

如何在Golang中处理数据库操作错误_Golang数据库操作错误与回滚方法

Go 中 db.Exectx.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语言免费学习笔记(深入)”;

常见误区:

  • 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()

postgresqlmysql 对事务失败的响应差异影响错误处理逻辑

不同驱动对同一类错误的封装不同,比如唯一约束冲突:

  • 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 时不要混用 dbtx 的查询

一旦开始事务,所有相关操作必须走 tx 对象。混用 db.Querytx.Exec 会导致:

  • 读取不到未提交的变更(脏读被隔离)
  • 更新丢失(db.Exec 在另一个事务中提交,覆盖了 tx 中的意图)
  • 死锁风险升高(两个事务交叉持有不同行锁)

尤其注意日志、审计或中间件中隐式调用 db 查询的情况。事务边界要清晰,避免在 tx 执行中途切到全局 db

事务真正生效前,错误可能发生在任意环节:连接获取、预处理、执行、网络传输、Commit 协议。每个环节的 error 类型和含义都不一样,不能靠一个 if err != nil 统一处理。

text=ZqhQzanResources