我可以优化这个脚本更新 ~6000 行的大量数据吗

Can I optimize this script updating ~6000 rows with a lot of data

我有 ~5-6k $items 需要在数据库中更新。每个项目都需要一个 HTTP 请求来从页面获取数据。在 HTTP GET 请求中,我得到了大量的数组 (~500-2500),我只需要插入那些不在数据库中的行。在我的 vagrant scotch box 上使用我当前的脚本(每 2-4 分钟 1 个项目)似乎花费了很多时间。

简化示例:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use GuzzleHttp\Client;
use App\Item;
use App\ItemHistory;
use Carbon\Carbon;

use DB;

class UpdateController extends Controller
{
    public function getStart() {
        // Don't cancel the script
        ignore_user_abort(true);
        set_time_limit(0);

        $client = new Client();
        $items = Item::where('updated_at', '<=', Carbon::now()->subDay())->get();

        foreach($items as $item) {
            $response = $client->request('GET', 'API_URL');
            // get the body
            $body = $response->getBody()->getContents();

            $hugeArray = $body['history']; // can be from 100 to 5 000 lines and I use regex to get the "history" array from the body
            $arrayCollection = collect($hugeArray);

            foreach($arrayCollection->take(-100) as $row) { // I take the last 100 since each row = 1 hour, so I get items in the last 100 hours
                $date = new \DateTime($row['created_at']);
                if( ! ItemHistory::whereItemId($item->id)->whereSoldAt($date)->count()) { // Checking if it already exists
                    // I insert the new rows..
                    $history = new ItemHistory;
                    // ....
                    $history->save();
                }
            }
        }
    }
}

我实际上是在抓取数据并使用正则表达式来查找正文响应中的数组。 难道我做错了什么?它需要很长时间才能移动到下一个 $item.

我可以提供一个简化的答案 - 同步执行、对象水合和批量数据库查询。

考虑以下示例:

$requests = function () use ($items) {
    foreach ($items as $item) {
        yield new GuzzleHttp\Psr7\Request($method, $uri);
    }
};

$client = new GuzzleHttp\Client();

foreach ($requests() as $request) {
    $client->sendAsync($request)
        ->then(
            function(Psr7\Http\Message\ResponseInterface) {
                // process the response into array;

                return $arrayFromResponse;
        })
        ->then(
            function ($unfilteredArray) {
                // filter the array as necessary

                return $filteredArray;
        })
        ->then(
            function($filteredArray) { 
                // create the array for bulk insert / update

                return $sqlArray;
        })
        ->then(
            function($sqlArray) {
                // perform bulk db operations.
            }
        );
}
  1. 同步 Http 查询 - 上面的示例突出显示了 Guzzle 的一些异步功能,同时分解了处理步骤。您上面链接的代码是同步的。执行请求、等待响应、处理响应、重复并重复。异步 Http 请求将确保在处理其他信息的同时下载数据。请注意,您的结果会有所不同,并且根据您的特定用例,资源使用量可能会增加。

  2. 对象水化——也就是当你执行查询时你的 ORM 正在做什么,它 returns 一个对象实例(而不是一个数组)是耗时和内存密集型的。 @orcamius(Doctrine 的开发者之一)在 subject 上写了一篇相当技术性的文章。虽然这不是 Eloquent 特定的,但它确实提供了对所有 ORM 的幕后操作的洞察力。代码片段执行其中的许多操作(参考 $itemHistory$historyItem::where)。

  3. 批量数据库操作- 一个众所周知的事实是数据库操作很慢。当与物体水合作用时,这个时间会进一步增加。执行具有 1000 条记录的单个插入与 1000 条插入要好得多。为此,必须将代码从使用 ORM 修改为直接使用数据库表。如 docs

  4. 中所示,可以通过 DB::table('itemHistory')->insert($arrayOfValues) 执行批量插入

更新:虽然未显示 then() 的方法签名为 then(callable $fulfilled, callable $onError)。如果请求出现问题,您可以执行类似

的操作
// promise returned from a request
$p->then(
    function (Psr\Http\Message\ResponseInterface $response) use ($p)
        if ($response->getResponseCode() >= 400) {
            $p->cancel();
        }
        //perform processing
        return $someArray;
    },
    function (RequestException $e) {
        echo $e->getMessage() . "\n";
        echo $e->getRequest()->getMethod();
    })
->then(
    function($someArray) use ($p) {
        // filter or other processing
    });

可以在 Github Repo

中找到有关 Guzzle Promise 的更多信息