Laravel - Collection 关系需要很多时间

Laravel - Collection with relations take a lot of time

我们正在开发带有 LUMEN 的 API。 今天我们在获取 "TimeLog" 模型的 collection 时遇到了一个困惑的问题。 我们只是想从板模型和任务模型中获取带有附加信息的所有时间日志。 在一行时间日志中,我们有一个 board_id 和一个 task_id。两者都是 1:1 关系。

这是我们获取全部数据的第一个代码。这花了很多时间,有时我们会超时: BillingController.php

public function byYear() {

       $timeLog = TimeLog::get(); 

        $resp = array(); 

        foreach($timeLog->toArray() as $key => $value) {  

            if(($timeLog[$key]->board_id && $timeLog[$key]->task_id) > 0 ) {      

                 array_push($resp, array(
                    'board_title' => isset($timeLog[$key]->board->title) ? $timeLog[$key]->board->title : null,
                    'task_title' => isset($timeLog[$key]->task->title) ? $timeLog[$key]->task->title : null,
                    'id' => $timeLog[$key]->id
                )); 
            }
        }


        return response()->json($resp);
    }   

建立关系的TimeLog.php

public function board()
        {
            return $this->belongsTo('App\Board', 'board_id',  'id');
        }

        public function task()
        {
            return $this->belongsTo('App\Task', 'task_id',  'id');
        }

我们的新方法是这样的: BillingController.php

 public function byYear() {



            $timeLog = TimeLog::
join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
                            ->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
                            ->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
                            ->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
                            ->getQuery()
                            ->get(); 

            return response()->json($timeLog);
        }   

我们删除了 TimeLog.php 中的关系,因为我们不再需要它了。现在我们有大约 1 秒的加载时间,这很好! 时间日志中大约有 20k 个条目 table。

我的问题是:

  1. 为什么第一种方法超出范围(超时是什么原因?)
  2. 什么是getQuery();到底是做什么的?

如果您需要更多信息,请问我。

我遇到过类似的问题。这里的主要问题是 Elloquent 在执行大量任务时真的很慢,因为它同时获取所有结果,所以简短的回答是使用 PDO 获取逐行获取它。

简短示例:

$db = DB::connection()->getPdo();

$query_sql = TimeLog::join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
                            ->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
                            ->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
                            ->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
                            ->toSql();

$query = $db->prepare($query->sql);
$query->execute();
$logs = array();
 while ($log = $query->fetch()) {
   $log_filled = new TimeLog();
   //fill your model and push it into an array to parse it to json in future
   array_push($logs,$log_filled);
}
return response()->json($logs);

--第一题--

您可能面临的一个问题是内存中有大量数据,即:

$timeLog = TimeLog::get();

这已经很庞大了。然后,当您尝试将集合转换为数组时:

  1. 集合中存在循环。
  2. 根据我的理解,在初始化循环时使用 $timeLog->toArray() 效率不高(虽然我对此可能不完全正确)
  3. 进行了数千次查询以检索相关模型

所以我建议的是 五个 方法(一个可以让你免于数百次查询),最后一个在 returning 结果方面是有效的定制:

  1. 因为你有很多数据,那么 chunk 结果参考:Laravel chunk 所以你有这个:

    $timeLog = TimeLog::chunk(1000, function($logs){
        foreach ($logs as $log) {
        // Do the stuff here
        }
    }); 
    
  2. 另一种方式是使用游标(只运行一个条件匹配的查询)游标的内部操作理解为使用 Generators.

    foreach (TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->cursor() as $timelog) {
      //do the other stuffs here
    }
    
  3. 这看起来像第一个,但您已经将查询范围缩小到您需要的范围:

    TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->get()
    
  4. Eager Loading 已经可以即时呈现您需要的关系 ,但也可能会在内存中产生更多数据。所以可能 chunk 方法会让事情更容易管理(即使你 eagerload 相关模型)

    TimeLog::with(['board','task'],  function ($query) {
        $query->where([['board_id','>',0],['task_id', '>', 0]]);
    }])->get();
    
  5. 你可以简单地使用Transformer

    • 使用 Transformer,您可以加载相关模型,即使尺寸很大,也可以优雅、干净和更可控的方法,而且一个更大的好处是您可以转换结果,而不必担心如何 循环它 您可以简单地参考 以执行简单的使用。但是,如果您不需要转换您的回复,那么您可以选择其他选项。

Although this might not entirely solve the problem, but because the main issues you face is based on memory management, so the above methods should be useful.

--第二题--

基于Laravel API here 你可以看到:

它只是 return 底层查询构建器实例。根据我的观察,根据您的示例不需要它。

更新

对于问题1,既然你想简单地return结果作为回应,说实话,对这个结果进行分页效率更高。 Laravel offers pagination 其中最简单的是 SimplePaginate,它很好。唯一的问题是它对数据库进行了一些查询, 会检查最后一个索引;我猜它也使用 cursor 但不确定。我想最后这可能更理想,具有:

return TimeLog::paginate(1000);