Skip to content

Latest commit

 

History

History
363 lines (270 loc) · 8.24 KB

File metadata and controls

363 lines (270 loc) · 8.24 KB

事务管理

daog 通过 TransContext (事务上下文) 管理数据库事务。所有数据库操作都需要在事务上下文中执行。

事务类型

daog 支持三种事务类型,定义在 txrequest 包中:

类型 常量 说明
无事务 txrequest.RequestNone 0 每条 SQL 独立执行,不保证原子性
只读事务 txrequest.RequestReadonly 1 只读操作,数据库可优化性能
读写事务 txrequest.RequestWrite 2 支持读写操作,保证 ACID

选择事务类型

import txrequest "github.com/rolandhe/daog/tx"

// 只读操作(查询)使用 RequestReadonly
tc, err := daog.NewTransContext(datasource, txrequest.RequestReadonly, "trace-id")

// 写操作(插入、更新、删除)使用 RequestWrite
tc, err := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")

// 不需要事务保证时使用 RequestNone
tc, err := daog.NewTransContext(datasource, txrequest.RequestNone, "trace-id")

创建事务上下文

NewTransContext

创建单库单表的事务上下文:

tc, err := daog.NewTransContext(
    datasource,                // 数据源
    txrequest.RequestWrite,    // 事务类型
    "trace-id-123",           // 链路追踪 ID
)
if err != nil {
    return err
}

NewTransContextWithSharding

创建支持分库分表的事务上下文:

tc, err := daog.NewTransContextWithSharding(
    datasource,                // 数据源
    txrequest.RequestWrite,    // 事务类型
    "trace-id-123",           // 链路追踪 ID
    tableShardingKey,         // 分表键(可为 nil)
    datasourceShardingKey,    // 分库键(可为 nil)
)

自动事务管理(推荐)

使用 AutoTransAutoTransWithResult 可以自动管理事务的创建、提交、回滚。

AutoTrans

用于不需要返回值的操作:

err := daog.AutoTrans(
    // 事务创建函数
    func() (*daog.TransContext, error) {
        return daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    },
    // 业务逻辑函数
    func(tc *daog.TransContext) error {
        // 执行数据库操作
        _, err := dal.UserInfoDao.Insert(tc, user)
        if err != nil {
            return err  // 返回错误会触发回滚
        }

        _, err = dal.OrderDao.Insert(tc, order)
        if err != nil {
            return err  // 返回错误会触发回滚
        }

        return nil  // 返回 nil 会触发提交
    },
)

AutoTransWithResult

用于需要返回值的操作:

user, err := daog.AutoTransWithResult(
    func() (*daog.TransContext, error) {
        return daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    },
    func(tc *daog.TransContext) (*dal.UserInfo, error) {
        user := &dal.UserInfo{Name: "张三"}
        _, err := dal.UserInfoDao.Insert(tc, user)
        if err != nil {
            return nil, err
        }
        return user, nil
    },
)

自动事务的工作原理

  1. 调用创建函数创建 TransContext
  2. 执行业务逻辑函数
  3. 如果业务函数返回 nil,提交事务
  4. 如果业务函数返回错误或发生 panic,回滚事务
  5. 无论成功或失败,都会释放数据库连接

手动事务管理

如果不使用自动事务管理,必须手动处理事务的提交/回滚和资源释放。

基本模式

func doSomething() error {
    var err error  // 必须在 defer 之前定义

    tc, err := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    if err != nil {
        return err
    }

    // 必须使用 defer 确保事务完成
    defer func() {
        tc.CompleteWithPanic(err, recover())
    }()

    // 执行业务逻辑,注意使用 = 而非 :=
    _, err = dal.UserInfoDao.Insert(tc, user)
    if err != nil {
        return err
    }

    _, err = dal.OrderDao.Insert(tc, order)
    if err != nil {
        return err
    }

    return nil
}

