服务开发中的乐观锁
【摘要】 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 实现电商平台下单操作,结合乐观并发控制算法避免锁冲突,包括行锁冲突避免、表锁冲突避免和列锁冲突避免。
-
安装依赖 初始化数据库连接
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.") }
-
实现乐观并发控制:下单操作
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"}) }
-
启动 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)