在本文中,我們將瞭解 Laravel Eloquent 中的懶載入和預載入以及它如何在後臺執行。
Eloquent 模型關係
Factories
工廠表 與 Workers
工人表,是一對多關係。
// Factory.php
class Factory extends Model
{
public function workers()
{
return $this->hasMany(Worker::class);
}
}
// Worker.php
class Worker extends Model
{
public function factory()
{
return $this->belongsTo(Factory::class);
}
}
當我們在控制器中請求時:
public function index()
{
$factories = Factory::query()->find(1);
}
此時執行的 SQL 語句只有一條
select * from `factories` where `factories`.`id` = '1'
當我們要訪問 Factory 1 的工人時:
public function index()
{
$factories = Factory::query()->find(1);
$factories->workers;
}
此時執行的 SQL 語句是
select * from `factories` where `factories`.`id` = '1'
select * from `workers` where `workers`.`factory_id` = '1'
產生了一次額外查詢。
因為 Eloquent 僅處理了對 Factory
模型進行了查詢,並不知道你想要 workers
的關聯資料,所以並沒有為你準備好它,這樣可以避免不必要的查詢,來加快返回效率。
Laravel 中對所有模型關聯關係的訪問,如果沒有使用 with()
提前告訴 Eloquent 你想要關聯的關係,從而進行訪問時,就叫 懶載入
。通常也是 N+1
問題經常會出現的地方。
假如我們要訪問所有工廠的工人呢?
public function index()
{
$factories = Factory::query()->get(); // 工廠表有 10 條記錄
foreach($factories as $factory)
{
$factories->workers;
}
}
這時就會產生 N+1
的的問題,看一下 SQL 語句
select * from `workers` where `workers`.`factory_id` = '1'
select * from `workers` where `workers`.`factory_id` = '2'
...
select * from `workers` where `workers`.`factory_id` = '9'
select * from `workers` where `workers`.`factory_id` = '10
產生了 10 次 SQL 查詢,加上本身對所有工廠的查詢,一共 11 次,這就是 N+1
了。
我為什麼用 Facotry
工廠表 和 Workers
工人表來舉例呢?因為我要用更直白的話語來描述。
工廠晚上 5 點下班,工人們都回宿舍休息了。晚上 10 點時,流水線長突然接到上頭指示,來了個急活,需要工人加班來工作。因為工人並不知道晚上要加班,所以都脫衣服上床睡覺了,這個時候線長是不是要把他們挨個都叫起來呀?工人從被窩起來,打著哈欠,一邊穿衣服一邊嘴裡罵罵咧咧,然後回到生產線幹活,這個過程就是 `懶載入`。
其實 懶載入
並沒有什麼壞處,它在執行效率上是最優的。程式只查詢預期中的的資料,並不知道你要訪問它的模型關係,當你需要訪問模型關係時,再去查詢一次就好了。
但是我們需要注意的是,當查詢結果不是一個單條記錄(Model
),而是多條記錄(Collection
)時,如果這個時候要去訪問 Collection
中每條記錄的模型關係,那就需要使用接下來的 預載入
了。否則就會產生上文的 N+1
的問題。
還是剛才的查詢,這次使用 with()
預載入工人關係。
public function index()
{
$factories = Factory::query()->with('workers')->get(); // 工廠表有 10 條記錄
foreach($factories as $factory)
{
$factory->workers;
}
}
此時 SQL 查詢就只有 2 條。
select * from `factories`
select * from `workers` where `workers`.`factory_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
還是上面的工廠例子,再舉一次:
工廠接到一筆新訂單,是個急活,廠長週一就通知下去本週六加班,所有工人必須守候在流水線隨時待命,這就叫`預載入`。
工人資訊已經準備好了,只等著你去訪問它就可以了。
由此可以得出結論:
當你的查詢結果返回的是一個 單條記錄(Model
),此時 懶載入
和 預載入
其實沒有區別,因為每一個模型關係就是一次查詢,所以這裡不論是使用 with()
預載入,還是直接使用 懶載入
,對於單條記錄的模型來說,最終 SQL 執行條數都是一樣的。
但當你的查詢結果返回的是多條記錄(Collection
) 時,如果要訪問模型關係,就必須使用 with()
預載入,否則就會產生 N+1
問題。
那麼 load()
是幹嘛的?
我們這樣查詢可以嗎?
public function index()
{
$factory = Factory::query()->load('workers')->find(1);
}
結果是肯定不行的
BadMethodCallException: Call to undefined method Illuminate\Database\Eloquent\Builder::load()
在一個查詢沒有使用 get()
或 find()
返回之前,它都是一個 EloquentBuilder
物件,我們的所有 where()
、with()
、whereIn()
、等方法都是在構造查詢語句,但其實並沒有資料被真正的查詢。
當這條語句被執行時,並沒有 SQL 語句被執行。
Factory::query()->with('workers');
load()
是模型 Model
才能使用的方法, EloquentBuilder
是不能使用的。
看一下如何使用 load()
。
public function index()
{
$factory = Factory::query()->find(1);
$factory->load('workers');
}
此時被執行的 SQL 語句是:
select * from `factories` where `factories`.`id` = '1' limit 1
select * from `workers` where `workers`.`factory_id` in (1)
其實上面的查詢和下面的這句 with()
是一模一樣的:
public function index()
{
$factory = Factory::query()->with('workers')->find(1);
}
那麼 load()
的使用場景是?
假如我們使用依賴注入的方式來查詢 Factory
,但同時我們還要把 workers
關聯一併返回的時候,就會用到它了。
public function index(Factory $factory)
{
$factory->load('workers');
}
with()
是查詢時一併載入模型關聯,load()
是先有模型被查詢後,再載入模型的關聯時使用的。
希望本篇文章能幫你理清這些概念,如果我有哪裡表達不清楚的,還請指正。
本作品採用《CC 協議》,轉載必須註明作者和本文連結