iT邦幫忙

2021 iThome 鐵人賽

DAY 11
2
Software Development

Laravel 新手初見 API系列 第 11

Day11-Database——效能的儲備足夠嗎?-N+1 query

標題參考來源
大家好~
今天來簡單認識一下 N+1 query 吧!

什麼是 N+1 query 呢?

假設我們查詢10篇文章與文章作者,
那麼這個查詢將會做11次查詢。
1次是查詢10篇文章,
另外10次是查詢文章的作者。
上述情況就是發生了 N+1 query。

另外在這篇文章內有用烤蛋糕的方式解釋 N+1 query,
挺有趣的~

實作 N+1 query

來試試如何引發 N+1 query 吧~
目前我的資料庫內有10筆留言,
然後使用下方範例做關聯查詢。

use Illuminate\Support\Facades\DB;

public function index()
{
    DB::listen(function ($query) {
        var_dump($query->sql);
    });
    
    $messages = Message::all();
    $messages->each->user;
    return $messages;
}

為了方便測試,
我在該 function 內使用 DB::listen() 監聽事件。
下圖為測試結果:

如我們預期的發生 N+1 query 了~
那麼接下來讓我們認識該如何避免發生 N+1 query 吧!

如何避免 N+1 query?

在我的認知內,
加載關聯時用以下方式可以避免:

  1. Eloquent: Relationships——with()
  2. Eloquent: Collections——load()
  3. Eloquent: API Resources——whenLoad()

那麼來用 with() 解決剛剛發生的 N+1 query 吧~

public function index()
{
    DB::listen(function ($query) {
        var_dump($query->sql);
    });
    
    $messages = Message::with('user')->get();
    return $messages;
}

也能用 load() 解決:

public function index()
{
    DB::listen(function ($query) {
        var_dump($query->sql);
    });
    
    $messages = Message::all();
    $messages->load('user');
    return $messages;
}

whenLoad() 就必須搭配 API Resources 一起使用,
whenLoad() 會判斷有無加載關聯,
有加載才會顯示關聯資料,
進而避免 N+1 query。
至於用法的話,
我在 Day07 時有用到 whenLoad()
有興趣的夥伴可以參考一下~

特殊手段

在 Laravel 8 後,
有支援一種方式可以有效避免 N+1 query!
只要將 AppServiceProvider.phpboot() 中加入這一段:

Model::preventLazyLoading(! app()->isProduction());

完整使用方式如下:

app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;

public function boot()
{
    Model::preventLazyLoading(! app()->isProduction());
}

這樣設置後,
Laravel 會直接 Disabling Lazy Loading,
之後只要遇到 N+1 query,
Laravel 就會直接回報錯誤訊息給你喔!

小結

當資料量大時,
N+1 query 產生的大量且多餘的查詢,
會對我們的資料庫造成效能上的負擔。
只要我們在使用 ORM 時稍加留意,
就可以有效避免發生 N+1 query 喔~

大家明天見啦~
若文章有任何問題,
還請大家不吝賜教!

參考資料:


上一篇
Day10-為了讓表單資料不要太過自大,給予其正確的絕望-Validation(III)
下一篇
Day12-Webhook 實作(一)LINEBot Channel 申請、SDK 安裝
系列文
Laravel 新手初見 API30

尚未有邦友留言

立即登入留言