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")创建单库单表的事务上下文:
tc, err := daog.NewTransContext(
datasource, // 数据源
txrequest.RequestWrite, // 事务类型
"trace-id-123", // 链路追踪 ID
)
if err != nil {
return err
}创建支持分库分表的事务上下文:
tc, err := daog.NewTransContextWithSharding(
datasource, // 数据源
txrequest.RequestWrite, // 事务类型
"trace-id-123", // 链路追踪 ID
tableShardingKey, // 分表键(可为 nil)
datasourceShardingKey, // 分库键(可为 nil)
)使用 AutoTrans 和 AutoTransWithResult 可以自动管理事务的创建、提交、回滚。
用于不需要返回值的操作:
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 会触发提交
},
)用于需要返回值的操作:
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
},
)- 调用创建函数创建
TransContext - 执行业务逻辑函数
- 如果业务函数返回
nil,提交事务 - 如果业务函数返回错误或发生 panic,回滚事务
- 无论成功或失败,都会释放数据库连接
如果不使用自动事务管理,必须手动处理事务的提交/回滚和资源释放。
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
}- 提前定义 err 变量: 在 defer 之前定义
var err error - 使用 defer: 必须通过 defer 调用
CompleteWithPanic - 使用 = 赋值: 后续操作使用
=而非:=,确保 err 变量被正确捕获 - 处理 panic:
CompleteWithPanic会处理 panic 情况
func (tc *TransContext) CompleteWithPanic(e error, fetal any)参数说明:
e: 业务逻辑返回的错误fetal:recover()的返回值
行为:
- 如果
fetal != nil(发生 panic),回滚事务并重新 panic - 如果
e != nil,回滚事务 - 如果
e == nil,提交事务 - 最后释放数据库连接
func (tc *TransContext) Complete(e error)Complete 不处理 panic 情况,建议使用 CompleteWithPanic。
控制是否记录 SQL 日志:
tc, _ := daog.NewTransContext(datasource, txrequest.RequestWrite, "trace-id")
// 临时关闭 SQL 日志
tc.LogSQL = false
// 临时开启 SQL 日志
tc.LogSQL = true扩展信息,可用于传递自定义数据:
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,
)traceId := daog.GetTraceIdFromContext(tc.ctx)goid := daog.GetGoroutineIdFromContext(tc.ctx)// 推荐
err := daog.AutoTrans(tcCreate, func(tc *daog.TransContext) error {
// 业务逻辑
return nil
})// 查询操作
tc, _ := daog.NewTransContext(datasource, txrequest.RequestReadonly, "trace-id")// 使用有意义的 Trace ID,便于日志追踪
tc, _ := daog.NewTransContext(datasource, txrequest.RequestWrite, requestId)事务持有连接期间,其他请求可能无法获取连接。尽量减少事务内的操作时间。
var err error // 统一使用一个 err 变量
defer func() {
tc.CompleteWithPanic(err, recover())
}()