4.7 贪婪获取

假设你要获取一个包含10条博文记录的记录集,且你想使用模型系统的延迟加载特性显示每条记录的作者和归总数据:

<?php
/**
 * @var Solar_Sql_Model_Catalog $model
 */

$list = $model->blogs->fetchAll(array(
    'page'   => 1,
    'paging' => 10,
));

foreach ($list as $item) {
    echo "Blog ID# " . htmlspecialchars($item->id)
       . " by "      . htmlspecialchars($item->author->name)
       . " has "     . htmlspecialchars($item->summary->comment_count)
       . " comments<br />";
}

这将产生多达21次查询。第一次是查询博文的原始列表。但是,当我们进入循环,我们使用一次查询获取博文作者名,另一次查询获取归总数据记录。遍历10篇博文,每次叠代产生2次查询,那么总共就产生了20次额外的查询。这种行为就被称为“N+1”问题,其中“1”是指原始的查询,“N”是指所有额外的延迟查询。

使用延迟加载相关的数据是非常方便的。但是如果你预先知道你需要哪些相关数据,你应该使用贪婪方式获取它,以减少所涉及的查询次数。要执行贪婪获取,指定你想要接收的相关的属性名即可。

<?php
/**
 * @var Solar_Sql_Model_Catalog $model
 */

$list = $model->blogs->fetchAll(array(
    'page'   => 1,
    'paging' => 10,
    'eager'  => array('author', 'summary'), // eager fetch
));

foreach ($list as $item) {
    echo "Blog ID# " . htmlspecialchars($item->id)
       . " by "      . htmlspecialchars($item->author->name)
       . " has "     . htmlspecialchars($item->summary->comment_count)
       . " comments<br />";
}

现在我们只会产生一次查询。因为authorsummary与博文都是“对一”关系。Solar模型系统将在博文查询后面LEFT JOIN(左联)它们,然后把数据合并到一个数据库结果集中,最后在他们所属PHP对象中会出现相关的数据。

“对一”关系的数据都加入到原始结果集中了;然而众多的“对多”关系中,每一个”对多“关系会产生一次额外的查询。这不是“N+1”问题;不管原始结果集中有多少记录,每一个”对多“关系只产生一次额外的查询。例如,我们获得博文的标签和评论,代码如下:

<?php
/**
 * @var Solar_Sql_Model_Catalog $model
 */

$list = $model->blogs->fetchAll(array(
    'page'   => 1,
    'paging' => 10,
    'eager'  => array('author', 'summary', 'comments', 'tags'),
));

上面的代码一共会将产生3次查询:一次是联接有作者和归总数据的博文列表查询,一次是博文数据集中的所有评论查询,最后一次是博文数据集中的所有标签查询。(如果你使用延迟加载特性来处理,并且循环遍历结果集,那么一共会产生41次查询:1次原始博文查询,10次作者查询,10次归总数据查询,10次标签查询,最后,10次评论查询)