缓存有限的关联

时间:2012-02-03 08:30:57

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

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

通常情况下,我只是使用预先加载Rails.cache.write('post', Post.includes(:comments).find(99))进行缓存。这似乎有效,但问题是我只想缓存关联的有限子集,并且在预先加载时会忽略限制(例如,提到here)。因此,Post.includes(:popular_comments).find(99)会返回所有条评论,而不仅仅是热门评论。

所以我在延迟加载关联后尝试缓存对象,但是在拉出对象时不幸发生了一个查询:

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片段会更有效。

3 个答案:

答案 0 :(得分:6)

尝试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. 将有限关联存储在memcache
  2. 从缓存加载后未触发SQL查询
  3. 我存储帖子的脚本:

    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

    完成的

答案 1 :(得分:0)

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

我在控制器中使用的代码

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

当我运行页面时,我在日志中看到以下语句,表明模型没有重新加载 enter image description herehttps://skitch.com/aroop/g9w5t/untitled

处可获得更大的图片

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

答案 2 :(得分:0)

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

  

ArgumentError:未知密钥:: 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)