作者:拾年之璐 公众号:知行研究院
1、预加载,就是解决关联查询中产生的N+1 次查询带来的资源消耗。
所谓 N+1 条,就是起初获取全部数据的 1 条和,遍历的 N 条。看下面的示例。
比如在下面的关联查询
中,要获取所有书籍的作者(或拥有者),即通过获取所有的书籍,查询每个书籍对应的所有者:
//获取所有书籍列表
$books = Book::all();
//遍历每一本书
foreach ($books as $book) {
//每一本书的关联用户的姓名(关联查询)
DebugBar::info($book->user->username);
}
其执行的SQL为:
select * from `laravel_books`
select * from `laravel_users` where `laravel_users`.`id` = 19 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 20 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 21 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 24 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 25 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 26 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 27 limit 1
select * from `laravel_users` where `laravel_users`.`id` = 29 limit 1
......
通过调试器 Debugbar 中 SQL 语句的分析,发现包含十多条 SQL 语句。
这是因为,在关联查询时,每遍历一次(foreach循环)就会执行一遍 SQL 语句,导致性能欠佳。
2、使用 with()
关键字,进行预载入设置,提前将 SQL 整合,即可大大减少执行的SQL语句数目。如:
//with 关键字预载入
$books = Book::with('user')->get();
foreach ($books as $book) {
DebugBar::info($book->user->username);
}
相比较于原来的例子,只增加了with关键字。
这是,执行的结果是一致的。而执行的SQL为:
select * from `laravel_books`
select * from `laravel_users` where `laravel_users`.`id` in (19, 20, 21, 24, 25, 26, 27, 29, 79)
此时的SQL 执行数目为:1+1 条。
另外,使用 with()
关键字也支持设置显示的列,比如:
$books = Book::with('user:id,username')->get();
foreach ($books as $book) {
DebugBar::info($book->user);
}
其输出的结果,是个模型User,并且只包含两个属性:
另外,使用 with()
关键字也支持数组,多个关联:with(['book', 'prifile' ])
。
3、如果每次都必须使用预载入进行关联查询,可以在模型中定义:
即在books模型中,写入:
protected $with = ['user'];
然后就可以像上面的第一种方法使用(不需要加with()方法
),即可自动使用预加载。
4、也可以使用闭包的方式,进行预载入筛选
查询:
//闭包查询
$books = Book::with(['user' => function ($query) {
$query->where('id', 19);//这样写可能没啥意义,看结果吧
}])->get();
//输出
foreach ($books as $book) {
DebugBar::info($book->user);
}
//可以增加个为空的判断:
foreach ($books as $book) {
if($book->user != null){
DebugBar::info($book->user->username);
}
}
首先看其执行的SQL语句:
select * from `laravel_books`
select * from `laravel_users` where `laravel_users`.`id` in (19, 20, 21, 24, 25, 26, 27, 29, 79) and `id` = 19
然后其执行结果为:
预载入筛选不可以使用limit、take 方法。
5、在上面的所有示例中,预加载是回提前进行管理执行的。
但有时,可能会产生逻辑判断,进而判断是否查询数据。为了避免自愿性能的浪费,可以采用延迟预载入
:
$books = Book::all();//这里的with()方法省了,用下面的load延迟加载。
if (true) {
$books = $books->load('user');
//这个load也可以使用闭包,如:
//load(['user' => function () {
// 条件信息
//}])
foreach ($books as $book) {
DebugBar::info($book->user->username);
}
}
6、使用loadCount()方法,可以实现延迟关联统计;
$users = User::all();
if (true) {
return $users->loadCount('book');
}
执行的SQL为:
select * from `laravel_users`
select `id`, (select count(*) from `laravel_books` where `laravel_users`.`id` = `laravel_books`.`user_id`) as `book_count` from `laravel_users` where `laravel_users`.`id` in (19, 20, 21, 24, 25, 26, 27, 29, 76, 79, 80, 99, 100, 101, 106)
执行结果为:
[
{
"id": 19,
"username": "蜡笔小新",
......
"book_count": 5
},
{
"id": 20,
"username": "路飞",
......
"book_count": 1
},
{
"id": 21,
"username": "黑崎一护",
......
"book_count": 1
},
{
"id": 24,
"username": "小明",
......
"book_count": 1
},
{
"id": 25,
"username": "孙悟饭",
......
"book_count": 1
},
......
]
以上。