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
相关产品推荐
相关产品推荐

