我正在构建一个 Rails 4.1 应用程序(使用 Postgres 作为我的数据库),它有几个按以下方式设置的模型:
class Components < ActiveRecord::Base
has_many :compositions
scope :abridged, -> { where(abridged: true) }
end
class Compositions < ActiveRecord::Base
belongs_to :foo
belongs_to :component
scope :abridged, -> { joins(:component).where(components: { abridged: true }) }
# Or alternatively, { joins(:component).merge(Component.abridged) }
end
总结一下:
Component
模型由 Compositions
连接模型引用 - 每个组合都属于一个组件。这些数据是从外部 CSV 文件导入的。组件表有一个 bool 值 abridged
列,它定义了哪些组件是数据的一个缩减子集的一部分(总共 360 个组件中约有 85 个组件)。我想轻松访问属于这个精简子集的组合(400,000 中约有 180,000 个组合),因此我声明了一个 Composition.abridged
命名范围,它依赖于与组件表的连接为了检查相关组件的abridged
条件。
这可以正常工作,但是对于某些查询来说它非常慢。例如,如果我像这样在我的 Controller 中对删节的合成数据进行分页:
Composition.abridged.order(:foo_id).page(params[:page])
我得到一个像这样的 SQL 查询:
SELECT compositions.* FROM compositions INNER JOIN components
ON components.id = compositions.component_id
WHERE components.abridged = 't'
ORDER BY compositions.foo_id ASC
LIMIT 20 OFFSET 185284
- 在我的开发 VM 中平均需要大约 2000 毫秒,而对完整数据集进行等效查询需要大约 30 毫秒!
如果我删除 ORDER BY
子句,它会将它减少到 ~80ms,这不是很有帮助,因为这样就无法保证返回记录的顺序,但它确实表明也许我的索引有问题。但是,我在两个表上尝试了所有可能的单一/组合索引组合,但没有任何改进。执行一些 EXPLAIN
查询确认数据库根本没有使用索引。在考虑之后,我认为这是有道理的——数据库不能有效地利用索引,因为过滤条件在另一个表上。如果我删除 WHERE components.abridged = 't'
条件并在没有它的情况下进行连接,则 EXPLAIN
显示索引使用得很好并且查询非常好快。
在寻找解决此问题的方法时,我遇到了 materialized views .基本上这解决了我的速度问题,因为它使用连接查询数据预先填充了本质上是附加表的内容,因此该部分最初只需要执行一次。然而,这种方法在我的应用程序中引入了一些主要缺点 - 最重要的是它需要(据我所知)第二个模型,这反过来需要 hacky workarounds 以避免重复业务逻辑,正确关联,确保更改发生在原始表而不是在物化 View 上尝试(不能直接更改),并且当某些事情发生变化时刷新 View (它不会自动执行)等。如果有办法我可以只需告诉 Compositions.abridged
范围切换表而不使用额外的模型,那么这种方法可能是理想的。
所以我的问题是:是否有一种方法可以查询精简的组合子集,从而可以在不显着降低速度的情况下简单地使用基本范围?
我没有提到将 bool 列添加到 compositions
表的可能性。我对这个想法持开放态度,但由于以下几个原因而犹豫不决:
- 这是重复数据。
- ~400,000 行最初需要用正确的 bool 值填充(导入过程在我的 VM 上已经花费了一个多小时),然后在删减的组件发生变化时妥善维护。
- 我听说数据库甚至可能不会使用
abridged
列上的索引,因为它占数据集的近一半。
欢迎提出任何建议。
最佳答案
您可以尝试使用子查询:
Composition.where(component_id: Component.abrigded.pluck(:id)).order(foo_id: :asc)
因此 order
子句已经涉及到缩减的结果集,而不是整个结果集。
为了保持连接,您可能应该将 abridged = 't'
子句放入连接条件中:
SELECT compositions.* FROM compositions
INNER JOIN components ON components.abridged = 't' AND components.id = compositions.component_id
ORDER BY compositions.foo_id ASC
LIMIT 20 OFFSET 185284
但是,尽管使用了 find_by_sql
,但我并不完全确定如何使用 ActiveRelation
来做到这一点。
关于ruby-on-rails - rails : database queries too slow for scope that depends on join with another table,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24475188/