Как мне заставить Rails рассчитывать на загрузку? - PullRequest
14 голосов
/ 05 февраля 2011

Это связано с вопросом год и изменением назад .

Я привел пример вопроса, который должен работать из коробки, при условии, что у вас есть sqlite3: https://github.com/cairo140/rails-eager-loading-counts-demo

Инструкции по установке (для основной ветки)

git clone git://github.com/cairo140/rails-eager-loading-counts-demo.git
cd rails-eager-loading-counts-demo
rails s

У меня более полная запись в репозитории, но мой общий вопрос такой:

Как я могу заставить Rails активно рассчитывать нагрузку таким образом, чтобы минимизировать количество запросов к базе данных по всем направлениям?

Проблема n+1 возникает всякий раз, когда вы используете #count в ассоциации, несмотря на то, чтоассоциация через #includes(:associated) в ActiveRelation.Обходной путь должен использовать #length, но это работает хорошо только тогда, когда объект, к которому он вызывается, уже загружен, не говоря уже о том, что я подозреваю, что он дублирует что-то, что внутренняя часть Rails уже сделала.Кроме того, проблема с использованием #length заключается в том, что это приводит к неудачной перегрузке, когда ассоциация не была загружена для начала, и счетчик - это все, что вам нужно.

Из файла readme:

Мы можем избежать этой проблемы, запустив #length в массиве posts (см. Приложение), который уже загружен, но было бы неплохо также иметь доступный счетчик.Мало того, что это более последовательно;он обеспечивает путь доступа, который не обязательно требует загрузки сообщений.Например, если у вас есть частичное, которое отображает счет независимо от того, что, но половину времени, частичное вызывается с загруженными сообщениями, а половину - без, вы сталкиваетесь со следующим сценарием:

  • Использование #count
    • n COUNT запросов в стиле, когда сообщения уже загружены
    • n COUNT запросов в стиле, когда сообщения еще не загружены
  • Использование #length
    • Ноль дополнительных запросов, когда сообщения уже загружены
    • n * Стиль запросов, когда сообщения еще не загружены

Между этими двумя вариантами нет доминирующего варианта.Но было бы неплохо изменить #count, чтобы отложить до #length или получить доступ к длине, которая каким-то другим образом хранится за кулисами, чтобы у нас был следующий сценарий:

  • Использование пересмотренного #count
    • Ноль дополнительных запросов, когда сообщения уже загружены
    • n COUNT Стиль запросов, когда сообщения еще не загружены

Так какой правильный подход здесь?Есть что-то, что я упустил (очень, очень вероятно)?

Ответы [ 4 ]

7 голосов
/ 19 февраля 2011

Как подсказывает @apneadiving, counter_cache работает хорошо, поскольку столбец counter автоматически обновляется при добавлении или удалении записей.Поэтому, когда вы загружаете родительский объект, счетчик включается в объект без необходимости доступа к другой таблице.

Однако, если по какой-либо причине вам не нравится этот подход, вы можете сделать это:*

Post.find(:all,
          :select => "posts.*, count(comments.id) `comments_count`",
          :joins  => "left join comments on comments.post_id = posts.id")
2 голосов
/ 16 февраля 2014

Альтернативный подход к тому из Зубина:

Post.select('posts.*, count(comments.id) `comments_count`').joins(:comments).group('posts.id')
2 голосов
/ 11 февраля 2012

Я установил небольшой драгоценный камень, который добавляет метод includes_count к ActiveRecord, который использует SELECT COUNT для получения количества записей в ассоциации, не прибегая к JOIN, который может быть дорогим (в зависимости от случая).

См. https://github.com/manastech/includes-count

Надеюсь, это поможет!

2 голосов
/ 27 февраля 2011

Похоже, что наилучшим способом реализации такого рода средств может быть создание представлений SQL (ref: здесь и здесь ) для отдельного счета моделей и детейобъекты, которые вы хотите;и связанные с ними модели ActiveRecord.

Вы можете быть очень умным и использовать подклассы в исходной модели в сочетании с set_table_name :sql_view_name, чтобы сохранить все исходные методы для объектов и, возможно, даже некоторые из их ассоциаций.

Например, скажем, мы должны были добавить «Post.has_many: comments» к вашему примеру, как в ответе @ Zubin выше;тогда можно было бы сделать:

   class CreatePostsWithCommentsCountsView < ActiveRecord::Migration
      def self.up
        #Create SQL View called posts_with_comments_counts which maps over 
        # select posts.*, count(comments.id) as comments_count from posts 
        #   left outer join comments on comments.post_id = posts.id 
        #   group by posts.id
        # (As zubin pointed out above.) 
        #*Except* this is in SQL so perhaps we'll be able to do further 
        # reducing queries against it *as though it were any other table.*
      end    
   end

   class PostWithCommentsCount < Post         #Here there be cleverness.
                                              #The class definition sets up PWCC 
                                              # with all the regular methods of 
                                              # Post (pointing to the posts table
                                              # due to Rails' STI facility.)

    set_table_name :posts_with_comment_counts #But then we point it to the 
                                              # SQL view instead.
                                              #If you don't really care about
                                              # the methods of Post being in PWCC
                                              # then you could just make it a 
                                              # normal subclass of AR::Base.
   end

   PostWithCommentsCount.all(:include => :user)  #Obviously, this sort of "upward
     # looking" include is best used in big lists like "latest posts" rather than
     # "These posts for this user." But hopefully it illustrates the improved 
     # activerecordiness of this style of solution.
   PostWithCommentsCount.all(:include => :comments) #And I'm pretty sure you 
     # should be able to do this without issue as well. And it _should_ only be 
     # the two queries.
...