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();
            }
        }
    }
}
相关推荐
pulinzt2 分钟前
【python第三节】循环+函数初步
开发语言·python
zfj3212 分钟前
java垃圾收集 minorgc majargc fullgc
java·开发语言·jvm·gc·垃圾收集器
三维空间7 分钟前
如何在 Python 中使用 MySQL 数据库使用存储过程
python
skywalk816312 分钟前
Auto-Coder常用秘籍 autocoder.chat启动之后的处理
开发语言·人工智能
叠叠乐13 分钟前
bash sh为什么终端不能tab补全
开发语言·bash
旦莫16 分钟前
怎么才能算对自动化测试框架二次开发(以Pytest为例)
python·测试开发·pytest·ai测试
superman超哥22 分钟前
Rust Rc与Arc的引用计数机制:共享所有权的两种实现
开发语言·后端·rust·编程语言·rust rc与arc·引用计数机制·共享所有权
提笔忘字的帝国24 分钟前
【2026版】macOS 使用 Homebrew 快速安装 Java 21 教程
java·开发语言·macos
半壶清水24 分钟前
【开源免费】使用 Python + Whisper + PyDub 自动切割长音频文件
开发语言·python·语言模型·开源·whisper
ghostwritten25 分钟前
go.mod 与go.sum有什么区别?
开发语言·后端·golang