【问题标题】:Rails: database queries too slow for scope that depends on join with another tableRails:数据库查询对于依赖与另一个表连接的范围来说太慢了
【发布时间】:2014-06-29 10:27:16
【问题描述】:

我正在构建一个 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 文件导入的。组件表有一个布尔值abridged 列,它定义了哪些组件是数据删节子集的一部分(总共 360 个组件中的大约 85 个组件)。我希望轻松访问属于这个删节子集的组成部分(400,000 个中的约 180,000 个组成部分),因此我声明了一个 Composition.abridged 命名范围,它依赖于与组件表的连接以检查 abridged相关组件的条件。

这可以正常工作,但是对于某些查询来说它非常慢。例如,如果我在我的控制器中对删节的组合数据进行分页,如下所示: 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

- 在我的开发虚拟机中平均需要大约 2000 毫秒,而对完整数据集进行等效查询大约需要 30 毫秒!

如果我删除 ORDER BY 子句,它会将其减少到 ~80 毫秒,这不是很有帮助,因为无法保证返回记录的顺序,但它确实表明我的索引可能有问题。但是,我在两个表上都尝试了单个/组合索引的所有可能组合,但没有任何改进。执行一些EXPLAIN 查询确认数据库根本没有使用索引。在考虑之后,我认为这是有道理的——数据库不能有效地利用索引,因为过滤条件在另一个表上。如果我删除了WHERE components.abridged = 't' 条件并在没有它的情况下进行连接,那么EXPLAIN 表明索引使用得很好并且查询非常快。

在寻找解决此问题的方法时,我遇到了materialized views。基本上,这解决了我的速度问题,因为它使用连接查询数据预填充了本质上是附加表的内容,因此该部分最初只需要执行一次。然而,这种方法在我的应用程序中引入了一些主要缺点 - 最重要的是它需要(据我所知)第二个模型,这反过来又需要一些变通方法以避免重复业务逻辑,正确关联,确保更改发生在原始表而不是在物化视图上尝试(不能直接更改),并且当某些内容发生更改时会刷新视图(它不会自动执行)等。如果有办法我可以只需告诉Compositions.abridged 范围在不使用额外模型的情况下切换表,那么这种方法可能是理想的。

所以我的问题是:有没有一种方法可以查询合成的删节子集,从而可以简单地使用基本范围而不会导致显着的速度损失?

我没有提到向compositions 表添加布尔列的可能性。我对这个想法持开放态度,但由于以下几个原因犹豫不决:

  • 它正在复制数据。
  • 最初需要使用正确的布尔值填充约 400,000 行(导入过程在我的 VM 上已经花费了一个多小时),然后在删节的组件发生变化时正确维护。
  • 我听说数据库可能甚至不使用abridged 列上的索引,因为它占数据集的近一半。

欢迎提出任何建议。

【问题讨论】:

  • 你试过了吗:Composition.where(component_id: Component.abrigded.pluck(:id)).order(foo_id: :asc)?我认为这应该导致子查询。而且它应该只预先订购结果而不是整个集合。

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


【解决方案1】:

您可以尝试使用子查询:

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

但是,我不完全确定如何使用 ActiveRelation 来做到这一点,尽管使用了 find_by_sql

【讨论】:

  • 在做一些适当的测试后,我确定在相同的上下文中使用时,第一个查询实际上比我的原始查询慢一些(大约 200 毫秒)。第二个在速度上没有区别。
【解决方案2】:

一个带有连接的简单查询对于 Postgres 来说不是什么大问题。我认为您的表上缺少索引是问题的原因。

您是否尝试在组件表的删节列上添加索引?我没有在您的问题中看到此信息。

还要检查您的合成表是否在 component_id 列上有索引。

您可以在新的数据库迁移中添加索引:

add_index :compositions, :component_id
add_index :components, :abridged

【讨论】:

  • 是的,我有这些索引和其他索引。引用:“我在两张表上尝试了所有可能的单/组合索引组合,但没有任何改进。”
【解决方案3】:

我最终在 Postgres 中使用 materialized view 实现了这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-03
    • 2016-05-28
    • 1970-01-01
    • 2020-11-13
    • 2021-01-23
    • 2013-06-21
    • 2020-08-08
    • 2015-03-09
    相关资源
    最近更新 更多