ruby-on-rails - rails : database queries too slow for scope that depends on join with another table

标签 ruby-on-rails database postgresql activerecord ruby-on-rails-4

我正在构建一个 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/

相关文章:

ruby-on-rails - 向现有模型添加新的 has_many 关系

database - 支持 NUnit 中的数据库测试吗?

mysql - 不停机优化MySql 5.7表

postgresql - 鹡鸰断言错误 "Unmatched tags: expected img, got p"

sql - SQL中如何获取某个用户每次对话的最新消息?

ruby-on-rails - 搜索结果未显示

ruby-on-rails - Ruby on Rails - 如何正确映射和链接到新 Controller ?

ruby-on-rails - 非常缓慢的迁移

php - PHP如何从两个不同的表中获取信息

postgresql - 我应该使用哪个 JDBC 驱动程序版本来访问 PostgreSQL 9.5?