@Version 注解不实现MySQL行级锁,仅通过版本号比对防止脏写,适用于读多写少场景;库存扣减应优先使用SELECT ... FOR UPDATE悲观锁配合原子UPDATE,确保强一致性。

@Version 注解本身不实现 MySQL 行级锁,也不能直接用于库存扣减事务。 它是 JPA(如 Hibernate)提供的乐观锁机制,作用是在更新时检查版本号是否被其他事务修改过,从而避免脏写。MySQL 行级锁(如 SELECT ... FOR UPDATE)属于悲观锁,需显式 SQL 控制,与 @Version 无直接关系。混淆两者容易导致库存超卖或事务失效。
理解 @Version 的真实作用
@Version 标注在实体类字段上(如 long version),Hibernate 在执行 UPDATE 时自动追加 WHERE version = ? 条件。若数据库中该行 version 已被更新,则 update 影响行数为 0,抛出 OptimisticLockException。它不加锁、不阻塞、不依赖数据库锁机制,纯靠版本比对实现并发控制。
- 适合读多写少、冲突概率低的场景(如用户资料修改)
- 不适合高并发库存扣减——因失败后需重试,可能引发大量重试/回滚,反而降低吞吐
- MySQL 中无对应锁行为,不会触发 innodb_row_lock_waits 等指标上升
库存扣减应优先用悲观锁 + 数据库原子操作
电商等场景要求强一致性,推荐基于 MySQL 的悲观行锁配合 SQL 原子更新,避免应用层判断和重试开销:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 使用 SELECT * FROM stock WHERE sku_id = ? FOR UPDATE 获取当前库存并加锁(需在事务内,且隔离级别为 RR 或 RC)
- 紧接着执行 UPDATE stock SET quantity = quantity - ? WHERE sku_id = ? AND quantity >= ? —— 条件判断+扣减一步完成
- 检查 UPDATE 返回影响行数:=1 表示扣减成功;=0 表示库存不足或已被扣完
- Spring 中可用 JdbcTemplate 或自定义 Repository 方法封装,确保整个逻辑在同一个 @Transactional 内
若坚持用 @Version,必须配套重试机制
仅当业务能接受“失败重试”且并发冲突率极低时才考虑。关键点:
立即学习“Java免费学习笔记(深入)”;
- 实体类中添加 @Version private Long version; 字段,并在建表时包含 version 列(默认初始值 0)
- 扣减方法需捕获 OptimisticLockException,主动重试(建议加退避,如 Thread.sleep(10)),最多 3 次
- 每次重试前重新查询最新实体(含当前 version 和 quantity),再尝试扣减
- 注意:重试期间其他事务可能已多次变更库存,version 变化不可预测,仍存在最终失败可能
更优替代方案:数据库层面兜底
真正可靠的库存控制,应把核心校验下沉到数据库:
- 在 stock 表上增加 CHECK (quantity >= 0),防止负库存(MySQL 8.0.16+ 支持)
- 用存储过程封装“查-判-扣”逻辑,减少网络往返和应用层复杂度
- 结合 Redis 预减库存(缓存+DB 双写一致),再用 DB 最终落库,兼顾性能与强一致
- 分布式场景下,可引入 Seata AT 模式或 Saga,但需权衡复杂度

















