Laravel进口商例程随着时间的推移而减慢

时间:2016-12-28 15:11:26

标签: php mysql laravel

我有这个例程,从webservice中获取一些数据并将其存储在我的数据库中。这个数据有20k +项。要将它们保存到数据库中,我必须首先检索一些信息然后存储它们。所以我有一个运行20k次的foreach循环,每次都对数据库执行读取和写入。

但这种方法会随着时间的推移而减慢。完成需要一个多小时!

我已停用查询日志(DB::disableQueryLog()),但我没有发现任何性能提升。

这是我的代码:

$data = API::getItems();

foreach ($data as $item) {
    $otherItem = OtherItem::where('something', $item['something'])->first();
    if (!is_null($otherItem)) {
        Item::create([
            ...
        ]);
    }
}

作为一种解决方案,我决定将所有OtherItem预取到一个集合中,它解决了问题:

$data = API::getItems();

$otherItems = OtherItem::all();

foreach ($data as $item) {
    $otherItem = otherItems->where('something', $item['something'])->first();
    if (!is_null($otherItem)) {
        Item::create([
            ...
        ]);
    }
}

但我想理解为什么第一种方法会随着时间的推移而大幅减速,以及做这类事情的最佳方法是什么。

修改

澄清: 我知道执行20k查询并不具备高性能,在这种情况下,性能并不重要(除非需要数小时而不是几分钟)。我只会偶尔在开发过程中运行这个例程。我最后的方法是两个答案的混合(我没有想过缓冲项目并分批插入)。 以下是感兴趣的人的代码:

$data = collect(API::getPrices());
$chunks = $data->chunk(500);

$otherItems = OtherItem::all();

foreach ($chunks as $items) {
    $buffer = [];
    foreach ($items as $item) {
        $otherItem = otherItems->where('something', $item['something'])->first();
        if (!is_null($otherItem)) {
            $buffer[] = [
                ...
            ];
        }
    }
    Item::insert($buffer);
}

所以,困扰我的是为什么痛苦缓慢(即使有所有查询)。我决定做一些基准测试来进一步分析这个问题。 使用两种查询方法,我得到以下结果:

对于6000循环:

  • 最多阅读:11.5232 s
  • 敏读:0.0044 s
  • 平均读数:0.3196 s

  • 最大写:0.9133 s

  • Min写:0.0007 s
  • 平均写:0.0085秒

每10-20次迭代,读取时间超过一秒,进行2-3次迭代,这很奇怪,我没有想法为什么。

出于好奇,我还在插入数据库之前对分块和缓冲项之间的差异进行了基准测试:

  • 无缓冲:1 115,4 s(18 min 35 s)
  • 分块和缓冲:1064.7秒(17分45秒)

2 个答案:

答案 0 :(得分:2)

在第一个代码段中,您要为20000个项目创建40000个查询。每个项目有两个查询 - 首先是获取数据,第二个是存储内容。

第二个代码段将创建20001查询,它也是一个非常慢的解决方案。

每次要存储某些数据时,您都可以构建一个数组并使用insert()而不是create()方法。因此,此代码将只创建2个查询,而不是40000和20001。

$otherItems = OtherItem::all();
$items = [];

foreach ($data as $item) {
    $otherItem = otherItems->where('something', $item['something'])->first();
    if (!is_null($model)) {
        $items[] = [.....];
    }
}

Item::insert($items);

答案 1 :(得分:0)

它变慢了,因为只有很多查询 - 每一个都是数据库的往返。

您可以做的另一件事是尝试使用数据库事务对插入进行分块。使用确切的数字进行游戏,但尝试分批插入几百个左右。

即。

  • 启动交易
  • 循环块,执行插入
  • 提交
  • 重复下一个块,直到没有块为止

Laravel的ORM为这种用例提供​​chunk method