当前位置: 首页 > article >正文

【GORM】事务,嵌套事务,保存点事务的使用,简单电商平台go案例

GORM 中的嵌套事务与 SavePoint 事务

1. 嵌套事务

嵌套事务是指一个事务中包含另一个事务。例如,在一个数据库操作中,你可能需要在一个主事务中嵌套执行子事务。

GORM 对嵌套事务的支持:

  • GORM 中通过 DB.Transaction 方法嵌套事务。
  • 内部事务出错时,会回滚到父事务的状态。
  • 如果外部事务提交了,嵌套的事务也会生效。

示例:

package main

import (
	"fmt"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func main() {
	db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

	// 外层事务
	err := db.Transaction(func(tx *gorm.DB) error {
		// 内层事务
		err := tx.Transaction(func(tx2 *gorm.DB) error {
			if err := tx2.Create(&User{Name: "Nested Transaction"}).Error; err != nil {
				return err // 内层回滚
			}
			return nil // 内层提交
		})

		if err != nil {
			return err // 外层回滚
		}

		if err := tx.Create(&User{Name: "Outer Transaction"}).Error; err != nil {
			return err // 外层回滚
		}

		return nil // 外层提交
	})

	if err != nil {
		fmt.Println("事务失败:", err)
	} else {
		fmt.Println("事务成功")
	}
}

type User struct {
	ID   uint
	Name string
}

注意:

  • 如果子事务回滚,父事务仍然可以正常执行。
  • 子事务的回滚不会影响父事务中的其他操作。

2. SavePoint 事务

SavePoint 是数据库事务的一种控制方式,它允许你在事务中创建检查点(SavePoint),并在发生错误时回滚到特定的检查点,而不是完全回滚整个事务。

GORM 中 SavePoint 的使用:

  • 使用 SavePoint 创建检查点。
  • 使用 RollbackTo 回滚到某个检查点。

示例:

package main

import (
	"fmt"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func main() {
	db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	tx := db.Begin()

	// SavePoint 检查点
	tx.SavePoint("sp1")

	if err := tx.Create(&User{Name: "First SavePoint"}).Error; err != nil {
		tx.RollbackTo("sp1") // 回滚到 sp1
	}

	// SavePoint 检查点
	tx.SavePoint("sp2")

	if err := tx.Create(&User{Name: "Second SavePoint"}).Error; err != nil {
		tx.RollbackTo("sp2") // 回滚到 sp2
	}

	// 提交事务
	if err := tx.Commit().Error; err != nil {
		fmt.Println("提交失败:", err)
	} else {
		fmt.Println("事务成功提交")
	}
}

type User struct {
	ID   uint
	Name string
}

注意:

  • SavePointRollbackTo 需要底层数据库支持。例如,SQLite 和 MySQL 支持 SavePoint。
  • 如果底层数据库不支持 SavePoint,使用时会报错。

区别和选择
功能嵌套事务SavePoint 事务
实现方式通过 GORM 提供的 Transaction 方法封装。显式创建检查点并回滚到特定检查点。
适用场景子事务相对独立,但需要依赖主事务的提交或回滚。需要在事务中多次设置状态点,灵活回滚到某点。
复杂度比较简单,直接使用 Transaction 方法嵌套。较复杂,需要手动管理 SavePoint 和回滚点。

以下是一个企业级案例,一个电商平台需要创建订单并更新库存,同时提供对操作失败的容错机制。

案例背景

  1. 场景描述:

    • 用户提交订单时,需要:
      • 在数据库中创建订单。
      • 扣减库存。
      • 记录操作日志。
    • 如果任意一步失败,需要保证事务的一致性。
  2. 技术需求:

    • 使用嵌套事务确保不同模块的独立性。
    • 使用 SavePoint 处理中间步骤的可回滚。
  3. 数据库表结构:

    • orders 表:存储订单信息。
    • products 表:存储商品库存。
    • logs 表:记录操作日志。

代码实现

package main

import (
	"errors"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type Order struct {
	ID         uint   `gorm:"primaryKey"`
	UserID     uint   // 用户ID
	ProductID  uint   // 商品ID
	Quantity   int    // 购买数量
	Status     string // 订单状态
}

type Product struct {
	ID       uint   `gorm:"primaryKey"`
	Name     string
	Stock    int // 库存数量
}

type Log struct {
	ID      uint   `gorm:"primaryKey"`
	Message string // 日志信息
}

func main() {
	// 初始化数据库连接(假设使用 MySQL)
	dsn := "user:password@tcp(127.0.0.1:3306)/ecommerce?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("数据库连接失败")
	}

	// 自动迁移(仅用于演示,实际生产中建议手动管理表结构)
	db.AutoMigrate(&Order{}, &Product{}, &Log{})

	// 调用企业级事务流程
	err = createOrder(db, 1, 1, 2) // 用户 1 购买商品 1,购买数量为 2
	if err != nil {
		fmt.Println("订单创建失败:", err)
	} else {
		fmt.Println("订单创建成功")
	}
}

func createOrder(db *gorm.DB, userID, productID, quantity int) error {
	return db.Transaction(func(tx *gorm.DB) error {
		// 创建 SavePoint 检查点
		tx.SavePoint("start_order")

		// Step 1: 扣减库存
		if err := reduceStock(tx, productID, quantity); err != nil {
			tx.RollbackTo("start_order")
			return err
		}

		// Step 2: 创建订单
		order := Order{
			UserID:    uint(userID),
			ProductID: uint(productID),
			Quantity:  quantity,
			Status:    "Created",
		}
		if err := tx.Create(&order).Error; err != nil {
			tx.RollbackTo("start_order")
			return err
		}

		// Step 3: 记录日志
		log := Log{
			Message: fmt.Sprintf("用户 %d 创建订单 %d", userID, order.ID),
		}
		if err := tx.Create(&log).Error; err != nil {
			tx.RollbackTo("start_order")
			return err
		}

		// 提交事务
		return nil
	})
}

func reduceStock(tx *gorm.DB, productID, quantity int) error {
	var product Product
	if err := tx.First(&product, productID).Error; err != nil {
		return errors.New("商品不存在")
	}

	if product.Stock < quantity {
		return errors.New("库存不足")
	}

	// 扣减库存
	product.Stock -= quantity
	if err := tx.Save(&product).Error; err != nil {
		return errors.New("库存更新失败")
	}

	return nil
}

功能讲解

  1. 事务嵌套:

    • 订单创建逻辑封装在 createOrder 函数中,库存更新 (reduceStock) 是一个嵌套的子操作。
    • db.Transaction 保证了 reduceStock 和日志记录的原子性。
  2. SavePoint 使用:

    • 在事务开始时设置检查点 SavePoint("start_order")
    • 任意步骤失败时,通过 RollbackTo 回滚到检查点,避免影响全局事务。
  3. 模块化设计:

    • 每个业务逻辑拆分为独立的函数,如 reduceStock 和日志记录。
    • 确保代码可读性和可维护性。
  4. 数据库层的幂等性:

    • 通过事务机制保证多次调用不会产生脏数据。
    • 在高并发场景下可结合数据库锁进一步优化。

扩展建议

  1. 并发控制:

    • 针对库存扣减,可以结合数据库的乐观锁或悲观锁机制,避免超卖。
  2. 日志的异步化:

    • 日志记录可以通过消息队列(如 Kafka 或 RabbitMQ)异步处理,减少事务时长。
  3. 监控和告警:

    • 在生产环境中,可通过 APM 工具(如 Prometheus 或 Jaeger)监控事务耗时和失败情况。

练习案例

package _case

import (
	"gorm.io/gorm"
	"log"
)

// Transaction 事务
func Transaction() {
	t := Teacher{
		Name:   "nick",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@gmail.com",
	}
	c := Course{
		Name:  "golang",
		Price: 12345.1234,
	}
	DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&t).Error; err != nil {
			return err
		}
		c.UserID = t.ID
		if err := tx.Create(&c).Error; err != nil {
			return err
		}
		return nil
	})
}

