Rails优化:将AR关联的多次map调用转为单条SQL查询?
优化关联查询为单条SQL的解决方案
当然可以优化成单条SQL查询,你现在的写法会触发N+1查询问题:先查Bookmark列表,然后每个Bookmark单独查Collection(N次),接着每个Collection单独查User(又N次),总共会执行 1 + 2N 条SQL,数据量越大性能越差。下面针对你的关联场景给出几种单条SQL的实现方式:
方式一:从User模型反向关联查询(推荐)
因为你最终要获取的是User对象,直接从User出发,通过嵌套关联链关联到Bookmark,再加上条件过滤,这样只需要一条SQL:
# 假设你的Bookmark ID数组是 bookmark_ids User.joins(collections: :bookmarks).where(bookmarks: { id: bookmark_ids }).distinct
代码解释:
joins(collections: :bookmarks):链式关联User→Collection→Bookmark,利用Active Record的嵌套join语法,自动生成JOIN语句关联三张表where(bookmarks: { id: bookmark_ids }):指定Bookmark的ID过滤条件,Active Record会自动帮你处理表名前缀distinct:如果同一个User对应多个符合条件的Bookmark,这个方法会去重,返回唯一的User集合;如果需要保留和原map一致的重复User(比如一个User对应多个Bookmark时重复出现),可以去掉distinct
方式二:从Bookmark模型出发关联查询
如果你习惯从Bookmark开始查询,也可以通过JOIN直接关联到User,然后获取对应的User对象:
Bookmark.joins(collection: :user).where(id: bookmark_ids).select('users.*').map { |bm| User.new(bm.attributes.slice(*User.attribute_names)) }
不过这种方式需要手动将查询结果转换为User对象,不如方式一简洁,所以更推荐方式一。
为什么之前的has_many经验可以复用?
其实belongs_to和has_many的JOIN逻辑是互通的:
- 对于
has_many关联,你用joins(:关联名);对于belongs_to关联,同样可以用joins(:关联名)来建立JOIN - 嵌套关联不管是has_many还是belongs_to,都可以用
joins(外层关联: 内层关联)的语法来链式关联
最后再对比下性能:优化后单条SQL只需要一次数据库请求,相比原来的多次请求,在数据量较大时性能提升非常明显。
内容的提问来源于stack exchange,提问作者L457
相关产品推荐
相关产品推荐

