如何从 Ruby on Rails 中的数据库查询中消除 N+1 查询?
Posted
技术标签:
【中文标题】如何从 Ruby on Rails 中的数据库查询中消除 N+1 查询?【英文标题】:How to eliminate N+1 queries from database query in Ruby on Rails? 【发布时间】:2022-01-13 10:11:28 【问题描述】:在我的 Rails 6 应用程序中,我有这些模型:
class User < ApplicationRecord
has_many :read_news_items
has_many :news_items, :through => :read_news_items
end
class NewsItem < ApplicationRecord
has_many :read_news_items
has_many :users, :through => :read_news_items
def read?(user)
read_news_items.where(:user_id => user.id).any?
end
end
class ReadNewsItem < ApplicationRecord
belongs_to :user
belongs_to :news_item
end
在我的控制器操作中,我想列出所有新闻项目并突出显示用户尚未阅读的新闻:
class NewsItemsController < ApplicationController
def index
@news_items = NewsItem.all
end
end
问题在于,这会为每个记录生成 N+1 个查询,因为每个 user
记录都会调用 read?(current_user)
。
如何解决这个问题?
我尝试将includes(:read_news_items)
和joins(:read_news_items)
附加到控制器中的数据库查询中,但无济于事。
【问题讨论】:
读取情况如何?(current_user) 被调用了? 你想运行什么查询。你能分享一下日志吗 下一个怎么样:@news_items = NewsItem.where.not(id: @user. read_news_items.pluck(:new_items_id)
?并且您不需要在视图中调用read?
。
@kunashir 所说的除了你不应该打电话给pluck
,改用select
。 select
将创建一个带有子查询的 IN 子句; pluck
将创建一个带有一次性 Array
的 IN 子句
【参考方案1】:
你可以试试:
class NewsItem < ApplicationRecord
has_many :read_news_items
def read?(user)
if read_news_items.loaded?
read_news_items.any? |rni| rni.user_id == user.id
else
read_news_items.where(:user_id => user.id).any?
end
end
end
class NewsItemsController < ApplicationController
def index
@news_items = NewsItem.includes(:read_news_items).all
end
end
【讨论】:
谢谢。我不知道loaded?
方法。【参考方案2】:
好的,我从这里给出的每个答案中都学到了一些东西。非常感谢。
我将read?
方法更改为以下似乎消除了N+1 个查询:
class NewsItem < ApplicationRecord
def read?(user)
user.read_news_items.pluck(:news_item_id).include?(id)
end
end
【讨论】:
user.read_news_items.pluck
周围的括号不需要。
谢谢,我刚刚更新了我的代码。以上是关于如何从 Ruby on Rails 中的数据库查询中消除 N+1 查询?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Ruby on Rails 中查看给定 ActiveRecord 查询将生成的 SQL
如何从 Ruby on Rails 中的字符串值创建变量名?
如何从 ruby on rails 上的现有远程 oracle 数据库中获取数据