Как Hibernate решает порядок обновления / вставки / удаления

Давайте сначала забудем о Hibernate. Предположим, что у меня есть две таблицы, A и B. Две транзакции обновляют одни и те же записи в этих двух таблицах, но txn 1 обновляет B и затем A, а txn 2 обновляет A и B. Это типичный пример взаимоблокировки. Наиболее распространенным способом избежать этого является предварительное определение порядка получения ресурсов. Например, мы должны обновить таблицу A, а затем B.

Вернитесь в спящий режим. Когда мы обновляем много сущностей в одном сеансе, когда я очищаю сеанс, изменения разных сущностей будут генерировать соответствующие операторы вставки / обновления / удаления в БД. Есть ли в Hibernate какой-то алгоритм для определения порядка обновления между сущностями? Если нет, то каким образом Hibernate используется для предотвращения тупиковой ситуации, описанной в первом абзаце?

Если Hibernate поддерживает заказ, как я могу узнать или контролировать заказ? Я не хочу, чтобы мое явное обновление в БД конфликтовало с Hibernate и вызывало взаимоблокировку.

2 ответа

Решение

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

Вы должны предпринять четкие шаги, чтобы избежать проблем.

Hibernate делает часть работы за вас. Как и в предыдущем ответе, Hibernate гарантирует, что при изолированной очистке вставки, удаления и обновления упорядочиваются таким образом, чтобы гарантировать, что они будут применяться в достижимом порядке. Смотрите executeExecutions(сеанс EventSource) в классе AbstractFlushingEventListener:

Выполните все SQL (и обновления кэша второго уровня) в особом порядке, чтобы ограничения внешнего ключа не могли быть нарушены:

  1. Вставки, в порядке их выполнения
  2. Обновления
  3. Удаление элементов коллекции
  4. Вставка элементов коллекции
  5. Удаляет, в порядке их выполнения

При наличии уникальных ограничений очень важно знать этот порядок, особенно если вы хотите заменить дочерний элемент "один ко многим" (удалить старый / вставить новый), но и старый, и новый дочерние элементы имеют одинаковые уникальные ограничения (например, один и тот же адрес электронной почты).). В этом случае вы можете обновить старую запись вместо удаления / вставки, или вы можете очистить только после удаления, чтобы затем продолжить вставку. Для более подробного примера вы можете проверить эту статью.

Обратите внимание, что в нем не указан порядок обновлений. Изучение кода Hibernate заставляет меня думать, что порядок обновления будет зависеть от порядка, в котором объекты были добавлены в контекст постоянства, а не от порядка их обновления. Это может быть предсказуемо в вашем коде, но чтение кода Hibernate не оставляет у меня ощущения, что я буду полагаться на этот порядок.

Есть три решения, которые я могу придумать:

  1. Попробуйте установить hibernate.order_updates в true. Это должно помочь избежать взаимоблокировок при обновлении нескольких строк в одной таблице, но не поможет при взаимоблокировках между несколькими таблицами.
  2. Сделайте так, чтобы ваши транзакции выполняли блокировку PESSIMISTIC_WRITE на одном из объектов перед выполнением каких-либо обновлений. Какой объект вы используете, будет зависеть от вашей конкретной ситуации, но до тех пор, пока вы убедитесь, что объект выбран последовательно, если существует риск тупика, это будет блокировать оставшуюся часть транзакции до тех пор, пока блокировка не будет получена.
  3. Напишите свой код, чтобы поймать тупики, когда они возникают, и повторите разумным способом. Компонент, управляющий повторной блокировкой блокировки, должен находиться за пределами текущей границы транзакции. Это связано с тем, что сбойный сеанс должен быть закрыт, а соответствующая транзакция откатана. В этой статье вы можете найти пример автоматической повторной попытки AOP Aspect.

Что касается первого примера - такого рода вещи обрабатываются базой данных (подробнее об уровнях изоляции транзакций и стратегиях блокировки вашей базы данных). Есть много разных способов это сделать.

Что касается Hibernate, то в javadoc для org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(EventSource) сказано:

Выполните все обновления кэша SQL и второго уровня в особом порядке, чтобы ограничения внешнего ключа не могли быть нарушены:

  1. Вставки, в порядке их выполнения
  2. Обновления Удаление элементов коллекции
  3. Вставка элементов коллекции
  4. Удаляет, в порядке их выполнения

Я предполагаю, что это единственная оптимизация выполняемых SQL-запросов, выполняемых Hibernate. Остальные проблемы обрабатываются базой данных.

Другие вопросы по тегам