ThinkPHP中with()和withJoin()f方法预载入查询的一些区别
在说ThinkPHP的关联预载入之前,首先需要明确一点,关联预载入仅在数据集时有效,对单条数据并无影响,如:
$article = Article::find(1);
$article->detail->content;
或:
$article = Article::with('detail')->find(1);
$article->detail->content;
实际的SQL语句都如下:
SELECT * FROM `think_article` WHERE `id` = 8 LIMIT 1
SELECT * FROM `think_article_detail` WHERE `aid` = 8 LIMIT 1
即:关联预载入对单条数据查询的性能等并无影响。
一、什么是N+1问题
以往我们要查询多条数据,然后通过循环的方式获取其关联属性的子属性时,会存在N+1的问题,如下:
$article = Article::select([1,2,3]);
foreach ($article as $key=>$value){
echo $value->detail->content;
}
实际产生的SQL语句如下:
SELECT * FROM `think_article` WHERE `id` IN (1,2,3)
SELECT * FROM `think_article_detail` WHERE `aid` = 1 LIMIT 1
SELECT * FROM `think_article_detail` WHERE `aid` = 2 LIMIT 1
SELECT * FROM `think_article_detail` WHERE `aid` = 3 LIMIT 1
即,每一条数据的关联属性的子属性都需要一条SQL进行查询。查询3条数据,需要4条SQL查询。
但使用了with()预载入查询之后,则只会产生两条SQL语句,如下:
Article::with('detail')->select([1,2,3]);
实际SQL
SELECT * FROM `think_article` WHERE `id` IN (1,2,3)
SELECT * FROM `think_article_detail` WHERE `aid` IN (1,2,3)
使用with()查询将原来的SQL语句减少到2条,在数据量较大时,对于性能的提升很明显。
二、withJoin()方法查询
with()方法查询为IN查询,这点我们可以根据其SQL看出,如果想使用JOIN方式进行查询,则可以使用withJoin()方法来进行查询。
Article::withJoin('detail')->select([8,9,11]);
实际SQL如下:
SELECT `article`.`id`,`article`.`title`,`detail`.`id` AS `detail__id`,`detail`.`aid` AS `detail__aid`,`detail`.`content` AS `detail__content` FROM `think_article` `article` INNER JOIN `think_article_detail` `detail` ON `article`.`id`=`detail`.`aid` WHERE `article`.`id` IN ('8','9','11')
即withJoin()通过使用JOIN方式的查询,只使用了一句SQL。withJoin()方法默认为INNER JOIN查询,如需修改为其他的查询方式,需添加第二个参数,如:withJoin('detail','left')。
三、with()方法参数
with()方法默认的参数为:关联属性名,一般为字符串,当对多个关联属性进行预载入时,或对关联模型进行条件约束时,需要使用数组方式,如:Article::with(['detail', 'info'])->select();
四、对关联模型进行约束
with()方法和withJoin()对关联模型进行约束时,可支持使用闭包的形式,此时参数需为数组形式,如:对关联属性的字段进行过滤:
Article::with(['detail'=>function($query){
$query->field('id,aid,content');
}])->select([8,9,11]);
这里需要注意的是:with()方法可直接使用field()方法进行过滤,withJoin()方法需要使用withField()方法。
另外,withJoin()可以采用如下的简写形式:
withJoin(['detail' => ['id', 'aid', 'content']]) // 这种约束字段的简写形式仅对withJoin有效
with()方法虽然也支持同样的简写形式,如:
with(['detail' => ['id', 'aid', 'content']])
但代表的意思则完全不同,with()使用该写法,代表,要同时获取detail关联模型的id,aid和content子关联模型的数据,这点非常容易混淆,一定要注意。
当然对Filed字段进行过滤的话,可以在定义关联模型时通过增加条件的方式进行约束。
还需注意,在指定字段约束时,一定要包含关联字段。在指定约束时,都需使用withLimit()方法,且需对$query参数指定Relation约束,因为withLimit方法是关联类才有的方法。
五、with()方法和withJoin()方法的区别:
除了上面我们讲到的在进行字段约束时,witn()方法需要使用field()而withJoin()方法则需要使用withField()之外,with()和withJoin()还存在一些其他的区别。
with()方法获取到的数据,打印时,会展示关联数据,withJoin()方法的则不会。
