【问题标题】:Caching limited associations缓存有限的关联
【发布时间】:2012-02-25 21:33:36
【问题描述】:

我正在尝试缓存 ActiveRecord 及其关联。 问题是在访问检索到的记录上的关联时存在数据库查询。

通常情况下,我会使用即时加载 Rails.cache.write('post', Post.includes(:comments).find(99)) 进行缓存。这似乎可行,但问题是我只想缓存关联的有限子集,并且在急切加载时会忽略限制(例如提到here)。所以Post.includes(:popular_comments).find(99) 将返回所有 cmets,而不仅仅是流行的。

所以我尝试在延迟加载关联后缓存对象,但不幸的是在拉出对象时会出现查询:

class Post < ActiveRecord::Base
  has_many :comments
  has_many :popular_comments, :class_name > 'Comment', :limit => 20, :order => :votes

post = Post.find(99)
post.popular_comments # lazy-load limited associations
Rails.cache.write('post', post)
...
Rails.cache.read('post').popular_comments # Unwanted SQL query :(

我已经尝试缓存一个克隆,同样不需要的 SQL 查询。我已经尝试过 redis 和 memcached 实现,结果相同。奇怪的是,这个序列确实可以在控制台上运行,但是在控制器或视图中的简单用法会失败(即发生 SQL)。

更新(2017 年 4 月):我现在想说这是一个愚蠢的前提。缓存整个对象通常是浪费的,因为它使用大量缓存存储并且序列化/反序列化它们很慢。缓存关联(如在这个问题中所问的)也将浪费乘以 N。通常只缓存原始 ID 和 HTML 片段会更有效。

【问题讨论】:

  • 你运行的是哪个版本的 Rails?
  • @Brandan 这是在 Rails 3.1 上
  • 您能否发布日志的 sn-p 以准确显示正在执行的 SQL 以及它在请求中发生的位置?我无法重现这个。
  • @Brandan 现在没有这个确切的代码,但我会看看我是否可以用一个虚拟项目来重现它。
  • 您的 Rails 版本是什么?我在我的设置中尝试了这个,它对我有用。

标签: ruby-on-rails ruby-on-rails-3 caching activerecord


【解决方案1】:

试试post.popular_comments.reload

首先,在急切加载时实际上会忽略限制。来自the docs

如果您使用指定的 :limit 选项急切加载关联,它将被忽略,并返回所有关联的对象

这意味着,就像您发现的那样,您必须自己将关联强制到父对象中。在我的实验中,post.popular_comments 不起作用(这是有道理的,因为它返回一个代理对象),有趣的是post.popular_comments.all 也不起作用。但是,post.popular_comments(true) 可以解决问题。在该代码下方调用 reload,只需执行 post.popular_comments.reload 也会将关联加载到父类中。

我不确定这两个哪个更正确,post.popular_comments(true)post.popular_comments.reload。两者看起来都有点脆弱,但第二个读起来更好,更清楚地表达了你的意图。

我验证了这两种方法:

  1. 将有限关联存储在内存缓存中
  2. 从缓存加载后未触发 SQL 查询

我存储帖子的脚本:

require 'pp'
Rails.cache.clear
post = Post.first

#post.popular_comments(true)
post.popular_comments.reload

Rails.logger.info "writing to cache"
s = Rails.cache.write "post", post
Rails.logger.info "writing to cache done"

然后检索:

require 'pp'
Rails.logger.info "reading from cache"
post = Rails.cache.read "post"
Rails.logger.info "reading from cache done"
Rails.logger.info post.popular_comments.inspect

如果我一个接一个地运行,我的日志会显示:

  Post Load (0.5ms)  SELECT `posts`.* FROM `posts` LIMIT 1
  Comment Load (0.5ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 1 ORDER BY votes LIMIT 20
writing to cache
writing to cache done
reading from cache
reading from cache done
[#<Comment id: 1, ...

我的 mySQL 日志还确认第二个脚本没有触发关联查询。

这是使用 Rails 3.1.1 完成的

【讨论】:

  • 很好,回答,谢谢。我现在无法对其进行测试,但您已经理解了问题所在,这听起来是最好的解决方案。
【解决方案2】:

我没有看到你在我的测试中描述的问题。

我在控制器中使用的代码

def show
  unless Rails.cache.exist?('faq_category')
    @faq_category = Faq::Category.first
    @faq_category.questions
    Rails.cache.write('faq_category', @faq_category)
  end
  @faq_category = Rails.cache.read('faq_category')
end

当我运行页面时,我在日志中看到以下语句,表示模型没有重新加载 可在https://skitch.com/aroop/g9w5t/untitled获取更大的图像

问题可能出在您的视图文件上。评论视图,看看问题是否仍然存在。

【讨论】:

  • 这是我看到的一个 has_many 关联受限的问题。我在问题中提供了更多信息。
  • 有时间我会试着模拟你的情况
  • 非常感谢。它不应该那么复杂。只是一个 has_many 关联,并在关联上定义了一个限制。
【解决方案3】:

Rails 5 根本不支持 :limit =&gt; 20 选项。你会得到以下错误:

ArgumentError: Unknown key: :limit。有效键是::class_name, :anonymous_class, :foreign_key, :validate, :autosave, :table_name, :before_add, :after_add, :before_remove, :after_remove, :extend, :primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors

您必须明确使用post.popular_comments.limit(20)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-13
    • 2021-09-13
    • 2012-03-14
    • 2015-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多