Laravel 學習筆記 — 專案練習 Part 8 — N+1 問題

ViNciHsu
Oct 21, 2020

--

這篇主要在了解,什麼是 N+1 或者說 1+N 問題

前幾篇我們的 CRUD 透過 ORM 可以很快地實作出來,但其實其中隱藏了影響效能的 N+1 問題

我們來實際了解一下,何謂 N+1 問題

我們來看一下"文章列表",我們想想,在呈現這些列表的過程中,做了幾次的查詢?看起來是不是 1 次,也就是我們在 App\Http\Controllers\ArticlesController::index 所寫的 $articles = Article::orderBy(‘id’,’desc’)->paginate(3);

我們以為"文章列表"的內容僅做了一次查詢,撈出我們畫面看到的資料
$articles = Article::orderBy(‘id’,’desc’)->paginate(3); 看起來是做了一次查詢

但是事實上,"文章列表"的查詢次數,是做了 1 次(該作者全部的 article ) + 列表上顯示"作者"的 3 次,什麼意思呢?

意思就是說,其實我們看到"文章列表"中,由 XXX 分享的字樣,這個 {{ $article->user->name }} 撈到作者名字的資料,是透過迴圈,每一個 article 都會查一次,這個 article 的 user.name ,因此以我練習的例子來看,首頁的"文章列表",實際查詢了 1+3 次

實際查詢了 1+3 次

我們怎麼確認真的查了 1+3 次呢?我們可以安裝 laravel-debugbar

直接 google laravel-debugbar (https://github.com/barryvdh/laravel-debugbar),透過 composer 安裝,這個套件可以幫助我們看到查詢相關的資訊

在終端機輸入:composer require barryvdh/laravel-debugbar — dev 讓系統自動安裝該套件

安裝完畢後,我們重新啟動 php artisan serve,並且刷新網頁,可以看到瀏覽器下方多了一條 bar

瀏覽器下方多了一條 bar

我們點開 bar ,查看 Queries,可以看到,select * from “users” where “users”.”id” = ‘1’ limit 1 這個 SQL 語法做了 3 次,為什麼會這樣呢?就是我們前面所說的, ORM 除了一次撈出這個登入的使用者的文章後,又針對每一篇文章去查作者是誰,因此在列表總共有 3 筆時,就會多 3 次查詢;隨著網頁編寫越來越複雜,這樣的查詢會不知不覺的影響效能

整體畫面
放大畫面

既然現在知道問題在哪了,我們可以到官網,找尋相關解決方法,關鍵字是:Eager (急切) Loading

官方文件有提到 N+1 問題

繼續往下查找,可以看到文件有提到相關解決方法,即透過 with()

透過 with() 可解決 N+1 問題

我們回到 App\Http\Controllers\ArticlesController::index 修改一下編寫方式

將 $articles = Article::orderBy('id','desc')->paginate(3); 改成

$articles = Article::with('user')->orderBy('id','desc')->paginate(3); 之後,我們重整網頁,再透過 laravel-debugbar 查看,會發現,此時的 SQL 語法變成了 select * from "users" where "users"."id" in (1),這裡改成使用 IN 來查詢

什麼意思呢?我在創建另一個使用者登入,並且發表文章後,重新查看 SQL 語法會變成怎樣

實際操作後,我們發現 SQL 語法現在是 select * from "users" where "users"."id" in (1, 2)

SQL 語法現在是 select * from “users” where “users”.”id” in (1, 2)

我們可以將程式碼改回去,看一下本來 N+1 問題的 SQL 語法查出來會長怎樣

N+1 問題的 SQL 語法查出來是這樣

如果看不太出來,我們可以先將 paginate(10) 參數設定大一點,讓首頁可以列出全部文章,再來比較查詢次數

使用 with() 只查詢了 1 次,而沒用 with() 則查詢了 5 次

N+1 問題,是新手常會忽略/沒感覺的問題,我們可以將這點學起來

--

--

No responses yet