0

我们正在使用 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(); 是什么?究竟是做什么的?

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

4

2 回答 2

2

--第一个问题--

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

$timeLog = TimeLog::get();

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

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

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

  1. 因为你有很多数据,chunk所以结果 ref: Laravel 块所以你有这个:

    $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

    • 使用transformer,即使大小很大,您也可以以优雅,干净和更可控的方法加载相关模型,还有一个更大的好处是您可以转换结果而不必担心如何循环它 您可以简单地参考this回答以执行它的简单使用。但是,如果您不需要转换您的响应,那么您可以采取其他选择。

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

--第二个问题--

基于 Laravel API here你可以看到:

在此处输入图像描述

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

更新

对于问题 1,由于您似乎只想将结果作为响应返回,因此,对结果进行分页更有效。Laravel 提供分页功能,其中最简单的是 SimplePaginate,它很好。唯一的问题是它对数据库进行了更多查询,会检查最后一个索引;我想它也可以使用cursor,但不确定。我想最后这可能更理想,具有:

return TimeLog::paginate(1000);
于 2017-07-26T21:29:10.950 回答
1

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

简短的例子:

$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);
于 2017-07-26T10:16:01.990 回答