代码之家  ›  专栏  ›  技术社区  ›  Ernesto G

在这种情况下如何避免N+1

  •  1
  • Ernesto G  · 技术社区  · 6 年前

    我正在尝试在我的应用程序中实现一个“喜欢”系统。我用订单呈现一个表,然后当前用户可以“喜欢”订单,以便在订单状态更改时收到通知。问题是,我遇到了一个N+1问题,因为每次呈现表时,程序都会进行与显示订单一样多的查询,以检测用户是否已经“喜欢”了订单。

    我已经读到,通过使用“includes”来急切地加载相关记录可以避免这种情况,但我不知道该怎么做,尤其是在我的情况下。

    我有这些模型和关联:

    user.rb 我是否包括了喜欢的内容?触发N+1警报的方法:

    class User < ApplicationRecord
      devise :database_authenticatable, :recoverable, :rememberable, :trackable, 
      :validatable
      has_many :likes
    
      def likes?(order)
        order.likes.where(user_id: id).any?
      end
    end
    

    like.rb

    class Like < ApplicationRecord
      belongs_to :user
      belongs_to :order
    end
    

    order.rb

    class Order < ApplicationRecord
    
     has_many :likes
     .
     .
     .
    

    对于表中的每一行,我呈现此部分以显示顺序是否合适:

    <% if current_user.likes?(order) %>
      <%= link_to "<i class='fa fa-fire fa-2x fa-like'></i>".html_safe, 
      order_like_path(order), method: :delete, remote: true %>
    <%else%>
      <%= link_to "<i class='fa fa-fire fa-2x fa-unlike'></i>".html_safe, 
      order_like_path(order), method: :post, remote: true %>
    <%end%>
    

    以下是查询:

    Rendered orders/_likes.html.erb (135.5ms)
    Like Exists (0.5ms)  SELECT  1 AS one FROM "likes" WHERE "likes"."order_id" 
    =$1 AND "likes"."user_id" = $2 LIMIT $3  [["order_id", 7875], ["user_id", 
    1], ["LIMIT", 1]]
    

    编辑如果索引操作有用,我会添加它:

      def index
        orders = request.query_string.present? ? Order.search(params, 
        current_user) : Order.pendientes
        if params[:button] == 'report'
          build_report(orders)
        else
        @orders = orders.order("#{sort_column} # 
        {sort_direction}").page(params[:page]).per(params[:paginas])
        end
      end
    
    3 回复  |  直到 6 年前
        1
  •  2
  •   Jignesh Gohel    6 年前
    class User < ApplicationRecord
      has_many :likes
    
      has_many :liked_orders, through: :likes, class_name: 'Order'
    
      def liked_orders_id
        @liked_orders_id ||= liked_orders.pluck(:id)
      end
    
      def liked_order?(order_id)
        liked_orders_id.include?(order_id)
      end
    end
    

    在我看来,您的问题背后的根本原因似乎是您实施 likes?(order) 中的方法 User 模型

      def likes?(order)
        order.likes.where(user_id: id).any?
      end
    

    每次在加载的 使用者 ,它首先加载 Order 实例,然后在该加载顺序上加载其关联的 Like 实例和加载的实例 喜欢 实例应用 user_id 滤器

    使现代化

    这个 liked_orders 关联应定义为

      has_many :liked_orders, through: :likes, source: :order
    
        2
  •  2
  •   Deepesh elements    6 年前

    在这种情况下,我通常会做的是 orders 已在视图中,并且您拥有 user 因此,我获取:

    likes = current_user.likes.where(order: orders)
    liked_order_ids = likes.pluck(:order_id)
    

    我会通过 liked_order_ids 每次到 _likes 部分和检查 liked_order_ids.include?(order.id)

    我还没有拿到 user.likes 直接因为可能有很多 订单 他很喜欢,但所有内容都没有出现在当前页面上。如果是,您可以直接这样取:

    liked_order_ids = current_user.likes.pluck(:order_id)
    

    因此,这样它也不会执行任何新查询或缓存查询。

    您尝试的方式是在 order 喜欢,所以要经历 顺序 对象相反,你有 使用者 通过它,您可以找到 likes 按原样 belongs to 他也是。像 顺序 将始终是多个 使用者 它将执行一个查询来查找它,而不是使用 顺序

    显然,有更多的方法可以解决这个问题。选择将取决于你和你的处境。

        3
  •  0
  •   Vasilisa    6 年前

    它在OrdersController显示或索引操作中,对吗?您需要像这样重新定义实例变量:

    @orders = current_user.orders.includes(:likes)
    or 
    @order = current_user.orders.find(params[:id]).includes(:likes)
    

    然后移动 likes? 订单模型的方法(更改它 liked_by 例如)。

    def liked_by?(user)
      likes.where(user_id: user.id).exists?
    end
    

    在视图中

    <% if order.liked_by?(current_user) %>
    

    在这种情况下,likes将被预加载,您可以避免N+1问题。

    添加是个好主意 bullet gem到应用程序,它将警告您N+1查询,并提供有关的建议 includes

    更新时间:

    只需添加 包括 现有@订单

    @orders = orders.includes(:likes).order("#{sort_column} # 
        {sort_direction}").page(params[:page]).per(params[:paginas])