Однією з найбільш поширених проблем з продуктивністю в Rails є проблема N+1 запитів. Вона виникає, коли ви запитуєте записи з асоціаціями, але кожен асоційований запис завантажується окремим запитом. Це може призвести до сотень запитів, що значно сповільнює вашу програму. Рішення? Eager loading (жадібне завантаження) за допомогою методу includes
в Rails.
Давайте розберемося детальніше, як працює includes
, як він вирішує проблему N+1 запитів і коли його ефективно використовувати.
Що таке 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
Ось що відбувається:
- 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 попередньо завантажує всі асоційовані коментарі.
- Один запит для отримання всіх постів.
2.
Один запит для отримання всіх коментарів для цих постів.
Всього: 2 запити, незалежно від того, скільки постів у вас є!
Як працює includes
?
- Preload: Виконує окремі запити для основної моделі та її асоціацій.
- Eager Load: Об'єднує основну модель і асоціації в один SQL запит за допомогою
JOIN
.
Рішення Rails
Rails автоматично обирає між preload
та eager_load
в залежності від вашого запиту:
- Якщо ви не фільтруєте або не звертаєтесь до асоційованої таблиці, Rails використовує preload.
- Якщо ви застосовуєте умови або фільтри до асоційованої таблиці, Rails переключається на eager_load.
Preload vs Eager Load
Приклад 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)
Ось що відбувається:
includes(:comments)
: Попередньо завантажує коментарі для всіх постів.where('comments.content = ?', 'Great Post!')
: Фільтрує пости за вмістом їхніх коментарів.
3.
references(:comments)`: Забезпечує, щоб Rails правильно приєднав таблицю коментарів до запиту.
Без references
Rails може викинути помилку або не згенерувати правильний SQL запит.
Переваги
- Перезавантаження даних: Використання
includes
, коли асоційовані дані не потрібні, може завантажити зайві записи. - Складні запити: Eager loading може згенерувати великі та складні SQL запити, що може вплинути на продуктивність.
- Використання пам'яті: Попереднє завантаження великих наборів даних може збільшити споживання пам'яті.
Кращі практики
- Використовуйте
includes
для уникнення N+1 запитів: Лише коли ви знаєте, що будете звертатися до асоціацій. - Уникайте перезавантаження даних: Не використовуйте
includes
, якщо асоціація не потрібна.
3.
Використовуйтеjoins
для фільтрації: Якщо вам потрібно лише фільтрувати, краще використовуватиjoins
, а неincludes
.
# Фільтрація без завантаження асоційованих даних
Post.joins(:comments).where(comments: { content: 'Great Post!' })
Висновок: Коли використовувати includes
Підсумовуючи, includes
— це потужний інструмент для покращення продуктивності та зменшення кількості запитів до бази даних. Він особливо корисний, коли потрібно попередньо завантажити асоціації для уникнення проблеми N+1. Однак важливо використовувати його обережно, щоб уникнути надмірного завантаження даних або створення зайво складних запитів.
Перекладено з: Understanding includes in Rails