Laravel大批量数据处理应分场景:只读用cursor()逐行低内存遍历;写入用chunkById()分批+事务;封装为Artisan命令+Supervisor守护;批量写入用upsert()或原生SQL。

在 Laravel 命令行中循环处理大量业务数据,核心是避免内存溢出、防止超时、保障数据一致性,不能简单写 while(true) 或用 get() 全量加载。正确做法是结合分块查询、惰性遍历、事务控制和进程守护来构建稳定可靠的后台任务。
用 cursor() 逐行读取(只读场景最省内存)
适合导出、统计、发送通知等不修改数据库的操作。它不缓存批次,数据库连接保持,每轮只取一条,内存占用极低。
- 直接在模型上调用:
User::where('status', 'active')->cursor() - 配合
each()或foreach处理:
User::cursor()->each(function ($user) {
// 发送邮件、记录日志、生成 CSV 行
Mail::to($user)->send(new WeeklyReport());
}); - 注意:不可在循环中调用
$user->update(),会导致游标失效或数据错乱
用 chunkById() 分批更新(需写入且数据量大)
比 chunk() 更安全,基于主键范围分页,不会因数据增删导致漏处理或重复处理,适合批量更新、状态变更等操作。
- 按每 500 条一批处理订阅记录:
Subscription::where('status', 'pending')
->chunkById(500, function ($subscriptions) {
foreach ($subscriptions as $sub) {
$sub->update(['status' => 'processed']);
}
}); - 如需事务保障,可在闭包内包裹
DB::transaction(),但建议单批事务不宜过大(例如不超过 100 条更新) - 若中途想退出,返回
false即可中断后续分块
封装为 Artisan 命令 + Supervisor 守护运行
命令行脚本不能依赖 Web 请求生命周期。应将循环逻辑抽离到服务类,通过自定义 Artisan 命令触发,并用 Supervisor 保证常驻与自动恢复。
- 创建命令:
php artisan make:command ProcessIncomeData - 在
handle()中调用服务类的无限循环方法(含sleep(10)防止 CPU 占满) - 用 Supervisor 管理进程:
配置autostart=true、autorestart=true、startsecs=3,确保崩溃后秒级重启 - 务必在循环体中捕获异常并记录日志,避免一次错误导致整个进程退出
配合 upsert() 或原生 SQL 批量写入
如果循环中要插入/更新成千上万条记录,千万别在 foreach 里反复调用 save() 或 insert() —— 这会生成 N 条 SQL,慢且易超时。
- 收集一批数据(例如 500 条),统一调用
Income::upsert($batch, ['transaction_id'], ['amount', 'updated_at']) - 确保
transaction_id字段有唯一索引,否则 upsert 失效 - 对纯数值更新(如所有用户积分 +10),直接写原生 SQL:
DB::statement("UPDATE users SET points = points + 10 WHERE active = 1")


















