MiniProfiler 列出的额外查询

Posted

技术标签:

【中文标题】MiniProfiler 列出的额外查询【英文标题】:Extra queries listed by MiniProfiler 【发布时间】:2015-01-12 08:21:25 【问题描述】:

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


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


这些 17 extra queries 不是必需的。由于我已经从控制台测试了控制器查询,并且它成功收集了部分 _dropdown(在 5 个查询 中)所需的所有数据,而无需任何进一步的数据库通信。

这是我的控制器中的query,它意味着避免N+1的问题。 (包括视图中调用的所有变量)


这里是下拉代码:

- @messages.each do |message|
    %li.conversation-container
        %ahref: conversation_path(message.conversation_id)
            - if message.sender != current_user 
                .notification-avatarstyle: "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-avatarstyle: "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 

如何阻止这些查询无目的运行?

【问题讨论】:

实际上我已经尝试断开与控制台的连接,一切都按预期工作,但是当我从服务器将它添加到我的控制器(访问相同的属性)时出现错误 能否请您发布您的索引和下拉视图文件以及对话控制器索引操作的代码? Rails 范围可让您构建查询并在使用它们时执行。要执行,您可以调用 .all、.count、.each 或 .first。您是否尝试在控制器操作中将.all 添加到查询的末尾? 是的,我也是,to_a。事情是预加载似乎比它应该采取更多的查询。 从日志中,您能否粘贴您想要消除/认为不需要的 17 个查询?如果它们几乎相同,您可以只粘贴其中一个查询。 【参考方案1】:

* 调试

在调试可能导致此问题的所有可能因素之后。 我试图在我的development.rb 中将config.cache_classes 设置为true。这成功删除了所有额外的查询。

我的结论是(默认情况下)当没有缓存类时,不会为任何模型加载架构。换句话说,当 config.cache_classes 设置为 false 时,每个模型的架构都会作为单独的查询为每个请求加载。

这是一个类似的问题column_definitions method being called before and after every single SQL statement on PostgreSQL。

* 结论

cache_classes 应在 development 环境中设置为 false。 忽略来自 postgresql 连接适配器的额外内部查询 为每个模型加载架构,因为它不会影响您的 生产环境(生产有 config.cache_classes 设置为 true)。

【讨论】:

config.cache_clases 与查询无关。我提供的是正确的解决方案,不应导致查询变为 18。 @Arkhitech 您的解决方案只不过是一个额外的查询。尽管我知道它是错误的解决方案,但我已经对其进行了测试。我正在使用 postgresql,检查类似的问题。 hmmm...我没有手动测试这个的环境设置,但是 config.cache_classes 不可能对查询产生任何影响。 加载类(模型)架构在 mini-profiler 中显示为查询 问题已报告给迷你分析器:link1 和 link2【参考方案2】:

你可以试试子弹 gem,它会告诉你查询中是否有任何 N+1 问题。如果没有 N+1 问题,那么你应该尝试实现片段缓存。

【讨论】:

这怎么可能只发生在服务器上(在控制台中一切正常)? 试试 rails-footnotes gem,它将显示视图的所有查询以及带有行号的控制器。 github.com/josevalim/rails-footnotes【参考方案3】:

简单的问题。您是否尝试在方法调用的末尾放置一个 .to_a ? 喜欢@messages.to_a

【讨论】:

这真的是拥抱我没有尝试过to_a。现在17 移动到了控制器,5 消失了,如果我想出如何消除无用的,我会给你赏金。感谢您的提示。 很高兴我能帮助你,最后!让我们知道!【参考方案4】:

我会检查日志以查看这 17 个查询是什么,或者单击 17 sql 链接可能会显示这些查询。从那里,您可能会看到您忘记了 includes 导致 N+1 问题的表。

编辑:

如this site 的“延迟加载”部分所述,您可以在控制器操作中将.all 添加到查询的末尾以触发其执行,并防止查询在您的视图中延迟执行。正如我在评论中提到的,Rails 范围允许您构建查询并在使用它们时执行。要执行,您可以调用 .all、.count、.each 或 .first。在 Rails 4 中,您可以使用.load 在控制器中执行查询。

【讨论】:

在 Rails 4+ 中,.all 将构建一个 ActiveRecord::Relation 对象并且不会立即触发查询。相反,您需要在查询中调用 to_a true,但是对于 Rails 4,我会使用 all.loadto_a 上返回 ActiveRecordRelation。 太棒了,不知道。谢谢!【参考方案5】:

这可能是所谓的“N + 1”问题,它是由于延迟加载而发生的。如果没有应用程序日志,我不能肯定地说。您可以按照here 的描述使用急切加载。

【讨论】:

我已经在使用 preloadeager_load 在控制器中收集我的数据。【参考方案6】:

您的查询表述不当。您应该使用包含或连接。

将您的查询分为以下两部分:

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)

【讨论】:

好吧,我尊重你的方法,但这增加了 1 个查询。我无法完全理解控制台和服务器之间的区别。 我没有说控制台和服务器。基本上,不能使用包含的连接是 ActiveRecords 的一个限制。我为您提供的是最好和最正确的解决方案。您只需增加一个查询,而不是原来的 17 个,这就是您开发这些解决方案的方式。 好吧,你的方法使它们变成了 18 个。如果你查看控制台输出,这不是限制。

以上是关于MiniProfiler 列出的额外查询的主要内容,如果未能解决你的问题,请参考以下文章

MiniProfiler使用点滴记录-2017年6月23日11:08:23

将 Miniprofiler 集成到控制台 WCF 自主机

mysql 查询优化 ~ 善用profie利器

MiniProfiler(MiniProfiler.EF6监控调试MVC5和EF6的性能)

MiniProfiler 与 EntityFramework 6 代码优先

Miniprofiler 监控ef执行详解