乐观锁(MyBatis-Plus)
- ✅ 一句话定义
- ✅ 实现原理(版本号机制,MP默认)
- ✅ MyBatis-Plus 使用3步
- ✅ MP乐观锁的局限(重点)
- ✅ 乐观锁其他实现方案
- ✅ 适用场景&不适用场景
- ✅ 乐观锁 vs 悲观锁
- ✅ 常见坑点
- ✅ 面试一句话
- ✅
✅ 一句话定义
乐观锁是并发控制方案,思想:乐观地认为数据很少发生并发冲突,读取数据不加锁;更新的时候校验版本号,判断数据有没有被别人修改过。如果版本不一致,更新失败,防止并发更新覆盖数据。
场景:多个人同时修改同一条记录,避免更新丢失(后提交的人把前面人的修改覆盖掉)
✅ 实现原理(版本号机制,MP默认)
- 数据库表增加
version数字字段,默认值=1 - 查询数据时,同时读出当前
version - 更新时:SQL 自动带上条件
where id=? and version=?;更新成功,version +1 - 如果别人已经抢先修改,数据库里version已经变大,条件不成立,更新行数为0,修改失败
自动生成SQL示例:
update user set name="xxx",version=2 where id=1 and version=1
✅ MyBatis-Plus 使用3步
- 数据库表添加
version int default 1 - 实体类字段上加
@Version
@Version private Integer version;- 配置类注册乐观锁插件
OptimisticLockerInnerInterceptor
@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//分页插件 return interceptor; } }✅ MP乐观锁的局限(重点)
- 仅在特定更新方式生效
✅ 生效:updateById(实体对象)、update(实体对象, wrapper)
❌ 不生效:只传Wrapper,不带实体对象的更新,MP不会自动携带version、不会自动+1版本号
// ✅ 有效,自动维护version User user = userMapper.selectById(1L); user.setName("新名字"); userMapper.updateById(user); // ❌ 无效!没有实体,不会处理version UpdateWrapper<User> wrapper = new UpdateWrapper<>(); wrapper.set("name","新名字").eq("id",1L); userMapper.update(null, wrapper);- 一次更新只能识别一次冲突:如果并发非常激烈,乐观锁更新失败,需要代码手动重试机制,框架不会自动重试。
- 只能防止更新丢失,不能解决脏读:乐观锁无法阻止事务读取到未提交的数据,它只控制提交更新时的覆盖问题。
✅ 乐观锁其他实现方案
MP默认是版本号(version);还有另一种常用方案:时间戳timestamp
原理:每次更新时校验读取时的时间戳,更新成功刷新时间戳。
缺点:时间戳依赖服务器时间,集群环境时间不同步会产生问题,所以项目优先使用version数字版本。
✅ 适用场景&不适用场景
✅ 适合(读多写少):商品信息修改、用户资料编辑、文档修改,并发冲突概率低,不想用数据库锁降低性能。
❌ 不适合(写多读少,频繁并发争抢):库存扣减、秒杀,冲突概率极高,大量更新失败不断重试,性能很差,这种场景推荐悲观锁/分布式锁。
✅ 乐观锁 vs 悲观锁
| 乐观锁 | 悲观锁 | |
|---|---|---|
| 锁位置 | 业务层版本号,不占用数据库锁 | 数据库行锁,查询时锁住记录 |
| 并发性能 | 高,读操作无阻塞 | 低,写操作会阻塞其他请求 |
| 冲突处理 | 更新失败,业务重试/提示用户 | 其他事务阻塞等待锁释放 |
| 数据库支持 | 数据库无关,纯业务逻辑 | 依赖数据库引擎(InnoDB支持行锁) |
| 典型实现 | MP @Version版本号 | select … for update |
✅ 常见坑点
- version字段类型:推荐
Integer/Long,不要用tinyint,版本号不断增长容易溢出。 - 新增数据:必须给version设置默认值1,数据库字段
version int default 1,否则新增数据version为null,后续更新校验失效。 - 多表关联更新:MP乐观锁插件不支持多表SQL,手写多表更新SQL不会自动维护版本号。
✅ 面试一句话
乐观锁是一种并发控制策略,假设并发冲突很少发生,查询不加锁,在提交更新时校验版本号判断数据是否被修改。MyBatis-Plus通过@Version注解+乐观锁插件实现,自动拼接version条件并递增版本;但它只对带实体对象的update方法生效,高并发写场景下大量失败,适合读多写少业务。
✅
乐观锁是解决并发更新丢失问题的方案,核心思想认为同一数据很少会同时被多人修改,读取数据时不加锁,更新的时候校验版本号。
在MyBatis-Plus中使用乐观锁,需要在数据表增加version字段,实体类使用@Version标记版本属性,并且在配置类注册乐观锁拦截器。当执行更新时,MP会自动在where条件带上当前版本号,更新成功则版本号+1;如果版本号不匹配,说明数据已经被其他用户修改,本次更新失败。
⚠️ 注意:MP乐观锁仅在传入实体对象的update方法生效。纯Wrapper更新不会自动维护版本号。乐观锁适合读多写少场景;如果是高并发争抢数据场景,不推荐乐观锁。