0

0

Eloquent查询优化:提升关联数据统计性能

心靈之曲

心靈之曲

发布时间:2025-07-29 14:26:22

|

404人浏览过

|

来源于php中文网

原创

Eloquent查询优化:提升关联数据统计性能

本文深入探讨了如何优化Laravel Eloquent中涉及关联模型数据统计的慢查询问题。通过分析whereHas和withCount的冗余用法,逐步演示了如何精简查询逻辑,消除不必要的数据库操作,从而显著提升查询性能。教程强调了理解Eloquent底层SQL的重要性,并提供了具体的优化策略和代码示例,帮助开发者构建更高效的数据库交互。

1. 原始问题分析

laravel应用开发中,当需要统计关联模型的数据并进行排序时,不当的eloquent查询写法可能导致严重的性能瓶颈。一个常见的场景是,我们需要查询在特定时间范围内(如本周、上周、总计)发布照片最多的用户列表。以下是一个典型的、但效率低下的查询示例:

public function show()
{
    // 查询本周发布照片最多的用户
    $currentWeek = User::whereHas('pictures') // 冗余的whereHas
        ->whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()]))
        ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    // 查询上周发布照片最多的用户
    $lastWeek = User::whereHas('pictures') // 冗余的whereHas
        ->whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()]))
        ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    // 查询总计发布照片最多的用户
    $overall = User::whereHas('pictures') // 冗余的whereHas
        ->whereHas('pictures') // 冗余的whereHas
        ->withCount('pictures')
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    return view('users.leaderboard', [
        'currentWeek' => $currentWeek,
        'lastWeek' => $lastWeek,
        'overall' => $overall,
    ]);
}

上述代码在实际运行时可能耗时1.5秒甚至更久,主要原因是查询中存在冗余的whereHas调用。例如,对于$currentWeek查询,它生成类似如下的SQL:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
from `users`
where exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `pictures`.`deleted_at` is null) -- 第一个whereHas
    and exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null) -- 第二个whereHas
    and `users`.`deleted_at` is null
    order by `pictures_count` desc
    limit 10

whereHas方法会生成一个EXISTS子查询来判断是否存在关联记录。当存在多个whereHas调用,或者whereHas的条件与withCount的条件重复时,就会产生不必要的数据库查询,显著降低性能。

2. 优化策略一:消除冗余的whereHas

仔细观察原始代码,可以发现每个查询都调用了两次whereHas。例如,->whereHas('pictures') 是一个无条件的检查,它只判断用户是否有任何照片。而紧随其后的 ->whereHas('pictures', fn ($q) => $q->whereBetween(...)) 则是在特定日期范围内检查照片。

由于第二个whereHas已经包含了更具体的条件,并且如果用户在指定日期范围内没有照片,那么他们也不会满足第一个无条件whereHas的要求。因此,第一个无条件的whereHas是完全冗余的。

移除这个冗余调用后的代码如下:

$currentWeek = User::whereHas('pictures', fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()]))
    ->withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])])
    ->orderBy('pictures_count', 'DESC')
    ->limit(10)
    ->get();

此时生成的SQL将只包含一个EXISTS子查询:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
from `users`
where exists (select * from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null)
    and `users`.`deleted_at` is null
    order by `pictures_count` desc
    limit 10

这已经是一个改进,但仍有进一步优化的空间。

3. 优化策略二:深入理解withCount与whereHas的协同

withCount方法不仅可以统计关联模型的数量,还可以通过闭包传入条件来限制统计范围。更重要的是,它会将统计结果作为一个新的字段(例如pictures_count)添加到主模型中,并且这个字段可以直接用于排序。

考虑到我们的目标是获取按照片数量排序的用户列表,即使某个用户在指定时间范围内没有照片,其pictures_count也会是0。由于我们最终会按pictures_count降序排序并限制结果数量,那些照片数量为0的用户自然会排在后面,甚至不会出现在前10名中。

新快购物系统
新快购物系统

