技术分享

Laravel统计查询从4秒到0.5秒,我是这样优化的

作者头像 人称外号大脸猫
15 阅读
Laravel统计查询从4秒到0.5秒,我是这样优化的

数据统计查询缓慢?这份优化指南让你告别等待

在日常开发中,我们经常会遇到数据统计查询缓慢的问题。最近我就接手了一个Laravel项目,其中一个统计查询接口响应时间长达4秒,经过一系列优化后降到了0.5秒。今天就来分享我的优化思路和具体方案。

问题背景

这是一个订单结算系统的统计查询,需要展示:

  • 平台资金概览(用户余额、保证金、冻结金额等)
  • 核心统计数据(订单数、金额、收入、退款等)
  • 图表数据(按日/月统计的收入趋势)

原始代码存在多个性能瓶颈,导致查询缓慢。

性能瓶颈分析

1. 数据库查询问题

// 问题代码示例
$total = $query->clone()->selectRaw('...')->first();
$data = $query->clone()->where('status', 'success')->selectRaw('...')->first();

问题:多次克隆查询,重复扫描大表,缺乏合适的复合索引。

2. 代码逻辑问题

  • 重复构建相同的时间范围查询
  • 大量聚合计算在代码层完成
  • 缺乏缓存机制

3. 数据特性不匹配

  • 资金余额:实时变化,需要快速响应
  • 订单统计:分钟/小时级变化
  • 图表数据:天级变化,变化不频繁

优化方案

第一阶段:数据库索引优化

-- 创建复合索引
ALTER TABLE order_settlements 
ADD INDEX idx_order_at_status (order_at, status);

ALTER TABLE platform_daily_expenditure_stat 
ADD INDEX idx_stat_date (stat_date);

效果:索引优化后,查询速度提升约30%。

第二阶段:查询重构

将多次查询合并为单次查询,使用条件聚合:

// 优化后的查询
$stats = $query->selectRaw('
    COUNT(*) as total_order_count,
    SUM(total_amount) as total_amount,
    COUNT(CASE WHEN status = "success" THEN 1 END) as success_order_count,
    SUM(CASE WHEN status = "success" THEN total_amount ELSE 0 END) as success_total_amount,
    -- 更多条件聚合字段...
')->first();

效果:数据库查询次数减少50%,性能提升约40%。

第三阶段:接口拆分

根据数据特性拆分为三个独立接口:

// 1. 资金概览接口(实时数据)
public function getPlatformOverview()
{
    // 快速返回资金数据
    return response()->json([...]);
}

// 2. 核心统计数据接口
public function getMainStats($params)
{
    // 返回主要统计指标
    return response()->json([...]);
}

// 3. 图表数据接口
public function getChartsData($params)
{
    // 返回图表数据
    return response()->json([...]);
}

第四阶段:缓存策略

为不同数据设置不同的缓存策略:

class StatisticalCache
{
    // 资金概览 - 缓存1分钟(实时性要求高)
    const PLATFORM_OVERVIEW_TTL = 60;
    
    // 核心统计 - 缓存5分钟
    const MAIN_STATS_TTL = 300;
    
    // 图表数据 - 缓存30分钟(变化不频繁)
    const CHARTS_TTL = 1800;
}

前端优化策略

接口拆分后,前端可以采用并行加载策略:

// 并发请求所有数据
const loadStatisticalData = async (params) => {
    const [overview, mainStats, charts] = await Promise.all([
        api.get('/api/statistical/platform-overview'),
        api.get('/api/statistical/main-stats', { params }),
        api.get('/api/statistical/charts', { params })
    ]);
    
    return { overview, mainStats, charts };
};

用户体验提升

  • 资金数据最先显示(0.1-0.3秒)
  • 统计数据随后显示(1-2秒)
  • 图表数据最后加载(1-3秒)
  • 用户无需等待所有数据加载完成

性能对比

优化阶段 响应时间 性能提升
优化前 4秒 -
索引优化 2.8秒 30%
查询重构 1.7秒 40%
接口拆分+缓存 0.5秒 70%

经验总结

  1. 索引是基础:合适的复合索引是性能优化的前提
  2. 减少查询次数:使用条件聚合合并查询
  3. 按需加载:根据数据特性拆分接口
  4. 缓存要分层:不同数据使用不同的缓存策略
  5. 用户体验优先:渐进式加载比整体加载更友好

进一步优化思路

对于超大规模数据,还可以考虑:

  1. 预计算汇总表:定时任务预先计算统计结果
  2. 读写分离:统计查询走从库
  3. 队列处理:复杂统计通过队列异步处理
  4. 数据分区:按时间对大数据表进行分区

结语

性能优化是一个持续的过程,需要根据业务特点和数据规模不断调整。通过这次优化,我深刻体会到:好的架构设计远比硬件升级更有效

希望这些经验对你在Laravel项目中的性能优化有所帮助。如果你有更好的优化方案,欢迎在评论区分享交流!


PS:优化完成后,不仅性能提升了,代码的可维护性也大大增强。单一职责的接口更易于测试和扩展,这才是优化的最大价值。