跳到主要内容

事务处理

问题

Go 中如何正确管理数据库事务?有哪些最佳实践?

答案

database/sql 事务

func transfer(ctx context.Context, db *sql.DB, from, to int, amount float64) error {
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted, // 隔离级别
})
if err != nil {
return err
}
// 确保事务不会挂起
defer tx.Rollback()

// 扣款
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
amount, from, amount,
)
if err != nil {
return err
}

// 入账
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, to,
)
if err != nil {
return err
}

return tx.Commit()
}

事务封装模式

// 通用事务包装函数
func WithTransaction(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()

if err := fn(tx); err != nil {
return err
}
return tx.Commit()
}

// 使用
err := WithTransaction(ctx, db, func(tx *sql.Tx) error {
// 事务内的操作
_, err := tx.ExecContext(ctx, "INSERT ...", args...)
return err
})

GORM 事务

// 自动事务(推荐)
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err // 自动回滚
}
if err := tx.Create(&orderItems).Error; err != nil {
return err // 自动回滚
}
return nil // 自动提交
})

// 嵌套事务(使用 SavePoint)
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user)

tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&post) // SavePoint
return errors.New("inner fail") // 只回滚 post
})

return nil // user 仍然提交
})

事务最佳实践

事务注意事项
  1. 事务尽量短——持有锁的时间越短越好
  2. 不要在事务中做网络调用——调用外部 API、发消息等应在事务外
  3. defer Rollback——确保异常时事务能正确回滚
  4. 注意读写顺序——先写后读可能更新锁升级
  5. Context 超时——设置合理的事务超时

常见面试问题

Q1: 事务中 defer tx.Rollback() 和 tx.Commit() 不冲突吗?

答案:不冲突。tx.Commit() 后再调用 tx.Rollback()no-op(什么都不做,返回 sql.ErrTxDone)。这是一种防御性编程——确保无论执行路径如何,事务都不会挂起。

Q2: Go 如何实现分布式事务?

答案:Go 没有内置分布式事务支持。常用方案:

  • Saga 模式:每步操作搭配补偿操作
  • 本地消息表:事务 + 消息表保证一致性
  • DTM 框架:Go 的分布式事务框架,支持 TCC、Saga、XA

相关链接