参考laravel官网文档:https://learnku.com/docs/laravel/5.5/eloquent-relationships/1333
代码上,有大量的left join, 这时候我是想着通过mysql索引和laravel的with预加载来优化
下面给大家看看没用预加载时候的样子
public function list(Request $request)
{
$rows = $request->rows ?? 20;
$page = is_numeric($request->page ?? 1) ? $request->page : 1;
$sql = HandworkOrders::where('m_handwork_orders.status' , '<>' , -1)->select([
'm_handwork_orders.*',
'm_users.name' ,
'm_third_party_users.nickname' ,
'm_goods.name as goods_name' ,
'm_users.head_img' ,
'm_third_party_users.head_img as third_head_img',
'm_admin_users.nickname as admin_name',
'uu.name as donor_name',
'uu.head_img as donor_head_img',
'up.nickname as donor_third_name',
'up.head_img as donor_third_head_img'
])->distinct('m_handwork_orders.transfer_no');
$sql = $sql->leftjoin( 'm_users' , 'm_handwork_orders.user_id', '=', 'm_users.id');
$sql = $sql->leftjoin( 'm_users as uu' , 'uu.id' , '=' , 'm_handwork_orders.donor_user_id');
$sql = $sql->leftjoin( 'm_third_party_users' , 'm_third_party_users.id' , '=' , 'm_users.third_party_user_id');
$sql = $sql->leftjoin( 'm_third_party_users as up' , 'up.id' , '=' , 'uu.third_party_user_id');
$sql = $sql->leftjoin( 'm_goods' , function($join){
$join->on('m_goods.sku' , '=' , 'm_handwork_orders.sku')
->where('m_goods.status' , '<>' , -1);
});
$sql = $sql->leftjoin( 'm_admin_users' , 'm_admin_users.id' , '=' , 'm_handwork_orders.admin_id');
if($request->user_id){
$sql = $sql->where('m_handwork_orders.user_id' , $request->user_id);
}
if($request->sku){
$sql = $sql->where('m_handwork_orders.sku' , $request->sku);
}
if($request->donor_user_id){
$sql = $sql->where('m_handwork_orders.donor_user_id' , $request->donor_user_id);
}
if($request->user_mobile){
$sql = $sql->where('m_users.mobile' , $request->user_mobile);
}
if($request->donor_user_mobile){
$sql = $sql->where('uu.mobile' , $request->donor_user_mobile);
}
if($request->startDate){
$sql = $sql->where('m_handwork_orders.created_at' , '>=' , $request->startDate);
}
if($request->endDate){
$sql = $sql->where('m_handwork_orders.created_at' , '<=' , $request->endDate);
}
$count = $sql->count();
$sql = $sql->skip( ($page - 1) * $rows )->take($rows)->orderBy(request('sort'), request('sortOrder'));
return response()->json([
'total' => $count,
'rows' => $sql->get()
]);
}
这里面有七个left join ,说到这个,有人会说left join 会比预加载快
我个人认为,各有优点,预加载,他的原理就是通过in 来做where的,如果数据量比较大,当然是不推荐使用预加载。因为你可以想想
那么多 id 去 where in 这当然是不可取的,这时候就要考虑用left join 了,但是left join 也不能太依赖,下面我来给大家看看 他的原理
参考文章:https://www.jianshu.com/p/16ad9669d8a9
图片中,说到多次回表查询,然后再看看我上面的这串代码,有7个left join 呢,说明什么?7次回表查询,这样的效率是非常慢的
left join 越多,回表的次数是不是就越多了,效率是不是会更慢。如果是join 会更慢,为什么呢?因为底层匹配到了数据之后,还要做拼接,再根据你想要的结果返回给你。
这时候我就考虑了用with预加载了,一般查询量没有一万几万的,都是with预加载比较占优势的。
public function getListTest(Request $request)
{
$rows = $request->rows ?? 20;
$page = is_numeric($request->page ?? 1) ? $request->page : 1;
$handworkOrders = new HandworkOrders();
if($request->user_id){
$handworkOrders = $handworkOrders->where('m_handwork_orders.user_id' , $request->user_id);
}
if($request->sku){
$handworkOrders = $handworkOrders->where('m_handwork_orders.sku' , $request->sku);
}
if($request->donor_user_id){
$handworkOrders = $handworkOrders->where('m_handwork_orders.donor_user_id' , $request->donor_user_id);
}
if($request->user_mobile){
$user_mobile = $request->user_mobile;
$handworkOrders = $handworkOrders->whereHas('getUsers',function($req) use($user_mobile) {
$req->where('mobile', $user_mobile);
});
}
if($request->donor_user_mobile){
$donor_user_mobile = $request->donor_user_mobile;
$handworkOrders = $handworkOrders->whereHas('getUsersUparam',function($req) use($donor_user_mobile) {
$req->where('mobile', $donor_user_mobile);
});
}
if($request->startDate){
$handworkOrders = $handworkOrders->where('m_handwork_orders.created_at' , '>=' , $request->startDate);
}
if($request->endDate){
$handworkOrders = $handworkOrders->where('m_handwork_orders.created_at' , '<=' , $request->endDate);
}
if ($request->input('sort', '') && $request->input('sortOrder', '')) {
$handworkOrders = $handworkOrders->orderBy($request->input('sort'),$request->input('sortOrder'));
}
// 预加载 with
$data = $handworkOrders->with([
'getAdminUsers:nickname,id',// 操作人信息
'getUsers:name as user_name,head_img as user_head_img,id,third_party_user_id',// 用户信息(获益者)
'getUsers.getThirdPartyUsers:nickname as t_nickname,head_img as t_head_img,id',// 用户信息(获益者) 第三方数据
'getUsersUparam:name as uname,head_img as uhead_img,id,third_party_user_id',// 赠送者用户表信息
'getUsersUparam.getThirdPartyUsers:nickname as tpnickname,head_img as tphead_img,id',// 赠送者第三方表信息
'getGoods:name as goods_name,sku',// 商品信息
])
->where('status', '<>', -1)
->skip( ($page - 1) * $rows )
->take($rows)
->get([
'id',
'goods_price',
'user_id',
'donor_user_id',
'sku',
'admin_id',
'amount',
'remarks',
'transfer_no',
'created_at',
]);
$count = $handworkOrders->count();
$lists = collect($data)->map(function($item) {
// 用户信息 (获益者)
if (isset($item->getUsers) && $item->getUsers) {
$item->name = $item->getUsers->user_name ?? '';
$item->head_img = $item->getUsers->user_head_img ?? '';
}
// 用户第三方 (获益者)
if (isset($item->getUsers->getThirdPartyUsers) && $item->getUsers->getThirdPartyUsers) {
$item->nickname = $item->getUsers->getThirdPartyUsers->t_nickname ?? '';
$item->third_head_img = $item->getUsers->getThirdPartyUsers->t_head_img ?? '';
}
// 赠送者
if (isset($item->getUsersUparam) && $item->getUsersUparam) {
$item->donor_name = $item->getUsersUparam->uname ?? '';
$item->donor_head_img = $item->getUsersUparam->uhead_img ?? '';
}
// 赠送者第三方
if (isset($item->getUsersUparam->getThirdPartyUsers) && $item->getUsersUparam->getThirdPartyUsers) {
$item->donor_third_name = $item->getUsersUparam->getThirdPartyUsers->tpnickname ?? '';
$item->donor_third_head_img = $item->getUsersUparam->getThirdPartyUsers->tphead_img ?? '';
}
// 商品
if (isset($item->getGoods) && $item->getGoods) {
$item->goods_name = $item->getGoods->goods_name ?? '';
$item->sku = $item->sku ?? '';
}
// 操作人
if (isset($item->getAdminUsers) && $item->getAdminUsers) {
$item->admin_name = $item->getAdminUsers->nickname ?? '';
}
unset($item->getUsers);
unset($item->getUsersUparam);
unset($item->getGoods);
unset($item->getAdminUsers);
return $item;
})->toArray();
return response()->json([
'total' => $count,
'rows' => $lists
]);
}
model类
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
class HandworkOrders extends Model
{
protected $table = 'm_handwork_orders';
protected $guarded = [];
/***
* 赠送者信息
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function getUsers()
{
return $this->hasOne(User::class, 'id', 'user_id');
}
/***
* 获益者信息
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function getUsersUparam()
{
return $this->hasOne(User::class, 'id', 'donor_user_id');
}
/***
* sku获取goods
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function getGoods()
{
return $this->hasOne(Goods::class, 'sku', 'sku')
->where('status', '<>', -1);
}
/***
* 管理员信息
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function getAdminUsers()
{
return $this->hasOne(AdminUser::class, 'id', 'admin_id');
}
}
你要必须保证,每个表都有用到索引,我这里是都用上了索引
再来看看久的sql leftjoin的sql
是不是优化了很多
介绍下这个吧 with 预加载
with([
'getAdminUsers:nickname,id',// 操作人信息
'getUsers:name as user_name,head_img as user_head_img,id,third_party_user_id',// 用户信息(获益者)
'getUsers.getThirdPartyUsers:nickname as t_nickname,head_img as t_head_img,id',// 用户信息(获益者) 第三方数据
'getUsersUparam:name as uname,head_img as uhead_img,id,third_party_user_id',// 赠送者用户表信息
'getUsersUparam.getThirdPartyUsers:nickname as tpnickname,head_img as tphead_img,id',// 赠送者第三方表信息
'getGoods:name as goods_name,sku',// 商品信息
])
getUsersUparam:name as uname,head_img as uhead_img,id,third_party_user_id
getUsersUparam 是模型里面的方法
name as uname,head_img as uhead_img,id,third_party_user_id 是字段,他们通过逗号分隔开
特别要注意的是,关联的关键字段,必须要select出来,不然是查不到的,因为它底层是要获取你关联的字段去where in 的
如果你想获取关联模型里面的关联模型,就这样写:
getUsers.getThirdPartyUsers
下面的whereHas 是对模型里面的一对一方法,进行where条件搜索之类的,会约束到整个查询哦~
好比如,你查询的是mobile = 12311231 也就是说,你整个查询,都会受约束,只要getUsersUparam这个一对一方法里面,没有mobile=12311231,那么你这一整个查询会返回null,也就是说你这个列表没有数据返回的。
if($request->donor_user_mobile){
$donor_user_mobile = $request->donor_user_mobile;
$handworkOrders = $handworkOrders->whereHas('getUsersUparam',function($req) use($donor_user_mobile) {
$req->where('mobile', $donor_user_mobile);
});
}
with()
with()方法是用作“渴求式加载”的,那主要意味着,laravel将会伴随着主要模型预加载出确切的的关联关系。这就对那些如果你想加在一个模型的所有关联关系非常有帮助。因为“渴求式加载”缓解了1+N的查询问题,仅需1+1次查询就能解决问题,对查询速度有了质的提升。
has()
has()方法是基于关联关系去过滤模型的查询结果,所以它的作用和where条件非常相似。如果你只使用has('post'),这表示你只想得到这个模型,这个模型的至少存在一个post的关联关系。
whereHas()
whereHas()方法的原理基本和has()方法相同,但是他允许你自己添加对这个模型的过滤条件。
优化前和优化后对比图,一般一页不要让管理员查询那么多,一般100条足够了,建议最大到500条一页就好了,看公司业务