【问题标题】:Prevent Rails from caching results of ActiveRecord query防止 Rails 缓存 ActiveRecord 查询的结果
【发布时间】:2011-10-19 13:12:04
【问题描述】:

我有一个 rake 任务,需要遍历大量记录(称为 Merchants),每条记录都有大量关联项目。我的问题是,由于 Rails 自动缓存了我的数据库查询的结果,我很快就将我的工作人员放入了交换空间。

简而言之,我想知道如何运行如下命令:

Merchant.all.each { |m| items = m.items }

每次都不会缓存“items”的值。

我试过了:

Merchant.all.each do |m|
  ActiveRecord::Base.connection.uncached do
   items = m.items
 end
end

我也尝试将其添加到我的 Merchant 模型中:

def items_uncached
  self.class.uncached { items }
end

然后改为调用 items_uncached,但最终我访问的每组新项目仍然会占用内存。

我正在运行 Rails 2.3.10、Ruby 1.9.2 并使用 Mysql 进行存储。

提前感谢您的想法!

***编辑:

这是我正在处理的实际代码:

File.open(output, "w") do |f|
  Merchant.all.each do |m|
    items = m.items
    invalid_image_count = 0
    items.each do |i|
      invalid_image_count += 1 unless i.image_valid?
    end
    invalid_categories = items.select { |i| !i.categories_valid? }.count
    f.puts "#{m.name} (#{m.id}): #{invalid_image_count} invalid images, " +
            "#{invalid_categories} invalid categories"
  end
end

尝试进行一些错误检查,然后记录结果。

【问题讨论】:

  • 你想做什么在这里不是很清楚;也许您正在寻找类似ActiveRecord::Base#find_each 的东西?
  • 好的,是的,我刚刚查看了 find_each,这实际上可能会有所帮助。我试试看。
  • 为了更好地表达,当我在控制台中运行这段代码时:Merchant.all.each { |m|项目 = m.项目; print "#{m.id} " },每次迭代时我的内存使用量都会增加。我的猜测是,这是因为 m.items 产生了 1 到 10,000 多个 ActiveRecords。
  • 这是意料之中的:Ruby 的垃圾收集器很少被触发,因此它在退出块时不太可能释放内存。相反,它会在获得下一个结果时重新使用它。如果下一组结果更大,将分配更多内存。这不考虑 AR(或 Rails,或 gem/插件,或您的代码等)中的潜在内存泄漏。我对 2.3(从 3 开始)了解不多,但我认为它不会在不显式打开它的情况下查询缓存(即使用 memcache)。
  • 什么是物品?该代码实际上是做什么的?它是在它自己的块中还是其他什么东西,而你刚刚取出了这个块?

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


【解决方案1】:

查询缓存不是这里的主要问题。无论如何,Rails 都会“缓存”您的对象。

查询缓存只是一种“哈希查找”,它可以防止 Rails 不必要地访问数据库,它不控制 ruby​​(或 Rails)如何在内部存储关联返回的对象。

例如试试这个(即使未缓存):

m = Merhant.first # <- m is loaded from DB
m.items           # <- items are loaded from DB and STORED(!) in m
m.items           # <- items are returned from the association stored in m
m.items.reload    # <- hits the DB (or the query cache)
m.instance_variable_get("@items") # <- returns the actual stored items

因此,现在当您在 each 循环中执行 m.items 时,您只需使用其所有项目填充所有 Merhcant 实例,并且垃圾收集器无法释放任何内容,因为所有对象都是从 @987654325 引用的@array 当你在循环中时。

所以解决方案就是按照 Victor 建议的那样做,防止触发“关联存储”。

【讨论】:

    【解决方案2】:

    如果你的关联是一个简单的has_many,你可以试试这个:

    Merchant.all.each do |m| 
      items = Item.find_all_by_merchant_id(m.id) 
      ...
    end 
    

    甚至:

    Merchant.find(:all, :select => "id, name").each do |m| 
      items = Item.find_all_by_merchant_id(m.id) 
      ... 
    end
    

    【讨论】:

    • 我不确定这是否可以避免缓存。这是一个简单的 has_many 关系,但我认为在这种情况下,m.items 应该与 Item.find_all_by_merchant_id(m.id) 具有相同的效果/返回值。听起来对吗?
    • 我相信m.items 在你释放Merchant.all 数组之前不会被释放(即直到你完成each 循环),而在我的情况下items 将在你之后被释放并收集垃圾在下一次 each 迭代中重新分配它。
    猜你喜欢
    • 2017-05-10
    • 1970-01-01
    • 2018-01-18
    • 2013-06-06
    • 1970-01-01
    • 2018-01-31
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    相关资源
    最近更新 更多