更新大量记录 -​​ 性能优化

时间:2011-11-16 01:44:59

标签: mysql ruby-on-rails ruby performance optimization

我有一个棒球工具,允许用户分析玩家的历史击球统计数据。例如,在夜间条件下,A-Rod在过去7天内有多少次点击?我想扩展时间范围,以便用户可以将玩家的击球统计数据分析到365天。但是,这样做需要一些严格的性能优化。以下是我目前的模型集:

class AtBat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :pitcher
  belongs_to :weather_condition

  ### DATA MODEL ###
  # id
  # batter_id
  # pitcher_id
  # weather_condition_id
  # hit (boolean)
  ##################
end

class BattingStat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :recordable, :polymorphic => true # e.g., Batter, Pitcher, WeatherCondition

  ### DATA MODEL ###
  # id
  # batter_id
  # recordable_id
  # recordable_type
  # hits7
  # outs7
  # at_bats7
  # batting_avg7
  # ...
  # hits365
  # outs365
  # at_bats365
  # batting_avg365
  ##################
end

class Batter < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class Pitcher < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class WeatherCondition < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

为了让我的问题保持合理的长度,让我讲述我正在做什么来更新batting_stats表而不是复制一堆代码。让我们从7天开始。

  1. 检索过去7天内的所有at_bat记录。
  2. 迭代每个at_bat记录......
  3. 给定一个at_bat记录,抓住相关的batter和相关的weather_condition,找到正确的batting_stat记录(BattingStat.find_or_create_by_batter_and_recordable(batter,weather_condition),然后更新batting_stat记录。
  4. 对击球手和投手(可录音)重复步骤3.
  5. 步骤1-4也重复其他时间段--15天,30天等

    现在我想象如果我将时间段从可管理的7/15/30扩展到7/15/30/45/60/90 /,那么每天运行脚本来进行这些更新是多么费力。三百六十五分之一百八十○。

    所以我的问题是你如何才能让它以最高水平的性能运行?

4 个答案:

答案 0 :(得分:3)

AR并不是真正意味着像这样进行批量处理。您可能最好通过放入SQL并执行INSERT FROM SELECT(或者使用为您执行此操作的gem)来完成批量更新。

答案 1 :(得分:1)

您基本上需要以这样一种方式存储数据,即您可以在最后一天丢失数据并将其替换为新的第一天,以免您不必重新计算总数。

执行此操作的一种方法是存储添加的先前值并从中减去最后一天的值,然后添加新的日期值,然后除以15/30/90/365。

将366个操作变为3.现在从数据库中读取的操作比363操作慢吗?

这也可以节省您的迭代次数,因此您需要做的就是每天检查哪些天气条件需要更新。

答案 2 :(得分:0)

我们每周批量加载600,000条美国租赁数据记录存在类似问题。连续处理每条记录需要24小时。但它不一定是数据库的瓶颈 - 尽管每次插入都需要一段固定的时间,但数据库并没有被活动进行maxxed / pegged / flatlined。

我知道将文件拆分成单独的字符串记录非常简单快捷。在我们的例子中,输入文件是XML格式,我使用简单的Java StringTokenizer将文件拆分为...标签。

很快就给了我一大堆XML代码片段,其中包含我需要解析和导入的租赁属性信息。

然后我使用Java ThreadPoolExecutor / FutureTask / Callable约定来创建一个包含20个线程的池,这些线程将每个XML片段作为输入,提取相关数据并执行数据库插入。我不知道你的架构会是什么样的,但我猜是有类似的东西。

最后,通过监视不同测试条件下的数据库服务器负载,我能够调整线程池的大小以最大化记录吞吐量。我们确定了一个25的线程池。

答案 3 :(得分:0)

当我以前不得不做这种工作时,我会破坏我的SQL引用并重新思考如何进行复杂的更新。通常,您可以通过良好的查询以简短的方式进行大量更新。此外,您应该能够找到查询的直接帮助(发布您的架构并在要点中开始查询,如果它们真的很大)

我最近不得不播种一个counter_cache值,在把它作为一堆红宝石代码加载父母并计算他们的孩子之前,我给了这个查询一个镜头:

UPDATE rates r SET children_count = child_counts.my_count from (SELECT parent_id, count(*) as my_count FROM rates GROUP BY parent_id having parent_id is not null) as child_counts where child_counts.parent_id = r.id;

在几秒内更新了200k行

如果您无法在一个查询中执行此操作,并且如果是一次性操作,则可以将您的过程分为两个步骤。首先进行繁重的工作并将结果存储在新表中,然后从该表中读取并进行最终更新。最近我不得不做一些大规模的数据聚合,所有繁重的工作都需要2天的处理和计算。结果被放入一个新表中,其中包含相关的行ID和最终总数。在生产中,我只有一个快速脚本,从该新表读取并更新相关行。这也允许我停止并从我离开的地方重新启动,并在prod更新之前预先检查结果。此外,它使prod更新非常快。

在这样做的同时,我还了解到,如果可以的话,批量完成工作非常重要,并且尽可能经常/安全地提交交易,这样您就不会长时间地处理大型交易。