foreach 循环内部或外部调用 flush() 的区别,使用哪一个?
Difference between call flush() inside foreach loop or outside it, which one to use?
我有这个疑问有一段时间了,但现在是时候问一问了。请参阅下面的代码,在 $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();
- 两个电话会做同样的事情吗?意思是从数据库中删除记录?
- 在性能级别,最好使用哪个? (教义有时会成为记忆杀手)
- 如果这两种方法都很好,我也可以使用相同的 per UPDATE 查询吗?
- 如果任何查询由于某种原因失败,我如何捕捉到哪一个?也可能是 Doctrine
给出的错误
真实案例测试
在答案中提供了很好的信息后,我仍有一些疑问。看看这段代码:
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()
,我知道这是不好的做法,但是,从 foreach 循环中取出 flush
并在 try-catch 中执行,有没有办法得到哪个 $item['value']
被删除了?怎么样?
实际上,运行 每次删除后的 flush() 是一个反模式。理想情况下,您应该在所有查询结束时 运行 它一次。
在大多数情况下,Doctrine 2 已经为您处理了正确的事务划分:所有写操作(INSERT/UPDATE/DELETE)都排队等待 EntityManager#flush() 被调用,它将所有这些更改包装在单笔交易。
但是,如果您想要更高的一致性,可以将查询包装在事务中。事实上,它受到 Doctrine 的鼓励,您可以在其 best practices.
中阅读
两个调用会做同样的事情吗?意思是从数据库中删除记录?
是的,尽管对实体调用删除不会导致立即对数据库发出 SQL DELETE。该实体将在下次调用涉及该实体的 EntityManager#flush() 时被删除。这意味着计划删除的实体仍然可以查询并出现在查询和收集结果中。
因此,循环内的刷新意味着大量 SQL 查询和对数据库的访问,实体将被立即删除。
循环外的刷新意味着 Doctrine 执行一个有效的事务(一次访问您的数据库),但在调用刷新之前实体不会被实际删除。实体只会被标记为已删除。
在性能层面,哪个最好用? (Doctrine 有时会成为记忆杀手)
毫无疑问,在循环外刷新。这也是最佳实践。在某些情况下,您每次 persist/remove/update 实体时确实需要执行刷新,但很少见。
如果这两种方法都很好,我也可以使用相同的 per UPDATE 查询吗?
同样适用于 update/persist。尽量避免刷新内部循环。
最后,请查看 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 失败抛出异常并结束。
第二个选项->Flush outside loop: 4失败,回滚,none被删除,程序结束。
如果您想要实现的行为是案例 1 中所示的行为,那么一个选项可能就是您正在使用的那个。但是,就性能而言,它确实很昂贵。
然而,有更好的解决方案:例如,您可以使用 preRemove/postRemove 事件的组合来存储在刷新中成功删除的那些实体的 ID(或您想要的任何值)(尽管由于回滚而没有持续存在)。例如,您可以将它们存储在属于 class 的静态数组中(或使用单例或其他)。然后在异常的 catch 子句中,您可以使用该数组对这些项目进行迭代和执行删除操作(当然是在循环外刷新)。
然后您可以 return 数组,以便让用户知道您实际上删除了那些实体,并且是错误的,因为删除过程中出现了问题。
我有这个疑问有一段时间了,但现在是时候问一问了。请参阅下面的代码,在 $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();
- 两个电话会做同样的事情吗?意思是从数据库中删除记录?
- 在性能级别,最好使用哪个? (教义有时会成为记忆杀手)
- 如果这两种方法都很好,我也可以使用相同的 per UPDATE 查询吗?
- 如果任何查询由于某种原因失败,我如何捕捉到哪一个?也可能是 Doctrine 给出的错误
真实案例测试
在答案中提供了很好的信息后,我仍有一些疑问。看看这段代码:
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()
,我知道这是不好的做法,但是,从 foreach 循环中取出 flush
并在 try-catch 中执行,有没有办法得到哪个 $item['value']
被删除了?怎么样?
实际上,运行 每次删除后的 flush() 是一个反模式。理想情况下,您应该在所有查询结束时 运行 它一次。
在大多数情况下,Doctrine 2 已经为您处理了正确的事务划分:所有写操作(INSERT/UPDATE/DELETE)都排队等待 EntityManager#flush() 被调用,它将所有这些更改包装在单笔交易。
但是,如果您想要更高的一致性,可以将查询包装在事务中。事实上,它受到 Doctrine 的鼓励,您可以在其 best practices.
中阅读两个调用会做同样的事情吗?意思是从数据库中删除记录?
是的,尽管对实体调用删除不会导致立即对数据库发出 SQL DELETE。该实体将在下次调用涉及该实体的 EntityManager#flush() 时被删除。这意味着计划删除的实体仍然可以查询并出现在查询和收集结果中。
因此,循环内的刷新意味着大量 SQL 查询和对数据库的访问,实体将被立即删除。
循环外的刷新意味着 Doctrine 执行一个有效的事务(一次访问您的数据库),但在调用刷新之前实体不会被实际删除。实体只会被标记为已删除。
在性能层面,哪个最好用? (Doctrine 有时会成为记忆杀手)
毫无疑问,在循环外刷新。这也是最佳实践。在某些情况下,您每次 persist/remove/update 实体时确实需要执行刷新,但很少见。
如果这两种方法都很好,我也可以使用相同的 per UPDATE 查询吗?
同样适用于 update/persist。尽量避免刷新内部循环。
最后,请查看 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 失败抛出异常并结束。
第二个选项->Flush outside loop: 4失败,回滚,none被删除,程序结束。
如果您想要实现的行为是案例 1 中所示的行为,那么一个选项可能就是您正在使用的那个。但是,就性能而言,它确实很昂贵。
然而,有更好的解决方案:例如,您可以使用 preRemove/postRemove 事件的组合来存储在刷新中成功删除的那些实体的 ID(或您想要的任何值)(尽管由于回滚而没有持续存在)。例如,您可以将它们存储在属于 class 的静态数组中(或使用单例或其他)。然后在异常的 catch 子句中,您可以使用该数组对这些项目进行迭代和执行删除操作(当然是在循环外刷新)。
然后您可以 return 数组,以便让用户知道您实际上删除了那些实体,并且是错误的,因为删除过程中出现了问题。