并发系统中常见锁的使用示例汇总

举报
码乐 发表于 2025/02/11 10:40:03 2025/02/11
255 0 0
【摘要】 1 简介在常见的并发系统,如实现的某个电商系统中,由于系统的高并发特性和多个用户同时访问共享资源的情况,锁的选择对于确保数据一致性、避免死锁和提高系统性能至关重要。本文一些在电商系统中经常使用的锁,并举例说明它们在具体场景中的应用示例。 2 行锁 (Row-Level Lock)用途:行锁用于锁定单个数据行,可以在高并发情况下确保多个事务可以并发访问不同的行数据,避免全表锁定。它常用于需要...

1 简介

在常见的并发系统,如实现的某个电商系统中,由于系统的高并发特性和多个用户同时访问共享资源的情况,

锁的选择对于确保数据一致性、避免死锁和提高系统性能至关重要。

本文一些在电商系统中经常使用的锁,并举例说明它们在具体场景中的应用示例。

2 行锁 (Row-Level Lock)

用途:行锁用于锁定单个数据行,可以在高并发情况下确保多个事务可以并发访问不同的行数据,避免全表锁定。它常用于需要高并发读取和写入数据的场景。

使用场景:

库存操作:当多个用户同时购买同一商品时,行锁可以确保每个用户操作的是独立的库存记录,避免多个事务同时修改同一商品的库存,导致数据不一致。

有以下一个商品库存表

    type Product struct {
        ProductID   int    `json:"product_id"`
        ProductName string `json:"product_name"`
        Stock       int    `json:"stock"`
    }

商品库存更新时使用行锁

  func updateProductStock(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.Set("gorm:query_option", "FOR UPDATE").First(&product, "product_id = ?", order.ProductID).Error; err != nil {
          tx.Rollback()
          c.JSON(500, gin.H{"error": "Product not found"})
          return
      }

      if product.Stock < order.Quantity {
          tx.Rollback()
          c.JSON(400, gin.H{"error": "Insufficient stock"})
          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. 表锁 (Table-Level Lock)

用途:表锁锁定整个表,适用于需要确保表中所有数据一致性的操作。

由于锁住整个表,其他事务必须等待该锁释放才能继续操作,因此对于高并发应用而言,表锁的使用较为谨慎,主要适用于不频繁变动的表或者操作。

使用场景:

订单支付操作:如果一个支付操作需要涉及到订单表和其他相关表(如支付表),且事务操作比较复杂,可以考虑对订单表加表锁,确保在一个复杂操作中不会发生并发冲突。

示例: 假设有一个如下订单表

        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"`
            CreatedAt  time.Time
        }

支付或修改订单时使用表锁

          func payOrder(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()

              // 使用表锁确保订单表在支付时不会发生并发修改
              if err := tx.Set("gorm:query_option", "LOCK TABLES orders WRITE").First(&order, "order_id = ?", order.OrderID).Error; err != nil {
                  tx.Rollback()
                  c.JSON(500, gin.H{"error": "Order not found"})
                  return
              }

              if order.Status == "Paid" {
                  tx.Rollback()
                  c.JSON(400, gin.H{"error": "Order already paid"})
                  return
              }

              // 更新订单状态为已支付
              order.Status = "Paid"
              if err := tx.Save(&order).Error; err != nil {
                  tx.Rollback()
                  c.JSON(500, gin.H{"error": "Failed to update order status"})
                  return
              }

              tx.Commit()
              c.JSON(200, gin.H{"message": "Payment successful"})
          }

通过表锁可以确保整个表的数据一致性,防止其他事务对该表进行操作,适合于处理较为复杂的跨表操作。

4 意向锁 (Intention Lock)

用途:意向锁用于声明一个事务在行级别上想要加锁,它是对数据库表或行的“意图”进行声明,目的是减少多用户并发操作时对资源的竞争,避免死锁。

使用场景:

批量更新或删除商品信息:例如在库存管理、促销活动中可能会批量更新商品库存或删除商品信息,使用意向锁可以避免其他事务操作相同数据时发生冲突。

示例假设在批量更新商品库存时使用意向锁

    func batchUpdateProductStock(c *gin.Context) {
        var products []Product
        if err := c.ShouldBindJSON(&products); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }

        tx := db.Begin()

        // 设置意向锁,表示该事务想对多个行加锁
        if err := tx.Set("gorm:query_option", "FOR UPDATE").Where("product_id IN ?", getProductIDs(products)).Find(&products).Error; err != nil {
            tx.Rollback()
            c.JSON(500, gin.H{"error": "Failed to fetch products"})
            return
        }

        // 执行库存更新
        for _, product := range products {
            product.Stock -= 1
            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": "Batch update successful"})
    }

意向锁通过对表的加锁声明,减少了锁的竞争和不必要的全表锁操作,提高了并发性。

5 AUTO-INC 锁

用途:AUTO-INC 锁用于保证自增字段的唯一性,确保多个事务并发插入时自增主键不会冲突。

使用场景:

订单编号生成:当生成订单编号时,通常会使用自增字段作为订单的唯一标识。
在高并发情况下,AUTO-INC 锁确保了多个事务并发插入时主键不会重复。

示例:订单表中的 OrderID 使用自增字段

    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()

        // 使用 AUTO-INC 锁确保并发插入时主键不冲突
        if err := tx.Create(&order).Error; err != nil {
            tx.Rollback()
            c.JSON(500, gin.H{"error": "Failed to place order"})
            return
        }

        tx.Commit()
        c.JSON(200, gin.H{"message": "Order placed successfully", "order_id": order.OrderID})
    }

优点:AUTO-INC 锁可以确保在并发插入时主键的唯一性,避免因并发插入导致的主键冲突。

6 间隙锁(Gap Lock)

用途:间隙锁是一种防止幻读的锁,它锁定的是某个索引范围内的空隙,而不是具体的行。
间隙锁常用于可重复读隔离级别下。

使用场景:

商品库存检查:防止在高并发的情况下,多个事务同时插入库存数据,导致重复下单等问题。

间隙锁通常由 MySQL 自动管理,无需显式指定,在 SELECT 查询中使用 FOR UPDATE 时,MySQL 会自动使用间隙锁。

7 小结

在web服务中实现的电商系统中,不同的锁类型有不同的用途。常用的锁包括:

    行锁:用于库存、订单等高并发场景。
    表锁:适用于复杂的跨表操作或需要全表一致性的场景。
    意向锁:减少锁竞争,提高并发性能。
    AUTO-INC 锁:用于保证自增主键的唯一性。
    间隙锁:防止幻读,避免在并发查询时发生不一致数据。

合理选择和使用这些锁,可以有效避免死锁并提高系统的并发能力和数据一致性。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

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

    全部回复

    上滑加载中

    设置昵称

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

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

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