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

Rails模型方法本地正常运行但Heroku生产环境异常

问题

我在应用中编写了一个简易方法用于设置chapter.sort字段:story与chapters为一对多关联,因是遗留代码库,需从现有created_at日期填充sort列。

以下是story.rb中的方法:

def set_chapter_order
  chapters = Chapter.where(story_id: id)
  n = 1
  chapters.order("created_at").each do |c|
    c.update(sort: n)
    c.save
    n += 1
  end
end

在本地rails c中执行以下代码时,方法完全符合预期:

s = Story.all
s.find_each(&:set_chapter_order)

测试用story的6个chapters的sort值为1到6。

但在Heroku的控制台执行完全相同的操作后,示例story的chapters的sort值出现4、4、4、4、4、5这类异常数值。以下是heroku run rails c中的示例输出:

irb(main):002:1* Story.find(1).chapters.each do |c|
irb(main):003:1*   puts  c.sort
irb(main):004:1* end
1
1
1
1
1
2

为何该方法在生产环境中运行结果异常?


原因分析与解决方案

核心原因

  • 惰性查询重复加载数据:生产环境(Heroku默认使用PostgreSQL)中,Chapter.where(story_id: id)返回的是惰性查询对象,而非直接加载数据到内存。每次循环内执行c.save后,数据库数据更新,后续循环迭代时查询对象会重新执行SQL拉取最新数据,导致部分记录被重复处理,打乱了n的递增逻辑。本地环境因数据库隔离级别、缓存机制差异,未触发该问题。
  • 冗余的保存操作:c.update(sort: n)本身已完成持久化,后续调用c.save属于重复操作,可能额外触发数据库交互或回调,加重数据加载异常。

优化方案

方案1:预加载数据到内存

将查询结果一次性加载到内存,避免惰性查询重复执行:

def set_chapter_order
  # 用.to_a将所有章节加载到内存,固定循环处理的数据集
  chapters = Chapter.where(story_id: id).order(:created_at).to_a
  n = 1
  chapters.each do |c|
    c.update!(sort: n) # 使用update!抛出异常,便于排查潜在问题
    n += 1
  end
end

方案2:数据库层面批量更新(更高效)

直接通过SQL窗口函数批量设置排序值,避免循环单条更新的性能问题和数据加载异常:

def set_chapter_order
  Chapter.connection.execute(<<~SQL)
    UPDATE chapters
    SET sort = subquery.row_num
    FROM (
      SELECT id, ROW_NUMBER() OVER (PARTITION BY story_id ORDER BY created_at) AS row_num
      FROM chapters
      WHERE story_id = #{id}
    ) AS subquery
    WHERE chapters.id = subquery.id;
  SQL
end

注意事项

  • 若Chapter模型存在必须执行的回调或验证,优先选择方案1;批量更新会跳过Active Record的回调与验证。
  • 生产环境执行数据修改前,建议先备份数据或在测试环境验证逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 19:35:19