关键点说明

  1. 提前定义 err 变量: 在 defer 之前定义 var err error
  2. 使用 defer: 必须通过 defer 调用 CompleteWithPanic
  3. 使用 = 赋值: 后续操作使用 = 而非 :=,确保 err 变量被正确捕获
  4. 处理 panic: CompleteWithPanic 会处理 panic 情况

CompleteWithPanic

func (tc *TransContext) CompleteWithPanic(e error, fetal any)

参数说明:

  • e: 业务逻辑返回的错误
  • fetal: recover() 的返回值

行为:

  • 如果 fetal != nil(发生 panic),回滚事务并重新 panic
  • 如果 e != nil,回滚事务
  • 如果 e == nil,提交事务
  • 最后释放数据库连接

Complete(已废弃)

func (tc *TransContext) Complete(e error)

Complete 不处理 panic 情况,建议使用 CompleteWithPanic

TransContext 属性

LogSQL

控制是否记录 SQL 日志:

tc, _ := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")

// 临时关闭 SQL 日志
tc.LogSQL = false

// 临时开启 SQL 日志
tc.LogSQL = true

ExtInfo

扩展信息,可用于传递自定义数据:

tc.ExtInfo = map[string]any{
    "userId":   123,
    "clientIP": "192.168.1.100",
}

错误处理模式

正确的错误处理

func createUserAndOrder(user *dal.UserInfo, order *dal.Order) error {
    var err error

    tc, err := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    if err != nil {
        return err
    }

    defer func() {
        tc.CompleteWithPanic(err, recover())
    }()

    // 使用 = 赋值,确保 err 能被 defer 捕获
    _, err = dal.UserInfoDao.Insert(tc, user)
    if err != nil {
        return err
    }

    order.UserId = user.Id
    _, err = dal.OrderDao.Insert(tc, order)
    if err != nil {
        return err
    }

    return nil
}

错误的写法

// 错误示例:使用 := 创建新变量
func badExample() error {
    var err error

    tc, err := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    if err != nil {
        return err
    }

    defer func() {
        tc.CompleteWithPanic(err, recover())  // 这里的 err 总是 nil!
    }()

    // 错误::= 创建了新的 err 变量
    _, err := dal.UserInfoDao.Insert(tc, user)  // 语法错误,但说明问题
    if err != nil {
        return err
    }

    return nil
}

事务传播

daog 不支持嵌套事务,一个 TransContext 对应一个数据库事务。如果需要在多个函数间共享事务,传递 TransContext 参数:

func businessLogic(tc *daog.TransContext) error {
    if err := createUser(tc); err != nil {
        return err
    }
    if err := createOrder(tc); err != nil {
        return err
    }
    return nil
}

func createUser(tc *daog.TransContext) error {
    _, err := dal.UserInfoDao.Insert(tc, &dal.UserInfo{Name: "张三"})
    return err
}

func createOrder(tc *daog.TransContext) error {
    _, err := dal.OrderDao.Insert(tc, &dal.Order{Amount: 100})
    return err
}

// 使用
err := daog.AutoTrans(
    func() (*daog.TransContext, error) {
        return daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
    },
    businessLogic,
)

上下文信息获取

获取 Trace ID

traceId := daog.GetTraceIdFromContext(tc.ctx)

获取 Goroutine ID

goid := daog.GetGoroutineIdFromContext(tc.ctx)

最佳实践

1. 优先使用自动事务

// 推荐
err := daog.AutoTrans(tcCreate, func(tc *daog.TransContext) error {
    // 业务逻辑
    return nil
})

2. 只读操作使用 RequestReadonly

// 查询操作
tc, _ := daog.NewTransContext(datasource, txrequest.RequestReadonly, "trace-id")

3. 合理设置 Trace ID

// 使用有意义的 Trace ID,便于日志追踪
tc, _ := daog.NewTransContext(datasource, txrequest.RequestWrite, requestId)

4. 避免长事务

事务持有连接期间,其他请求可能无法获取连接。尽量减少事务内的操作时间。

5. 统一错误变量

var err error  // 统一使用一个 err 变量
defer func() {
    tc.CompleteWithPanic(err, recover())
}()

相关文档