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

Ruby on Rails百万级数据批量增改如何降低数据库与服务器负载?

1000万条数据批量Upsert的最优方案分析

先给结论:别用逐条处理,直接上数据库原生批量Upsert

不管你之前给出的两段代码是啥,先排除掉循环每条数据调用find_or_create_by或单独更新的方案——1000万条数据这么玩,数据库和服务器直接扛不住,光是千万级的SQL请求就能把数据库打崩。

常见方案的优劣对比

方案1:逐条查改(绝对别用)

循环每条数据,先查有没有对应fk的记录,没有就创建,有就更新。核心问题:

  • 每条数据至少发1次查询,更新还要再加1次,1000万条就是2000万次数据库请求,负载直接拉满
  • 服务器要处理大量循环和网络请求,内存、CPU直接飙到顶,大概率会出现内存溢出

方案2:先批量查现有数据再分组处理

先一次性(或分批)查出数据库里已有的fk列表,然后把待处理数据分成“要创建”和“要更新”两组,再分别批量创建和更新。这种比逐条强,但仍有缺点:

  • 要额外占用内存存储分组数据,1000万条数据的分组逻辑对服务器内存压力不小
  • 还是要发至少两次批量请求(查+创建+更新),效率不如原生Upsert

最优实现:用Rails的upsert_all分批次处理

Rails 6.0之后支持的upsert_all直接调用数据库的原生Upsert能力(PostgreSQL的ON CONFLICT,MySQL的ON DUPLICATE KEY UPDATE),所有操作在数据库端批量完成,是最低负载的方案。

代码示例

# 先把get_data返回的数组转成符合数据库字段的哈希结构
processed_data = get_data.map do |item|
  { fk: item[:fk], name: item[:name], detail: item[:detail] }
end

# 分批次处理,避免一次性加载1000万条数据到内存
batch_size = 1000 # 可根据数据库性能调整,比如PostgreSQL可以调到5000-10000
processed_data.each_slice(batch_size) do |batch|
  YourModel.upsert_all(
    batch,
    unique_by: :fk, # 指定唯一索引字段,用来判断冲突
    update_only: [:name, :detail] # 冲突时只更新这两个字段,避免覆盖其他字段
  )
end

为啥这方案最优?

  • 数据库请求极少:1000万条按1000条一批,只需要1万次请求,和千万级请求比差了两个数量级
  • 数据库原生高效处理:Upsert是数据库层面的原子操作,比应用层循环快N倍
  • 内存可控:分批次加载数据,不会一次性把1000万条数据塞进内存
  • 原子性保障:每一批的操作都是原子的,不会出现部分成功部分失败的情况(除非主动关掉事务)

额外优化小技巧

  1. 确认fk字段是唯一索引——这是原生Upsert能工作的前提,你已经提到了,这点很重要
  2. 如果不需要跨批次的事务,可以加transaction: false参数,减少事务开销
  3. 临时调大数据库的内存配置,比如PostgreSQL的work_mem,MySQL的innodb_buffer_pool_size,提升批量操作速度
  4. 把这个操作放到Sidekiq之类的异步任务里跑,别阻塞Web进程
  5. 运行时盯着数据库的CPU、IO和连接数,根据实际情况调整批次大小

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 21:03:34