GORM 的 db.Transaction 不支持自动重试,仅回滚并退出;手动重试需限定错误类型、次数(建议≤3次)、指数退避,并每次新建事务。

db.Transaction 本身不支持自动重试
GORM 的 db.Transaction 函数只负责捕获 panic 并回滚,它不会重试、不感知业务错误(比如校验失败、库存不足)、也不重试网络超时或死锁。你写 db.Transaction(func(tx *gorm.DB) error { ... }),一旦函数返回非 nil error 或 panic,事务就回滚并退出——仅此而已。
手动实现重试必须控制重试次数和错误类型
自动重试不是“包一层 for 循环”就行,重点是:什么错误值得重试?重几次够?怎么避免重复执行副作用?
- 只对可重试的底层错误重试:如
driver.ErrBadConn、sql.ErrTxDone、mysql.ErrDeadlock(MySQL)、"deadlock detected"(PostgreSQL) - 绝对不要重试业务逻辑错误:比如
errInventoryShortage、errUserNotFound,重试只会让问题更糟 - 重试前必须清空状态:每次重试都应新建事务,不能复用已失败的
tx实例;否则会 panic 报"invalid transaction" - 建议上限 3 次,配合指数退避(如 10ms → 30ms → 100ms),避免雪崩
典型死锁重试示例(带退避)
func transferWithRetry(db *gorm.DB, fromID, toID uint, amount int) error {
var err error
for i := 0; i if err == nil {
return nil
}
// 只对死锁错误重试,其他错误直接返回
if isDeadlockError(err) {
if i <p>}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2662" title="GORM框架 1.30.3"><img
src="https://img.php.cn/upload/manual/001/589/237/6a7d710f658be447.png" alt="GORM框架 1.30.3" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2662" title="GORM框架 1.30.3" class="overflowclass">GORM框架 1.30.3</a>
<p class="overflowclass">GORM框架 1.30.3版本源码包下载,版本号 1.30.3,适合在 1.30 主线早期节点做源码留档、依赖回放和升级前后行为对照。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2662" title="GORM框架 1.30.3" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>func isDeadlockError(err error) bool {
return strings.Contains(err.Error(), "deadlock") ||
strings.Contains(err.Error(), "ErrDeadlock") ||
err == mysql.ErrDeadlock
}
</p>
容易被忽略的关键点
重试逻辑里最常漏掉的是:事务外的副作用没做幂等防护。比如你在事务成功后发 MQ 消息或调 HTTP 回调,重试会导致消息重复、通知发两次。正确做法是把这类 I/O 移到 tx.AfterCommit() 钩子里,并确保回调本身支持幂等(例如带上唯一 business_id)。另外,tx.AfterCommit() 只对当前事务实例生效,如果重试时用了新 tx,旧钩子就丢了——所以钩子注册必须放在每次事务函数体内,不能提前提前定义。










