Rails: ActiveRecord 对同一模型多次执行无意义的查询

Rails: ActiveRecord perform pointless query multiple times over the same model

在我的应用程序中,我稍微覆盖了 current_user 设计方法。这个想法是,如果存在某些 cookie,则方法通过该 cookie 中的 id 和该组织的 returns 所有者而不是普通用户来检查组织:

  def current_user
    user = warden.authenticate(scope: :user)
    return nil if user.nil?

    if user.admin? && cookies.key?('mock_admin_login')
      organization = Organization.includes(:creator).find(cookies.encrypted[:mock_admin_login])
      return organization.creator
    end
    user
  end 

一切正常,但当我查看我的控制台时,我注意到组织查询被执行了多次:

CACHE Organization Load (0.5ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.9ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.7ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.3ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.4ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (2.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (4.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.4ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (42.8ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (4.5ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = LIMIT [["id", 9], ["LIMIT", 1]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user'

虽然看起来没什么大不了的,但每次调用 current_user 方法时,服务器都会额外花费 30-40 毫秒来执行此操作。为什么这个查询被调用了这么多次而不是一次,我该如何解决?

您需要记住结果,这样它就不会在您每次调用时都重新计算 current_user

如果您查看 devise 生成的帮助程序,您会发现它就是这样做的:

def current_#{mapping}
  @current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end

如果您想修复现有方法,您需要确保记住数据库调用:

def current_user
  @current_user ||= warden.authenticate(scope: :#{mapping})
  if @current_user&.admin? && cookies.key?('mock_admin_login')
    @current_org || = Organization.includes(:creator)
                                    .find(cookies.encrypted[:mock_admin_login])
    @current_user = @current_org.creator
  end
  @current_user
end 

但您确实应该将此实现为 a custom Warden strategy instead