减少Rails中的数据库命中率

我有两个模型, projectswords ,其中project has_many :words (单词实际上只是一个模型,其中包含每个项目每天写入的单词数量。

我认为我是这样构建的,它显示了项目从开始到结束的所有日期以及当天写了多少字,如果有的话:

     

在我的帮助下:

 def project_range(start, finish) project_days = (start..finish).collect end def get_word_count_by_date(project, date) word_count = Word.find_by_project_id_and_wrote_on(project, date) if word_count word_count.quantity else 0 end end 

在视图中,麻烦是对我的数据库的影响很大。 例如,如果项目是30天,我得到:

  Word Load (0.2ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-01' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-02' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-03' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-04' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-05' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-06' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-07' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-08' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-09' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-10' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-11' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-12' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-13' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-14' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-15' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-16' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-17' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-18' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-19' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-20' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-21' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-22' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-23' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-24' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-25' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-26' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-27' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-28' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-29' LIMIT 1 Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-30' LIMIT 1 

有没有办法在没有查询项目长度的每一天的情况下这样做? 我首先尝试加载所有项目的单词,但是无法弄清楚如何在那里获得零日。

您可以使用块帮助程序来保持其清洁并避免查找它们:

 def project_range(project, start, finish, &blk) words = project.words.where(:wrote_on => start..finish) word_map = words.index_by(&:wrote_on) for day in start..finish word_count = word_map[day] ? word_map[day].quantity : 0 blk.call(day, word_count) end end 

然后就像使用它一样

 <% project_range(project, start, finish) do |day, word_count| %> <%= day %> <%= word_count %> <% end %> 

您也可以稍微清理帮助程序(避免使用SQL),也可以通过传递预取单词列表或使用scope

编辑:m_x建议在start..finish where上的start..finish where子句更干净!

这是一个“n + 1”问题……您要做的是在查询中加入单词和项目,以便每个项目的所有单词都包含在结果集中。

假设您的项目“has_many:words”:

 @project = Project.find(:id, :include => :words) 

现在,每个项目上的单词集合将预先填充1个查询中的单词。

阅读“Eager Loading of Associations”下的更多内容http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

我会去做类似的事情:

 @words = @project.words.where("wrote_on >= ? and wrote_on <= ?", start, end) 

而不是使用group_by在视图中显示它们:

 @words.group_by(&:wrote_on).each do |day, word| <%= day %> <%= word.quantity %> end