Разница между вызовом flush () внутри цикла foreach или вне его, какой из них использовать?

У меня такое сомнение какое-то время, но сейчас настало время спросить об этом. См. Код ниже и наличие огромных элементов в $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(); 
  • Оба вызова будут делать то же самое? что означает удаление записей из БД?
  • На уровне производительности, который лучше всего использовать? (Доктрина иногда ведет себя как убийца памяти)
  • Если оба подхода хороши, могу ли я использовать один и тот же запрос UPDATE?
  • Если какой-либо из запросов по какой-то причине не удается, как я поймаю, какой? И, возможно, ошибка, данная Учение

Тестирование с помощью реального случая

Имея хорошую информацию, представленную в ответе, на мой взгляд оставалось несколько сомнений. Взгляните на этот фрагмент кода:

 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 уже заботится о надлежащей демаркации транзакций для вас: все операции записи (INSERT / UPDATE / DELETE) ставятся в очередь до тех пор, пока не будет вызван EntityManager # flush (), который обертывает все эти изменения в одной транзакции.

Тем не менее, вы можете обернуть свои запросы в транзакции, если хотите больше согласованности. Фактически, это поощряет Учение, как вы можете читать в своих лучших практиках .

Оба вызова будут делать то же самое? что означает удаление записей из БД?

Да, хотя вызов remove в сущности НЕ приводит к немедленному выпуску SQL DELETE в базе данных. Сущность будет удалена при следующем вызове EntityManager # flush (), который включает этот объект. Это означает, что объекты, запланированные для удаления, могут быть запрошены и появляться в результатах запроса и сбора.

Таким образом, очистка внутри цикла означает множество SQL-запросов и обращений к вашей базе данных, и сущности будут немедленно удалены.

Flush вне цикла означает одну эффективную транзакцию (один доступ к вашей БД), выполняемый Doctrine, но сущности не будут удалены фактически до тех пор, пока не будет вызвана флеш. Объекты будут отмечены только как удаленные.

На уровне производительности, который лучше всего использовать? (Доктрина иногда ведет себя как убийца памяти)

Вне всякого сомнения, выходите за пределы петли. Это тоже лучшая практика. Могут быть случаи, когда вам действительно нужно выполнять флеш каждый раз, когда вы сохраняете / удаляете / обновляете объект, но очень мало.

Если оба подхода хороши, могу ли я использовать один и тот же запрос UPDATE?

То же самое относится к обновлению / сохранению. Старайтесь избегать зачистки внутренней петли любой ценой.

Чтобы завершить работу, просмотрите документацию . Это действительно хорошо объяснено.

Если какой-либо из запросов по какой-то причине не удается, как я поймаю, какой? И, возможно, ошибка, данная Учение

Вы всегда можете обернуть свой flush в блоки try / catch и элегантно захватить исключения, возникающие при сбоях запросов.

 try { $em->flush() }(\Exception $e) { // do stuff throw $e;// re-thrown Exception } 

При использовании неявной демаркации транзакций и исключения возникает во время EntityManager # flush (), транзакция автоматически откатывается и EntityManager закрывается.

Подробнее об этой теме.

Обновить

В представленном вами коде, если вы используете флеш вне цикла, все операции удаления будут принадлежать одной транзакции. Это означает, что если какой-либо из них не выполняется, генерируется исключение и выдается откат (все удаленные операции выполняются, и они не сохраняются в БД).

Например: представьте, что у нас есть четыре элемента с идентификаторами 1,2,3,4,5,6 и предположим, что удаление 4 не удалось.

Первый вариант -> Скрытый внутренний цикл: 1,2,3 удаляются. 4 выдает исключение и заканчивается.

Второй вариант -> Скрытый внешний цикл: 4 сбой, откат, ни один не удаляется и программа заканчивается.

Если поведение, которое вы хотите достичь, – это тот, который показан в случае 1, одним из вариантов может быть тот, который вы используете. Однако это очень дорого с точки зрения производительности.

Тем не менее, есть более эффективные решения: например, вы можете использовать комбинацию событий preRemove / postRemove для хранения идентификаторов (или любого другого значения, которое вы хотите) тех объектов, которые были успешно удалены во флеше (хотя они не были сохранены из-за отката ). Вы можете хранить их, например, в статическом массиве, принадлежащем классу (или использовать одноэлементный или любой другой). Затем в предложении catch исключения вы можете использовать этот массив для повторения и выполнения операции удаления для этих элементов (конечно, за пределами цикла).

Затем вы можете вернуть массив, чтобы пользователь знал, что вы фактически удалили эти сущности и false, потому что в процессе удаления возникла проблема.