ActiveRecord: joins, includes и preload — кто есть кто?

Оптимизация запросов в Rails — ключевой навык для разработчиков, работающих с PostgreSQL. В этой статье разбираемся, как правильно использовать методы ActiveRecord (joins, includes, preload, eager_load), чтобы избежать проблем с производительностью и N+1 запросами.


Когда запросы становятся медленными, а лог растет на десятки строк — пора разбираться, чем отличаются joins, includes и preload в ActiveRecord.


🤝 joins

Создаёт SQL JOIN, но не загружает связанные записи в память.

User.joins(:company).where(companies: { active: true })

🔎 SQL:

SELECT "users".* FROM "users"
INNER JOIN "companies" ON "companies"."id" = "users"."company_id"
WHERE "companies"."active" = TRUE
  • Работает быстро
  • Позволяет фильтровать по ассоциациям
  • Но user.company вызовет доп. запрос

📦 includes

Подгружает ассоциации, чтобы избежать N+1:

User.includes(:company).each { |u| puts u.company.name }

🔎 SQL:

SELECT "users".* FROM "users";
SELECT "companies".* FROM "companies" WHERE "companies"."id" IN (...)

ActiveRecord решает сам, использовать ли JOIN или отдельный SELECT. Иногда это неожиданно приводит к eager_load.


🚚 preload

Заставляет ActiveRecord загрузить ассоциации через отдельный запрос:

User.preload(:company)

🔎 SQL:

SELECT "users".* FROM "users";
SELECT "companies".* FROM "companies" WHERE "companies"."id" IN (...)
  • Никогда не делает JOIN
  • Работает стабильно и предсказуемо

🔍 eager_load

Это includes + JOIN, независимо от условий:

User.eager_load(:company).where(companies: { name: "Acme" })

🔎 SQL:

SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ..., 
       "companies"."id" AS t1_r0, "companies"."name" AS t1_r1, ... 
FROM "users"
LEFT OUTER JOIN "companies" ON "companies"."id" = "users"."company_id"
WHERE "companies"."name" = 'Acme'
  • Ассоциации загружаются сразу
  • Выглядит как SELECT ... LEFT OUTER JOIN

💡 Когда использовать что

Задача Метод
Нужно фильтровать по полям ассоциации joins
Нужно избежать N+1 includes или preload
Нужно всё в одном запросе eager_load
Нужно 100% предсказуемо preload

🔚 Вывод: если не хочешь сюрпризов — используй preload. А joins — твой друг, когда нужен SQL-контроль.

🗓 Дата публикации: 23.10.2024, но это не точно...

Rails ActiveRecord PostgreSQL оптимизация запросы N+1