Как я могу получить Rails для увеличения количества загрузок?
Это связано с вопросом год и изменение назад.
Я приведу пример вопроса, который должен работать из коробки, если у вас есть доступный sqlite3: https://github.com/cairo140/rails-eager-loading-counts-demo p >
Инструкции по установке (для основной ветки)
git clone git://github.com/cairo140/rails-eager-loading-counts-demo.git
cd rails-eager-loading-counts-demo
rails s
У меня есть полная запись в репозитории, но мой общий вопрос в этом.
Как я могу подсчитать количество загрузок Rails, чтобы свести к минимуму запросы db по всей доске?
Проблема n+1
возникает всякий раз, когда вы используете #count
в ассоциации, несмотря на включение этой ассоциации через #includes(:associated)
в ActiveRelation. Обходным путем является использование #length
, но это хорошо работает только тогда, когда объект, на который он был вызван, уже загружен, не говоря уже о том, что я подозреваю, что он дублирует то, что уже сделали внутренности Rails. Кроме того, проблема с использованием #length
заключается в том, что она приводит к неудачной перегрузке, когда ассоциация не была загружена для начала, и счет - это все, что вам нужно.
Из файла readme:
Мы можем уклониться от этой проблемы, запустив #length в массиве posts (см. приложение), который уже загружен, но было бы неплохо иметь счетчик также доступным. Это не только более последовательное; он обеспечивает путь доступа, который не обязательно требует загрузки сообщений. Например, если у вас есть частичное, которое отображает счетчик, независимо от того, что, но в половине случаев частичное вызывается с сообщениями, загруженными и половину времени без, вы столкнулись со следующим сценарием:
- Использование
#count
- n
COUNT
запросы стиля, когда сообщения уже загружены - n
COUNT
запросы стиля, когда сообщения еще не загружены
- Использование
#length
- Нулевые дополнительные запросы, когда сообщения уже загружены
- n
*
запросы стиля, когда сообщения еще не загружены
Между этими двумя вариантами выбора нет доминирующего варианта. Но было бы неплохо пересмотреть #count, чтобы отложить до #length или получить доступ к длине, которая каким-то другим способом хранится за кулисами, чтобы мы могли иметь следующий сценарий:
- Использование измененного
#count
- Нулевые дополнительные запросы, когда сообщения уже загружены
- n
COUNT
запросы стиля, когда сообщения еще не загружены
Итак, какой правильный подход здесь? Есть ли что-то, что я забыл (очень, очень вероятно)?
Ответы
Ответ 1
Похоже, что наилучшим способом реализации такого средства может быть создание SQL-представлений (ref: здесь и здесь) для отдельных целевых объектов model-and-child-count; и связанные с ними модели 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.
Ответ 2
В качестве предлагаемого @apneadiving, counter_cache работает хорошо, потому что столбец счетчика автоматически обновляется при добавлении или удалении записей. Поэтому, когда вы загружаете родительский объект, счетчик включается в объект без необходимости доступа к другой таблице.
Однако, если по какой-либо причине вам не нравится этот подход, вы можете сделать это:
Post.find(:all,
:select => "posts.*, count(comments.id) `comments_count`",
:joins => "left join comments on comments.post_id = posts.id")
Ответ 3
Я установил небольшой камень, который добавляет метод includes_count
к ActiveRecord, который использует SELECT COUNT для извлечения количества записей в ассоциации, не прибегая к JOIN, который может быть дорогим (в зависимости от случая).
См. https://github.com/manastech/includes-count
Надеюсь, что это поможет!
Ответ 4
Альтернативный подход к одному из Zubin:
Post.select('posts.*, count(comments.id) `comments_count`').joins(:comments).group('posts.id')