記一次關於Laravel model查詢返回大量資料的效能優化

Jiangqh發表於2021-04-09

近期優化了一個響應速度比較慢的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

相關文章