Laravel 11 中每日统计应异步化触发、聚合与落库,通过 Artisan 命令定时 dispatch DailyStatsAggregationJob,在 handle() 中用 Eloquent 聚合查询并 upsert 到专用统计表,确保时区统一、幂等、可重试。

在 Laravel 11 中,用队列支撑每日数据统计查询,核心不是“把查数据库的动作塞进队列”,而是把统计计算的触发、聚合、落库过程异步化、可重试、可监控。真实场景中,你通常不需要队列去“查”数据,而是用队列来“生成”或“更新”统计结果——比如每天凌晨汇总昨日订单量、用户活跃数、文章阅读分布等。
明确统计任务的生命周期
每日统计本质是定时+聚合+持久化的过程,应拆解为三步:
- 触发:由 Artisan 命令(如
php artisan stats:aggregate --date=yesterday)在 Laravel Scheduler 中定时调用 - 执行:命令内不直接写 DB,而是 dispatch 一个
DailyStatsAggregationJob到队列(推荐 Redis 或 database 驱动) - 落库:Job 的
handle()方法中执行 Eloquent 聚合查询(sum、count、groupBy('date')等),再写入专用统计表(如daily_stats)
设计专用统计模型与表结构
避免在业务表上反复 count(*) where created_at between ...,性能差且易受锁影响。建议建独立统计表:
- 字段至少含:
stat_date(DATE 类型,主键或联合索引)、metric(如 'user_active', 'order_total')、value(BIGINT/DECIMAL)、context(JSON,存渠道、区域等维度) - 迁移中加复合索引:
INDEX idx_date_metric (stat_date, metric),加速按日+指标查询 - 模型使用
$casts = ['context' => 'array'],方便存取多维标签
用事件+队列联动保障一致性
如果统计依赖实时业务动作(如每笔订单完成需计入当日成交额),不要靠定时扫表,改用事件驱动:
- 订单支付成功时 dispatch
OrderPaid事件 - 监听器中 dispatch
IncrementDailyRevenueJob::withChain([...]),链式处理:先更新缓存计数器,再批量写入统计表 - Job 内通过
Carbon::yesterday()->toDateString()确定目标日期,用upsert()原子写入,避免重复统计
规避常见陷阱
每日统计容易踩坑,关键细节要卡死:
-
时区统一:所有时间计算用
Carbon::setTestNow()或配置app.timezone为 UTC,数据库stat_date字段也按 UTC 存储,前端展示再转本地时区 -
幂等性:Job 执行前先查
DailyStat::where('stat_date', $date)->where('metric', $metric)->exists(),已存在则跳过 -
失败兜底:设置
tries = 3和backoff = 60;失败后发通知,并允许手动重试:php artisan queue:retry {id} -
避免长事务:聚合查询加
->useReadPdo()走只读副本;大表统计用chunkById()分批,不一次性 load 全量


















