doctrine2 – Как повысить эффективность флеша?

Я должен обновить сущности Doctrine, чтобы соответствовать записям внутри файла (potentionaly very large) XML. Я также должен обновлять ассоциации ManyToMany в соответствии с данными в XML. Это то, что я делаю внутри цикла:

  1. получать данные из XML
  2. получить сущность из БД (если не существует, создать новый)
  3. установить новые свойства объекта
  4. получить текущие ассоциации сущностей (getter возвращает объект ArrayCollection )
  5. очистить все ассоциации (вызывая ArrayCollection::clear() )
  6. установить новые ассоциации (вызывая ArrayCollection::add() в ArrayCollection::add() )
  7. сохранить объект EntityManager

После цикла я вызываю EntityManager::flush() .

Проблема заключается в том, что очистка генерирует большое количество запросов вместо обновления / вставки / удаления нескольких строк одновременно. Для каждого объекта выполняются следующие запросы:

  • SELECT для получения объекта из базы данных
  • UPDATE для обновления свойств объекта (это фактически пропущено сейчас, поскольку свойства не изменены … пока)
  • УДАЛИТЬ, чтобы удалить предыдущие ассоциации
  • ВСТАВИТЬ вставить новые ассоциации

Так что всего за 305 записей в XML я получаю 915 запросов (я думаю, он мог бы доходить до 1220 запросов, если бы все сущности были изменены), что делает импорт очень медленным.

Я мог бы воспользоваться преимуществами IdentityMap и предварительной выборки сущностей до цикла, но все еще есть запросы UPDATE / DELETE / INSERT.

  • Есть ли способ позволить методу флеша лучше оптимизировать запросы (используйте multi-insert, WHERE IN вместо нескольких запросов DELETE и т. Д.)?
  • Является ли это обычным поведением метода flush или я делаю что-то неправильно?
  • Возможно, проблема в том, как я обновляю ассоциации сущности. Есть ли лучший способ, как это сделать? (вместо метода get / clear / add)
  • Я знаю, что Doctrine не предназначен для массовой обработки betch, но я думаю, что использование этого для импорта XML – лучший способ избежать несоответствий БД, которые могут появиться с использованием подхода без ORM. Это правильно?
  • Если вышеприведенный подход неверен, как мне решить проблему?

Вы делаете это правильно – это просто медленно, потому что добавленная абстракция ORM означает, что вы не можете сделать нужные вам оптимизации.

Тем не менее, EntityManager не спешит с транзакциями, большими. Если вам не нужны все они в одной крупной транзакции, вы можете получить более эффективный код с помощью flush () ing, а затем очистить () от EM каждые 20-200 итераций вашего цикла.

Если это не дает вам достаточной производительности, единственной альтернативой, о которой я могу думать, является возврат к пользовательскому коду, который запускает собственный SQL непосредственно против вашей СУБД.

Я знаю, что это не отличный ответ, но, по крайней мере, я могу сказать вам, что вы не сумасшедший.

—— редактировать ——

Из официальной статьи Doctrine2 о пакетной обработке :

Некоторые люди, похоже, задаются вопросом, почему Doctrine не использует мульти-вставки (вставлять в (…) значения (…), (…), (…), …

Прежде всего, этот синтаксис поддерживается только в mysql и более поздних версиях postgresql. Во-вторых, нет простого способа получить все сгенерированные идентификаторы в такой мультиинсталляции при использовании AUTO_INCREMENT или SERIAL, а ORM нужны идентификаторы для управления идентификацией объектов. Наконец, вставка производительности редко является узким местом ORM. Обычные вставки более чем достаточно быстры для большинства ситуаций, и если вы действительно хотите делать быстрые вставки вставки, то мульти-вставка не является наилучшим способом, то есть Postgres COPY или Mysql LOAD DATA INFILE на несколько порядков быстрее.

Это причины, по которым не стоит пытаться реализовать абстракцию, которая выполняет множественные вставки в mysql и postgresql в ORM.

Также существует значительная разница в производительности при использовании удаленной vs локальной базы данных, поскольку накладные расходы на отправку каждого запроса на удаленный сервер довольно велики. Накладные расходы намного ниже при использовании локальной базы данных благодаря транзакциям и оптимизации БД. (например, в случае примера в вопросе на 70 с ниже до 300 мс)

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

… Что касается скорости очистки, убедитесь, что ваш профайлер xdebug не включен.

 [php.ini] ; PROFILING ;xdebug.profiler_enable = 1 ;xdebug.profiler_output_name = "cachegrind.out.%t.%s.%p" ;xdebug.profiler_output_dir = "C:\xampp\tmp" 

В качестве примера того, насколько это повлияло на операцию по очистке Doctrine в моем случае, это было 55 секунд для 3000 записей, тогда как профайлер выключен, это было 5 секунд!