在分布式系统中,单独使用乐观锁或悲观锁都有局限:
因此,“乐观锁为主,冲突阈值触发悲观锁” 的混合策略成为更优解 —— 低冲突时用乐观锁保持性能,高冲突时自动切换悲观锁避免无效重试。
version)实现,通过UPDATE ... WHERE version = ?校验;SELECT ... FOR UPDATE)实现,锁定资源直到事务提交。CREATE TABLE `product_stock` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`stock` int NOT NULL COMMENT '当前库存',
`version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT '商品库存表';
-- 初始化数据:商品1001,库存100
INSERT INTO `product_stock` (`id`, `stock`, `version`) VALUES (1001, 100, 0);
// 库存实体类
@Data
public class ProductStock {
private Long id;
private Integer stock;
private Integer version; // 乐观锁版本号
}
// MyBatis Mapper接口
public interface ProductStockMapper {
// 乐观锁查询:获取当前库存和版本号(无锁)
ProductStock selectForOptimistic(Long id);
// 乐观锁扣减:WHERE条件校验版本号和库存
int deductByOptimistic(
@Param("id") Long id,
@Param("deductNum") int deductNum,
@Param("version") int version
);
// 悲观锁查询:加行锁(FOR UPDATE),确保后续更新独占
@Select("SELECT id, stock, version FROM product_stock WHERE id = #{id} FOR UPDATE")
ProductStock selectForPessimistic(Long id);
// 悲观锁扣减:无需版本号(已通过行锁保证独占)
int deductByPessimistic(
@Param("id") Long id,
@Param("deductNum") int deductNum
);
}
<!-- 乐观锁扣减SQL -->
<update id="deductByOptimistic">
UPDATE product_stock
SET stock = stock - #{deductNum}, version = version + 1
WHERE id = #{id}
AND version = #{version} <!-- 版本号校验:确保未被其他线程修改 -->
AND stock >= #{deductNum} <!-- 库存充足校验 -->
</update>
<!-- 悲观锁扣减SQL(无需版本号,依赖行锁) -->
<update id="deductByPessimistic">
UPDATE product_stock
SET stock = stock - #{deductNum}, version = version + 1 <!-- 版本号仍递增,便于后续乐观锁使用 -->
WHERE id = #{id}
AND stock >= #{deductNum} <!-- 库存充足校验 -->
</update>
@Service
public class StockService {
@Autowired
private ProductStockMapper stockMapper;
// 乐观锁最大重试次数(超过则切换悲观锁)
private static final int OPTIMISTIC_MAX_RETRY = 3;
/**
* 库存扣减主方法:乐观锁重试 -> 阈值触发悲观锁
*/
public boolean deductStock(Long productId, int deductNum) {
// 1. 先尝试乐观锁扣减
int retryCount = 0;
while (retryCount < OPTIMISTIC_MAX_RETRY) {
// 查库存和版本号(无锁)
ProductStock stock = stockMapper.selectForOptimistic(productId);
if (stock == null) {
throw new RuntimeException("商品不存在");
}
// 提前校验库存(减少无效更新)
if (stock.getStock() < deductNum) {
System.out.println("库存不足,当前库存:" + stock.getStock());
return false;
}
// 乐观锁更新
int affectRows = stockMapper.deductByOptimistic(productId, deductNum, stock.getVersion());
if (affectRows > 0) {
System.out.println("乐观锁扣减成功,剩余库存:" + (stock.getStock() - deductNum));
return true;
}
// 冲突,重试计数+1
retryCount++;
System.out.println("乐观锁冲突,重试次数:" + retryCount);
}
// 2. 乐观锁重试达阈值,切换悲观锁
System.out.println("乐观锁重试超限,切换悲观锁");
return deductByPessimistic(productId, deductNum);
}
/**
* 悲观锁扣减(独立方法,方便事务控制)
*/
@Transactional(rollbackFor = Exception.class) // 悲观锁必须在事务中,否则锁会提前释放
public boolean deductByPessimistic(Long productId, int deductNum) {
// 加行锁查询(锁定当前行,其他线程需等待事务提交)
ProductStock stock = stockMapper.selectForPessimistic(productId);
if (stock == null) {
throw new RuntimeException("商品不存在");
}
// 校验库存
if (stock.getStock() < deductNum) {
System.out.println("库存不足,当前库存:" + stock.getStock());
return false;
}
// 扣减库存(无需版本号校验,因行锁已保证独占)
int affectRows = stockMapper.deductByPessimistic(productId, deductNum);
if (affectRows > 0) {
System.out.println("悲观锁扣减成功,剩余库存:" + (stock.getStock() - deductNum));
return true;
}
return false;
}
}
UPDATE ... WHERE version = ?实现并发控制;SELECT ... FOR UPDATE对目标行加行锁(InnoDB 引擎下,基于主键查询会触发行锁,避免表锁);@Transactional),否则FOR UPDATE的锁会在查询后立即释放,失去锁定意义;OPTIMISTIC_MAX_RETRY需根据业务压测调整(冲突率高则阈值设小,如 2 次;冲突率低则设大,如 5 次);SELECT ... FOR UPDATE需基于主键 / 唯一索引查询,避免因索引失效导致表锁,影响并发;这种混合策略兼顾了乐观锁的高性能和悲观锁的高可靠性,是分布式高并发场景下的实用方案。