数据库+模型 - AR模型查询优化 ¶
作者:KK
发表日期:2017.01.19
查询时声明with附加其它查询 ¶
以下查询会有明显的性能问题(代码来自官方文档示例)
// SELECT * FROM customer LIMIT 100
$customers = Customer::find()->limit(100)->all();
foreach ($customers as $customer) {
// SELECT * FROM order WHERE customer_id=...
$orders = $customer->orders; //这个orders是指一对多里的getOrders方法返回hasMany的值
// ...处理 $orders 的更多事情...
}
以上代码示例了从100个客户中依次查询这些客户的订单集合再进行处理,这样for循环里就会有100次客户订单的查询,不能这么做,可以通过with这样来优化:
// 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 的更多事情...
}
这样就能只执行2次SQL了
查询指定字段时的with注意事项 ¶
$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
echo $orders[0]->customer; // 空的
不是with一起查出来了吗?为什么是空的呢?
因为with的附加查询里需要关联字段才可以,但是select指定的id和amount两个字段都不是与customer表发生关联的字段,所以在底层进行with附加的查询时,根本找不到关联字段,所以没查到相关数据
所以这里要注意的是,select要附加上关联字段,以下才是正确姿势:
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
在with时追加其它条件 ¶
重复一下上面的代码,第2条SQL的where条件一直是in逻辑:
// SELECT * FROM customer LIMIT 100;
// SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)->with('orders')->all();
那如果突然想要在in的同时又加上其它条件,就要在with里换一下参数的写法了:
// SELECT * FROM customer LIMIT 100;
// SELECT * FROM orders WHERE customer_id IN (1,2,...) AND status = 1
$customers = Customer::find()->limit(100)->with([
'orders' => function($arQuery){
//传来的参数是一个AR查询器,通过查询器提供的API来调整with查询的条件,比如andWhere
$query->andWhere(['status' => 1]);
}
])->all();
逆向关系定义 ¶
在学习这个之前先看这段代码:
$user1 = new User();
$user1->id = 1;
$user1->name = 'a';
$user2 = new User();
$user2->id = 1;
$user2->name = 'a';
var_dump($user1 !== $user2); //true
这是两个user对象的属性值一样而已,但根本就不是同一个内存对象变量(内存地址都不一样嘛),这种现象在某种AR模型的用法下会导致一些问题,下面说明一下
以下代码中,Customer通过getOrders定义了它有多个订单的关系,而Order也通过getCustomer定义了它有一个客户的关系
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']);
}
}
可是这样用起来会有个问题:
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1 这里又查了一次 id=1 的用户
if ($customer->orders[0]->customer === $customer) {
echo '相同';
} else {
echo '不相同'; // 会运行到这里,输出 "不相同"
}
在我们客观看来,两个customer是同一个东西才对,但实际上在程序里,findOne返回的是一个实例,人家的内存地址就是A吧,而$customer->orders[0]->customer这里查询关联数据时又返回了另一个实例,内存地址是B吧,虽然两个对象里的属性值相同,但是不是同一块内存,所以并不相等
最不希望的更加是它多了一个查询(SELECT * FROM customer WHERE id=1)
逆关系这个知识点就是为了解决这个问题的,做法很简单,在Customer->getOrders的hasMany里后追加inverseOf定义就行了:
class Customer extends ActiveRecord
{
....
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
}
}
这里的inverseOf('customer')
大概是指hasMany查出来的各个orders(Order模型实例)把这个模型里的getCustomer()
这个getter对应的属性customer
给查出来,只是这个查询其实并不真正执行,而是直接在当前这个Customer里取引用
则后面调用orders里的customer都不会造成多余的查询了