2016-11-18 2 views
0

A post имеет likers и comments детей. Я хочу сортировать сообщения на их основе.Рельсы, избегающие N + 1 Запросы

class Post < ApplicationRecord 
    scope :latest, -> { 
    all.sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

Это вызывает вопросы, как показано ниже:

Post Load (0.7ms) SELECT "posts".* FROM "posts" 
    (0.4ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 52]] 
    (0.4ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 52]] 
    (0.2ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 53]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 53]] 

Поэтому я стараюсь следующий вместо:

Post.includes(:comments, :likers).all.sort_by(&:ranking) 

Это называет запросы, как показано ниже:

Post Load (0.7ms) SELECT "posts".* FROM "posts" 
    Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71) 
    UserPostLike Load (0.3ms) SELECT "user_post_likes".* FROM "user_post_likes" WHERE "user_post_likes"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71) 
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 46 
    (0.3ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 52]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 52]] 
    (0.2ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 53]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 53]] 

Почему это и как я могу так Да?

UPDATE:

Я понял, как решить эту проблему, но ответ с очень хорошим объяснением будет приятно:

мне пришлось заменить count с size.

Начальное:

class Post < ApplicationRecord 
    scope :latest, -> { 
    all.sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

После:

class Post < ApplicationRecord 
    ... 

    def ranking 
    likers.size + comments.size 
    end 
end 

Затем N+1 Query ушел. Я получил намек на то, что, когда вы используете counter_cache, происходит то же самое. В этом случае я не использовал counter_cache, но мне все равно пришлось использовать size вместо count. Я предполагаю, что вызов count сил Rails для вызова COUNT SQL-запрос и вызов size заставляют использовать загруженные записи в памяти.

+0

посмотрите на это http://stackoverflow.com/a/9209705/4758119 –

ответ

2

Вы можете использовать eager_load вроде:

Post.eager_load(:comments, :likers).sort_by(&:ranking) 

жадной загрузки загружает все ассоциации в одном запросе с помощью LEFT OUTER JOIN.

Eager Loading Associations

3 ways to do eager loading (preloading) in Rails 3 & 4

+0

Проблема здесь не в высокой загрузке, так как вы можете видеть из запроса, что его ассоциации загружены , – fbelanger

+0

Спасибо за ответ, но это не решит проблему. –

+0

eager_load выполняет только 1 запрос и загружает все данные (записи assosation) в память. –

0

Проблема здесь две складки:

Во-первых, sort_by немедленно поднимает флаг для меня: http://apidock.com/ruby/Array/sort%21

Это Array метод, то есть вы больше не создавая запрос ActiveRecord, вы делаете преобразования массива.

Поскольку вы включаете comments и likers, запросы не так плохи, как они могут быть, но вот и другая проблема.

Путь .count работ - это сборный запрос на подсчет SELECT * FROM table.

Чтобы получить желаемый результат, вам нужно будет создать свой собственный счет и запрос сортировки.

Посмотрите на этот пост, и мы надеемся, что это даст вам лучшее представление о том, как оптимизировать это далее: Rails 3 ActiveRecord: Order by count on association

+0

Вы отправляете пример для mysql, в Postgres вам необходимо агрегировать все атрибуты. –

+0

См. Мое обновление –

+0

@ Зелёный, что вы имеете в виду совокупность всех атрибутов? Я ускользнул от создания запроса для подсчета и сортировки рейтинга, не более того. – fbelanger

0

в вашем случае лучше всего использовать для counter_cachelikers и comments. Подробнее вы можете прочитать SHORT ARTICLE. Это очень легко, и это будет безопасное время и память.

Если вы используете counter_cache, вы не должны делать несколько запросов к своей БД. и теперь ваш метод будет:

def ranking 
    likers_count + comments_count 
end 

с другой стороны, если вы не хотите, чтобы добавить столбцы в таблице просто использовать includes:

class Post < ApplicationRecord 
    scope :latest, -> { 
    includes(:likers, :comments).sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

, но в этом случае вы будете рассчитывать likers и comments каждый раз, когда метод звонит

Смежные вопросы