laravel 13 中优化大表每日统计需三步:1. 用索引+原生聚合替代 eloquent 全量加载;2. 预计算写入 daily_stats 表并 redis 缓存;3. 超千万行启用 mysql 时间分区,避免 join 导致 count 失真。

在 Laravel 13 中优化大表(如日志、订单、行为记录等)的每日数据统计查询,核心是避免全表扫描、减少锁争用、提升聚合效率,并兼顾应用层可维护性。Laravel 本身不改变 SQL 执行本质,但提供了更安全、可读、可扩展的抽象方式。
用原生聚合 + 索引驱动替代 Eloquent 全量加载
Eloquent 的 groupBy() 和 sum() 看似简洁,但如果未配合索引或条件过滤,底层仍是全表扫描。对千万级表,应优先让数据库完成聚合:
- 确保统计字段(如
created_at、status、user_id)有复合索引,例如:ALTER TABLE orders ADD INDEX idx_date_status (DATE(created_at), status); - 直接使用
DB::select()或查询构造器写聚合 SQL,避免模型实例化开销:DB::table('orders')->selectRaw('DATE(created_at) as date, status, COUNT(*) as count')->whereBetween('created_at', [$start, $end])->groupBy('date', 'status')->get(); - 若需复用逻辑,封装为数据库视图或存储过程(尤其跨多表关联统计时),再通过 Laravel 查询该视图。
预计算 + 缓存每日快照(推荐高频场景)
对“每日 UV/PV/订单数”这类固定维度统计,实时查表成本高且结果稳定。应在数据写入后异步生成当日快照:
- 监听模型事件(如
OrderCreated)或使用队列任务,在每日凌晨 2 点触发汇总任务; - 将结果存入轻量表(如
daily_stats),含字段:stat_date、metric、value、scope; - 查询时直接查该快照表,加
INDEX(stat_date, metric),响应通常在毫秒级; - 搭配 Redis 缓存最近 7 天结果,缓存键如
stats:daily:orders:2025-04-05,失效策略设为定时刷新而非被动删除。
分库分表或按时间分区(超千万行必备)
Laravel 13 原生不支持自动分表,但可通过以下方式落地:
- 使用 MySQL 8.0+ 的
PARTITION BY RANGE (TO_DAYS(created_at))对大表按月/周分区,查询带WHERE created_at BETWEEN ? AND ?时自动剪枝; - 业务层路由:自定义查询构造器宏或 Repository,根据日期动态切换表名(如
orders_202504),配合迁移脚本每月新建分区表; - 谨慎评估是否引入 ShardingSphere 或 Vitess 等中间件——除非单机已达性能瓶颈,否则优先用分区+索引+快照组合方案。
避免 N+1 和冗余 JOIN 影响统计精度
统计时若关联用户、商品等维表,易因 JOIN 放大行数导致 COUNT 错误(如一个订单多个商品 → 订单数被重复计):
- 用
DISTINCT或子查询去重:SELECT DATE(o.created_at), COUNT(DISTINCT o.id) FROM orders o ... - 拆分统计:先查主表聚合(如订单数),再单独查关联维度(如各品类订单数),用应用层合并,而非单条复杂 JOIN;
- 禁用
with()在统计查询中加载关系——它只适用于展示,不适用于聚合上下文。











