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條一頁就好了,看公司業務

 

 

 

 

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