動態

詳情 返回 返回

Thinkphp中關聯預載入(Eager Loading)的核心實現邏輯 - 動態 詳情

thinkphp中我們常通過使用關聯預載入(Eager Loading)來解決關聯查詢中"N+1 查詢問題", 通過減少數據庫查詢次數來提升性能. 其底層實現邏輯可以分為以下幾個關鍵步驟:

1.關聯定義的基礎

以下面的代理為例子:

//$this->model = new \app\admin\model\device\Relation;
$list = $this->model
  ->where($where)
        ->with(['device'])
        //主模型默認使用模型名 relation 作為前綴
        //關聯模型默認使用關聯方法名作為前綴
        ->where(['relation.group_id' => $id])
        ->order($sort, $order)
  ->paginate($limit);

上面的查詢中使用了關聯預載入,依賴於Relation模型中預先定義的關聯關係.


public function device() {
        return $this
        ->belongsTo('app\admin\model\device\Lists', 
            'device_id', 'id', [], 'LEFT')
        //預載入方式 0 JOIN查詢 1 IN查詢
        ->setEagerlyType(0);
    }

這裏關聯定義包含了關聯類型(如: hasMany, hasOne, belongsTo等), 外鍵(關聯模型中指向主模型的字段), 主鍵(主模型的主鍵)

2.主查詢執行(獲取主表數據)

實際執行的時候, 框架會先執行主表的查詢, 獲取複合條件的主數據(如上覆合主表中relation.group_id=$id的分組數據). 對應的SQL如下:

SELECT * FROM `device_admin_relation` WHERE `group_id` = 1; 

3.收集關聯查詢的"條件參數"

主查詢獲取數據後, with(['device']) 會根據關聯定義, 從主數據中提取關聯查詢所需的關鍵字段值(通常是主模型的主鍵).
例如, 主查詢得到的分組數據主鍵id為[1,2,3], 則會收集這些id, 用於後續關聯查詢.

4.批量執行關聯查詢(核心優化點)

with方法的核心作用在這裏體現: 它會根據手機到的主表主鍵, 一次性批量查詢所有關聯數據, 而不是逐行查詢. 以上面的device關聯為例, 框架會生成類似這樣的SQL:

SELECT * FROM `device_lists` WHERE `id` IN (1, 2, 3); -- 用 IN 條件批量查詢

這一步解決了"N+1 問題": 如果不使用with, 循環主數據獲取每個分組的設備時, 會執行N次關聯查詢(N為主數據條數), 加上1次主查詢, 共N+1次; 而使用with後只需要1次主查詢+1次關聯查詢, 總共2次.

5.關聯數據與主數據的"組裝匹配"

關聯查詢得到數據後, 框架會根據關聯定義中的外鍵與主鍵對應關係, 將關聯數據"掛載"到主數據的對應字段上.
最終, 得到的$list數據結構類似如下格式:

[
    // 第一條主數據(group.id=1)
    [
        'id' => 1,
        'name' => '分組1',
        // 關聯的設備數據(自動掛載到 'device' 字段)
        'device' => [
            ['id' => 101, 'group_id' => 1, 'name' => '設備101'],
            ['id' => 102, 'group_id' => 1, 'name' => '設備102']
        ]
    ],
    // 第二條主數據(group.id=2)
    [
        'id' => 2,
        'name' => '分組2',
        'device' => [
            ['id' => 201, 'group_id' => 2, 'name' => '設備201']
        ]
    ]
    // ...
]

6.支持關聯查詢的"二次篩選"

with還支持通過閉包對關聯查詢進行條件限制,例如:

$list = $this->model
    ->with(['device' => function($query) {
        // 只查詢狀態為正常的設備
        $query->where('status', 1);
    }])
    ->order(...)
    ->select();

此時關聯查詢的SQL會自動帶上額外條件:

SELECT * FROM `device_list` WHERE `group_id` IN (1,2,3) AND `status` = 1;

延伸示例:

//Relation模型中定義的關聯預載入admin方法
public function admin() {
  return $this
    ->belongsTo('app\admin\model\device\Admin', 
    'device_admin_id', 
    'id', [], 'LEFT') //設置通過left左連接的方式進行關聯
    ->setEagerlyType(0); //預載入方式 0 JOIN查詢 1 IN查詢
}



// $this->model = new \app\admin\model\device\Relation;
$list = $this->model
->field("device_admin_id,group_id")
->with(['groups', 'admin'])
->where($where)
->where('admin.deletetime is null') //這裏直接過濾admin關聯中的數據
//篩選當前Relation模型對應數據表字段的時候為了不與其他表字段混淆需要
//加上當前模型前綴relation
->where(['relation.group_id' => $id]) 
->group('relation.device_admin_id')
->paginate($limit);

最終生成的sql語句如下:

SELECT
    `relation`.`device_admin_id`,
    `relation`.`group_id`,
    groups.id AS groups__id,
    groups.group_name AS groups__group_name,
    groups.remark AS groups__remark,
    groups.createtime AS groups__createtime,
    groups.updatetime AS groups__updatetime,
    groups.deletetime AS groups__deletetime,
    admin.id AS admin__id,
    admin.NAME AS admin__name,
    admin.idno AS admin__idno,
    admin.phone AS admin__phone,
    admin.pwd AS admin__pwd,
    admin.state AS admin__state,
    admin.remark AS admin__remark,
    admin.createtime AS admin__createtime,
    admin.updatetime AS admin__updatetime,
    admin.deletetime AS admin__deletetime 
FROM
    `fa_device_admin_relation` `relation`
    LEFT JOIN `fa_device_group` `groups` ON `relation`.`group_id` = `groups`.`id`
    LEFT JOIN `fa_device_admin` `admin` ON `relation`.`device_admin_id` = `admin`.`id` 
WHERE
    ( admin.deletetime IS NULL ) 
    AND `relation`.`group_id` = '6' 
GROUP BY
    `relation`.`device_admin_id`

總結

with(['device']) 的核心邏輯是:

  1. 先查主表數據
  2. 提取主表主鍵, 批量查詢關聯表數據(用IN條件)
  3. 根據關聯規則(外鍵-主鍵) 將關聯數據匹配到主數據中
    通過上面的方式, 大幅減少了數據庫查詢次數, 是關聯查詢中提升星能的關鍵手段.
user avatar zjkal 頭像 tpwonline 頭像 hantianfeng 頭像
點贊 3 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.