Laravel Actions 默认不防并发,需手动加锁;可用数据库行锁、Redis分布式锁或ShouldBeUnique接口实现安全并发,具体策略依一致性要求与性能权衡而定。

并发场景下直接用 Laravel Actions 不会自动加锁或去重,必须手动干预。默认的 asJob 或 dispatch() 调用只是把任务推入队列,不解决竞态问题。
并发写入冲突:为什么 Action 类本身不防重入
Laravel Actions 本质是封装执行逻辑的类,不是事务协调器。它不感知同一 Action 实例是否被多个请求/队列进程同时调用 —— 尤其在库存扣减、余额更新、状态机推进这类场景,handle() 方法里不做防护,就可能产生超卖、负余额、重复触发等错误。
- 数据库层面没加
SELECT ... FOR UPDATE或乐观锁字段,默认读-改-写三步是分离的 -
AsJob的$jobTries只控制失败重试,不阻止并发执行 - 即使用了
dispatchAfterResponse(),也只是延迟执行时机,不改变并发本质
如何让 Action 支持安全并发:三个可落地的策略
选哪种取决于你的数据一致性要求和性能容忍度。没有银弹,只有权衡。
- 用数据库行级锁:在
handle()开头加DB::transaction()+where(...)->lockForUpdate()->firstOrFail()。适合读多写少、锁持有时间短的操作(如订单状态更新) - 用 Redis 原子操作:对关键资源加分布式锁,例如用
Redis::lock('order:123', 10)包裹核心逻辑。注意锁释放必须用finally块兜底,避免死锁 - 用 Laravel 的
ShouldBeUnique接口:在 Action 类里实现它,并设置$jobUniqueFor(单位秒)。适用于“相同输入参数的任务只允许一个在运行中”的场景,比如用户激活流程
asJob + 队列配置对并发的实际影响
队列驱动和 Worker 配置决定了并发上限,但 Action 本身不控制这个层级。你得配合调整:
-
redis驱动支持并发消费,sync驱动完全串行 —— 但后者失去异步意义 - Worker 启动多个进程(
php artisan queue:work --concurrent=4)会放大竞态风险,必须前置加锁 -
jobConnection和jobQueue字段只决定投递目标,不影响执行时的并发行为
真正难的是判断哪里该加锁、加什么粒度的锁。比如“扣减库存”要锁商品记录,“生成唯一订单号”可能得锁整个业务单据类型。Action 封装了逻辑,但锁的边界得你根据领域语义来划。


















