使用实例变量进行缓存会降低性能

时间:2014-10-21 14:09:16

标签: ruby-on-rails performance

以下所有示例都会同时调用pending_notifications?reviewable_notifications

我为User提供了以下一组实例方法:

def pending_notifications?
  return true if reviewable_notifications.size > 0
end

def reviewable_notifications
  @reviewable_notifications ||= self.employee.notifications.where(read: [nil, false])
end

视图以下列方式使用它们:

<% if current_user.pending_notifications? %>
  <li><%= link_to fa_icon("envelope") + " #{current_user.reviewable_notifications.count} Notification(s)", user_notifications_path(id: current_user.id) %></li>
<% else %>
   <li><%= link_to fa_icon("inbox") + " Notification Center", user_notifications_path(id: current_user.id) %></li>
<% end %>

当我分析负载时,正在调用一个查询:

SELECT COUNT(*) FROM "notifications"  WHERE "notifications"."employee_id" = ? AND (("notifications"."read" = 'f' OR "notifications"."read" IS NULL))

这很好,但在我的重构之前,我没有使用推荐的缓存技术和实例变量,但我在分析中得到了完全相同的查询。此外,它一直运行在20毫秒左右。下面是代码最初编写的方式。为什么Rails没有两次调用同一个查询?使用这种方式编写的代码为什么性能更好?

def pending_notifications?
  return true if self.employee.notifications.where(read: [nil, false]).size > 0
end

def reviewable_notifications
  self.employee.notifications.where(read: [nil, false])
end

2 个答案:

答案 0 :(得分:1)

记住这样的值只有在Ruby中进行昂贵的计算并且在多个地方使用该值时才有用。

此特定计算发生在SQL中,Rails默认已缓存数据库查询,因此您没有看到任何更改。

答案 1 :(得分:0)

差异不是基于正在生成的查询...
如果你使用

那就是
`@reviewable_notifications ||= self.employee.notifications.where(read: [nil, false])`

只要@reviewable_notifications为零,您就只会点击数据库。

在它需要一个值时,它将被使用。

作为一个简单的例子,您可以在控制台中编写:

2.1.2 :001 > 5.times { User.first } # no caching, hit 5 times your DB
  User Load (0.2ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
  User Load (0.1ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
  User Load (0.1ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
  User Load (0.1ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
  User Load (0.1ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
 => 5 
2.1.2 :002 > 5.times { @user ||=  User.first } # caching, only 1 hit
  User Load (0.1ms)  SELECT  `users`.* FROM `users`   ORDER BY `users`.`id` ASC LIMIT 1
 => 5 

当然,mysql有自己的查询缓存,所以如果同一个查询命中数据库,结果可能会从数据库查询缓存中返回(你可以在上面的例子中看到最后的查询需要比第一个更少的时间,这可能是因为mysq服务于其缓存的结果)