深入理解行锁发生时数据分布和范围
1简介
行锁(FOR UPDATE)我们可以从这些逻辑和组织角度来理解 MySQL 的行锁机制,尤其是 FOR UPDATE 语句的作用。
2 数据分布和范围
数据分布与锁范围维度
MySQL 的 行锁(Row Lock) 是基于索引的,在 FOR UPDATE 语句中,只有命中了索引的行才会被锁定。
若未使用索引,全表扫描时,会退化为 表锁,影响整个表的并发访问。
锁的范围:
索引命中 → 只锁定相关行
索引未命中 → 可能锁整个表
时间维度(锁的持续时间)
FOR UPDATE 获取的行锁会在 事务提交(COMMIT)或回滚(ROLLBACK) 后释放。
在长事务中,锁持有时间越长,阻塞其他事务的可能性越大,容易导致死锁或性能下降。
快照读 vs 当前读:
SELECT * FROM table WHERE id=1; (快照读,不加锁)
SELECT * FROM table WHERE id=1 FOR UPDATE; (当前读,加锁)
逻辑维度(业务逻辑与锁的关系)
FOR UPDATE 适用于 防止“脏读”或“幻读”,确保同一行的数据在事务执行期间不会被其他事务修改。
典型应用场景:
库存扣减(防止超卖)
分布式锁(MySQL 充当分布式事务协调器)
乐观锁 vs 悲观锁(FOR UPDATE 属于悲观锁)
并发与架构设计:
高并发下,减少锁粒度:尽量使用 索引,避免不必要的锁范围扩大。
死锁检测与处理:
业务层避免循环依赖(A 锁 X → B 锁 Y → A 需要 Y,导致死锁)
数据库层使用 innodb_lock_wait_timeout 机制 处理超时
3 web服务 实现 MySQL 行锁(FOR UPDATE)
我们可以使用 Gin + GORM 搭建一个简单的服务,模拟一个扣减库存的业务,确保在高并发下不会出现超卖问题。
-
初始化数据库
CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, stock INT NOT NULL ); INSERT INTO products (name, stock) VALUES ('iPhone 15', 10);
-
GORM 代码示例
// Product 模型 type Product struct { ID uint `gorm:"primaryKey"` Name string `gorm:"type:varchar(255)"` Stock int } var db *gorm.DB func main() { // 连接数据库 dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local" var err error db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("数据库连接失败:", err) } r := gin.Default() r.POST("/buy/:id", buyProduct) // 购买商品接口 r.Run(":8080") // 运行服务 } // 购买商品(行锁 FOR UPDATE 防止超卖) func buyProduct(c *gin.Context) { var product Product productID := c.Param("id") // 开启事务 tx := db.Begin() if err := tx.Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "无法开启事务"}) return } // 查询产品并加锁 if err := tx.Raw("SELECT * FROM products WHERE id = ? FOR UPDATE", productID).Scan(&product).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "查询产品失败"}) return } // 检查库存 if product.Stock <= 0 { tx.Rollback() c.JSON(http.StatusBadRequest, gin.H{"error": "库存不足"}) return } // 扣减库存 if err := tx.Exec("UPDATE products SET stock = stock - 1 WHERE id = ?", productID).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "库存扣减失败"}) return } // 提交事务 if err := tx.Commit().Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "事务提交失败"}) return } c.JSON(http.StatusOK, gin.H{"message": "购买成功", "剩余库存": product.Stock - 1}) }
4 总结
FOR UPDATE 通过 索引行锁 防止高并发写入时数据不一致,避免超卖问题。
事务控制 是核心,确保 BEGIN → 查询 + FOR UPDATE → 更新 → COMMIT。
GORM 可用于构建高并发场景的 RESTful API,结合 FOR UPDATE 实现数据库级并发控制。
这样,我们就实现了一个安全的扣库存系统,防止并发超卖
- 点赞
- 收藏
- 关注作者
评论(0)