mybatis-plus乐观锁
乐观锁vs悲观锁
悲观锁
假设:数据冲突是常态
策略:先锁定,再操作
心态:防御性编程
成本:前期投入大(锁的开销)
场景:写多读少
sql
-- 悲观锁的思维方式
BEGIN TRANSACTION;
-- "我不相信别人,先锁住再说"
SELECT * FROM account WHERE id = 1 FOR UPDATE; -- 立即加锁
-- 只有我能操作
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 最后才释放锁
乐观锁
假设:数据冲突是例外
策略:通过版本号/时间戳检测数据变化,读取时不加锁,更新时检查
心态:乐观协作
成本:失败时重试成本
场景:读多写少
sql
UPDATE product
SET stock = 90, version = version + 1
WHERE id = 1 AND version = 5
-- 如果version=5的记录存在,更新并返回1
-- 如果version已变(别人改了),更新0行
mybatis-plus乐观锁
添加版本字段
java
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer stock;
private BigDecimal price;
@Version // 核心注解!
private Integer version;
}
配置乐观锁插件
java
@Configuration
public class MybatisPlusConfig {
/**
* 乐观锁插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
使用乐观锁更新
java
@Service
@Slf4j
public class ProductServiceV2 {
private static final int MAX_RETRY = 3;
@Transactional(rollbackFor = Exception.class)
public boolean deductStockWithRetry(Long productId, Integer quantity) {
int retryCount = 0;
while (retryCount < MAX_RETRY) {
try {
// 每次重试都需要重新查询最新数据
Product product = productMapper.selectById(productId);
if (product.getStock() < quantity) {
log.warn("库存不足,productId: {}, stock: {}",
productId, product.getStock());
return false;
}
product.setStock(product.getStock() - quantity);
int rows = productMapper.updateById(product);
if (rows > 0) {
log.info("扣减成功,productId: {}, 剩余库存: {}",
productId, product.getStock());
return true;
}
// 更新失败,版本冲突,重试
retryCount++;
log.info("版本冲突,第{}次重试,productId: {}",
retryCount, productId);
// 指数退避:减少竞争
Thread.sleep(50 * retryCount);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("操作被中断");
}
}
log.error("扣减失败,达到最大重试次数,productId: {}", productId);
throw new BusinessException("系统繁忙,请稍后重试");
}
}
秒杀系统
一般采用 乐观锁 + 分布式锁、
分布式锁控制流量
乐观锁控制数据的一致性
java
@Slf4j
@Service
public class SeckillService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductMapper productMapper;
public boolean seckillProduct(Long productId, Long userId) {
String lockKey = "seckill:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 分布式锁:控制并发入口
if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) {
throw new BusinessException("排队人数过多,请稍后");
}
// 乐观锁:保证数据一致性
return deductStockWithRetry(productId, 1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("系统繁忙");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}