我有一段时间有这个疑问,但现在是时候询问它了。请参阅下面的代码并在 $someVar
中有一个巨大的项目,例如 200 项:
// First approach
foreach($someVar as $item) {
$item = $em->getRepository('someEntity')->find($item['value']);
$em->remove($item);
$em->flush();
}
// Second approach
foreach($someVar as $item) {
$item = $em->getRepository('someEntity')->find($item['value']);
$em->remove($item);
}
$em->flush();
真实案例测试
在答案中提供了很好的信息后,我心中仍有一些疑问。看看这段代码:
foreach ($request->request->get( 'items' ) as $item) {
$items = $em->getRepository( 'AppBundle:FacturaProductoSolicitud' )->find( $item['value'] );
try {
$em->remove( $items );
$em->flush();
array_push($itemsRemoved, $item['value']);
$response['itemsRemoved'] = $itemsRemoved;
$response['success'] = true;
} catch ( \Exception $e ) {
dump( $e->getMessage() );
array_push($itemsNonRemoved, $item['value']);
$response['itemsNonRemoved'] = $itemsNonRemoved;
$response['success'] = false;
}
}
我正在使用
try {} catch() {}
这句话是因为我需要知道哪个$item['value']
是否被删除以将其添加到正确的数组(参见 $itemsRemoved
和 $itemsNonRemoved
)以及 flush()
为每个循环执行,我知道不好的做法,但是,退出 flush
从 foreach 循环并在 try-catch 中执行,有什么方法可以获取哪个 $item['value']
被删除了没有?如何?
最佳答案
实际上,每次删除后运行 flush() 是一种反模式。理想情况下,您应该在所有查询结束时运行一次。
在大多数情况下,Doctrine 2 已经为您处理了适当的事务划分:所有写操作(插入/更新/删除)都在排队,直到 EntityManager#flush() 被调用,它将所有这些更改包装在单个事务中。
但是,如果您想要更高的一致性,您可以将查询包装在事务中。事实上,正如您在其 best practices 中所读到的,它受到 Doctrine 的鼓励。 .
两个调用都会做同样的事情吗?意思是从数据库中删除记录?
是的,尽管对实体调用 remove 不会导致立即在数据库上发出 SQL DELETE。该实体将在下次调用涉及该实体的 EntityManager#flush() 时被删除。这意味着计划删除的实体仍然可以被查询并出现在查询和收集结果中。
因此,在循环内刷新意味着大量 SQL 查询和对数据库的访问,并且实体将被立即删除。
在循环外刷新意味着由 Doctrine 执行的一项有效事务(对您的数据库的一次访问),但在调用刷新之前不会实际删除实体。实体只会被标记为已删除。
在性能级别,最好使用哪一种? (教义有时表现为内存杀手)
毫无疑问,冲洗循环外。这也是最佳实践。在某些情况下,您确实需要在每次持久/删除/更新实体时执行刷新,但这种情况很少。
如果这两种方法都很好,我也可以对每个 UPDATE 查询使用相同的方法吗?
同样适用于更新/持久化。不惜一切代价避免在循环内刷新。
总结一下,看看 documentation .这真的很好解释。
如果任何查询由于某种原因失败,我如何捕获哪个?可能还有 Doctrine 给出的错误
您可以随时包装您的 flush
在 try/catch 块中并优雅地捕获查询失败时抛出的异常。
try {
$em->flush()
}(\Exception $e) {
// do stuff
throw $e;// re-thrown Exception
}
使用隐式事务划分时,EntityManager#flush() 过程中出现异常时,事务自动回滚,EntityManager 关闭。有关该主题的更多信息 here .
更新
在您提供的代码中,如果您在循环外使用flush,则所有删除操作都将属于同一个事务。这意味着如果它们中的任何一个失败,则会引发异常并发出回滚(所有已删除的操作都将被回滚,因此不会保留在数据库上)。
例如:假设我们有四个 ID 为 1、2、3、4、5、6 的项目,并假设删除 4 失败。
第一个选项->Flush inside loop: 1,2,3 被删除。 4 失败抛出异常并结束。
第二个选项-> 刷新外循环:4 失败,回滚,没有删除,程序结束。
如果您想要实现的行为是案例 1 中所示的行为,则一种选择可能是您正在使用的一种。但是,就性能而言,它确实很昂贵。
不过,还有更好的解决方案:例如,您可以使用 preRemove/postRemove 事件的组合来存储在刷新中成功删除的那些实体的 id(或您想要的任何值)(尽管由于回滚而没有持久化) )。例如,您可以将它们存储在属于该类的静态数组中(或使用单例或其他)。然后在异常的 catch 子句中,您可以使用该数组对这些项目进行迭代并执行删除操作(当然是在循环外刷新)。
然后,您可以返回该数组,以便让用户知道您实际上删除了这些实体,并返回 false,因为删除过程中存在问题。
关于php - 在 foreach 循环内部或外部调用 flush() 之间的区别,该使用哪一个?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27988758/