Обновившись до Spring Boot 3.3.0 (конкретно до Hibernate 6.5), мы столкнулись со 100% загрузкой процессора на БД из-за небольшого изменения в SQL коде, сгенерированного Hibernate после преобразования JPQL в SQL.

Image
100% потребление CPU

Посмотрите на следующий JPQL-запрос и обратите внимание на различия в генерации SQL для Hibernate 6.4 и Hibernate 6.5 при передаче пустого списка в качестве параметра.

interface ArticleRepository extends CrudRepository<Article, UUID> {  
  @Query("from Article where publisherId in :ids")
  List<Article> findByPublisherId(List<UUID> ids);
}
var articles = articleRepository.findByPublisherId(List.of());

Hibernate 6.4.X:

select a1_0.id, a1_0.publisher_id, a1_0.title
from article a1_0
where 1=0

Hibernate 6.5.X:

select a1_0.id, a1_0.publisher_id, a1_0.title
from article a1_0
where (1 = case when a1_0.publisher_id is not null then 0 end)

Может показаться, что разница незначительная, однако в PostgreSQL первый запрос выполняется практически мгновенно, а второй приводит к полному сканированию таблицы (full table scan).

Стоит отметить, что при использовании derived-query вместо @Query с JPQL запрос будет отличаться:

interface ArticleRepository extends CrudRepository<Article, UUID> {
List<Article> findByPublisherId(List<UUID> ids);
}
var articles = articleRepository.findByPublisherId(List.of());
select a1_0.id, a1_0.publisher_id, a1_0.title
from article a1_0
where a1_0.publisher_id in (?)

Команда Hibernate уже поправила этот баг: https://github.com/hibernate/hibernate-orm/pull/8528

Осталось дождаться включения этого фикса в модуль Spring Data JPA.


Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм - Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Ждем всех, присоединяйтесь!

Комментарии (11)


  1. Sipaha
    07.06.2024 08:44
    +4

    А зачем вообще hibernate отправляет заведомо бесполезный запрос в бд в этом случае?


    1. spring_aio Автор
      07.06.2024 08:44
      +2

      Конкретно здесь, может быть и можно было обойтись без запроса вообще. Наверно это не так просто сделать, когда это подзапрос, или какой-то более сложный кейс, с джоинами например.
      Возможно, чтобы не городить множество оптимизаций на все случаи жизни, просто отключают ветку с помощью условия 1 = 0;


      1. qwzx
        07.06.2024 08:44

        Интересно другое: перед каждым запросом к базе с новыми параметрами генерируется новый SQL в зависимости от параметров?


    1. ris58h
      07.06.2024 08:44

      Этот вопрос надо не Hibernate адресовать, а Spring-у.


      1. Suvitruf
        07.06.2024 08:44

        Почему?


        1. ris58h
          07.06.2024 08:44

          Вы правы - не туда посмотрел - думал что речь про derived-query.


  1. aleksandy
    07.06.2024 08:44
    +2

    Осталось дождаться включения этого фикса в модуль Spring Data JPA.

    А зачем дожидаться? Если можно просто обновить используемую версию хибера? Или автор свидетель тождественности спрингдатажпа и с хибером?


    1. spring_aio Автор
      07.06.2024 08:44
      +2

      На самом деле, надо дождаться, пока выйдет новая версия Hibernate. Баг поправили в мастере.


      1. aleksandy
        07.06.2024 08:44

        Ну, собственно, о том и речь. Багу исправили в хибере и к спринг дате он относится чуть менее, чем никак.


  1. Slobodator
    07.06.2024 08:44
    +5

    С in-clause вообще лучше быть осторожным. В постгресе вроде бы ограничений нет, а в оракле, например, по дефолту не более 1000 аргументов -- соответственно, надо разбивать на чанки и конкатенировать результат.

    Если бизнес-логика позволяет (как в данном конкретном случае), имеет смысл предварить в репозитории

    interface ArticleRepository extends CrudRepository<Article, UUID> {
      default List<Article> findByPublisherId(List<UUID> ids) {
        if (ids.isEmpty()) {
         return Collections.emtpyList();
        }
        return _findByPublisherId(ids);
      }
      
      @Query("from Article where publisherId in :ids")
      List<Article> _findByPublisherId(List<UUID> ids);
    }
    

    Если метод совсем безобразно могут вызывать, ещё и проверку на null добавить.


  1. DieSlogan
    07.06.2024 08:44
    +2

    Не была печали, апдейтов накачали.

    Сори, но зачем вам edge обновления, когда можно спокойно сидеть на стабильной ветке, просто со всеми патчами безопасности?