1、更高效率地查询:使用批量查询代替 foreach 查询(多次 io 操作转换为一次 io操作)
在维护的项目中, 我发现了有不少需要查询关联数据的时候是这样做的:先查询出列表,然后 foreach 列表去查询列表每一条记录的关联数据,好比如这样:
$products = \DB::table('product')->where('category_id', 1)->paginate(); foreach ($products as $key => $product) { $product['some_other_info'] = \DB::table('some_other_info') ->where('product_id', $product['id']) ->get(); $products[$key] = $product; }
解决方法:使用一次查询代替多次查询
a. 定义模型关联,使用 Model 的 with 进行查询。如:
AdminUser::with('admin_user_info')->where('id', '>', 1)->get();
b. 获取查询结果集的 id 列(根据实际情况而定),使用 whereIn 一次查询关联数据。对于那些不能定义关联的才用这种方法,因为这种写法有点啰嗦
$products = \DB::table('product')->where('category_id', 1)->paginate(); $product_ids = $products->pluck('id')->toArray(); // 获取所有 id
$some_other_infos = \DB::table('some_other_info') // 根据 id 数组一次查询所有关联数据 ->whereIn('product_id', $product_ids) ->get();
$some_other_infos = array_column($some_other_infos, null, 'product_id'); // 使用 id 把结果集转换为关联数组,这样下面可以更高效地操作,否则我们只能两次 foreach 了 foreach ($products as $key => $product) { $product['some_other_info'] = array_get($some_other_infos, $product['id']); $products[$key] = $product; }
先说说 关联查询:我们在 Model 类里定义的关联,我们不一定在第一次查询就全部查出来,我们可以在需要的时候再去查询 ,使用 load 方法,可以实现这个目标,但是这个 load 只是针对单个 model 对象的,如果我们 Model::xxx()->xx() 链式操作最后是 get(),我们得到的结果将会是一个 Collection 实例,最后调用的方法是 first() 或其他类似的 firstOrFail() 的时候,返回的才是一个 Model 实例。也就是说 load 方法只针对 Model 实例。如下:
我们假设有如下模型定义:
AdminUser
<?php namespace App\Model; class AdminUser extends BaseModel { public function admin_user_info() { return $this->hasOne(AdminUserInfo::class); } }
AdminUserInfo
<?php namespace App\Model; class AdminUserInfo{ }
$admin_users = \App\Model\AdminUser::where('id', '>', 1)->get(); $admin_users->load('admin_user_info'); // 错误, 因为 $admin_users 是一个 Collection 实例 $admin_user = \App\Model\AdminUser::first(); $admin_user->load('admin_user_info'); // 正确, $admin_user 是一个 Model 实例
好像有点跑题了,而我们使用 with 的话,效果相当于一次性给结果集 load 完了所有的关联。如果我们有使用 laravel-debugbar 来,就会发现一对多关联使用 with 时候执行的 sql 都是使用了 whereIn 进行查询的。