新快购物系统是集合目前网络所有购物系统为参考而开发,不管从速度还是安全我们都努力做到最好,此版虽为免费版但是功能齐全,无任何错误,特点有:专业的、全面的电子商务解决方案,使您可以轻松实现网上销售;自助式开放性的数据平台,为您提供充满个性化的设计空间;功能全面、操作简单的远程管理系统,让您在家中也可实现正常销售管理;严谨实用的全新商品数据库,便于查询搜索您的商品。

下载

这意味着,whereHas的判断(用户是否存在满足条件的照片)在很多情况下是多余的。因为withCount已经完成了计数,并且我们依赖这个计数进行排序和筛选。如果一个用户在指定时间范围内没有照片,withCount会返回0,这与whereHas排除该用户达到的效果是等效的(因为0会使其在降序排列中靠后)。

基于此理解,我们可以完全移除whereHas调用,仅依赖withCount进行计数和排序:

public function show()
{
    // 查询本周发布照片最多的用户 (优化后)
    $currentWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    // 查询上周发布照片最多的用户 (优化后)
    $lastWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()])])
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    // 查询总计发布照片最多的用户 (优化后)
    $overall = User::withCount('pictures')
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    return view('users.leaderboard', [
        'currentWeek' => $currentWeek,
        'lastWeek' => $lastWeek,
        'overall' => $overall,
    ]);
}

现在,生成的SQL将变得非常简洁,不再包含任何EXISTS子查询:

select `users`.*, (
    select count(*) from `pictures` where `users`.`id` = `pictures`.`user_id` and `created_at` between ? and ? and `pictures`.`deleted_at` is null
) as `pictures_count`
from `users`
where `users`.`deleted_at` is null
order by `pictures_count` desc
limit 10

通过移除where exists子句,数据库查询的复杂性大大降低,执行效率会得到显著提升。

注意事项

这种优化确实改变了结果集。原始查询只返回那些在指定时间范围内至少有一张照片的用户。而优化后的查询会返回10个用户,即使其中一些用户的pictures_count为0(即他们在指定时间内没有照片)。

如果你的业务逻辑要求只显示有照片的用户,你可以选择在获取结果后进行过滤:

$currentWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])])
    ->orderBy('pictures_count', 'DESC')
    ->limit(10)
    ->get()
    ->filter(fn ($user) => $user->pictures_count > 0); // 过滤掉照片数为0的用户

然而,对于排行榜这类场景,通常直接显示前N名(即使某些用户照片数为0)并无大碍,甚至可以帮助识别活跃度较低的用户。

4. 总结与最佳实践

  1. 理解底层SQL: Eloquent的便利性有时会掩盖其生成的实际SQL。使用Laravel Debugbar等工具检查生成的SQL语句是优化查询的关键第一步。理解whereHas如何转换为EXISTS子查询,以及withCount如何转换为子查询或JOIN,有助于识别性能瓶颈。
  2. 避免冗余条件: 当withCount或withSum等聚合方法已经包含了所需的过滤条件,并且你主要关心聚合结果的排序时,whereHas可能不再是必需的。仔细分析业务逻辑,避免重复的筛选条件。
  3. 索引优化: 确保数据库表中相关的列(如pictures.created_at和pictures.user_id)有合适的索引。这对于whereBetween和关联查询的性能至关重要。
  4. 按需加载: 对于不需要的关联数据,避免使用with或load方法,以减少内存消耗和不必要的JOIN操作。
  5. 批量操作: 对于需要多次执行类似查询的场景,考虑是否可以合并为一次更复杂的查询,或者使用数据库视图、存储过程等方式来优化。

通过以上优化策略,可以显著提升Laravel应用中涉及关联模型数据统计的查询性能,从而提供更流畅的用户体验。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

313

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

270

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

363

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

363

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

80

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

63

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

62

2025.08.05

数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

675

2023.10.12

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Laravel---API接口
Laravel---API接口

共7课时 | 0.6万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

PHP面向对象基础课程(更新中)
PHP面向对象基础课程(更新中)

共12课时 | 0.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号