事务是数据一致性的保障,但使用不当会带来性能问题和死锁风险。掌握最佳实践,才能用好事务。
事务占用数据库资源,时间越长,风险越大:
// 错误:事务中调用外部服务
func CreateOrder(db *gorm.DB, order Order) error {
return db.Transaction(func(tx *gorm.DB) error {
tx.Create(&order)
// 调用支付接口,可能很慢
if err := paymentService.Pay(order); err != nil {
return err
}
return nil
})
}
// 正确:先完成数据库操作,再调用外部服务
func CreateOrder(db *gorm.DB, order Order) error {
err := db.Transaction(func(tx *gorm.DB) error {
return tx.Create(&order).Error
})
if err != nil {
return err
}
return paymentService.Pay(order)
}
长事务的危害:
// 错误:批量操作放在一个事务
func BatchImport(db *gorm.DB, users []User) error {
return db.Transaction(func(tx *gorm.DB) error {
for _, user := range users {
if err := tx.Create(&user).Error; err != nil {
return err
}
}
return nil
})
}
// 正确:分批处理
func BatchImport(db *gorm.DB, users []User) error {
batchSize := 100
for i := 0; i < len(users); i += batchSize {
end := i + batchSize
if end > len(users) {
end = len(users)
}
batch := users[i:end]
if err := db.Transaction(func(tx *gorm.DB) error {
return tx.Create(&batch).Error
}); err != nil {
return err
}
}
return nil
}
死锁的常见原因是锁顺序不一致:
// 可能死锁
func Transfer(db *gorm.DB, fromID, toID uint, amount float64) error {
return db.Transaction(func(tx *gorm.DB) error {
var from, to Account
tx.First(&from, fromID)
tx.First(&to, toID)
from.Balance -= amount
to.Balance += amount
tx.Save(&from)
tx.Save(&to)
return nil
})
}
如果 A 转给 B 和 B 转给 A 同时执行,可能死锁。
解决方案:按固定顺序锁定:
func Transfer(db *gorm.DB, fromID, toID uint, amount float64) error {
return db.Transaction(func(tx *gorm.DB) error {
ids := []uint{fromID, toID}
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
var accounts []Account
tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("id IN ?", ids).
Find(&accounts)
// 处理转账...
return nil
})
}
事务要有超时限制:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 事务操作
return nil
})
超时后事务会自动回滚。
正确处理事务错误:
func CreateOrder(db *gorm.DB, order Order) error {
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Commit().Error; err != nil {
return err
}
return nil
}
或者用闭包:
func CreateOrder(db *gorm.DB, order Order) error {
return db.Transaction(func(tx *gorm.DB) error {
return tx.Create(&order).Error
})
}
只读操作不需要事务:
// 不必要的事务
func GetUser(db *gorm.DB, id uint) (*User, error) {
var user User
err := db.Transaction(func(tx *gorm.DB) error {
return tx.First(&user, id).Error
})
return &user, err
}
// 直接查询
func GetUser(db *gorm.DB, id uint) (*User, error) {
var user User
err := db.First(&user, id).Error
return &user, err
}
不同场景选择不同隔离级别:
// 报表统计:读已提交即可
db.Begin(&sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
// 金融交易:可重复读
db.Begin(&sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
})
// 高一致性要求:串行化
db.Begin(&sql.TxOptions{
Isolation: sql.LevelSerializable,
})
隔离级别越高,并发性能越低。
某些错误可以重试:
func WithRetry(db *gorm.DB, fn func(tx *gorm.DB) error, maxRetries int) error {
for i := 0; i < maxRetries; i++ {
err := db.Transaction(fn)
if err == nil {
return nil
}
if isRetryableError(err) {
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
continue
}
return err
}
return errors.New("重试次数耗尽")
}
func isRetryableError(err error) bool {
return strings.Contains(err.Error(), "deadlock") ||
strings.Contains(err.Error(), "lock wait timeout")
}
事务操作要有日志:
func CreateOrder(db *gorm.DB, order Order) error {
start := time.Now()
err := db.Transaction(func(tx *gorm.DB) error {
return tx.Create(&order).Error
})
duration := time.Since(start)
if err != nil {
log.Printf("事务失败: %v, 耗时: %v", err, duration)
} else {
log.Printf("事务成功, 耗时: %v", duration)
}
return err
}
监控事务相关指标:
var transactionDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "transaction_duration_seconds",
Help: "事务执行时间",
},
[]string{"status"},
)
func MonitoredTransaction(db *gorm.DB, fn func(tx *gorm.DB) error) error {
start := time.Now()
err := db.Transaction(fn)
duration := time.Since(start).Seconds()
status := "success"
if err != nil {
status = "error"
}
transactionDuration.WithLabelValues(status).Observe(duration)
return err
}
事务是双刃剑,用好了保证一致性,用不好带来性能问题。记住:短小、有序、超时、监控。