1、预加载,就是解决关联查询中产生的N+1 次查询带来的资源消耗。

    所谓 N+1 条,就是起初获取全部数据的 1 条和,遍历的 N 条。看下面的示例。

    比如在下面的关联查询中,要获取所有书籍的作者(或拥有者),即通过获取所有的书籍,查询每个书籍对应的所有者:

    1. //获取所有书籍列表
    2. $books = Book::all();
    3. //遍历每一本书
    4. foreach ($books as $book) {
    5. //每一本书的关联用户的姓名(关联查询)
    6. DebugBar::info($book->user->username);
    7. }

    其执行的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,并且只包含两个属性:

    12. 模型的预加载 - 图1

    另外,使用 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
    

    然后其执行结果为:

    12. 模型的预加载 - 图2

    预载入筛选不可以使用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
        },
        ......
    ]
    

    以上。