首页 > yii2教程 > yii2数据库使用

yii2 延迟加载和即时加载

延迟加载和即时加载(又称惰性加载与贪婪加载)

如前所述,当你第一次连接关联对象时, AR 将执行一个数据库查询 来检索请求数据并填充到关联对象的相应属性。 如果再次连接相同的关联对象,不再执行任何查询语句,这种数据库查询的执行方法称为“延迟加载”。如:

 

// SQL executed: SELECT * FROM customer WHERE id=1

$customer = Customer::findOne(1);

// SQL executed: SELECT * FROM order WHERE customer_id=1

$orders = $customer->orders;

// 没有 SQL 语句被执行

$orders2 = $customer->orders; //取回上次查询的缓存数据

延迟加载非常实用,但是,在以下场景中使用延迟加载会遭遇性能问题:

 

// SQL executed: SELECT * FROM customer LIMIT 100

$customers = Customer::find()->limit(100)->all();

 

foreach ($customers as $customer) {

    // SQL executed: SELECT * FROM order WHERE customer_id=...

    $orders = $customer->orders;

    // ...处理 $orders...

}

假设数据库查出的客户超过100个,以上代码将执行多少条 SQL 语句? 101 条!第一条 SQL 查询语句取回100个客户,然后, 每个客户要执行一条 SQL 查询语句以取回该客户的所有订单。

 

为解决以上性能问题,可以通过调用 yii\db\ActiveQuery::with() 方法使用即时加载解决。

 

// SQL executed: SELECT * FROM customer LIMIT 100;

//               SELECT * FROM orders WHERE customer_id IN (1,2,...)

$customers = Customer::find()->limit(100)

    ->with('orders')->all();

 

foreach ($customers as $customer) {

    // 没有 SQL 语句被执行

    $orders = $customer->orders;

    // ...处理 $orders...

}

如你所见,同样的任务只需要两个 SQL 语句。 >须知:通常,即时加载 N 个关联关系而通过 via() 或者 viaTable() 定义了 M 个关联关系, 将有 1+M+N 条 SQL 查询语句被执行:一个查询取回主表行数, 一个查询给每一个 (M) 中间表,一个查询给每个 (N) 关联表。 注意:当用即时加载定制 select() 时,确保连接 到关联模型的列都被包括了,否则,关联模型不会载入。如:

 

$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();

// $orders[0]->customer 总是空的,使用以下代码解决这个问题:

$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();

有时候,你想自由的自定义关联查询,延迟加载和即时加载都可以实现,如:

 

$customer = Customer::findOne(1);

// 延迟加载: SELECT * FROM order WHERE customer_id=1 AND subtotal>100

$orders = $customer->getOrders()->where('subtotal>100')->all();

 

// 即时加载: SELECT * FROM customer LIMIT 100

//          SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100

$customers = Customer::find()->limit(100)->with([

    'orders' => function($query) {

        $query->andWhere('subtotal>100');

    },

])->all();

逆关系

关联关系通常成对定义,如:Customer 可以有个名为 orders 关联项, 而 Order 也有个名为customer 的关联项:

 

class Customer extends ActiveRecord

{

    ....

    public function getOrders()

    {

        return $this->hasMany(Order::className(), ['customer_id' => 'id']);

    }

}

 

class Order extends ActiveRecord

{

    ....

    public function getCustomer()

    {

        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);

    }

}

如果我们执行以下查询,可以发现订单的 customer 和 找到这些订单的客户对象并不是同一个。连接 customer->orders 将触发一条 SQL 语句 而连接一个订单的 customer 将触发另一条 SQL 语句。

 

// SELECT * FROM customer WHERE id=1

$customer = Customer::findOne(1);

// 输出 "不相同"

// SELECT * FROM order WHERE customer_id=1

// SELECT * FROM customer WHERE id=1

if ($customer->orders[0]->customer === $customer) {

    echo '相同';

} else {

    echo '不相同';

}

为避免多余执行的后一条语句,我们可以为 customer或 orders 关联关系定义相反的关联关系,通过调用 yii\db\ActiveQuery::inverseOf() 方法可以实现。

 

class Customer extends ActiveRecord

{

    ....

    public function getOrders()

    {

        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');

    }

}

现在我们同样执行上面的查询,我们将得到:

 

// SELECT * FROM customer WHERE id=1

$customer = Customer::findOne(1);

// 输出相同

// SELECT * FROM order WHERE customer_id=1

if ($customer->orders[0]->customer === $customer) {

    echo '相同';

} else {

    echo '不相同';

}

以上我们展示了如何在延迟加载中使用相对关联关系, 相对关系也可以用在即时加载中:

 

// SELECT * FROM customer

// SELECT * FROM order WHERE customer_id IN (1, 2, ...)

$customers = Customer::find()->with('orders')->all();

// 输出相同

if ($customers[0]->orders[0]->customer === $customers[0]) {

    echo '相同';

} else {

    echo '不相同';

}

Note: 相对关系不能在包含中间表的关联关系中定义。 即是,如果你的关系是通过yii\db\ActiveQuery::via() 或 viaTable()方法定义的, 就不能调用yii\db\ActiveQuery::inverseOf()方法了。

 

美景欣赏

美景欣赏

关闭
感谢您的支持,我会继续努力!
扫码打赏,建议金额1-10元


提醒:打赏金额将直接进入对方账号,无法退款,请您谨慎操作。