ActiveRecord find_by在数据库有有效记录时返回nil问题求助
Action Cable 中
find_by_name 偶尔返回 nil 的问题排查与解决方案 我们生产环境使用Action Cable时,通道中的article = Article.find_by_name("cat")代码偶尔失效——数据库明明存在有效记录,却返回nil。推测是竞态条件导致,但生产日志和MySQL日志无异常。
使用版本
- rails: 5.2.3
- ActiveRecord: 5.2.3
- mysql2: 0.5.2
- actioncable: 5.2.3
database.yml 配置
default: &default adapter: mysql2 encoding: utf8 reconnect: true pool: 5 host: localhost username: root password: root development: <<: *default database: localdb test: <<: *default database: xipk_test production: <<: *default host: <%= ENV['Host'] %> database: <%= ENV['database'] %> username: <%= ENV['username'] %> password: <%= ENV['password'] %>
Article 通道代码
class ArticleChannel < ApplicationCable::Channel def subscribed stream_from "article_#{uuid}_channel" name = params["article[name]"] article = Article.find_by_name(name) if !article.nil? response_code = 404 else response_code = 200 end ActionCable.server.broadcast "article_#{uuid}_channel", { article_response: article_response, response_code: "#{response_code}" } end end
Article 模型代码
class Article < ApplicationRecord validates_presence_of :name validates_uniqueness_of :name, scope: :title, :case_sensitive=> false validates_presence_of :address belongs_to :user, optional: true has_many :request_histories, :dependent => :destroy default_scope { order('updated_at DESC') } before_create :generate_guid, unless: :guid? after_save :update_dashboard def self.default_scope where("is_test = ? AND (test_mode = ? OR test_mode IS NULL)", false, "ABB") end def generate_guid self.guid ||= SecureRandom.uuid end def check_witl_null_value(c_val) act_val = self.send("#{c_val}") act_val if act_val!= "null" end end
排查与解决方案
1. 修复重复定义的默认作用域
模型中重复定义了default_scope,Rails会叠加执行这些条件,可能意外过滤掉符合条件的记录。合并为单一默认作用域:
class Article < ApplicationRecord # 合并默认作用域,避免条件冲突 default_scope { where("is_test = ? AND (test_mode = ? OR test_mode IS NULL)", false, "ABB").order(updated_at: :desc) } # 其他代码保持不变 end
2. 解决竞态条件下的事务隔离问题
Action Cable运行在独立线程池,MySQL默认的REPEATABLE READ隔离级别可能导致线程读取到旧数据快照。查询时强制使用READ COMMITTED隔离级别:
# 在通道查询中添加事务隔离控制 article = Article.transaction(isolation: :read_committed) do Article.find_by_name(name) end
3. 修正大小写敏感的查询逻辑
模型中唯一性约束是大小写不敏感的,但find_by_name默认大小写敏感。修改查询为不区分大小写:
# 适配MySQL的不区分大小写查询 article = Article.where("LOWER(name) = LOWER(?)", name).first
4. 确保数据库连接有效性
Action Cable线程池可能存在连接失效未重连的情况,查询前验证连接:
# 先验证数据库连接状态 ActiveRecord::Base.connection.verify! article = Article.find_by_name(name)
5. 修复通道代码的逻辑错误
通道中存在两处逻辑错误:
- 存在记录时返回404,逻辑完全颠倒
article_response变量未定义
修正后代码:
def subscribed stream_from "article_#{uuid}_channel" name = params["article[name]"] article = Article.find_by_name(name) if article.nil? response_code = 404 article_response = nil else response_code = 200 article_response = article.as_json # 按需构造响应数据 end ActionCable.server.broadcast "article_#{uuid}_channel", { article_response: article_response, response_code: "#{response_code}" } end
内容的提问来源于stack exchange,提问作者Bala
相关产品推荐
相关产品推荐

