服务开发中的乐观锁

举报
码乐 发表于 2025/01/26 09:41:22 2025/01/26
135 0 0
【摘要】 1 简介乐观并发控制(Optimistic Concurrency Control)它假设大多数事务不会冲突,允许事务自由地请求资源,之后通过检测和回滚解决冲突。在电商平台的下单高峰期,为了保证多个用户同时下单时不会发生冲突,可以采用**乐观并发控制(Optimistic Concurrency Control,OCC)**算法。这种控制方法在数据库中通过避免使用过多的锁,允许在不直接加锁...

1 简介

乐观并发控制(Optimistic Concurrency Control)它假设大多数事务不会冲突,允许事务自由地请求资源,之后通过检测和回滚解决冲突。

在电商平台的下单高峰期,为了保证多个用户同时下单时不会发生冲突,可以采用**乐观并发控制(Optimistic Concurrency Control,OCC)**算法。这种控制方法在数据库中通过避免使用过多的锁,允许在不直接加锁的情况下进行并发操作,并在提交时进行冲突检测。如果在提交过程中发现冲突,则放弃当前操作,并要求用户重新提交订单。

2 乐观并发控制(OCC)的原理

乐观并发控制的核心思想是:

读取阶段:系统允许多个用户并发地操作数据,用户读取数据后,不会立即加锁,而是等待最终提交时再进行冲突检测。
验证阶段:在用户提交操作时,系统检查是否有其他并发操作修改了用户正在处理的数据。如果数据没有被其他事务修改,则允许提交;如果数据被修改,则回滚当前事务并要求重新提交。

这种方式避免了死锁,因为它不依赖于事务之间的相互等待,而是通过检测冲突来避免。通过减少锁的使用,乐观并发控制可以在高并发的场景下提高性能。

3 实现方法

在实际的电商下单表中,可能会有如下字段:

  order_id:订单ID
  user_id:用户ID
  product_id:商品ID
  quantity:下单数量
  status:订单状态(如:待支付、已支付)
  version:版本号,用于乐观并发控制

使用 version 字段来表示数据的版本,当有用户提交下单请求时,系统会检查该记录的 version 是否与用户读取时一致。如果不一致,说明该记录已经被其他事务修改过,从而避免冲突。

示例表结构

    CREATE TABLE orders (
        order_id INT AUTO_INCREMENT PRIMARY KEY,
        user_id INT NOT NULL,
        product_id INT NOT NULL,
        quantity INT NOT NULL,
        status VARCHAR(255) NOT NULL,
        version INT NOT NULL DEFAULT 0
    );

4 服务web开发实现乐观并发控制

下面是如何使用 Gin 和 MySQL 实现电商平台下单操作,结合乐观并发控制算法避免锁冲突,包括行锁冲突避免、表锁冲突避免和列锁冲突避免。

  1. 安装依赖 初始化数据库连接

     package main
    
     import (
         "fmt"
         "log"
         "github.com/gin-gonic/gin"
         "gorm.io/driver/mysql"
         "gorm.io/gorm"
     )
    
     var db *gorm.DB
    
     func initDB() {
         var err error
         dsn := "root:password@tcp(localhost:3306)/ecommerce?charset=utf8mb4&parseTime=True&loc=Local"
         db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
         if err != nil {
             log.Fatal("Failed to connect to the database:", err)
         }
         fmt.Println("Connected to the database successfully.")
     }
    
  2. 实现乐观并发控制:下单操作

     type Order struct {
         OrderID   int    `json:"order_id"`
         UserID    int    `json:"user_id"`
         ProductID int    `json:"product_id"`
         Quantity  int    `json:"quantity"`
         Status    string `json:"status"`
         Version   int    `json:"version"`
     }
    
     func placeOrder(c *gin.Context) {
         var order Order
         if err := c.ShouldBindJSON(&order); err != nil {
             c.JSON(400, gin.H{"error": err.Error()})
             return
         }
    
         // 开始一个事务
         tx := db.Begin()
    
         // 查询商品库存是否足够
         var product Product
         if err := tx.First(&product, order.ProductID).Error; err != nil {
             tx.Rollback()
             c.JSON(400, gin.H{"error": "Product not found"})
             return
         }
         if product.Stock < order.Quantity {
             tx.Rollback()
             c.JSON(400, gin.H{"error": "Insufficient stock"})
             return
         }
    
         // 查询当前订单的版本号并进行比较
         var existingOrder Order
         if err := tx.First(&existingOrder, "order_id = ?", order.OrderID).Error; err != nil {
             tx.Rollback()
             c.JSON(400, gin.H{"error": "Order not found"})
             return
         }
    
         // 乐观并发控制:如果版本号不匹配,说明数据已经被其他事务修改
         if existingOrder.Version != order.Version {
             tx.Rollback()
             c.JSON(409, gin.H{"error": "Conflict: Order data has been modified"})
             return
         }
    
         // 更新订单状态,并增加版本号
         order.Version++
         order.Status = "Pending"
         if err := tx.Save(&order).Error; err != nil {
             tx.Rollback()
             c.JSON(500, gin.H{"error": "Failed to place order"})
             return
         }
    
         // 扣除库存
         product.Stock -= order.Quantity
         if err := tx.Save(&product).Error; err != nil {
             tx.Rollback()
             c.JSON(500, gin.H{"error": "Failed to update product stock"})
             return
         }
    
         // 提交事务
         tx.Commit()
         c.JSON(200, gin.H{"message": "Order placed successfully"})
     } 
    
  3. 启动 Gin 服务

     func main() {
         initDB()
    
         r := gin.Default()
    
         // 定义下单接口
         r.POST("/place_order", placeOrder)
    
         // 启动Gin服务器
         r.Run(":8080")
     }
    

5 乐观并发控制如何避免死锁

在上述代码中,通过以下机制实现了乐观并发控制,并有效避免了死锁:

行锁冲突避免:

通过对特定订单的版本号进行比较来避免修改相同的订单记录。如果订单在提交之前已被其他用户修改,则本次操作会失败并回滚

行锁冲突的关键在于没有使用数据库的显式行锁(如SELECT FOR UPDATE),而是通过版本号机制(version 字段)来避免冲突。

表锁冲突避免:

因为我们没有在数据库中使用显式的表锁,所以不存在表级锁冲突的风险。乐观并发控制通过局部的事务和版本号检查来避免全表锁定。
每个订单的版本号独立管理,多个订单的并发操作不会互相阻塞。

列锁冲突避免:

同样,乐观并发控制没有使用显式的列锁(如LOCK IN SHARE MODE),因此不存在列级锁冲突。通过版本号字段的对比,确保修改的资源(订单或库存)是最新的,避免因其他事务修改数据导致的冲突。

6 小结

通过乐观并发控制算法,系统能够在下单高峰期避免锁冲突并有效减少死锁的风险。在代码实现中:

采用版本号(version)来避免并发修改相同的数据,避免行锁冲突。
不使用显式的表锁和列锁,而是通过事务的独立性和版本号的校验来避免冲突。
一旦检测到冲突(如版本号不匹配),则立即回滚事务,提示用户重新提交订单,确保数据一致性。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。