为什么Yii2的ActiveRecord使用大量单个SELECT而不是JOIN?

时间:2015-05-12 15:32:43

标签: php mysql activerecord orm yii2

我正在使用Yii2的ActiveRecord实现(希望)完全按照应该使用的方式,根据文档。

问题

在一个非常简单的设置中,表之间只有简单的关系,获取10个结果很快,100个很慢。 1000是不可能的。数据库非常小,索引完美。问题绝对是Yii2请求数据的方式,而不是数据库本身。

我正在使用标准的ActiveDataProvider,如:

$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'pagination' => false // to get all records
]);

我怀疑

使用Yii2工具栏进行调试显示了一个简单请求的数千个单一SELECT,它应该只从表A获得50行,并将一些简单的“JOIN”连接到表B到表C.在普通SQL中,每个人都可以用一个SQL语句来解决这个问题。和两个连接。然而,Yii2为每一行中的每个关系激活一个SELECT(这对于保持ORM清洁是有意义的)。导致(或多或少)1 * 50 * 30 = 1500个查询,只获得每行的两个关系。

问题

为什么Yii2使用这么多单一的SELECT,或者这是我的错误? 另外,有人知道如何“修复”这个吗?

由于这对我来说是一个非常重要的问题,我将在5月14日提供500赏金。

2 个答案:

答案 0 :(得分:24)

默认情况下,Yii2使用延迟加载以获得更好的性能。这样做的结果是只有在访问它时才会获取任何关系,因此成千上万的sql查询。您需要使用预先加载。您可以使用\yii\db\ActiveQuery::with()执行此操作:

  

指定应执行此查询的关系

假设您的关系为comments,解决方案如下:

'query' => Post::find()->with('comments'),

guide for Relations开始,with将执行额外的查询以获得关系,即:

SELECT * FROM `post`;
SELECT * FROM `comment` WHERE `postid` IN (....);

要使用proper joining,请将eagerLoading参数设置为true而使用joinWith代替:

  

此方法允许您重用现有关系定义来执行JOIN查询。根据指定关系的定义,该方法将一个或多个JOIN语句附加到当前查询。

所以

'query' => Post::find()->joinWith('comments', true);

将导致以下查询:

SELECT `post`.* FROM `post` LEFT JOIN `comment` comments ON post.`id` = comments.`post_id`;
SELECT * FROM `comment` WHERE `postid` IN (....);

来自@ laslov的评论和https://github.com/yiisoft/yii2/issues/2379

  

认识到使用joinWith()不会使用JOIN查询急切加载相关数据非常重要。由于各种原因,即使使用JOIN,仍将执行WHERE postid IN (...)查询以处理急切加载。因此,您只应在特别需要JOIN时使用joinWith(),例如过滤或订购相关表格的一列

<强> TLDR:

joinWith = with加上实际的JOIN(因此可以通过其中一个相关列过滤/订购/分组等)

答案 1 :(得分:3)

为了使用关系AR,建议为需要连接的表声明主外键约束。约束将有助于保持关系数据的一致性和完整性。

对外键约束的支持因DBMS而异。 SQLite 3.6.19或更早版本不支持外键约束,但您仍可以在创建表时声明约束。 MySQL的MyISAM引擎根本不支持外键。

在AR中,有四种类型的关系:

  • BELONGS_TO:如果表A和B之间的关系是一对多,则B属于A(例如,Post属于User);
  • HAS_MANY:如果表A和B之间的关系是一对多,那么A有很多B(例如用户有很多帖子);
  • HAS_ONE:这是HAS_MANY的特例,其中A最多有一个B(例如,用户最多有一个配置文件);
  • MANY_MANY:这对应于数据库中的多对多关系。需要一个关联表来将多对多关系分解为一对多关系,因为大多数DBMS不直接支持多对多关系。在我们的示例数据库模式中,tbl_post_category用于此目的。在AR术语中,我们可以将MANY_MANY解释为BELONGS_TO和HAS_MANY的组合。例如,Post属于许多类别,而类别有很多Post。

以下代码显示了我们如何声明User和Post类的关系。

class Post extends CActiveRecord
{
    ......

    public function relations()
    {
        return array(
            'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
            'categories'=>array(self::MANY_MANY, 'Category',
                'tbl_post_category(post_id, category_id)'),
        );
    }
}

class User extends CActiveRecord
{
    ......

    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}

查询结果将作为相关AR类的实例保存到属性中。这被称为延迟加载方法,即,仅在最初访问相关对象时执行关系查询。以下示例显示了如何使用此方法:

// retrieve the post whose ID is 10
$post=Post::model()->findByPk(10);
// retrieve the post's author: a relational query will be performed here
$author=$post->author;

你在某种程度上做错了,请从文档http://www.yiiframework.com/doc/guide/1.1/en/database.arr

开始