You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在订单点赞系统场景中避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:44:16