我正在构建一个导入模块,用于从csv文件导入大量订单。我有一个名为Order的模型,需要存储数据。
Order模型的简化版本在下面
sku
quantity
value
customer_email
order_date
status
导入数据时,必须发生两件事
目前我使用简单的csv导入代码
CSV.foreach("orders.csv") do |row|
order = Order.first_or_initialize(sku: row[0], customer_email: row[3])
order.quantity = row[1]
order.value= parse_currency(row[2])
order.order_date = parse_date(row[4])
order.status = row[5]
order.save!
end
其中parse_currency和parse_date是用于从字符串中提取值的两个函数。在日期的情况下,它只是Date.strptime的包装器。
我可以添加一个检查以查看记录是否已经存在,并且如果它已经存在则不执行任何操作并且应该节省一点时间。但我正在寻找速度更快的东西。目前导入大约100k行需要大约30分钟,空数据库。随着数据大小的增加,它会变慢。
所以我基本上在寻找一种更快的方式来导入数据。
任何帮助将不胜感激。
修改
根据这里的评论进行了一些测试后,我有一个观察和一个问题。我不确定他们是否应该去这里,或者我是否需要为这些问题打开一个新线程。所以,如果我不得不将其转移到另一个问题,请告诉我。
我使用Postgres副本运行测试以从文件导入数据,并且花了不到一分钟。我刚刚将数据导入到新表中而没有任何验证。因此导入可以更快。
Rails开销似乎来自2个地方
现在提出我的问题。如何将更新/创建逻辑移动到数据库,即如果基于sku和customer_email的订单已经存在,则需要更新记录,否则需要创建新记录。目前使用rails我使用first_or_initialize方法获取记录以防它存在并更新它,否则我创建一个新的并保存它。我如何在SQL中执行此操作。
我可以使用ActiveRecord连接执行运行原始SQL查询,但我认为这不是一种非常优雅的方式。有没有更好的方法呢?
答案 0 :(得分:2)
因为ruby 1.9 fastcsv现在是ruby核心的一部分。你不需要使用特殊的宝石。只需使用CSV
即可。
凭借100k记录,ruby需要0.018秒/记录。在我看来,你的大部分时间将在Order.first_or_initialize
内使用。这部分代码需要额外的往返数据库。初始化ActiveRecord
也需要时间。但是要确定我会建议您对代码进行基准测试。
Benchmark.bm do |x|
x.report("CSV evel") { CSV.foreach("orders.csv") {} }
x.report("Init: ") { 1.upto(100_000) {Order.first_or_initialize(sku: rand(...), customer_email: rand(...))} } # use rand query to prevent query caching
x.report('parse_currency') { 1.upto(100_000) { parse_currency(...} }
x.report('parse_date') { 1.upto(100_000) { parse_date(...} }
end
您还应该在导入期间观察内存消耗。也许垃圾收集不能经常运行或者没有清理对象。
要获得速度,您可以按照 Matt Brictson 提示并绕过ActiveRecord
。
您可以尝试使用gem activerecord-import
,也可以开始并行,例如使用fork
进行多处理或使用Thread.new
进行多线程处理。