You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MySQL+Laravel中处理库存读写的竞态条件问题?

解决销售订单库存扣减的竞态问题

针对你遇到的多job并发处理订单导致的库存竞态问题,以下是几种实用的解决方案:

1. 数据库行级悲观锁

直接在数据库层面锁定目标产品的库存记录,确保同一时间只有一个job能修改该记录:

  • 核心逻辑:将库存读取与扣减操作放在同一个事务中,用SELECT ... FOR UPDATE锁定行
  • 示例SQL:
-- 开启事务
BEGIN;
-- 锁定PRODUCTA的库存行,其他请求会阻塞直到锁释放
SELECT stock FROM product_inventory WHERE product_id = 'PRODUCTA' FOR UPDATE;
-- 执行库存扣减(你已处理库存不足判断,直接执行更新即可)
UPDATE product_inventory SET stock = stock - {order_quantity} WHERE product_id = 'PRODUCTA';
-- 提交事务
COMMIT;
  • 注意:事务要尽可能短,避免长时间占用锁影响并发性能。

2. 数据库乐观锁

无需显式加锁,通过版本号或库存值比对实现并发控制,适合高并发场景:

  • 核心逻辑:读取库存时获取版本号,更新时仅当版本号未变化才生效,失败则重试
  • 示例SQL:
-- 读取库存和当前版本号
SELECT stock, version FROM product_inventory WHERE product_id = 'PRODUCTA';
-- 尝试更新库存,仅当版本号与读取时一致才执行
UPDATE product_inventory 
SET stock = stock - {order_quantity}, version = version + 1 
WHERE product_id = 'PRODUCTA' AND version = {read_version};
  • 如果更新返回的影响行数为0,说明其他job已修改库存,需要重新读取数据并重试扣减操作,重试次数建议设为3-5次。

3. 分布式锁(多节点部署场景)

如果你的job运行在多个服务器节点上,数据库行锁无法跨节点生效,可使用分布式锁:

  • 核心逻辑:处理PRODUCTA订单前,先获取针对该产品的分布式锁,操作完成后释放锁
  • 示例(基于Redis):
# 伪代码
lock_key = "lock:product:PRODUCTA"
# 获取锁,设置过期时间(需大于库存处理的最长耗时)
if redis_client.set(lock_key, "locked", nx=True, ex=30):
    try:
        # 执行库存读取、扣减、更新操作
        pass
    finally:
        # 释放锁
        redis_client.delete(lock_key)
else:
    # 未获取到锁,等待重试或直接返回
    pass
  • 注意:要处理锁超时的情况,避免因job异常导致锁无法释放。

4. 订单串行化处理

将同一产品的订单请求放入单独的消息队列,让jobs依次处理:

  • 核心逻辑:所有针对PRODUCTA的订单处理请求都发送到专属队列,单个消费者依次处理队列中的任务
  • 优势:逻辑简单,从根源上避免竞态;劣势:若某产品订单量过大,可能出现队列堆积,需根据业务量级评估是否适用。

通用注意事项

  • 所有库存扣减操作必须保证原子性,要么全部成功,要么回滚到初始状态
  • 乐观锁的重试机制要设置合理次数,避免无限重试消耗资源
  • 无论采用哪种方案,都要做好异常日志记录,方便排查并发问题

内容的提问来源于stack exchange,提问作者Muhammad Adnan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:32:48