PHP - 在foreach循环外保存到数据库

Posted

技术标签:

【中文标题】PHP - 在foreach循环外保存到数据库【英文标题】:PHP - Save to database outside foreach loop 【发布时间】:2019-08-17 06:33:58 【问题描述】:

我有下面的 foreach 循环,它迭代了我的用户可以应用的一堆不同的自定义规则。

public function parse(Document $document)

        $content = $document->text;
        $rules = $document->stream->fieldrules;

        foreach ($rules as $rule) 
            $class = $this->getClass($rule);
            $content = $class->apply($content);

            //Save to database.
            $fieldresult = new FieldRuleResult();
            $fieldresult->create([
                'field_rule_id' => $rule->field_id,
                'document_id' => $document->id,
                'content' => $content
            ]);
        

如您所见,我在每次迭代时都调用数据库。一些用户可能定义了多达 50 个规则,这将导致 50 个插入查询。所以我相信我可能遇到了n+1 的问题。

我想知道 - 这里的最佳做法是什么?我试过搜索 Laravel 文档,找到了 createMany 方法。所以我尝试将迭代重构为以下内容:

$result = [];
foreach($rules as $rule)

  $class = $this->getClass($rule);
  $content = $class->apply($content);
  $fieldresult = new FieldRuleResult();

  $result[] = [
     'field_rule_id' => $rule->field_id, 
     'document_id' => $document->id, 
     'content' => $content];


return $rules->createMany($result);

这给了我以下错误:

Method Illuminate\Database\Eloquent\Collection::createMany does not exist.

现在我想这是因为$rules 返回了一个集合。我试图将其更改为:

return $document->stream->fieldrules()->createMany($result);

这给了我以下错误:

Call to undefined method Illuminate\Database\Eloquent\Relations\HasManyThrough::createMany()

【问题讨论】:

也许这会有所帮助***.com/questions/12702812/… 【参考方案1】:

Illuminate/Database/Query/Builder 中有一个可用的方法,名为 insert。此方法允许您在一个查询中插入多个模型。请注意,这不会像所有多合一查询方法那样触发雄辩的事件。

就像你已经做的那样,将数据存储在一个数组中,并在循环完成后执行查询。不过这次你可以使用insert

$content = 'your content here...';

$data = [];

foreach($rules as $rule) 

    $class = $this->getClass($rule);

    $content = $class->apply($content);

    $data[] = [
        // ...
        'content' => $content
    ];


FieldRuleResult::insert($data);

不是 100% 确定这直接适用于模型。但是,在 documentation 中,您可以找到带有 DB 外观的示例。

【讨论】:

谢谢托马斯!但是,有没有一种方法可以让我更优雅地插入数据(没有n+1 问题),并且仍然触发事件? 我认为,这是一个好方法。在准备 $data 之前,只需应用过滤器即可。 作为@TheAlpha cmets,仅用于DB Facade。 DB::table('table')->insert([ [$data1], [$data2] ]); @oliverbj 我们可以手动触发 eloquent 事件,但我不确定我们是否应该这样做。但是,根据您的需要,我们可以创建一个custom event and listener。【参考方案2】:

如果出于任何原因需要触发 Eloquent 事件,并且您想针对 n+1 问题进行优化,我建议使用事务。这仍然会产生n 查询,但数据库可以通过批量插入来优化它(因为事务)。它具有数据一致性的额外好处(所有插入都成功,或者都不成功)。

$result = getResult();

\DB::transaction(function() use ($result) 
    foreach($result as $item) 
        FieldRuleResult::create($item);
    
);

如果您不关心 Eloquent 事件被触发,而只想“批量插入”,则可以使用 F*** 提到的 insert 方法。不过,为了数据的一致性,我仍然建议使用数据库事务。

$result = getResult();

\DB::transaction(function() use ($result) 
    FieldRuleResult::insert($result);
);

在后一种情况下,如果您要插入的条目数量可能“很大”,您可能还需要对这些插入进行分块。 (例如,一次 100 个,而不是一次全部)

【讨论】:

谢谢,我会按照建议尝试使用transaction。快速提问:将实际的数据库插入进一步移动到作业中然后排队是否有意义? 根据您的项目,这可能是有道理的。特别是如果数据库可能承受重负载,或者该作业中要插入大量数据。请注意,让它在作业中排队通常是异步的,因此如果用户可以在某个地方查看他的提交,他可能会困惑为什么他们不在那里(还没有!)

以上是关于PHP - 在foreach循环外保存到数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何在 R 中嵌套 foreach 循环的内循环和外循环之间添加代码

如何在swiftUI中的Foreach循环上显示除一项之外的图像?

php foreach 嵌套循环大数组很慢?

如何在php中使用foreach循环将数据从数据库检索到jquery-ui手风琴

将数据从 javascript forEach 循环中的 php 数组发送到 ajax 调用的 url

php foreach怎样获取当前的循环次数