必须检查tx.Commit()返回的错误,它可能为sql.ErrTxDone、驱动自定义错误或连接断开误报成功;日志无错但数据未写入是常见陷阱,需立即记录并处理非nil错误。

事务提交失败时,tx.Commit() 返回的错误到底要不要检查
必须检查,而且不能只看 tx.Commit() 是否为 nil —— 它可能返回 sql.ErrTxDone、驱动自定义错误,甚至底层连接已断开却误报“成功”。常见现象是日志里没报错,但数据没写入,还以为事务自动回滚了。
实操建议:
-
tx.Commit()返回非nil错误时,应立刻记录并处理,不要假设它只是“重复提交” - 如果
tx.Commit()报sql.ErrTxDone,说明事务已被显式回滚或 panic 触发了自动回滚,此时再调tx.Rollback()会返回该错误,属于正常流程 - 某些数据库(如 MySQL)在事务中执行了 DDL(如
ALTER TABLE),会隐式提交当前事务,后续Commit()实际无效,但不会报错——这种静默行为需靠业务逻辑规避
defer tx.Rollback() 在 panic 场景下是否可靠
不可靠。Go 的 defer 只在函数 return 前执行,而 panic 会中断当前函数并向上冒泡;若 panic 发生在 defer 注册之后、函数返回之前,且未被 recover,则 tx.Rollback() 仍会执行——但前提是该 defer 语句已注册成功。
问题在于:如果 tx.Begin() 后立即 panic(比如结构体字段空指针),defer tx.Rollback() 根本没机会注册。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 把
tx.Rollback()放在明确的 error 分支里,而不是全靠 defer - 若坚持用 defer,务必确保它紧跟在
tx := db.Begin()之后、任何可能 panic 的操作之前 - 对关键事务,可加一层
recover()捕获 panic 并手动 Rollback,但注意:recover 仅对当前 goroutine 有效
事务中执行多条 SQL,某条出错后还能继续执行吗
不能。SQL 层面,大多数数据库(PostgreSQL、MySQL 默认模式)在语句出错后会将事务置于“失败状态”,后续语句即使语法正确也会被拒绝,直到 rollback 或 commit(后者通常直接报错)。Go 的 sql.Tx 本身不拦截后续操作,但底层驱动会返回类似 "pq: current transaction is aborted, commands ignored until end of transaction block" 的错误。
实操建议:
- 别指望“跳过失败语句继续跑”,事务是原子单元,中间出错就该终止并回滚
- 如果业务确实需要部分成功(比如批量导入容忍单条失败),应在应用层拆成多个独立事务,或改用保存点(
SAVEPOINT),但 Go 标准库不支持,需手拼 SQL 或换驱动(如 pgx) - 使用
db.QueryRow()等方法时,记得检查返回的err,不要只检查 scan 结果
用 sql.Open() 创建的 *sql.DB 连接池,会影响事务的错误传播吗
不影响事务本身的错误语义,但会影响错误的“可见性”和“时机”。连接池复用连接,如果一个连接在事务中途被其他 goroutine 关闭(如调用了 db.Close()),后续 tx.Commit() 可能返回 "sql: database is closed",而非原始业务错误。
更隐蔽的是:连接池中的坏连接(如网络闪断后未及时探测)可能让 tx.Query() 返回 "i/o timeout" 或 "connection refused",这类错误容易被当成临时故障重试,但事务状态其实已不可控。
实操建议:
- 避免全局共享
*sql.DB实例的同时随意调用Close() - 设置合理的
SetConnMaxLifetime()和SetMaxOpenConns(),减少坏连接残留 - 对事务内关键操作的错误,区分是 SQL 逻辑错误(如唯一键冲突)还是连接级错误(如超时、关闭),前者可重试事务,后者应放弃并告警










