ORM 事务:g.DB().Transaction 用法与错误回滚

引言

转账要"扣款 + 入账"同时成功或同时失败,这就是事务。gdb 提供 g.DB().Transaction(ctx, fn) 的事务封装:回调里出错自动回滚,全部成功自动提交。

概念

  • 事务入口:g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {...})
  • 事务内操作:用回调参数 tx 的 Model 执行读写(tx.Model("表")...),保证 SQL 走同一事务连接。
  • 提交/回滚规则:回调返回 nil 则提交;返回非 nil 错误则回滚,并把错误原样返回给调用方。
  • 嵌套事务:框架支持 SavePoint 方式的嵌套事务,细节以官方文档 gdb 事务章节为准。

代码示例

经典场景:给订单加一条记录,同时扣减库存,两步必须同生共死。

package main

import (
	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	ctx := gctx.New()

	err := g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		// 1. 写订单表
		result, err := tx.Model("order").Ctx(ctx).
			Data(g.Map{
				"user_id":  1,
				"goods_id": 100,
				"amount":   88.5,
			}).
			Insert()
		if err != nil {
			return err // 出错即回滚
		}
		orderId, _ := result.LastInsertId()
		fmt.Println("order id =", orderId)

		// 2. 扣减库存(注意 where 中带上数量条件做乐观校验)
		result, err = tx.Model("goods").Ctx(ctx).
			Data(g.Map{"stock": gdb.Raw("stock - 1")}).
			Where("id", 100).
			Where("stock > ?", 0).
			Update()
		if err != nil {
			return err // 出错即回滚
		}
		affected, _ := result.RowsAffected()
		if affected == 0 {
			// 库存不足:返回错误触发整体回滚
			return gerror.New("库存不足")
		}
		return nil // 全部成功,提交
	})

	if err != nil {
		fmt.Println("事务失败,已回滚:", err)
		return
	}
	fmt.Println("事务提交成功")
}

其中 gdb.Raw("stock - 1") 表示把该值原样作为 SQL 片段,而不是当作字符串参数,适合列自增/自减场景。

事务中查询也要走 tx,否则查不到未提交的数据(隔离级别内的一致性):

err := g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
	one, err := tx.Model("order").Ctx(ctx).Where("user_id", 1).One()
	if err != nil {
		return err
	}
	_ = one
	return nil
})

注意点

  • 回调里只允许用 tx 派生的 Model 操作本事务;用全局 g.DB().Model(...) 会脱离事务。
  • 事务内尽量短:别在回调里做慢查询、外部 HTTP 调用,连接会被长时间占用。
  • 库存/余额这类场景建议加条件更新(Where("stock > ?", 0))做并发保护,回滚救不了逻辑 bug。
  • 事务方法返回的错误自带上下文,可在外层用 gerror 再包装一层说明。

小结

Transaction(ctx, fn) 用"回调返回 nil 提交、返回 error 回滚"的约定,把事务复杂度收敛在一处,配合 tx.Model 保证操作归属同一连接。嵌套事务、隔离级别等高级话题以官方文档 goframe.org 的 ORM 事务章节为准。

笔记加载中…