// NestTransaction 嵌套事务指的是再一个外部事务内启动一或者多个内部事务,
// 每个内部事务可以独立回滚
// 外部事务提交成功,内部事务才能成功
// 内部事务失败,不影响其他同级或者高级事务,但是外部事物回滚,所有内部也会回滚
// 嵌套事务不支持预编译(Prepared Statements)
func NestTransaction() {
	t := Teacher{
		Name:   "nick",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@gmail.com",
	}
	t1 := Teacher{
		Name:   "king",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@gmail.com",
	}
	t2 := Teacher{
		Name:   "mark",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@gmail.com",
	}
	DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&t).Error; err != nil {
			return err
		}
		tx.Transaction(func(tx1 *gorm.DB) error {
			if err := tx1.Create(&t1).Error; err != nil {
				return err
			}
			return nil
		})
		tx.Transaction(func(tx2 *gorm.DB) error {
			if err := tx2.Create(&t2).Error; err != nil {
				return err
			}
			return nil
		})
		return nil
	})
}

// ManualTransaction 手动事务,模拟平常手写事务
func ManualTransaction() {
	t := Teacher{
		Name:   "nick",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@gmail.com",
	}
	c := Course{
		Name:  "golang",
		Price: 12345.1234,
	}

	tx := DB.Begin()
	defer func() { // 确保在发生未处理的 panic 时,能够及时回滚事务,避免数据不一致的问题。
		if err := recover(); err != nil {
			tx.Rollback()
		}
	}()
	if tx.Error != nil {
		log.Fatalln(tx.Error)
		return
	}
	if err := tx.Create(&t).Error; err != nil {
		tx.Rollback()
		return
	}
	c.UserID = t.ID
	if err := tx.Create(&c).Error; err != nil {
		tx.Rollback()
		return
	}
	tx.Commit()
}

