
本文介绍如何在 spring boot 多进程/多节点环境下安全地并发读取与更新同一数据库表,重点对比乐观锁与悲观锁的适用场景,并提供可落地的 jpa 实现方案。
本文介绍如何在 spring boot 多进程/多节点环境下安全地并发读取与更新同一数据库表,重点对比乐观锁与悲观锁的适用场景,并提供可落地的 jpa 实现方案。
在分布式微服务架构中,多个 Spring Boot 实例(部署于不同服务器或容器)可能同时访问并修改同一张数据库表的相同记录——例如库存扣减、订单状态流转、计数器更新等场景。此时若缺乏并发控制机制,极易引发脏读、不可重复读、丢失更新等问题。虽然 ShedLock 适用于“全局任务调度互斥”(即确保同一定时任务仅由一个节点执行),但它通过数据库行锁阻塞其他节点的整个任务执行,并不适用于“多节点需同时读写、但须保障数据一致性”的业务需求。
对此,Spring Data JPA 原生支持两种成熟的数据库级并发控制策略:乐观锁(Optimistic Locking) 和 悲观锁(Pessimistic Locking),二者均基于数据库行级锁机制,但实现逻辑与适用场景显著不同。
✅ 乐观锁:轻量高效,适合读多写少场景
乐观锁假设冲突概率低,不主动加锁,而是在提交更新时校验数据是否被其他事务修改过。典型实现是为实体添加 @Version 字段:
@Entity
public class Inventory {
@Id private Long id;
private String itemCode;
private Integer stock;
@Version
private Integer version; // 自动由 JPA 维护,无需手动赋值
}
当执行 repository.save(entity) 时,JPA 自动生成带版本校验的 SQL:
UPDATE inventory SET stock = ?, version = ? WHERE id = ? AND version = ?
若 WHERE 条件不匹配(即 version 已被其他事务更新),则抛出 OptimisticLockException,开发者需捕获异常并重试(如重新加载最新数据后计算再提交)。
✅ 优势:无数据库长时锁,吞吐高;✅ 适用:电商详情页浏览+偶发下单、用户资料查看+低频编辑等场景。
✅ 悲观锁:强一致性,适合写密集或临界资源场景
悲观锁假设冲突频繁,直接在查询阶段就对目标行加锁(如 MySQL 的 SELECT ... FOR UPDATE),阻塞其他事务对该行的写操作,直到当前事务结束。
在 Spring Data JPA 中,可通过 @Query + LockModeType.PESSIMISTIC_WRITE 显式声明:
@Repository
public interface InventoryRepository extends JpaRepository<inventory long> {
@Query("SELECT i FROM Inventory i WHERE i.itemCode = :code")
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<inventory> findForUpdate(@Param("code") String code);
}</inventory></inventory>
调用时需置于 @Transactional 方法内,确保锁在事务提交后释放:
@Transactional
public void deductStock(String itemCode, int quantity) {
Inventory inventory = inventoryRepository.findForUpdate(itemCode)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (inventory.getStock() <blockquote><p>⚠️ 注意:悲观锁会降低并发度,可能导致锁等待甚至死锁,务必保证事务粒度最小化,并避免跨库/跨表复杂查询。</p></blockquote><h3>? 总结与选型建议</h3>
-
优先尝试乐观锁:实现简单、性能好、符合 RESTful 无状态设计思想;配合幂等性设计与重试机制(如
@Retryable),可覆盖绝大多数业务场景。 -
谨慎使用悲观锁:仅在强一致性要求极高、且写冲突频繁(如秒杀库存预占、银行账户余额转账)时采用;务必结合数据库监控(如
SHOW ENGINE INNODB STATUS)分析锁竞争。 - 避免混合使用:同一业务逻辑中勿混用两种锁策略,易导致语义混乱与调试困难。
-
补充防护:无论采用哪种锁,均应配合唯一索引(如订单号唯一)、应用层幂等 Token、以及数据库约束(
CHECK stock >= 0)进行多重保障。
通过合理选用 JPA 内置的乐观/悲观锁机制,无需引入额外中间件,即可在 Spring Boot 多节点环境中实现安全、可控、高性能的数据并发访问。











