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()方法了。
美景欣赏
相关推荐
深度学习 -- 损失函数
深度残差网络(Deep Residual Networks (ResNets))
深度学习 -- 激活函数
神经网络训练 -- 调整学习速率
生成对抗网络(GAN)改进与发展
生成对抗网络(GAN)优点与缺点
生成对抗网络(GAN)的训练
生成对抗网络(GAN)基本原理
生成模型与判别模型