Rails: как загружать ограниченные записи упорядоченной ассоциации без N + 1 запросов - PullRequest
5 голосов
/ 12 июня 2019

Я знаю, что есть много вопросов по некоторым из этих тем, но я не нашел один, охватывающий все аспекты.

Рассмотрим модели User, Activity и Like. Когда я запрашиваю действие, я бы хотел загрузить первый лайк для каждого действия в коллекции , не делая N + 1 запросов и не загружая больше, чем необходимо записей. Мой код выглядит примерно так:

class User < ActiveRecord::Base
  has_many :likes, as: :liker
end

class Activity < ActiveRecord::Base
  has_many :likes
  has_one :first_like, -> { order(created_at: :asc) }, class_name: "Like"
end

class Like < ActiveRecord::Base
  belongs_to :activity
  belongs_to :liker, polymorphic: true
end

Я сделал всестороннюю суть, чтобы проверить различные стратегии и методы загрузки: https://gist.github.com/thisismydesign/b08ba0ee3c1862ef87effe0e25386267

Стратегии: N + 1 запрос, левое внешнее соединение, один дополнительный запрос

Методы: eager_load, includes, includes & references, includes & preload (это приведет либо к левому внешнему соединению, либо к одному дополнительному запросу)

Вот проблемы, которые я обнаружил:

  • Левое внешнее соединение не учитывает order(created_at: :asc) в области ассоциаций и default_scope { order(created_at: :asc) } (см .: рельсовый выпуск ). Он действительно соблюдает явное упорядочение, т.е. .order("likes.created_at asc").
  • Тем не менее следует избегать левого внешнего соединения, поскольку оно «может привести к тому, что во многих строках будут содержаться избыточные данные, и оно плохо работает в масштабе» (см .: apidoc rubydoc , рельсы api ). Это реальная проблема с большим количеством данных даже при индексированных поисках с обеих сторон.
  • Один дополнительный запрос создаст запрос без ограничений, потенциально получая огромные объемы данных (см .: rails отпуск )
  • Добавление явного limit(1) к ассоциации в надежде на ограничение одного дополнительного запроса приведет к поломке

Предпочтительным методом будет один дополнительный запрос, который запрашивает только необходимые записи. В общем, я не мог найти нативное решение с Rails. Есть ли?

1 Ответ

0 голосов
/ 13 июня 2019

В моем вопросе я ищу собственный способ использования Rails.Однако вот решение с использованием SQL и виртуальных атрибутов:

class Activity < ApplicationRecord
  has_one :first_like, class_name: "Like", primary_key: :first_like_id, foreign_key: :id

  scope :with_first_like, lambda {
    select(
      "activities.*,
      (
        SELECT like_id as first_like_id
        FROM likes
        WHERE activity_id = activities.id
        ORDER BY created_at ASC
        LIMIT 1
      )"
    )
  }
end

Activity.with_first_like.includes(:first_like)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...