Go语言数据库:事务处理
1. 事务基础概念
事务(Transaction)是数据库操作的最小逻辑单元,用于保证一组数据库操作的原子性。事务遵循ACID特性:
- 原子性(Atomicity):事务中的操作要么全部完成,要么全部不完成
- 一致性(Consistency):事务执行前后数据库状态保持一致
- 隔离性(Isolation):多个事务并发执行时互不干扰
- 持久性(Durability):事务提交后结果永久保存
Go语言通过`database/sql`标准库和GORM框架支持事务操作。
2. 原生SQL事务操作
使用`database/sql`的`Begin()`、`Commit()`和`Rollback()`方法管理事务:
package main
import (
"database/sql"
"fmt"
"github.com/go-sql-driver/mysql"
)
func transfer(db *sql.DB, from, to int, amount float64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 扣减转出账户
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
if err != nil {
tx.Rollback()
return err
}
// 增加转入账户
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func main() {
// 初始化数据库连接
cfg := mysql.Config{
User: "root",
Passwd: "password",
Net: "tcp",
Addr: "localhost:3306",
DBName: "bank",
}
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
panic(err)
}
defer db.Close()
// 执行转账事务
err = transfer(db, 1, 2, 100.0)
if err != nil {
fmt.Println("转账失败:", err)
} else {
fmt.Println("转账成功")
}
}
关键点说明:
- 使用`defer`确保事务异常时回滚
- 通过`Exec`执行修改操作,`Query`用于查询
- 提交前所有操作对其他事务不可见(默认隔离级别)
3. GORM框架事务支持
GORM提供`Transaction`方法简化事务操作,支持嵌套事务和保存点(Savepoint):
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Account struct {
gorm.Model
UserID uint `gorm:"uniqueIndex"`
Balance float64
}
func main() {
dsn := "root:password@tcp(localhost:3306)/bank?charset=utf8&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败:" + err.Error())
}
// 定义事务操作
err = db.Transaction(func(tx *gorm.DB) error {
// 扣减转出账户
if err := tx.Model(&Account{}).Where("user_id = ?", 1).Update("balance", gorm.Expr("balance - ?", 100.0)).Error;
err != nil {
return err
}
// 增加转入账户
if err := tx.Model(&Account{}).Where("user_id = ?", 2).Update("balance", gorm.Expr("balance + ?", 100.0)).Error;
err != nil {
return err
}
// 嵌套事务示例
err := tx.Transaction(func(tx2 *gorm.DB) error {
// 记录操作日志
return tx2.Create(&TransactionLog{From: 1, To: 2, Amount: 100.0}).Error
})
if err != nil {
return err
}
return nil // 所有操作成功时自动提交
})
if err != nil {
fmt.Println("事务执行失败:", err)
} else {
fmt.Println("事务执行成功")
}
}
// 事务日志表
type TransactionLog struct {
gorm.Model
From uint
To uint
Amount float64
}
进阶特性:
- `Transaction`方法接收一个回调函数,返回`nil`时提交,非`nil`时回滚
- 支持保存点(`Savepoint`/`RollbackTo`)实现部分回滚
- 自动处理嵌套事务,外层事务回滚时内层也会回滚
4. 事务隔离级别
数据库通过隔离级别控制事务间的可见性,Go中可通过`SetTxOptions`设置:
// 原生SQL设置隔离级别
tx, err := db.BeginTx(context.Background(), &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
// GORM设置隔离级别
err = db.Transaction(func(tx *gorm.DB) error {
tx.Statement.ConnPool = tx.Statement.DB.ConnPool.
WithContext(context.WithValue(context.Background(), "gorm:tx_options", &sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
}))
// 业务逻辑
return nil
})
常见隔离级别(从低到高):
- 读未提交(Read Uncommitted):允许脏读
- 读已提交(Read Committed):避免脏读(默认级别)
- 可重复读(Repeatable Read):避免不可重复读(MySQL默认)
- 串行化(Serializable):最高隔离级别,避免幻读
5. 事务最佳实践
为确保事务正确性和性能,建议遵循以下原则:
- 保持事务简短:长时间事务会占用锁,影响并发性能
- 最小化锁范围:优先使用行锁(Row Lock)而非表锁(Table Lock)
- 处理死锁:通过`SELECT ... FOR UPDATE`设置超时(`innodb_lock_wait_timeout`)
- 记录日志:在事务中记录操作日志,便于问题排查
- 测试边界条件:如网络中断、数据库崩溃等异常场景
func safeTransfer(db *sql.DB, from, to int, amount float64) error {
for i := 0; i < 3; i++ { // 重试3次
err := transfer(db, from, to, amount)
if err == nil {
return nil
}
if isDeadlockError(err) { // 自定义死锁检测函数
continue
}
return err
}
return fmt.Errorf("转账失败:超过最大重试次数")
}