Laravel 10万级邮件群发结果行分块处理的最佳实践是什么
性能问题根因分析
- 一次性加载10万条BlastLog模型到内存,内存占用过高,频繁GC拖累执行效率
- 遍历每条记录时单独执行BlastLog的save()操作,产生10万次单条更新SQL,数据库IO成为严重瓶颈
- 构造BlastEmail时重复查询关联的Blast模型,产生多余的10万次SQL查询
- 未充分利用批量处理和队列并发能力,执行效率上限低
优化方案
1. 用chunk分块处理关联数据
不要直接调用$blast->unsentLogs关联属性(会一次性加载所有数据),而是调用关联方法$blast->unsentLogs()返回查询构造器,使用Laravel内置的chunk方法分块加载,建议单块大小设置为100~500,可根据服务器内存灵活调整。
优化后的handle方法代码:
public function handle() { $blastId = $this->argument('blast'); $blast = Blast::findOrFail($blastId); activity()->performedOn($blast)->log('Blast is sending'); // 分块加载未发送记录,单块200条,同时预加载contact关联避免N+1查询 $blast->unsentLogs() ->with('contact') ->chunk(200, function ($logs) use ($blast) { foreach ($logs as $log) { try { // 直接传入公共的$blast对象,避免每条记录重复查询Blast Mail::queue((new BlastEmail($log, $blast))->onQueue('emails')); } catch (Exception $e) { activity('send_failed')->performedOn($log->contact)->causedBy($log)->log('Email send failed: '.$e->getMessage()); } } }); // 注意:此处直接标记为sent仅代表已全部推送到队列,而非全部发送完成 // 如需标记真实发送完成,建议配合队列完成事件统计实现 $blast->status = 'sent'; $blast->save(); activity()->performedOn($blast)->log('Blast is pushed to queue'); return 0; }
2. 优化BlastEmail构造逻辑
- 不要在构造函数中更新
sent_at字段:Mail::queue仅将邮件序列化后存入队列,此时邮件并未实际发送,提前标记为已发送会导致发送失败的记录也被误标为已发送 - 移除构造函数中查询Blast模型的逻辑,直接传入外部已查询到的
$blast对象,减少重复SQL sent_at更新逻辑建议迁移到邮件发送成功的回调中,可通过Laravel的MessageSent事件监听实现,也可以在队列任务执行完成后更新
优化后的构造函数代码:
public function __construct(BlastLog $log, Blast $blast) { $contact = $log->contact; // 直接使用传入的blast对象,无需重复查询 $this->content = str_replace('**UNSUB**', 'https://urltounsubscribe.com', $blast->content); $this->subject = $blast->subject; $this->to($contact->email, $contact->name ?? 'Name'); $this->from($blast->from_email, $blast->from_name); }
3. 队列配置优化
- 队列驱动优先使用Redis,相比Database驱动有更高的吞吐性能
- 启动多个队列工作进程消费
emails队列,例如启动20个进程:php artisan queue:work --queue=emails --tries=3,可根据服务器配置和邮件服务商限流规则调整进程数量 - 合理设置队列任务超时、重试次数,避免失败任务阻塞队列
4. 额外优化建议
- 如果邮件服务商支持批量发送接口,优先使用批量接口推送,减少API调用次数
- 可将邮件公共内容的处理逻辑提前执行,不需要每条都重复替换相同的基础内容,仅替换个性化部分即可
- 数据库层面为BlastLog的
blast_id、发送状态字段添加索引,提升分块查询的效率
内容的提问来源于stack exchange,提问作者Muhammad Dyas Yaskur
相关产品推荐
相关产品推荐

