我有这个例程,从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循环:
平均读数:0.3196 s
最大写:0.9133 s
每10-20次迭代,读取时间超过一秒,进行2-3次迭代,这很奇怪,我没有想法为什么。
出于好奇,我还在插入数据库之前对分块和缓冲项之间的差异进行了基准测试:
答案 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。