近期优化了一个响应速度比较慢的 Laravel
项目接口,经过一顿排查,最终发现有几处代码执行时间较慢,其中一处代码大致如下:
$keys = Model::query()
->where(...) // 中间一些查询条件省去
->pluck('id')
->toArray();
Copy
这段看起来平平无奇的代码执行起来居然要 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();
Copy
更换了上述代码之后效果立竿见影,速度快了许多!代码执行由开始的 400-500
毫秒下降到 100
毫秒左右。
小结
Laravel ORM
在日常开发中给我们带来诸多便利,但当使用 ORM
查询返回的数据条数较多(几千上万条)时,效率会有明显的下降;此时建议直接返回 array
类型数据,直接跳过实例化 Model
的步骤,这样可以明显减少代码的执行时间。
在实际开发中,如果是注重性能的接口应当尽量避免处理大量数据,在此次优化过程中还有一些其他小技巧,后续有时间再分享出来。