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

