mysql left join 和 laravel 的 with 预加载

参考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条一页就好了,看公司业务

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章