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

ActiveRecord:从多态关联的两个模型中高效查询User

优化多态关联下的User查询方法

嘿,我帮你搞定这个查询优化的问题!你现在的subscribers方法虽然能拿到结果,但性能问题会随着数据量变大越来越明显——毕竟它触发了一堆N+1查询,还得在内存里做flatten和uniq,实在太耗资源了。咱们换个思路,直接让数据库来做这些脏活,效率会高很多!

问题分析

你的当前实现:

def subscribers
  targets.map(&:target).map(&:users).flatten.uniq
end
  • 第一次map(&:target)会为每个Target触发一次查询,获取对应的List或Account(N+1查询问题)
  • 第二次map(&:users)又会为每个List/Account触发一次查询获取关联的User(再一次N+1)
  • 最后在内存里合并去重,数据量大时内存占用会飙升

优化方案:数据库层面合并查询

我们可以利用ActiveRecord的关联查询,把两个来源的User查询合并到一次数据库请求中,所有过滤和去重都在数据库完成。

方案1:使用Rails的or方法(简洁优雅,Rails 5+支持)

在Schedule模型里定义优化后的subscribers方法:

def subscribers
  # 从Account关联的用户
  account_users = User.joins(account: { targets: :schedule }).where(schedules: { id: id })
  # 从List关联的用户
  list_users = User.joins(lists_users: { list: { targets: :schedule } }).where(schedules: { id: id })

  # 合并两个查询并去重
  account_users.or(list_users).distinct
end

方案2:使用SQL UNION(兼容更低版本Rails)

如果你的Rails版本低于5,或者需要更灵活的SQL控制,可以用UNION来合并查询:

def subscribers
  account_users_sql = User.joins(account: { targets: :schedule }).where(schedules: { id: id }).to_sql
  list_users_sql = User.joins(lists_users: { list: { targets: :schedule } }).where(schedules: { id: id }).to_sql

  # 从合并的结果集中查询User
  User.from("(#{account_users_sql} UNION #{list_users_sql}) AS users").distinct
end

方案3:封装为可复用的Scope

如果需要在其他地方也能查询某个Schedule的订阅用户,可以把逻辑封装到User模型的Scope里:

# 在User模型中
scope :subscribed_to_schedule, ->(schedule) {
  joins(account: { targets: :schedule }).where(schedules: { id: schedule.id })
    .or(
      joins(lists_users: { list: { targets: :schedule } }).where(schedules: { id: schedule.id })
    )
    .distinct
}

# 在Schedule模型中调用
def subscribers
  User.subscribed_to_schedule(self)
end

为什么这个优化更好?

  • 减少查询次数:从原来的O(N)次查询变成1次查询,彻底解决N+1问题
  • 性能提升:数据库处理合并和去重比内存处理高效得多,尤其是数据量大时
  • 代码可读性:用ActiveRecord的关联语法,逻辑清晰,易于维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:13:10