记一次关于Laravel model查询返回大量数据的性能优化

近期优化了一个响应速度比较慢的Laravel项目接口,经过一顿排查,最终发现有几处代码执行时间较慢,其中一处代码大致如下:

$keys = Model::query()
    ->where(...) // 中间一些查询条件省去
    ->pluck('id')
    ->toArray();

这段看起来平平无奇的代码执行起来居然要400-500毫秒!!然后我打印了查询出来的行数count($keys),发现查出的数据大概在1万条以上时响应时间就会有明显的加长。出现这个问题我的第一反应是数据库查询响应太慢,但是看了sql日志却发现数据查询响应时间只要二三十毫秒,这跟想象的完全不一样,难道laravel真的有这么不堪吗?

需要说明一下,这个项目使用了laravel-s加速,已经比常规laravel项目要快很多。

然后我想了下,也许是因为查出数据后实例化Model导致的问题? 于是我利用了Builder::macro()扩展了一个cursorPluck方法,省去查出数据后new Model的步骤,代码大概如下

use Illuminate\Database\Eloquent\Builder;

Builder::macro('cursorPluck', function ($value, $key = null) {
    /* @var \Illuminate\Database\Eloquent\Collection $results */
    $results = $this->model->newCollection();

    $this->select(array_values(array_unique(array_filter([$value, $key]))));

    // 这里直接使用 Illuminate\Database\Query\Builder::cursor() 方法,可以省去实例化 Model 这一步骤
    foreach ($this->applyScopes()->query->cursor() as $record) {
        $record = is_array($record) ? $record : (array) $record;

        if ($key) {
            $results->put(($record[$key] ?? null), ($record[$value] ?? null));
        } else {
            $results->add(($record[$value] ?? null));
        }
    }

    return $results;
});

// 然后上述代码更改为
$keys = Model::query()
    ->where(...)
    ->cursorPluck('id')
    ->toArray();

更换了上述代码之后效果立竿见影,速度快了许多!代码执行由开始的400-500毫秒下降到100毫秒左右。

小结

Laravel ORM在日常开发中给我们带来诸多便利,但当使用ORM查询返回的数据条数较多(几千上万条)时,效率会有明显的下降;此时建议直接返回array类型数据,直接跳过实例化Model的步骤,这样可以明显减少代码的执行时间。

在实际开发中,如果是注重性能的接口应当尽量避免处理大量数据,在此次优化过程中还有一些其他小技巧,后续有时间再分享出来。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Jiangqh
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 2

直接用DB::table(xxx)开头是不是就不生成Model

3年前 评论
Jiangqh (楼主) 3年前
Tsukasa_Kanzaki 2年前

file
这跟 PHP 也没关系吧。。。。,任何语言的 ORM 不都是这样的吗

3年前 评论
Jiangqh (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
13
粉丝
304
喜欢
572
收藏
462
排名:170
访问:5.7 万
私信
所有博文
社区赞助商