如何在Rails关联数组条件查询中避免N+1查询问题?
问题描述
在使用includes预加载关联数组后,添加条件查询时出现了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)
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 %>
临时解决方案
通过遍历已预加载的关联数组筛选数据,解决了N+1但不够规范:
<% @list_array.each do |array| %> <%= array.name %> <% @check_condition = "Yes" %> <% array.rem_correlatives.each do |rem_correlative|%> <% if rem_correlative.pending_tr == 1 && rem_correlative.deleted == 0 %> <% @check_condition = "No" %> <% break %> <!-- 找到符合条件的就停止遍历,优化性能 --> <% end %> <% end %> <%= @check_condition == "Yes" ? "si" : "no" %> <% end %>
规范解决方案
方法1:添加带条件的关联并预加载
修改Rem模型,新增一个带筛选条件的关联:
class Rem < ApplicationRecord has_many :rem_correlatives has_many :active_pending_correlatives, -> { where(deleted: 0, pending_tr: 1) }, class_name: 'RemCorrelative' end
控制器中预加载这个新关联:
def index @list_array = Rem.includes(:active_pending_correlatives).all end
视图直接使用预加载后的关联判断:
<% @list_array.each do |rem| %> <%= rem.name %> <%= rem.active_pending_correlatives.present? ? "No" : "Yes" %> <% end %>
此方案仅执行2次查询,彻底避免N+1,同时代码可读性高。
方法2:数据库层面直接计算存在性(性能最优)
如果只需要判断是否存在符合条件的关联,不需要加载关联数据,可直接在数据库层面计算结果:
def index @list_array = Rem.left_joins(:rem_correlatives) .select("rems.*, EXISTS( SELECT 1 FROM rem_correlatives WHERE rem_correlatives.rem_id = rems.id AND deleted = 0 AND pending_tr = 1 ) AS has_active_pending") .distinct end
视图直接使用计算好的字段:
<% @list_array.each do |rem| %> <%= rem.name %> <%= rem.has_active_pending ? "No" : "Yes" %> <% end %>
此方案仅执行1次查询,性能最优,适合仅需判断存在性的场景。
方法3:优化Ruby层面筛选
如果不想修改模型或查询,可优化临时方案,利用Ruby内置方法简化逻辑:
<% @list_array.each do |rem| %> <%= rem.name %> <% has_match = rem.rem_correlatives.any? { |rc| rc.deleted == 0 && rc.pending_tr == 1 } %> <%= has_match ? "No" : "Yes" %> <% end %>
any?方法会在找到第一个符合条件的元素时停止遍历,比手动循环更简洁高效,且不会触发N+1。
内容的提问来源于stack exchange,提问作者Carlos Morales
相关产品推荐
相关产品推荐

