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

Sli*_*liq 13 php mysql orm activerecord yii2

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

问题

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

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

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

我怀疑的是什么

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

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

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

top*_*her 24

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

指定应执行此查询的关系

说你的关系是comments,解决方案如下:

'query' => Post::find()->with('comments'),
Run Code Online (Sandbox Code Playgroud)

关系指南,with将执行额外的查询以获得关系,即:

SELECT * FROM `post`;
SELECT * FROM `comment` WHERE `postid` IN (....);
Run Code Online (Sandbox Code Playgroud)

要使用适当的加入,使用joinWitheagerLoading设定参数true,而不是:

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

所以

'query' => Post::find()->joinWith('comments', true);
Run Code Online (Sandbox Code Playgroud)

将导致以下查询:

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

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

重要的是要意识到使用joinWith()不会使用JOIN查询来急切加载相关数据.由于各种原因,即使使用JOIN,WHERE postid IN (...)仍将执行查询以处理急切加载.因此,您应该仅joinWith()在特别需要JOIN时使用,例如,在相关表的列之一上过滤或排序

TLDR:

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

  • 这大多是正确的,但重要的是要意识到使用`joinWith()`将*不*使用JOIN查询来急切加载相关数据.由于各种原因,即使使用JOIN,仍然会执行`WHERE postid IN(...)`查询来处理急切加载.因此,当您特别需要JOIN时,您应该只使用`joinWith()`,例如过滤或排序相关表的列之一. (7认同)