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();
            }
        }
    }
}
相关推荐
VidDown5 小时前
24fps 转 25fps 不只是改个数字:帧率转换、PAL/NTSC 与音频补偿
python·网络协议·音视频·视频编解码·视频
死磕音视频6 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林7 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
pride.li8 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
databook8 小时前
用 Pydantic AI 从零搭建一个能干活的 AI Agent
人工智能·python
顽疲9 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
康小旺9 小时前
把 markitdown 做成 Windows 桌面版:拖进文档就出 Markdown,附 233MB→64MB 打包裁剪全过程
python
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友
启观川10 小时前
数据结构与算法-第 3 章 常用算法-贪心算法
数据结构·笔记·python·算法·贪心算法
南笙北梦10 小时前
本地开源大模型多轮对话显存泄漏排查与修复实录
python