MiniProfiler列出的额外查询

时间:2014-11-14 20:44:22

标签: ruby-on-rails ruby-on-rails-3 performance eager-loading preloading

在我的控制器操作中,我included视图所需的所有关联,以避免多次调用数据库。 (我试图将视图层隔离为仅渲染控制器收集的数据)。


  

我发现视图仍然与数据库通信(17个查询):   MiniProfiler


不需要这些 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 

如何阻止这些查询无意地运行?

6 个答案:

答案 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)