全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-18 10 分钟 ✍️ juanwangdev

Spring事务与数据库锁

数据库锁保证并发数据一致性,Spring事务管理锁的获取和释放时机。

锁类型对比

类型说明适用场景性能
悲观锁先锁再操作,防止冲突写多读少,强一致性较低
乐观锁先操作再检测,版本号控制读多写少,允许重试较高

悲观锁实现

SELECT FOR UPDATE

Java
@Repository
public class AccountDao {

    // 悲观锁查询
    @Query("SELECT a FROM Account a WHERE a.id = :id FOR UPDATE")
    Account findByIdForUpdate(@Param("id") Long id);

    // 或者使用原生SQL
    @Query(value = "SELECT * FROM account WHERE id = ? FOR UPDATE",
           nativeQuery = true)
    Account lockById(Long id);
}

@Service
public class AccountService {

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 加锁查询,按id顺序加锁防止死锁
        Account from = accountDao.findByIdForUpdate(Math.min(fromId, toId));
        Account to = accountDao.findByIdForUpdate(Math.max(fromId, toId));

        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
    }
}

JPA悲观锁

Java
@Entity
public class Product {
    @Id
    private Long id;
    private Integer stock;
}

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

    // PESSIMISTIC_WRITE - 写锁(排他锁)
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Product findByIdWithWriteLock(@Param("id") Long id);

    // PESSIMISTIC_READ - 读锁(共享锁)
    @Lock(LockModeType.PESSIMISTIC_READ)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Product findByIdWithReadLock(@Param("id") Long id);
}

@Service
public class ProductService {

    @Transactional
    public void decreaseStock(Long productId, int quantity) {
        // 获取写锁
        Product product = productRepository.findByIdWithWriteLock(productId);
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        product.setStock(product.getStock() - quantity);
    }
}

MyBatis悲观锁

XML
<!-- Mapper XML -->
<select id="selectByIdForUpdate" resultType="Account">
    SELECT * FROM account WHERE id = #{id} FOR UPDATE
</select>

<select id="selectByIdForUpdateNowait" resultType="Account">
    SELECT * FROM account WHERE id = #{id} FOR UPDATE NOWAIT
</select>

乐观锁实现

版本号方式

Java
@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private Integer stock;

    @Version  // JPA版本号注解
    private Integer version;
}

@Service
public class ProductService {

    @Transactional
    public void decreaseStock(Long productId, int quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("商品不存在"));

        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }

        product.setStock(product.getStock() - quantity);
        // 更新时自动检查version,失败抛出OptimisticLockException
        productRepository.save(product);
    }
}

手动版本号

Java
@Entity
public class Order {
    @Id
    private Long id;
    private Integer version;  // 手动维护
    private String status;
}

@Repository
public class OrderRepository {

    @Modifying
    @Query("UPDATE Order o SET o.status = :status, o.version = o.version + 1 " +
           "WHERE o.id = :id AND o.version = :version")
    int updateWithVersion(@Param("id") Long id,
                          @Param("status") String status,
                          @Param("version") Integer version);
}

@Service
public class OrderService {

    @Transactional
    public boolean updateStatus(Long orderId, String newStatus) {
        Order order = orderRepository.findById(orderId).orElse(null);
        if (order == null) return false;

        int updated = orderRepository.updateWithVersion(
            orderId, newStatus, order.getVersion());

        if (updated == 0) {
            // 版本冲突,更新失败
            throw new OptimisticLockException("数据已被修改,请刷新后重试");
        }
        return true;
    }
}

CAS方式

Java
@Repository
public class StockDao {

    // 条件更新,类似CAS
    @Modifying
    @Query("UPDATE Stock s SET s.quantity = s.quantity - :qty " +
           "WHERE s.productId = :productId AND s.quantity >= :qty")
    int decreaseStock(@Param("productId") Long productId,
                      @Param("qty") Integer qty);
}

@Service
public class StockService {

    @Transactional
    public boolean decreaseStock(Long productId, int quantity) {
        int affected = stockDao.decreaseStock(productId, quantity);
        if (affected == 0) {
            // 库存不足或不存在
            return false;
        }
        return true;
    }
}

死锁预防

按顺序加锁

Java
@Service
public class TransferService {

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 按ID排序加锁,避免死锁
        Long first = Math.min(fromId, toId);
        Long second = Math.max(fromId, toId);

        Account acc1 = accountDao.findByIdForUpdate(first);
        Account acc2 = accountDao.findByIdForUpdate(second);

        // 执行转账
        acc1.setBalance(acc1.getBalance().subtract(amount));
        acc2.setBalance(acc2.getBalance().add(amount));
    }
}

设置锁超时

Java
@Transactional(timeout = 10)  // 事务超时
public void process() {
    // MySQL: SELECT ... FOR UPDATE NOWAIT (MySQL 8.0+)
    // Oracle: SELECT ... FOR UPDATE NOWAIT
    // PostgreSQL: SELECT ... FOR UPDATE NOWAIT
}

减少锁持有时间

Java
@Service
public class OrderService {

    // 错误:锁持有时间过长
    @Transactional
    public void createOrder_bad(OrderRequest request) {
        Product product = productDao.findByIdForUpdate(request.getProductId());
        // 模拟耗时操作
        Thread.sleep(5000);  // 锁被长时间持有
        orderDao.insert(new Order(product));
    }

    // 正确:减少锁持有时间
    public void createOrder_good(OrderRequest request) {
        // 先执行不需要锁的操作
        validateRequest(request);

        // 只在必要时刻加锁
        Order order = createOrderWithLock(request);
        // 锁释放后再执行其他操作
        sendNotification(order);
    }

    @Transactional
    protected Order createOrderWithLock(OrderRequest request) {
        Product product = productDao.findByIdForUpdate(request.getProductId());
        Order order = new Order(product);
        orderDao.insert(order);
        return order;
    }
}

锁选择策略

场景推荐锁类型原因
商品秒杀乐观锁+重试高并发,写冲突少
账户转账悲观锁强一致性要求
库存扣减CAS或悲观锁防止超卖
订单状态更新乐观锁读多写少

要点总结

  • 悲观锁适合写多、冲突频繁场景
  • 乐观锁适合读多写少场景,配合@Version
  • SELECT FOR UPDATE实现悲观锁
  • 版本号机制实现乐观锁,冲突时抛异常
  • 按固定顺序加锁预防死锁
  • 缩短锁持有时间提高并发性能

📝 发现内容有误?点击此处直接编辑

← 上一篇 @Transactional注解详解
下一篇 → Spring事务管理接口PlatformTransactionManager
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库