Spring JPA中如何同步更新实体 解决并发更新库存结果不符问题
问题根因
这是典型的丢失更新问题,本质是并发场景下读写操作没有做互斥控制,后提交的请求会把先提交请求的修改结果覆盖,最终值和预期不符。
你当前的库存操作默认执行逻辑是「先查询当前库存→应用层计算新值→将新值写入数据库」,并发调用加减接口时,两个请求会几乎同时读取到同一个旧库存值,各自完成计算后先后写入数据库,自然会出现结果不符合预期的情况。
举个简单的示例,初始库存为10:
- 减库存接口读到库存10,计算得到新值9
- 加库存接口同时读到库存10,计算得到新值11
- 加库存接口先写入11,减库存接口后写入9,最终库存为9,和预期的10完全不符。
可行解决方案
1. 数据库原子更新(最推荐,实现成本最低)
不要把库存值读取到应用层做计算,直接在SQL层面完成增量操作,数据库本身的行锁会保证同一条商品记录的更新操作串行执行,完全避免丢失更新问题,SQL示例如下:
- 扣减库存:
UPDATE product SET quantity = quantity - 1 WHERE id = [商品ID] - 增加库存:
UPDATE product SET quantity = quantity + 1 WHERE id = [商品ID]
如果需要做库存不足的校验,也可以直接在SQL里加条件,避免扣减时库存为负:UPDATE product SET quantity = quantity - 1 WHERE id = [商品ID] AND quantity >= 1,根据返回的影响行数判断是否扣减成功即可。
2. 加锁控制
如果你的业务逻辑必须要先把库存读取到应用层做复杂计算/校验,再执行关联操作,可以通过加锁实现互斥:
- 单机部署场景可以直接用JVM进程内锁,以商品ID作为锁标识,请求拿到锁之后再执行完整的查库存→计算→写库存逻辑,执行完成后释放锁
- 分布式部署场景可以用Redis分布式锁,同样以商品ID作为锁key,加锁成功后再执行业务逻辑
3. 乐观锁实现
给product表新增一个版本号字段version,每次更新时携带版本号做条件判断:
- 先查询当前库存和对应版本号:
SELECT quantity, version FROM product WHERE id = [商品ID] - 应用层计算新的库存值
- 执行更新SQL:
UPDATE product SET quantity = [新库存值], version = version + 1 WHERE id = [商品ID] AND version = [之前查询到的版本号] - 如果返回的更新行数为0,说明当前商品库存已经被其他请求修改过,可以根据业务需要做重试或者直接返回操作失败
内容的提问来源于stack exchange,提问作者Nguyễn Minh
相关产品推荐
相关产品推荐

