为什么Laravel/Eloquent不能使用JOIN进行急切加载?


Why can't Laravel/Eloquent use JOIN for Eager Loading?

<?php
class Cat extends Eloquent {
    public function user() {
        return $this->belongsTo('User');
    }
}
class User extends Eloquent {
    public function cats() {
        return $this->hasMany('Cat');
    }
}

:

$cats = Cat::with('user')->get();

执行2次查询:

select * from `cats`
select * from `users` where `users`.`id` in ('1', '2', 'x')

为什么它不能做:

select * from cats inner join users on cats.user_id = users.id

对于那些说表中有两个id列的人,可以很容易地使用别名来避免:

select 
    c.id as cats__id,
    c.name as cats__name,
    c.user_id as cats__user_id,
    b.id as users__id,
    b.name as users__name
from cats c
inner join users b on b.id = c.user_id

有人指出Eloquent不知道模型中表的列,但我想他们可以提供一种在模型中定义它们的方法,这样它就可以使用别名并进行适当的连接,而不是额外的查询。

我的猜测是,这允许即时加载多个一对多关系。例如,我们还有一个dogs表:

class User extends Eloquent {
    public function cats() {
        return $this->hasMany('Cat');
    }
    public function dogs() {
        return $this->hasMany('Dog');
    }
}

现在我们想用User:

来加载它们
$users = User::with('cats','dogs')->get();

没有连接可以将这些组合成单个查询。但是,对每个"with"元素单独执行查询是可行的:

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')
select * from `dogs` where `user`.`id` in ('1', '2', 'x') 

因此,虽然这种方法可能在一些简单的情况下产生额外的查询,但它提供了渴望加载更复杂的数据的能力,在这些数据中,连接方法将失败。

这是我对为什么是这样的猜测。

我认为,当您想使用LIMIT和/或OFFSET时,连接查询方法有一个致命的缺点。

$users = User::with('cats')->get() -这将输出以下2个查询:

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')

而不是一个查询

select * from users inner join cats on cats.user_id = users.id

但是我们说,我们需要为这个记录集分页

User::with('cats')->paginate(10) -这将有限制地输出以下2个查询。

select * from `users` limit 10
select * from `cats` where `user`.`id` in ('1', '2', 'x')

加一个join,就像

select * from users inner join cats on cats.user_id = users.id limit 10

它将获取10条记录,但它并不意味着10个用户,因为每个用户可以有多个猫。

我认为另一个原因是关系数据库和NOSQL数据库之间的关系可以很容易地通过分离查询方法实现

和前面的答案一样,id是不明确的,你必须在每个语句前加上表名,这是不需要的。

另一方面,JOIN比EXISTS开销大,而EXISTS速度快,因为它不命令RDBMS获取任何数据,只检查相关行是否存在。EXISTS用于返回一个布尔值,JOIN返回另一个完整的表。

出于可伸缩性的目的,如果遵循分片架构,将不得不删除JOIN。这是pinterest在扩展期间的实践。http://hilicalability.com/blog/2013/4/15/scaling-pinterest-from-0-to-10s-of-billions-of-page-views-a.html

catsusers可能都有一个名为id的列,使您建议的查询模棱两可。Laravel的急切加载使用了一个额外的查询,但是避免了这个潜在的错误。