【问题标题】:Should I make calculations using Ruby, or should I make them using MySQL?我应该使用 Ruby 进行计算,还是应该使用 MySQL 进行计算?
【发布时间】:2019-07-26 15:33:32
【问题描述】:

我正在为使用 Ruby on Rails 进行的聊天构建一个报告系统,但收到一些 cmets 告诉我我的方法效率低下。

以下是我的报告工作方式的一个小示例:

我有一个处理程序,每个月都会调用一个报告邮件程序,如下所示:

ReportMailer.monthly_report(user).deliver_later

这是邮件的外观:

class ReportMailer < ApplicationMailer
  default from: ENV["DEFAULT_MAILER_FROM"],
          template_path: 'mailers/report_mailer'

  def monthly_report(agent)
    @agent = agent
    @organization = agent.organization
    @report = Report.new @organization
    mail(to: agent.email, subject: @report.email_subject)
  end
end

我正在尝试使用“普通”Ruby 类计算数据:

module Reports
  class Component < Report

    def initialize(subject)
      @component = subject
      @cache = {}
    end

    attr_reader :component

    # DELEGATIONS
    # -----------------------

    delegate :chat_messages, to: :component

    def response_count
      count = 0
      explore_msgs { |msg, next_msg| count += 1 if response? msg, next_msg }
      return count
    end

    def response_time
      time = 0
      explore_msgs { |msg, next_msg| time += time_difference msg, next_msg if response? msg, next_msg }
      return time.to_i.seconds
    end

    def avg_response_time
      @cache[__method__] ||= (response_time / response_count if response_count > 0)
    end

    private

    def response?(msg, next_msg)
      next_msg&.user_type == 'Agent' && msg.user_type == 'User' && msg.conversation_id == next_msg.conversation_id && time_difference(msg, next_msg).seconds < 8.hours
    end

    def time_difference(msg, next_msg)
      (next_msg.created_at - msg.created_at).abs
    end

    def explore_msgs
      chat_messages.each_with_index do |msg, i| 
        next_msg = chat_messages[i+1]
        yield msg, next_msg
      end
    end

  end
end

我关心的是提高性能。我在负责进行计算的类中实现了一个简单的缓存系统,这极大地提高了系统效率,但是,我担心在 Ruby 中进行这些计算可能会产生瓶颈,或者它可能不是一个可扩展的解决方案。

【问题讨论】:

  • 在 Web 开发方面有大约 10 年的 XP 经验,我得出的结论是,对于(几乎)任何操作(分组、排序、计数、计算、聚合、搜索),数据库(几乎)总是更好, ETC)。您可能希望将计算保存在 SQL 视图(或物化视图)中,或者将其保存在您的 Rails 代码中。
  • 这里要注意一点。由于您正在做的事情,您冒着构建相关查询的风险,这可能是一个缓慢的数据库操作。因此,如果您想获得比 ruby​​ 更快的数据库处理速度,请务必避免相关查询。
  • 正如@MrYoshiji 所说,在数据库中做尽可能多的事情,而不是检索原始数据并使用Ruby 来处理它。 DBM 包含针对与 DB 相关的事情的非常优化的代码,所以让它去做,因为它更接近数据。移动数据会影响 Rails 机器上的网络、驱动器和 CPU,这些机器确实需要专注于运行 Rails 和处理请求。此外,听起来您正在将 Rails 与 Ruby 区分开来。 Rails 是 Ruby 代码,Ruby 处理它,所以除非你重写 Rails 代码做得更好的轮子,否则没有区别。

标签: mysql ruby-on-rails performance greatest-n-per-group


【解决方案1】:

它可能会更快。我看到的问题是您正在查看一条记录和以下记录。那么如何让数据库比较这两条记录呢?

在直接 SQL 中,我会将表连接到自身,按表的第一个实例分组,并在表的第二个实例上执行 min(created_at)。

使用我们的companies 表,SQL 如下所示:

select rc1.id, rc1.created_at, min(rc2.created_at)
from companies rc1 inner join companies rc2 on rc1.created_at < rc2.created_at
group by rc1.id

您可以将差异添加到选择中。

如果created_at字段没有被索引并且表中的记录数很大,这肯定会很慢。

您可以将Agent 和User 的测试添加到have 子句中。

查询很棘手,数据库可能无法快速执行此操作。如果您尝试让 ActiveRecord 为您构建查询,这也会很棘手。

但是,我认为您在代码中尝试执行的所有操作都可以由数据库以这种方式完成。

您的查询可能如下所示:

select chat_messages.*,
       min(next_msg.created_at) as next_created_at,
       next_msg.created_at - chat_messages.created_at as created_at_diff
from chat_messages inner join chat_messages next_msg
  on chat_messages.created_at < chat_messages.created_at
     and chat_messages.user_type = 'User'
group by chat_messages.id
having next_msg.user_type = 'Agent'
   and TIMESTAMPDIFF(HOUR, min(next_msg.created_at), chat_messages.created_at) < 8

【讨论】:

  • 在字段user_type, created_at上创建索引。
  • 更正,你应该有两个索引。一个在user_type,一个在created_at。
猜你喜欢
  • 1970-01-01
  • 2013-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-16
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多