如何防止生成额外条件的数组出现 n+1 问题?

How can I prevent n+1 issue from array that is generating additional conditions?

提问人:Carlos Morales 提问时间:11/17/2023 最后编辑:user3402754Carlos Morales 更新时间:11/17/2023 访问量:57

问:

在来自包含查询的数组上使用条件时,我遇到了 n+1 问题。

下表如下:

rems
|id| |name|
 1  aaa
 2  bbb

rem_correlatives
|id| |rem_id| |name|  |deleted|  |pending_tr|
 1     1       qqqq1      0          0
 2     1       qqqq2      0          1 
 3     1       qqqq1      1          0
 4     1       qqqq2      1          1
 5     1       qqqq1      0          0
 6     1       qqqq2      0          1 
 7     1       qqqq1      1          0
 8     1       qqqq2      1          1
 9     2       qqqq1      0          0
 10    2       qqqq2      0          1 
 11    2       qqqq1      1          0
 12    2       qqqq2      1          1
 13    2       qqqq1      0          0
 14    2       qqqq2      0          1 
 15    2       qqqq1      1          0
 16    2       qqqq2      1          1

以下是模型:

 class Rem < ApplicationRecord
    has_many :rem_correlatives
 end

 class RemCorrelative < ApplicationRecord
    belongs_to :rem
 end

这是位于 /app/controllers/rems_controller.rb 的名为 rems_controller.rb 的控制器

 def index
    @list_array = Rem.includes(:rem_correlatives).all
 end

下面是位置 /app/views/rems/index.html.erb 的索引视图

 <% @list_array.each do |array| %>
      <% array.name %>
      <% @check_result = array.rem_correlatives.where("deleted=0 AND pending_tr= 1)%>
        <% if @check_result.present? %>
           No
        <% else %>
           Yes
        <% end %>
      <% end %>
   
 <% end%>

enter image description here

我尝试了使用列 pending_tr=1 和 deleted=0 显示数据数组的解决方法代码,但似乎不是一个好做法。

 <% @list_array.each do |array| %>
      <% array.name %>
      <% array.rem_correlatives.each do |rem_correlative|%>
        <!--  Create condition pending_tr=1 AND deleted=0  -->
        <% if rem_correlative.pending_tr == 1 && rem_correlative.deleted == 0%>
           <% @check_condition = "No" %>
        <% else %>
           <% @check_condition = "Yes"%>
        <% end %>
      <% end %>
      <!--  Check results from array if the word yes exists --> 
      <% if @check_condition.include?("Yes") %>
         si 
      <% else %>
        no
      <% end %>          
 <% end%>

这是使用解决方法代码时的后端结果,它有效且不显示 n+1 问题。

enter image description here

如何防止数组生成其他条件作为良好代码的 n+1 问题?

ruby-on-rails Ruby 数据库 ruby-on-rails-4 急切加载

评论

1赞 Stefan 11/17/2023
请参阅指定预先加载的关联(或使用联接)的条件)

答:

1赞 spickermann 11/17/2023 #1

我将创建与模型中的范围的专用关联。

# in app/models/rem.rb
has_many :pending_rem_correlatives,
  class_name: 'RemCorrelative', -> { where(deleted: false, pending_tr: true) }

然后将在控制器和视图中使用此关联。

# in the controller
@rems = Rem.includes(:pending_rem_correlatives).all

# in the view
<% @rems.each do |rem| %>
  <%= rem.name %>

  <% if rem.pending_rem_correlatives.any? %>
    No such rem correlatives
  <% else %>
    There are matching rem correlatives
  <% end %>
<% end %>

评论

0赞 Carlos Morales 11/17/2023
嗨,斯皮克曼,我尝试了你的代码,它仍在生成 n+1
0赞 spickermann 11/17/2023
当您仍然有子查询时,很可能是因为您在上面的评论中提到的视图中的其他子条件。你介意分享你的实际代码,还是至少分享一个更接近现实的例子?
1赞 user3402754 11/17/2023 #2

目前,在控制器中,您正在使用 Rem 记录及其关联的 .但是,当您稍后迭代和访问 时,它仍然会为每个 Rem 记录生成额外的查询,从而导致 N+1 查询问题。Rem.includes(:rem_correlatives).allrem_correlatives@list_arrayarray.rem_correlatives

为了解决这个问题,您可以使用 includes 方法预加载必要的关联,还可以利用查询条件来筛选关联的记录。以下是修改控制器代码的方法:

def index
  @list_array = Rem.includes(:rem_correlatives)
                  .where("rem_correlatives.deleted = 0 AND rem_correlatives.pending_tr = 1")
                  .references(:rem_correlatives)
                  .all
end

通过此更改,在您看来,您可以循环访问@list_array而不会对每个 Rem 记录产生额外的查询:

<% @list_array.each do |array| %>
  <%= array.name %>
  <% if array.rem_correlatives.present? %>
    No
  <% else %>
    Yes
  <% end %>
<% end %>

评论

0赞 Carlos Morales 11/17/2023
我正在尝试使用子条件,并且不想触摸控制器,因为我在视图上有其他子条件。