在我的控制器操作中,我included
视图所需的所有关联,以避免多次调用数据库。 (我试图将视图层隔离为仅渲染控制器收集的数据)。
我发现视图仍然与数据库通信(17个查询):
不需要这些 17 extra queries
。由于我已经从控制台测试了控制器查询,并且它成功地收集了部分_dropdown
(在 5个查询中)所需的所有数据,而没有任何进一步的数据库通信。
以下是我的控制器中的query,它可以避免N+1
问题。 (包括视图中调用的所有变量)
- @messages.each do |message|
%li.conversation-container
%a{href: conversation_path(message.conversation_id)}
- if message.sender != current_user
.notification-avatar{style: "background: url(#{message.sender.avatar_url}); background-size: contain; background-repeat: no-repeat; background-position: 50% 50%;"}
- else
- other_participant = message.conversation.conversation_participants.select{|p| p.user_id != current_user.id }.first.user
.notification-avatar{style: "background: url(#{other_participant.avatar_url}); background-size: contain; background-repeat: no-repeat; background-position: 50% 50%;"}
%p
%strong
- if message.sender != current_user
= message.sender.name
- else
= other_participant.name
%br
- if message.sender == current_user
%i.fa.fa-mail-reply-all
= truncate(message.body,length: 25)
.time
= time_ago_in_words(message.created_at)
ago
- if @messages.count == 0
%li
.empty-state-text-white
No messages
2.0.0-p353 :006 > ms = Message.dropdown_for(3).all
Message Load (1.2ms) SELECT "messages".* FROM "messages" LEFT JOIN messages AS m ON messages.id != m.id
AND m.conversation_id = messages.conversation_id
AND messages.created_at < m.created_at INNER JOIN conversation_participants AS cp ON cp.conversation_id = messages.conversation_id AND cp.user_id = 3 WHERE (m.id IS NULL) ORDER BY cp.seen , cp.updated_at DESC LIMIT 5
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (6, 4, 5)
Conversation Load (0.4ms) SELECT "conversations".* FROM "conversations" WHERE "conversations"."id" IN (4, 2, 3)
ConversationParticipant Load (0.2ms) SELECT "conversation_participants".* FROM "conversation_participants" WHERE "conversation_participants"."conversation_id" IN (4, 2, 3)
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (6, 3, 4, 5)
=> [#<Message id: 8, body: "saSasa", sender_id: 6, conversation_id: 4, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 16:05:40", updated_at: "2014-11-17 16:05:40">, #<Message id: 2, body: "asdnas dagsdashjdg jahs d", sender_id: 4, conversation_id: 2, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 11:32:36", updated_at: "2014-11-17 11:32:36">, #<Message id: 6, body: "SADASD A DSA ", sender_id: 5, conversation_id: 3, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 13:43:34", updated_at: "2014-11-17 13:43:34">]
2.0.0-p353 :007 > ms.first.conversation.conversation_participants.select{|cp| cp.user_id != 3}.first.user
=> #<User id: 6, first_name: "Ddsfsd", middle_name: nil, last_name: "Fsdfsd", photo: nil, email: "1@k.com", encrypted_password: "$2a$10$5sGIb2DbQ1ctMrTzD3AJ0uV18hhiC5Ei1wcfE7MSAvRU...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2014-11-17 15:27:06", last_sign_in_at: "2014-11-17 15:27:06", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmation_token: nil, confirmed_at: "2014-11-17 15:27:48", confirmation_sent_at: "2014-11-17 15:27:05", unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, authentication_token: nil, created_at: "2014-11-17 15:27:05", updated_at: "2014-11-17 15:27:48", slug: "ddsfsd_fsdfsd">
2.0.0-p353 :008 > ms.count
=> 3
答案 0 :(得分:6)
在调试可能导致此问题的每个可能因素之后。
我试图在我的config.cache_classes
中将development.rb
设置为true。这成功删除了所有额外的查询。
我得出结论(默认情况下),当没有缓存类时,不会为任何模型加载模式。换句话说,当config.cache_classes
设置为false
时,每个模型的架构都会作为单独的查询为每个请求加载。
以下是类似问题column_definitions method being called before and after every single SQL statement on PostgreSQL。
cache_classes
应设置为false
环境中的development
。 忽略postgresql连接适配器的额外内部查询 加载每个模型的模式,因为它不会影响您的模型 生产环境(生产已设置为config.cache_classes
true
)。
答案 1 :(得分:3)
您可以尝试使用bullet gem,它会告诉您查询中是否有N + 1个问题。如果没有N + 1问题,那么你应该尝试实现片段缓存。
答案 2 :(得分:2)
简单的问题。您是否尝试在方法调用结束时添加。to_a
?
喜欢@messages.to_a
?
答案 3 :(得分:2)
我会检查日志以查看这17个查询是什么,或者点击17 sql
链接会显示这些查询。从那里,您可能会看到忘记includes
导致N + 1问题的表格。
编辑:
正如“懒人装载”中所述。 this site的一部分,您可以在控制器操作中将.all
添加到查询的末尾以触发其执行,并防止查询在您的视图中懒惰地执行。正如我的评论中所提到的,Rails范围允许您构建查询并在使用它们时执行。要执行,您可以调用.all,.count,.each或.first。在Rails 4中,您可以使用.load
在控制器中执行查询。
答案 4 :(得分:1)
这可能是所谓的'N + 1'问题,它是由于延迟加载而发生的。没有应用程序日志我无法肯定地说。您可以按照here所述使用预先加载。
答案 5 :(得分:1)
您的查询没有很好地表达。您应该使用包含或连接。
将您的查询分解为两个,如下所示:
message_ids = Message.joins("LEFT JOIN messages AS m ON messages.id != m.id
AND m.conversation_id = messages.conversation_id
AND messages.created_at < m.created_at")
.where('m.id IS NULL')
.joins("INNER JOIN conversation_participants AS cp
ON cp.conversation_id = messages.conversation_id
AND cp.user_id = #{user_id}")
.order("cp.seen, cp.updated_at DESC")
.limit(5).map(&:id)
messages = Message.includes(:sender).
includes(conversation: [{conversation_participants: :user}]).
where(id: message_ids)