解决导入大XLSX文件到数据库时的NoMemoryError内存分配失败问题
解决Ruby处理大XLSX文件时的NoMemoryError问题
这个内存溢出的坑我之前踩过好几次,本质是你用的Roo::Spreadsheet.parse会一次性把整个工作表的所有数据加载到内存里,遇到超大文件直接就撑爆了。咱们一步步来解决:
首选方案:逐行处理+批量插入(最稳妥)
不要一次性把所有数据读进内存,改成逐行读取、处理,再批量插入数据库,这样内存占用会保持在很低的水平。修改你的脚本如下:
file = 'lib/assets/sicepatfull.xlsx' xlsx = Roo::Spreadsheet.open(file) sheet = xlsx.sheet(0) # 先处理表头,转成符合数据库字段的格式,同时排除:id attrs = sheet.row(1).map { |k| k.parameterize.underscore.to_sym }.reject { |k| k == :id } batch_size = 100 # 每100条数据批量插入一次,可根据你的服务器配置调整 records = [] # 从第2行开始遍历(第1行是表头) (2..sheet.last_row).each do |row_num| row_data = sheet.row(row_num) # 把行数据和表头对应成哈希 record_attrs = attrs.zip(row_data).to_h # 跳过空行(可选,根据你的数据情况决定是否保留) next if record_attrs.values.all?(&:nil?) records << record_attrs # 达到批量大小就插入数据库 if records.size >= batch_size # insert_all!是Rails 6+的方法,速度快但不触发模型回调/验证;如果需要回调用Courier.create!(records) Courier.insert_all!(records) records = [] # 清空数组准备下一批 end end # 插入最后一批剩余的记录 Courier.insert_all!(records) unless records.empty?
这个方案的好处是:
- 每次只加载一行数据到内存,处理完就释放,不会积累大量数据
- 批量插入减少了数据库交互次数,导入速度会快很多
备选方案:使用流式解析库(适合极大型文件)
如果你的XLSX文件大到离谱,Roo的逐行处理还是有点压力,可以试试Creek这个流式解析库——它不会把整个文件加载到内存,而是像读流一样逐行读取,内存占用极低。
首先安装gem:
gem install creek
然后修改脚本:
require 'creek' file = 'lib/assets/sicepatfull.xlsx' creek = Creek::Book.new(file) sheet = creek.sheets.first attrs = sheet.rows.first.map { |k| k.parameterize.underscore.to_sym }.reject { |k| k == :id } batch_size = 100 records = [] # 跳过表头,遍历数据行 sheet.rows.drop(1).each do |row| row_data = row.values record_attrs = attrs.zip(row_data).to_h next if record_attrs.values.all?(&:nil?) records << record_attrs if records.size >= batch_size Courier.insert_all!(records) records = [] end end Courier.insert_all!(records) unless records.empty?
应急方案:给Ruby分配更多内存
如果你的服务器有足够的物理内存,也可以临时给Ruby进程分配更多内存来应急,但这只是治标不治本的方法,不推荐作为长期解决方案。
运行脚本前先设置环境变量:
export RUBY_HEAP_MIN_SLOTS=1000000 export RUBY_HEAP_SLOTS_INCREMENT=1000000 export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1 export RUBY_GC_MALLOC_LIMIT=1000000000 export RUBY_HEAP_FREE_MIN=500000
或者直接在运行脚本时指定:
ruby -e "ENV['RUBY_HEAP_MIN_SLOTS']='1000000'; ENV['RUBY_GC_MALLOC_LIMIT']='1000000000'; require './your_import_script.rb'"
内容的提问来源于stack exchange,提问作者M Andi
相关产品推荐
相关产品推荐