// SavePointTransaction 保存点事务,即事务中可以创建多个保存点,可以回滚到指定保存点
func SavePointTransaction() {
	t := Teacher{
		Name:   "nick",
		Age:    40,
		Salary: 12345.123,
		Email:  "nick@0voice.com",
	}
	c := Course{
		Name:  "golang 云原生",
		Price: 12345.1234,
	}

	tx := DB.Begin()
	defer func() {
		if err := recover(); err != nil {
			tx.Rollback()
		}
	}()
	if tx.Error != nil {
		log.Fatal(tx.Error)
		return
	}

	if err := tx.Create(&t).Error; err != nil {
		tx.Rollback()
		return
	}
	tx.SavePoint("teacher") // SavePoint 设置回滚点
	c.UserID = t.ID
	if err := tx.Create(&c).Error; err != nil {
		tx.RollbackTo("teacher") // 指定回滚到
		return
	}
	tx.Commit()
}


https://github.com/0voice


http://www.kler.cn/a/516862.html

相关文章:

  • IDE提示:因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170
  • 深度学习系列76:流式tts的一个简单实现
  • Git代码管理工具 — 5 GitHub远程仓库
  • python 找出合并并排序两个有序列表后的第n个最小元素
  • 电脑办公技巧之如何在 Word 文档中添加文字或图片水印
  • 《鸿蒙Next应用商店:人工智能开启智能推荐与运营新时代》
  • 力扣动态规划-5【算法学习day.99】
  • 快速排序:一种高效的排序算法
  • Python 预训练:打通视觉与大语言模型应用壁垒——Python预训练视觉和大语言模型
  • WPS计算机二级•表格保护与打印
  • 深入解析:使用 Python 爬虫获取苏宁商品详情
  • 总结 uniapp 上不适配iphone的:new Date 时间、border线条、渐变
  • 深入探索 Nginx 的高级用法:解锁 Web 服务器的强大潜能
  • 2. Flink分区策略
  • vue3的组件v-model(defineModel()宏)
  • 第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
  • 深度学习|表示学习|卷积神经网络|通道 channel 是什么?|05
  • 怎样使用树莓派自己搭建一套ADS-B信号接收系统
  • 栈和队列刷题篇
  • 新能源汽车充电桩选型以及安装应用
  • 2025.1.20——四、[强网杯 2019]Upload1 文件上传|反序列化
  • STM32——KEY按键
  • ETLCloud在iPaas中的是关键角色?
  • 若依 v-hasPermi 自定义指令失效场景
  • Java核心技术解析:泛型与类型安全全面指南
  • android wifi AsyncChannel(WifiManager和WifiP2pManager)