乐观锁

乐观锁认为并发冲突很少发生,不加锁,而是在更新时检查数据是否被其他事务修改过。

实现方式

版本号机制

-- 1. 查询数据(获取版本号)
SELECT id, count, version FROM stock WHERE id = 1;
 
-- 2. 更新数据(带上版本号条件)
UPDATE stock 
SET count = count - 1, version = version + 1 
WHERE id = 1 AND version = ?;
 
-- 3. 检查更新行数
-- 如果行数为0,说明版本号不对,需要重试或报错

时间戳机制

类似版本号,使用时间戳代替版本号

SELECT id, count, update_time FROM stock WHERE id = 1;
 
UPDATE stock 
SET count = count - 1, update_time = NOW() 
WHERE id = 1 AND update_time = ?;

CAS 机制(Compare And Swap)

CAS(Compare And Swap,比较并交换)是java进程中,乐观锁的一种实现方式,由CPU提供硬件级别的原子操作。

详细内容请阅读:CAS-Compare-And-Swap

Spring Data JPA 乐观锁示例

JPA(Java Persistence API) 是 Java EE 规范中定义的对象关系映射(ORM)标准接口,用于将 Java 对象持久化到关系型数据库中。

  • 核心概念
    • 实体(Entity):使用 @Entity 注解标记的 Java 类,映射到数据库表
    • 实体管理器(EntityManager):负责实体的增删改查操作
    • JPQL(Java Persistence Query Language):面向对象的查询语言
  • Spring Data JPA 是对 JPA 的进一步封装,提供了:
    • 基于方法命名约定的自动查询生成
    • 内置的 CRUD 操作支持
    • 分页和排序功能
    • 声明式事务管理
@Entity
public class Stock {
    @Id
    private Long id;
    
    private int count;
    
    @Version
    private Long version;
    // getters and setters
}
 
@Transactional
public boolean deductStockOptimistic(Long stockId, int quantity) {
    Stock stock = stockRepository.findById(stockId).orElse(null);
    
    if (stock == null || stock.getCount() < quantity) {
        return false;
    }
    
    stock.setCount(stock.getCount() - quantity);
    
    try {
        stockRepository.save(stock);
        return true;
    } catch (OptimisticLockingFailureException e) {
        // 版本冲突,需要重试或返回失败
        return false;
    }
}
@Version 注解的自动机制

使用 @Version 注解后,JPA 会自动处理版本号的比较和更新,无需手动操作:

  1. 自动读取版本号:查询实体时,JPA 会自动加载 @Version 标记的字段值
  2. 自动比较版本号:更新时,JPA 会自动生成带版本条件的 SQL:
    UPDATE stock SET count = ?, version = version + 1 WHERE id = ? AND version = ?
  3. 自动递增版本号:更新成功后,JPA 会自动将实体的版本号加 1
  4. 自动抛出异常:如果版本不匹配(更新行数为 0),JPA 会抛出 OptimisticLockingFailureException

不需要手动做的事情:

  • 不需要手动传递 version 参数
  • 不需要手动在 WHERE 条件中比较 version
  • 不需要手动递增 version 值

需要注意的点:

  • @Version 字段必须存在于实体类和对应的数据库表中
  • 支持的类型:int, Integer, long, Long, short, Short, java.sql.Timestamp
  • 版本字段应使用 Long 类型以避免溢出问题

乐观锁 vs 悲观锁

特性乐观锁悲观锁
并发性能
适用场景读多写少写多读少
实现方式版本号/时间戳/CASselect … for update
冲突处理重试/报错阻塞等待

参考链接