Розуміння `includes` в Rails

Однією з найбільш поширених проблем з продуктивністю в Rails є проблема N+1 запитів. Вона виникає, коли ви запитуєте записи з асоціаціями, але кожен асоційований запис завантажується окремим запитом. Це може призвести до сотень запитів, що значно сповільнює вашу програму. Рішення? Eager loading (жадібне завантаження) за допомогою методу includes в Rails.

Давайте розберемося детальніше, як працює includes, як він вирішує проблему N+1 запитів і коли його ефективно використовувати.

pic

Що таке includes?

includes — це метод запиту в Rails, який використовується для жадібного завантаження асоційованих записів.
Це дозволяє попередньо завантажити пов'язані дані окремими запитами, зменшуючи загальну кількість запитів до бази даних і запобігаючи проблемі N+1 запитів.

Коли ви запитуєте модель з асоціаціями (наприклад, has_many, belongs_to, has_one), includes гарантує, що асоційовані дані будуть отримані заздалегідь, уникнувши додаткових запитів до бази даних під час доступу до цих асоціацій.

Проблема N+1 запитів

Приклад проблеми N+1 запитів

Уявіть, що у вас є модель Post, яка має багато моделей Comment. Якщо ви хочете відобразити список постів з їхніми коментарями, ви можете написати:

posts = Post.all  
posts.each do |post|  
 post.comments.each do |comment|  
 puts comment.content  
 end  
end

Ось що відбувається:

  1. Rails виконує один запит для отримання всіх постів.
    2.
    Для кожного поста Rails виконує ще один запит для отримання його коментарів.

Якщо у вас є 1000 постів, ви виконаєте 1001 запит (1 запит для постів + 1 запит для кожного поста для коментарів).

Як includes вирішує цю проблему?

Використовуючи includes, ви можете зменшити кількість запитів:

posts = Post.includes(:comments).all  
posts.each do |post|  
 post.comments.each do |comment|  
 puts comment.content  
 end  
end

З includes Rails попередньо завантажує всі асоційовані коментарі.

  1. Один запит для отримання всіх постів.
    2.
    Один запит для отримання всіх коментарів для цих постів.

Всього: 2 запити, незалежно від того, скільки постів у вас є!

Як працює includes?

  • Preload: Виконує окремі запити для основної моделі та її асоціацій.
  • Eager Load: Об'єднує основну модель і асоціації в один SQL запит за допомогою JOIN.

Рішення Rails

Rails автоматично обирає між preload та eager_load в залежності від вашого запиту:

  • Якщо ви не фільтруєте або не звертаєтесь до асоційованої таблиці, Rails використовує preload.
  • Якщо ви застосовуєте умови або фільтри до асоційованої таблиці, Rails переключається на eager_load.

Preload vs Eager Load

pic

Приклад Preload

Post.includes(:comments).all  
# Виконує два запити:  
# 1. SELECT * FROM posts  
# 2.
SELECT * FROM comments WHERE post_id IN (...)

Приклад Eager Load

Post.includes(:comments).where('comments.content = ?', 'Great Post!').references(:comments)  
# Виконується один запит з JOIN:  
# SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE comments.content = 'Great Post!'

Використання references з includes

Коли ви застосовуєте умови до асоційованих таблиць, потрібно використовувати references, щоб забезпечити правильне з'єднання таблиць у Rails.

Post.includes(:comments).where('comments.content = ?', 'Great Post!').references(:comments)

Ось що відбувається:

  1. includes(:comments): Попередньо завантажує коментарі для всіх постів.
  2. where('comments.content = ?', 'Great Post!'): Фільтрує пости за вмістом їхніх коментарів.
    3.
    references(:comments)`: Забезпечує, щоб Rails правильно приєднав таблицю коментарів до запиту.

Без references Rails може викинути помилку або не згенерувати правильний SQL запит.

Переваги

  • Перезавантаження даних: Використання includes, коли асоційовані дані не потрібні, може завантажити зайві записи.
  • Складні запити: Eager loading може згенерувати великі та складні SQL запити, що може вплинути на продуктивність.
  • Використання пам'яті: Попереднє завантаження великих наборів даних може збільшити споживання пам'яті.

Кращі практики

  1. Використовуйте includes для уникнення N+1 запитів: Лише коли ви знаєте, що будете звертатися до асоціацій.
  2. Уникайте перезавантаження даних: Не використовуйте includes, якщо асоціація не потрібна.
    3.
    Використовуйте joins для фільтрації: Якщо вам потрібно лише фільтрувати, краще використовувати joins, а не includes.
# Фільтрація без завантаження асоційованих даних  
Post.joins(:comments).where(comments: { content: 'Great Post!' })

Висновок: Коли використовувати includes

Підсумовуючи, includes — це потужний інструмент для покращення продуктивності та зменшення кількості запитів до бази даних. Він особливо корисний, коли потрібно попередньо завантажити асоціації для уникнення проблеми N+1. Однак важливо використовувати його обережно, щоб уникнути надмірного завантаження даних або створення зайво складних запитів.

Перекладено з: Understanding includes in Rails

Leave a Reply

Your email address will not be published. Required fields are marked *