加速给定循环的 ActiveRecord 插入
Speed up ActiveRecord insert for a given loop
设置
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 中解决这个问题,那会比那更快也有可能。
正如@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
当然,正如您所指出的,它根本没有效率,有两种方法可以解决这个问题。用一个很酷的 gem 叫 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而不是each,它是按批次查找记录的,在迭代大量记录时通常效率更高,您可以指定批次大小作为该方法的一个选项。
- 我将所有搜索结果的非持久对象建了一个数组,请注意,如果涉及的捐款很多,这会占用内存
- 结果没有 uniq 过滤器,我不知道这是否是预期的行为,但请注意,您可能保存了重复的结果。
希望这对您有用!
设置
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 中解决这个问题,那会比那更快也有可能。
正如@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
当然,正如您所指出的,它根本没有效率,有两种方法可以解决这个问题。用一个很酷的 gem 叫 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而不是each,它是按批次查找记录的,在迭代大量记录时通常效率更高,您可以指定批次大小作为该方法的一个选项。
- 我将所有搜索结果的非持久对象建了一个数组,请注意,如果涉及的捐款很多,这会占用内存
- 结果没有 uniq 过滤器,我不知道这是否是预期的行为,但请注意,您可能保存了重复的结果。
希望这对您有用!