Laravel - 关系密集需要花费大量时间

时间:2017-07-26 10:01:20

标签: laravel laravel-5.4 lumen lumen-5.4

我们正在使用LUMEN开发API。 今天我们在收集“TimeLog”模型时遇到了一个混乱的问题。 我们只想从板模型和任务模型中获取所有时间日志以及其他信息。 在一行时间日志中,我们有一个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个条目。

我的问题是:

  1. 为什么第一种方法超出范围(导致超时的原因是什么?)
  2. getQuery();究竟呢?
  3. 如果您需要更多信息,请问我。

2 个答案:

答案 0 :(得分:2)

- 第一个问题 -

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

$timeLog = TimeLog::get();

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

  1. 整个系列有一个循环。
  2. 根据我的理解初始化循环时使用$timeLog->toArray()效率不高(虽然我可能不完全正确)
  3. 进行了数千次查询以检索相关模型
  4. 所以我建议使用五个方法(一个可以节省数百个查询的方法),最后一个方法可以有效地将结果返回到自定义:

    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已经提供了您需要的关系,但也可能会在内存中导致更多数据。因此,块方法可能会使事情更容易管理(即使您需要加载相关模型)

      TimeLog::with(['board','task'],  function ($query) {
          $query->where([['board_id','>',0],['task_id', '>', 0]]);
      }])->get();
      
    5. 您只需使用Transformer

      即可
      • 使用变压器,您可以加载相关模型,即使尺寸很大,也可以采用优雅,干净和更加可控的方法,更大的好处是您可以转换结果,而不必担心如何循环它 您只需引用this answer即可简单地使用它。但是,如果您不需要转换您的回复,那么您可以采取其他选择。
    6.   

      虽然这可能不能完全解决问题,但是因为你面临的主要问题是基于内存管理,所以上面的方法应该是有用的。

      - 第二个问题 -

      基于Laravel API here您可以看到:

      enter image description here

      它只返回基础查询构建器实例。根据我的观察,根据你的例子不需要它。

      <强>更新

      对于问题1,因为看起来你只想简单地将结果作为响应返回,所以,对这个结果进行分页会更有效率。 Laravel offers pagination最简单的是SimplePaginate,这很好。唯一的问题是它会对数据库进行更多的查询,但是会检查最后一个索引;我猜它也使用cursor但不确定。我想最后这可能更理想,有:

      return TimeLog::paginate(1000);
      

答案 1 :(得分:0)

我遇到过类似的问题。这里的主要问题是,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);