业务场景:商品库存更新
1. 数据库表设计
我们在商品表 Product
中添加一个 version
字段,用来实现乐观锁。每当库存更新时,都会检查并更新该字段的值。
CREATE TABLE product ( id BIGINT PRIMARY KEY, name VARCHAR(255), stock INT, version INT DEFAULT 1 -- 版本号字段,用来实现乐观锁 );
2. 实体类设计
在实体类中,通过 MyBatis-Plus 的 @Version
注解来标记 version
字段。
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.Version; @TableName("product") public class Product { private Long id; private String name; private Integer stock; @Version // 使用 MyBatis-Plus 的乐观锁注解 @TableField("version") // 映射数据库中的 version 字段 private Integer version; // Getter 和 Setter }
3. 乐观锁更新库存操作
当用户进行购买时,我们首先查询商品库存,然后尝试更新库存。如果更新时发现版本号发生变化,说明该商品的库存已经被其他用户修改过,我们就会拒绝这次操作。
javapublic class ProductService { @Autowired private ProductMapper productMapper; public boolean purchase(Long productId, int quantity) { // 1. 查询商品信息 Product product = productMapper.selectById(productId); if (product == null || product.getStock() < quantity) { // 商品不存在或库存不足,购买失败 return false; } // 2. 扣减库存 product.setStock(product.getStock() - quantity); // 3. 使用乐观锁更新商品库存,version 字段自动处理 int updatedRows = productMapper.updateById(product); // 如果返回的更新行数为 0,说明版本号不一致,库存已经被其他用户修改 if (updatedRows == 0) { // 更新失败,可能因为并发冲突 return false; } return true; } }
4. 乐观锁执行过程
假设商品的 id = 1
,初始库存为 10,版本号为 1。
-
用户 A:
- 查询库存:库存为 10,版本号为 1。
- 扣减库存:购买 5 个,库存剩余 5,版本号自增为 2。
- 执行
updateById
,更新成功,库存变为 5,版本号变为 2。
-
用户 B:
- 查询库存:库存为 10,版本号为 1。
- 扣减库存:购买 5 个,库存剩余 5,版本号自增为 2。
- 执行
updateById
,但此时商品的版本号已经被用户 A 更新为 2。 - 用户 B 的更新操作失败,
updateById
返回更新行数为 0,库存没有被更新。
5. SQL 生成过程
在执行 updateById
时,MyBatis-Plus 会自动生成类似如下的 SQL:
UPDATE product SET stock = stock - 5, version = version + 1 WHERE id = 1 AND version = 1;
- 如果
version = 1
,更新操作会成功,库存减少 5,version
增加到 2。 - 如果
version
已经不是 1,说明其他用户已经更新了该记录,更新操作将失败,返回 0 行更新。
6. 乐观锁的优势:
- 减少锁竞争:与悲观锁不同,乐观锁不需要在更新时对数据进行加锁,适用于读多写少的场景,能够提高系统的并发性能。
- 避免死锁:乐观锁没有锁竞争,因此避免了死锁的发生。
- 业务简洁:只需在实体类中标注
@Version
,MyBatis-Plus 会自动处理版本号的增减。
7. 乐观锁的不足:
- 冲突时可能会丢失更新:当多个用户几乎同时操作相同数据时,可能会因为版本号不一致导致更新失败,需要在业务逻辑中捕获异常并做重试或其他处理。
- 适用场景有限:乐观锁适用于冲突概率低的场景,冲突频繁时,可能反而导致频繁的更新失败,效率低下。