mybatis-plus乐观锁

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();
            }
        }
    }
}
相关推荐
xcLeigh1 天前
Go入门:变量声明的五种方式详解
java·开发语言·golang
zmzb01031 天前
C++课后习题训练记录Day175
开发语言·c++
脱胎换骨-军哥1 天前
C++ 代码规范与格式化指南
开发语言·c++·代码规范
枕星而眠1 天前
C++ STL Map容器完全指南:从有序红黑树到无序哈希表
java·开发语言
lipku1 天前
数字人直播开源项目LiveStream
python·开源·数字人·数字人直播
阿童木写作1 天前
Python实现亚马逊商品图批量智能抠图教程
人工智能·python
爱吃牛肉的大老虎1 天前
Rust对象之结构体,枚举,特性
开发语言·后端·rust
axinawang1 天前
第24课:while循环的应用
python
bbq粉刷匠1 天前
HashMap 底层原理深度拆解(二):putVal 完整链路解析(懒加载 · 链表遍历 · 尾插法)
java·开发语言·哈希算法
三亚兴嘉装饰1 天前
三亚服装店装修
python