You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails优化指南:如何高效导出1亿条记录至CSV

1亿条销售数据CSV导出优化方案

针对你当前Rake任务导出速度慢的问题,核心瓶颈在于ActiveRecord模型实例化开销和频繁IO操作,下面是几个落地性强的优化方案:


1. 跳过模型实例化,直接拉取字段值

当前代码用find_each会为每条数据创建Sale实例,1亿条数据的实例化开销极大。换成pluck直接获取需要的字段值,能砍掉90%以上的不必要开销:

sales = Sales.where(year: 2024)
BATCH_SIZE = 50_000 # 可根据服务器内存调整,建议5万-10万
header_names = %w[user_id article_id amount currency date]

File.open(filepath, 'w') do |file|
  # 先写入表头
  file.puts header_names.join(';')
  
  sales.find_in_batches(batch_size: BATCH_SIZE) do |batch|
    # 批量拉取目标字段,直接得到二维数组
    rows = batch.pluck(*header_names)
    # 转成分号分隔的字符串,一次性写入文件
    file.puts rows.map { |row| row.join(';') }.join("\n")
  end
end

如果担心find_in_batches还是会实例化少量模型,也可以直接用范围查询+pluck的方式,完全绕过模型:

min_id = Sales.where(year: 2024).minimum(:id)
max_id = Sales.where(year: 2024).maximum(:id)
current_id = min_id
BATCH_SIZE = 100_000

File.open(filepath, 'w') do |file|
  file.puts header_names.join(';')
  
  while current_id <= max_id
    rows = Sales.where(year: 2024)
                .where(id: current_id...current_id+BATCH_SIZE)
                .pluck(*header_names)
    file.puts rows.map { |row| row.join(';') }.join("\n")
    current_id += BATCH_SIZE
  end
end

2. 调整批量大小,平衡内存与IO

原代码用1万的批次太小,导致文件写入过于频繁。增大到5万-10万(根据服务器内存情况调整),能大幅减少IO次数,提升整体速度。


3. 用数据库原生导出(最快方案)

如果你的数据库有权限,直接用数据库的导出命令,完全跳过Ruby层处理,速度能提升数倍。以MySQL为例:

# 注意:需要数据库用户有FILE权限,且导出路径是数据库服务器可访问的
sql = <<~SQL
  SELECT user_id, article_id, amount, currency, date
  INTO OUTFILE '#{filepath}'
  FIELDS TERMINATED BY ';'
  LINES TERMINATED BY '\n'
  FROM sales
  WHERE year = 2024;
SQL

ActiveRecord::Base.connection.execute(sql)

这个方案的速度是最快的,但需要注意数据库服务器的文件权限,导出后如果要移到应用服务器,可能需要额外的文件传输步骤。


4. 用标准CSV库处理,避免手动拼接的坑

手动用join(';')拼接字符串,遇到字段包含分号、换行或引号时会破坏CSV格式。用Ruby标准库CSV类,既能保证格式正确,效率也不比手动拼接差:

require 'csv'

sales = Sales.where(year: 2024)
BATCH_SIZE = 50_000
header_names = %w[user_id article_id amount currency date]

CSV.open(filepath, 'w', col_sep: ';') do |csv|
  csv << header_names
  
  sales.find_in_batches(batch_size: BATCH_SIZE) do |batch|
    rows = batch.pluck(*header_names)
    rows.each { |row| csv << row }
  end
end

5. 确保数据库索引生效

检查year字段是否有索引,没有的话赶紧加:

# 生成迁移文件运行即可
add_index :sales, :year

用EXPLAIN确认查询走了索引:

puts Sales.where(year: 2024).explain

效果参考

  • 原方案:1700万条耗时23分钟
  • 优化方案1/4:预计耗时5-10分钟
  • 数据库原生导出:1700万条耗时1-2分钟

内容的提问来源于stack exchange,提问作者sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 03:27:35