Thursday, June 16, 2011

Проблема N+1 SELECT'ов

Есть в мире ORM такая проблема как N+1 SELECT. Заключается она в следующем. Пусть у нас есть два домена, связанных между собой отношением one-to-many: например, заказ и его элементы.

@Entity
class Order {
  @Id
  private Long id;

  @Column(name="info", nullable=false)
  private String info;

  @OneToMany
  private List<Item> items;

  // Getters and setters
}

@Entity
class Item {
  @Id
  private Long id;

  @Column(name="info", nullable=false)
  private String info;

  @ManyToOne
  private Order order;

  // Getters and setters
}

Допустим нам нужно получить информацию о заказе и всех его элементах. Очевидным решением будет следующий код:

Order order = em.find(Order.class, 1L);
System.out.println(order.getInfo());

for (Item item : order.getItems()) {
  System.out.println(item.getInfo());
}

В данном примере (с учётом lazy fetch'a) будет выполнено 1 + N запросов к БД. Один запрос - на чтение информации о заказе, N запросов - на чтение информации о его элементах. Это безусловно грустно и неэффективно.

Если вы используете Hibernate, то проблема легко может быть решена с помощью HQL-запроса с явным указанием типа fetch'а для свойства items [1]. Вариант с повсеместным использованием eager fetch'a не рассматривается в виду его брутальности.

Если же у вас нет никакого ORM'а, то всё ещё проще: один запрос на чтение данных о заказе, один запрос на чтение данных элементов заказа. Если нужно прочесть несколько заказов - тоже самое: один запрос на чтение заказов, один запрос на чтение элементов заказов (всех вместе).


P.S.


Сей пост меня сподвигла написать моя последняя задача, суть которой заключалась в рефакторинге с целью увеличения производительности проекта. Банально исправив в быдлокоде несколько проблем с выбором N+1 записи (в ключевых местах проекта) удалось увеличить скорость его работы в 10-20 раз.

No comments:

Post a Comment