

























乐观锁的核心是“假设不会发生并发冲突,只在提交更新时检查数据是否被修改过”,而非像悲观锁(如SELECT ... FOR UPDATE)那样提前锁定数据。
version字段(整数,初始值0),每次更新数据时:
version值;version作为条件(WHERE version = 读取值),并把version自增1;update_time字段(时间戳/DateTime),更新时校验“当前读取的时间戳”和“数据库中的时间戳”是否一致。以“商品库存扣减”为例(典型的并发场景),完整实现乐观锁:
CREATE TABLE product (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '商品ID',
name VARCHAR(255) NOT NULL COMMENT '商品名称',
stock INT NOT NULL DEFAULT 0 COMMENT '库存数量',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'
);
-- 插入测试数据
INSERT INTO product (name, stock, version) VALUES ('手机', 100, 0);
public class Product {
private Long id;
private String name;
private Integer stock;
private Integer version; // 乐观锁版本号
// 省略getter/setter/toString
}
public interface ProductMapper {
/**
* 根据ID查询商品(获取version)
*/
Product selectById(Long id);
/**
* 扣减库存(乐观锁核心:WHERE version = #{version})
* @param product 商品对象(含id、扣减后的库存、读取时的version)
* @return 影响行数:1=更新成功,0=版本冲突
*/
int deductStock(Product product);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.ProductMapper">
<!-- 查询商品 -->
<select id="selectById" resultType="com.example.entity.Product">
SELECT id, name, stock, version FROM product WHERE id = #{id}
</select>
<!-- 扣减库存(乐观锁) -->
<update id="deductStock">
UPDATE product
SET stock = #{stock}, version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
</mapper>
乐观锁冲突后,通常有两种处理方式:① 返回失败(告知用户“操作失败,请重试”);② 自动重试(有限次数)。以下是带重试的实现:
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
// 最大重试次数
private static final int MAX_RETRY = 3;
/**
* 扣减商品库存(带乐观锁重试)
* @param productId 商品ID
* @param num 扣减数量
* @return true=成功,false=失败
*/
public boolean deductStock(Long productId, int num) {
int retryCount = 0;
while (retryCount < MAX_RETRY) {
// 1. 查询商品(获取当前version和库存)
Product product = productMapper.selectById(productId);
if (product == null) {
throw new RuntimeException("商品不存在");
}
if (product.getStock() < num) {
return false; // 库存不足,直接失败
}
// 2. 准备更新参数(扣减库存,携带读取的version)
Product updateParam = new Product();
updateParam.setId(productId);
updateParam.setStock(product.getStock() - num);
updateParam.setVersion(product.getVersion());
// 3. 执行更新(乐观锁校验)
int affectedRows = productMapper.deductStock(updateParam);
if (affectedRows == 1) {
return true; // 更新成功
}
// 4. 版本冲突,重试(休眠50ms避免高频重试)
retryCount++;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 重试次数耗尽,返回失败
return false;
}
}
用多线程测试乐观锁效果:
@Test
public void testConcurrentDeduct() throws InterruptedException {
Long productId = 1L;
int threadCount = 5; // 5个线程同时扣减
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
boolean success = productService.deductStock(productId, 1);
System.out.println(Thread.currentThread().getName() + ":" + (success ? "扣减成功" : "扣减失败"));
} finally {
latch.countDown();
}
}).start();
}
latch.await();
// 最终库存应为 100 - 5 = 95(无超卖)
Product product = productMapper.selectById(productId);
System.out.println("最终库存:" + product.getStock());
}
如果使用MyBatis-Plus,可以通过注解简化乐观锁实现,无需手动写UPDATE语句:
public class Product {
private Long id;
private String name;
private Integer stock;
@Version // MyBatis-Plus乐观锁注解
private Integer version;
}
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
// MyBatis-Plus会自动拼接version条件,无需手动写SQL
public boolean deductStock(Long productId, int num) {
int retryCount = 0;
while (retryCount < MAX_RETRY) {
Product product = productMapper.selectById(productId);
if (product.getStock() < num) return false;
product.setStock(product.getStock() - num);
// 更新时,MyBatis-Plus自动校验version并自增
int affectedRows = productMapper.updateById(product);
if (affectedRows == 1) return true;
retryCount++;
Thread.sleep(50);
}
return false;
}
UPDATE ... WHERE 条件)无法精准校验版本,容易失效。version = version + 1。version字段在更新时判断数据是否被修改,避免提前加锁;@Version注解简化实现,无需手动编写版本校验SQL,提升开发效率。乐观锁的核心价值是“无锁化提升并发性能”,但需结合业务场景选择——低冲突用乐观锁,高冲突用悲观锁/分布式锁。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。