如何高效遍历数据库中的大量记录并生成新列表
效率问题根源
- 原代码存在严重的N+1查询问题:每遍历1条Document记录,就会产生4次对primary_files表的查询,5000条记录就会产生20000+次数据库查询,开销极大
Document.all.reverse会一次性把所有Document记录加载到内存,数据量再增长很容易出现内存溢出- 同一关联表的聚合计算重复执行,没有做合并查询
优化方案
最优方案:数据库层面一次性完成聚合查询
直接通过分组聚合把所有计算逻辑放到数据库执行,全程只触发1次SQL查询,5000条数据基本可以毫秒级返回结果,优化后代码如下:
# 1. 关联查询+分组聚合,一次拿到所有需要的计算结果,直接在数据库层完成倒序 document_aggs = Document.left_joins(:primary_files) .group(:id, :document_name, :created_at) .select( :id, :document_name, :created_at, "COUNT(primary_files.id) AS records_count", "SUM(primary_files.amount_credit) AS total_collections", "SUM(CASE WHEN primary_files.status = true THEN primary_files.amount_credit ELSE 0 END) AS reconciled_sum", "SUM(CASE WHEN primary_files.status = false THEN primary_files.amount_credit ELSE 0 END) AS unreconciled_sum" ) .order(id: :desc) # 2. 直接遍历预计算结果生成目标列表,无额外数据库查询 files = document_aggs.map do |doc| { "id" => doc.id, "document_name" => doc.document_name, "records_count" => doc.records_count, "total_collections" => doc.total_collections || 0, "reconciled_sum" => doc.reconciled_sum || 0, "unreconciled_sum" => doc.unreconciled_sum || 0, "reconciliation_date" => doc.created_at } end
补充性能优化建议
- 可以给
primary_files表添加联合索引add_index :primary_files, [:document_id, :status, :amount_credit],进一步加快聚合查询的速度 - 如果后续数据量涨到10万+,可以用
find_each分批处理避免内存溢出,5000条场景下不需要额外处理
内容的提问来源于stack exchange,提问作者Philip Mutua
相关产品推荐
相关产品推荐

