ThinkPHP6定时任务需并发控制以防数据错乱,核心方案是加唯一执行标记(文件锁或缓存锁)、数据库行级锁保障关键操作、转为队列触发器模式,并注意crontab配置、环境一致性及避免sleep轮询。

ThinkPHP6 定时任务本身不支持原生并发控制,直接用 crontab 多次触发同一命令,或多个进程同时执行相同逻辑,极易引发数据重复、状态错乱、文件锁冲突甚至数据库死锁。这不是框架 Bug,而是调度方式不当导致的典型问题。核心解法是:**不让多个实例同时跑,或让它们能安全协同**。
加唯一执行标记(最常用、最稳妥)
在任务开始前检查是否已有同名任务正在运行,有则直接退出。推荐两种轻量方式:
-
文件锁:用
file_put_contents($lockFile, getmypid(), LOCK_EX)尝试写入带排他锁的 PID 文件;失败说明已被占用;成功则执行,结束后unlink($lockFile) -
缓存标记:用
cache('task_sync_order_lock', true, 300)设置 5 分钟有效期的锁;执行前if (cache('task_sync_order_lock')) exit(0);;任务结束无需手动删,超时自动释放
用数据库行级锁控制关键操作
当任务涉及更新状态、生成单号、扣减库存等敏感动作时,不能只靠外部锁,要在业务层加防护:
- 用
Db::table('jobs')->where('type', 'sync_order')->lock(true)->find()主动加 SELECT FOR UPDATE(需 MySQL InnoDB) - 或改用原子更新:
Db::name('job_log')->where(['type' => 'sync_order', 'status' => 'pending'])->update(['status' => 'running', 'start_time' => time()]);返回影响行数为 0,说明已被其他进程抢先占位
把定时任务转为“触发器”,真正干活交给队列
这是高并发场景下的推荐架构——定时任务只做轻量触发,重活交给 think-queue 异步处理:
立即学习“PHP免费学习笔记(深入)”;
- crontab 每 5 分钟调一次
php think trigger:sync-order,该命令只做一件事:Queue::push(new SyncOrderJob(['date' => 'yesterday'])) - 由 Redis 或 RabbitMQ 队列消费,配合 Supervisor 启动 10+ 个消费者进程并行处理;失败自动重试,不会互相干扰
- 天然具备并发隔离:每个 Job 实例独立执行,互不影响;还能按优先级分队列、限速、监控失败率
避免踩坑的关键细节
很多并发问题其实源于环境配置疏忽:
- crontab 条目别写成
* * * * * cd /app && php think sync:order—— 这等于每分钟启一个新进程,旧进程可能还在跑,必须加锁或改用单例守护 - CLI 模式下
.env默认加载,但某些部署会禁用;务必确认config('database.hostname')在命令行中和 Web 下一致,否则连接池行为不同易引发竞争 - 不要在定时任务里用
sleep(10)做轮询等待,它既不省资源又无法响应信号;真要等,用 Redis 的brpop或数据库长轮询 + 超时判断



















