正確使用 Laravel Eloquent

自 Laravel 5.3 之後,看到過許多專案的 Eloquent 關聯與查詢都不是使用相對有效率的用法。這些情況之所以存在是因為有不同的方法可以取得同一個資料集,而且多數看起來非常像。除非您非常了解底層做了什麼,不然判斷哪個方法比較好也不是那麼容易。

例如在 $blog->posts$blog->posts() 之間的選擇。

$blog->posts 會讀取資料庫,讀取跟 $blog 相關的文章,然後建立一個 Post Model 的 Collection。這個操作是很耗費效能的,因爲需要為每個相關的 Model 建立物件實例並賦值。

首先,您應該要先問問自己 - 是否需要 Model 的物件實例?一般來說,您可能只需要幾個欄位的資料,除非您需要欄位自動轉型之類的例如 Carbon 日期型別的資料,不然您應該直接從資料庫取得資料就好。

$blog->posts() 的話會回傳 Builder 而不會直接執行實際查詢。到此則是該再次問問自己 - 我需要整個資料集合嗎?查詢可以在更具體限縮一點嗎?比較好的方式是盡可能在對資料庫查詢多一點操作,在 PHP 少一點。舉例來說 where 操作在資料庫會比在 Collectionwhere 快。

讓我們繼續看一些範例。

不要使用 Collection 來計算數量

$blog->posts->count() 會先建立 Post Model 的資料集合,包含所有屬性欄位資料,然後才回傳 Model 的數量。如果 posts 已經載入了,那用這個方式計算當然沒什麼問題,但如果之前沒載入過那就會浪費很多效能。

$blog->posts()->count() 則會組合查詢的 SQL 然後從資料庫取得一個數字回來。沒有建立 Model,也沒有屬性。我們就只要數量而已。不是嗎?

假如是一個 5000 筆資料的查詢,第二個作法大概會比第一個作法快 20 倍左右。不過這會跟您的系統環境有關,注意到查詢的數量越大效率的差異會更加極端。

如果您只需要第一筆資料,不要讀取整個集合

這是一個顯而易見的錯誤,但我認為需要再提一次。

$blog->posts->first() 跟之前提到的一樣,它會讀取並建立整個集合的物件,然後您只需要第一筆。

如果您只需要第一筆資料那麼您應該使用 $blog->posts()->first() 如此在查詢中就會使用 OFFSETLIMIT 而且只會回傳建立一個 Model。

重點是要理解 Model 的方法在調用後回傳了什麼。

如果 Builder 能做到,請不要在 Collection 使用 where

$blog->posts->where('author', 1) 會先建立所有的 Model,然後遍歷這個集合找到符合條件 author 為 1 的 Model,再建立另一個新的集合,最後回傳資料給您。

我們在資料庫使用的任何索引都將無效,因為這裡是靠 PHP 完成查詢的。

$blog->posts()->where('author', 1)->get() 的話,所有具體的查詢都是在資料庫完成的,如果有索引的話,會加快效率,並且只會回傳符合的資料,就不會建立多餘的物件。

不要在 Collection 使用 pluck,使用 Builder 取代

Laravel 專案隨處可見 pluck。我們很多時候只是要某個欄位的值,舉例來說我們需要 Blog 的 post_id 作為 Key 和 name 的資料。

完成上述需求的其中一種方式就是使用 $blog->posts->pluck('name', 'id') 但再一次一樣會建立 Collection,也會建立所有列表資料的 Model 物件實例。實際上我們不需要 Model,只需要某個欄位的資料而已,此時我們可以利用 Builder 完成。

另外,$blog->posts()->pluck('name', 'id') 看似跟上面幾個點非常像,但行為卻不同。調用 pluck 一樣會傳遞給 Builder ,然後執行 SELECT name, id 的查詢,接著使用資料庫取得的資料建立一陣列。

前面提到,使用 Builder 不會利用 Model 的方法變更物件實例的欄位資料,因爲根本沒有 Model 的物件實例。

此時如果 name 是動態的或者使用 Model 的 getNameAttribute 方法變更,那麼利用 Builder 取得資料的 pluck 將會和 Model 的資料不一樣。請特別注意。

大多數類似的狀況下,使用 Builder 可能產生很大的不同。因此建議您應該多思考一下關於框架背後執行的操作。

參考資料

作者

andyyou(YOU,ZONGYAN)

發表於

2020-09-08

更新於

2023-12-05

許可協議