如何在订单点赞系统场景中避免N+1查询问题?
解决订单点赞系统的N+1查询问题
嘿,我来帮你搞定这个N+1查询的问题!你提到用includes预加载是对的方向,不过得结合你的模型关联来做具体调整,下面是一步步的解决方案:
第一步:确认模型关联设置正确
首先得保证你的关联关系是清晰的,假设你的模型结构是这样的(可以根据实际情况调整):
# app/models/user.rb class User < ApplicationRecord has_many :likes, dependent: :destroy end # app/models/order.rb class Order < ApplicationRecord has_many :likes, dependent: :destroy # 新增一个针对当前用户的关联,方便后续判断 has_many :user_likes, ->(user) { where(user_id: user.id) }, class_name: "Like", dependent: :destroy end # app/models/like.rb class Like < ApplicationRecord belongs_to :user belongs_to :order # 确保一个用户只能给同一个订单点一次赞 validates :user_id, uniqueness: { scope: :order_id } end
第二步:预加载关联,彻底消灭N+1
查询订单列表时,不能只简单用Order.all,要把当前用户的点赞记录一起预加载进来,这里有几种实用的方式:
方式一:用left_joins+select直接生成点赞状态字段
这种方式最直接,会给每个订单对象添加一个liked_by_current_user属性,不用再额外查询:
# 在控制器中 @orders = Order.left_joins(:likes) .select("orders.*, CASE WHEN likes.id IS NOT NULL THEN true ELSE false END AS liked_by_current_user") .where(likes: { user_id: current_user.id }) .or(Order.left_joins(:likes).where(likes: { id: nil }))
方式二:用自定义关联预加载当前用户的点赞
如果用了上面Order模型里的user_likes关联,可以这样预加载:
# 在控制器中 @orders = Order.includes(:user_likes).references(:user_likes) # 确保包含所有订单,不管有没有被当前用户点赞 @orders = @orders.where(user_likes: { user_id: current_user.id }).or(@orders.where.not(id: Like.where(user_id: current_user.id).select(:order_id)))
第三步:在视图里直接使用预加载的数据
现在渲染订单表格时,不用再针对每个订单发查询了,直接用预加载好的数据判断:
如果用的是方式一的liked_by_current_user:
<% @orders.each do |order| %> <tr> <td><%= order.order_number %></td> <td> <% if order.liked_by_current_user %> <%= link_to "取消点赞", order_like_path(order, order.likes.find_by(user_id: current_user.id)), method: :delete %> <% else %> <%= link_to "点赞", order_likes_path(order), method: :post %> <% end %> </td> <!-- 其他订单字段 --> </tr> <% end %>
如果用的是方式二的user_likes关联:
<% @orders.each do |order| %> <tr> <td><%= order.order_number %></td> <td> <% if order.user_likes.any? %> <%= link_to "取消点赞", order_like_path(order, order.user_likes.first), method: :delete %> <% else %> <%= link_to "点赞", order_likes_path(order), method: :post %> <% end %> </td> <!-- 其他订单字段 --> </tr> <% end %>
额外小优化:用缓存提升性能
如果订单数量比较多,还可以给每个订单行加缓存,结合用户和订单的变化自动失效:
<% @orders.each do |order| %> <% cache [order, current_user] do %> <tr> <!-- 订单内容和点赞按钮 --> </tr> <% end %> <% end %>
内容的提问来源于stack exchange,提问作者Ernesto G
相关产品推荐
相关产品推荐

