Active Storage结合STI单表继承不同附件场景下N+1查询问题及解决方案
Rails STI模型结合ActiveStorage多附件场景N+1查询问题与解决方案
问题背景
开发支持块编辑器的文档创建方案时使用ActiveStorage,在STI与ActiveStorage结合、各子类附件名称不同的场景下遇到了N+1查询问题。
现有模型定义
ContentBlock为多态关联contentable的基础模型:
class ContentBlock < ApplicationRecord belongs_to :contentable, polymorphic: true end
Document模型与多个不同类型的ContentBlock关联:
class Document has_many :content_blocks, -> { order(position: :asc) }, as: :contentable, dependent: :destroy end
示例ContentBlock子类ImageBlock、ProductBlock各自绑定了不同名称的附件:
class ImageBlock < ContentBlock has_one_attached :image end class ProductBlock < ContentBlock has_one_attached :product_image end
问题描述
通过includes(:content_blocks)可以方便查询Document及其关联的所有ContentBlock记录,但获取各ContentBlock子类关联的ActiveStorage::Attachment记录时存在限制:
with_attached_image仅适用于ImageBlock,不适用于ProductBlockwith_attached_product_image仅适用于ProductBlock,不适用于ImageBlock
无法一次性预加载所有关联的ActiveStorage::Attachment与ActiveStorage::Blob记录,无法避免N+1查询。
解决方案
该场景下没有原生的直接解法,最简单且性能最优的方案是将所有ContentBlock子类可能用到的关联关系统一定义在父类ContentBlock中,再声明全局默认作用域实现自动预加载:
class ContentBlock # 关联统一在此处定义,才能正常使用includes预加载能力 # 对应ImageBlock的附件关联 has_one_attached :image # 对应SignBlock的关联定义 has_many :signatures, as: :signable, dependent: :destroy DEFAULT_SCOPE = [ image_attachment: :blob, signatures: Signature::DEFAULT_SCOPE ].freeze default_scope do includes(DEFAULT_SCOPE) end end
方案优劣说明
- 缺点:部分场景存在数据过取,比如未绑定image附件的TextBlock,也会执行与active_storage_attachments表的关联查询
- 优点:彻底解决N+1查询问题,数据过取的性能损耗在大多数业务场景下完全可接受
内容的提问来源于stack exchange,提问作者Jens
相关产品推荐
相关产品推荐

