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

