Rails/Sidekiq中存在记录却报ActiveRecord::RecordNotFound错误
报错信息
09:26:23 sidekiq.1 | 2024-07-02T13:26:22.990Z pid=45032 tid=17cw WARN: ActiveRecord::RecordNotFound: Couldn't find Quote with 'id'=1758
核心代码
class CreateOrUpdateApiJob < ApplicationJob queue_as :default # Called with quote=1758 and action=create def perform(quote, action) Quote.find(quote) if action == "create" ApiService.create() elsif action === "update" ApiService.update() end end end # 任务调用方式 CreateOrUpdateApiJob.perform_later(quote, "create")
环境信息
- MacBook Pro 2015(2.8GHz四核Intel Core i7、16GB 1600MHz DDR3、1TB SSD、AMD Radeon R9 M370X 2GB),系统macOS Monterey 12.7.5
- Ruby 3.1.4(rbenv安装)
- PostgreSQL v15
- Gems:Rails 6.1.7.4、Sidekiq 7.1.2、ActiveRecord 6.1.7.4、redis 4.8.1、redis-client 0.15.0、pg 1.5.4
启动命令(foreman)
bundle exec rails server -p 3000 -e production redis-server # 生产环境省略,使用独立Redis bundle exec sidekiq -c 2 -q default -q mailers
已尝试的方法
- 传递完整记录
- 将记录转为字符串解析
- 重新安装Redis
排查与解决方案
1. 检查数据库环境一致性
Sidekiq默认使用development环境,而你的Rails服务器启动时指定了-e production,这会导致两者连接不同的数据库,出现“本地有记录但Sidekiq查不到”的情况。
解决:启动Sidekiq时显式指定生产环境:
bundle exec sidekiq -c 2 -q default -q mailers -e production
可以在Job中添加日志验证数据库配置,确保和Rails服务器一致:
def perform(quote, action) Rails.logger.info "Sidekiq DB config: #{ActiveRecord::Base.connection_config}" # ... 原有代码 end
2. 避免事务未提交导致的查询问题
如果调用perform_later时,Quote记录还在未提交的事务中,Sidekiq会在记录写入数据库前执行Job,自然找不到数据。
解决:使用after_commit回调触发Job,确保记录已持久化到数据库:
# app/models/quote.rb class Quote < ApplicationRecord after_commit :trigger_api_job, on: :create private def trigger_api_job CreateOrUpdateApiJob.perform_later(self.id, "create") end end
3. 验证参数类型与实际值
虽然注释说明传入的是1758,但实际参数可能是字符串或其他异常类型,导致find方法匹配失败。
解决:在Job中添加参数日志,并强制转换为整数:
def perform(quote, action) Rails.logger.info "Received quote param: #{quote.inspect}, type: #{quote.class}" quote_id = quote.to_i quote_record = Quote.find_by_id(quote_id) if quote_record.nil? Rails.logger.error "Quote with id #{quote_id} not found. Existing IDs: #{Quote.pluck(:id).inspect}" return end # ... 原有逻辑 end
4. 检查是否有进程删除了记录
确认是否有其他后台任务、定时任务或手动操作删除了id=1758的Quote记录。可以通过PostgreSQL的pg_stat_activity查看当前活跃进程,或检查数据库审计日志(若开启)。
5. 验证数据库隔离级别
PostgreSQL默认隔离级别为READ COMMITTED,确保Sidekiq的查询能读取到已提交的记录。结合第2点的after_commit方案即可解决这类隔离级别导致的不可见问题。
内容的提问来源于stack exchange,提问作者Evan Wilson

