加快ActiveRecord插入给定循环的速度

时间:2018-07-21 18:18:35

标签: sql ruby-on-rails postgresql activerecord ruby-on-rails-5.2

设置

rails new xyz
cd xyz
rails g scaffold Donor name
rails g scaffold Recipient name
rails g scaffold Donation amount:integer donor:references recipient:references
rails g scaffold Search query
rails g model SearchResult search:references donation:references

Rails 5.2,Ruby 2.5.1和Postgresql。

问题

我们正在谈论的是一个具有数百万个条目的大数据集,并且希望优化下面的代码,该代码在SearchResult中创建数万个条目。插入需要10秒钟以上。有没有一种方法可以优化以下代码,使其更快?

search = Search.new(query: "Smith")    
Donation.joins(:donor).
         where("donors.name like ?", "%#{search.query}%").each do |donation|
  search.search_results.build(donation: donation)
end
Donation.joins(:recipient).
         where("recipients.name like ?", "%#{search.query}%").each do |donation|
  search.search_results.build(donation: donation)
end
search.save

我不是在Rails中使用RAW SQL的忠实拥护者,但是如果有一种方法可以在纯SQL中解决该问题,那会更快,那也可能。

1 个答案:

答案 0 :(得分:1)

正如@matthewd所指出的那样,建立关联记录并保存父级实际上是可行的 您建议的代码可能存在问题。实际上,活动记录的构建方法不会像您期望的那样持久保存搜索结果:http://guides.rubyonrails.org/association_basics.html#methods-added-by-has-many-collection-build-attributes

一种正确的持久存储方式是:

search = Search.new(query: "Smith")    
Donation.joins(:donor).
         where("donors.name like ?", "%#{search.query}%").each do |donation|
  search.search_results.create(donation: donation)
end
Donation.joins(:recipient).
         where("recipients.name like ?", "%#{search.query}%").each do |donation|
  search.search_results.create(donation: donation)
end
search.safe

当然,正如您所指出的那样,它根本没有效率,有两种解决方法。或手工制作一个名为https://github.com/zdennis/activerecord-import的炫酷宝石

亲手

这不是推荐的方法,但是我在这里提供了一些信息。 这是您可以使用的SQL查询:

query = <<-SQL
INSERT INTO search_results (search_id, donation_id)
SELECT :search_id, id
FROM donations
INNER JOIN donor AS donor.id = donation.donor_id
WHERE donors.name LIKE :query
SQL

您可以使用ActiveRecord::Base.connection.execute方法来启动它,但这也意味着您需要自己清理查询。我可以走这条路,但让我们继续研究另一种我认为更安全,更易于维护的解决方案。

具有有效的记录导入功能

https://github.com/zdennis/activerecord-import

您可以使用此代码

search = Search.create(query: 'Smith')
results = Donation.joins(:donor)
                  .where('donors.name like ?', "%#{search.query}%")
                  .find_each.map do |donation|
  search.search_results.new(donation: donation)
end
results += Donation.joins(:recipient)
                   .where('recipients.name like ?', "%#{search.query}%")
                   .find_each.map do |donation|
  search.search_results.new(donation: donation)
end
SearchResult.import results

注意一些重要的事情:

  • 我在一开始就使用create,以便持久保存搜索并正确引用搜索结果
  • 我使用find_each而不是每个都按批次查找记录,并且在迭代大量记录时通常效率更高,您可以指定批量大小作为方法的选项。
  • 我用所有搜索结果的非持久对象构建了一个数组,请注意,如果有很多捐赠,这将占用内存
  • 结果上没有uniq过滤器,我不知道这是否是预期的行为,但请注意,您可能正在保存重复的结果。

希望这会很有用!