recover无法实现数据库事务回滚,它仅捕获panic且不感知数据库状态,也不触发ROLLBACK;正确做法是用defer确保tx.Rollback()执行,recover仅用于日志记录。

Go 里 recover 不能替代事务回滚
直接说结论:recover 无法实现数据库事务回滚。它只捕获 panic,不感知数据库状态,也不触发 ROLLBACK。很多开发者误以为“panic → recover → 回滚”,结果事务已提交或部分提交,数据不一致。
为什么 recover 和事务回滚根本不是一回事
recover 是 Go 运行时机制,用于从 panic 中恢复执行流;而事务回滚是数据库层面的显式操作(如执行 ROLLBACK 或调用驱动的 Tx.Rollback())。两者作用域、时机、语义完全不同。
- panic 可能发生在 SQL 执行前(比如参数校验失败),此时还没开启事务,根本无“回滚”可言
- panic 发生在
tx.Commit()之后,事务早已提交,recover挽不回数据 - 即使 panic 出现在
tx.Exec()后、Commit()前,recover也只是让代码继续跑,但tx对象仍处于 open 状态——你必须手动调用tx.Rollback(),否则连接泄漏,事务锁不释放
正确做法:用 defer + 显式 Rollback,配合 recover 仅作日志兜底
事务安全的核心是“无论是否 panic,都确保 Rollback 被调用”。靠 defer,不是 recover。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
// 正确模式:defer Rollback,recover 仅记录 panic
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
// 记录 panic,但不依赖它来 rollback
log.Printf("panic in tx: %v", r)
}
// 关键:无论 panic 还是正常 return,都尝试 Rollback
// 注意:如果已 Commit,Rollback 会返回 sql.ErrTxDone,可忽略
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
log.Printf("rollback failed: %v", err)
}
}()
// 业务逻辑:可能 panic
doSomethingThatMayPanic()
// 成功则 Commit
return tx.Commit()
-
defer tx.Rollback()必须放在db.Begin()后立即写,且不能被条件跳过 -
recover在 defer 函数里只做日志,绝不用于控制回滚逻辑 -
tx.Rollback()对已提交事务返回sql.ErrTxDone,需用errors.Is(err, sql.ErrTxDone)判断并忽略 - 不要在 defer 里直接
panic或重抛,否则上层无法感知原始错误
更健壮的封装:用函数接收 *sql.Tx 并统一处理
重复写 defer + recover 容易漏,建议抽成工具函数:
立即学习“go语言免费学习笔记(深入)”;
func WithTx(db *sql.DB, fn func(*sql.Tx) error) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
log.Printf("panic during tx: %v", r)
}
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
log.Printf("tx rollback error: %v", err)
}
}()
if err := fn(tx); err != nil {
return err
}
return tx.Commit()
}
// 使用
err := WithTx(db, func(tx *sql.Tx) error {
_, err := tx.Exec("INSERT INTO users(name) VALUES(?)", name)
return err
})
注意:这个封装仍不解决嵌套事务、Savepoint 或上下文取消问题。真要支持复杂事务控制,得用 sql.TxOptions、context.Context 传入,以及显式判断 ctx.Err() 后主动 Rollback——这些和 recover 依然无关。

















