优化查询以从不同的表中获取唯一(用户)记录

Posted

技术标签:

【中文标题】优化查询以从不同的表中获取唯一(用户)记录【英文标题】:Optimize query to get unique (user) records from different tables 【发布时间】:2013-02-12 11:56:32 【问题描述】:

我已经让这个查询完美运行,但问题是当我的 4 个表变得太大时,它变得很慢。

如何优化?

SELECT 
    all_records.user_id,
    users.NAME,
    users.IMAGE
FROM (
    SELECT user_id FROM comments
    WHERE commentable_id   = #object.id
      AND commentable_type = '#object.class.to_s'
    UNION ALL
    SELECT user_id FROM hello
    WHERE helloable_id     = #object.id
      AND helloable_type   = '#object.class.to_s'
    UNION ALL
    SELECT user_id FROM foo
    WHERE fooable_id       = #object.id
      AND fooable_type     = '#object.class.to_s'
    UNION ALL
    SELECT user_id FROM bar
    WHERE barable_id       = #object.id
      AND barable_type     = '#object.class.to_s'
) AS all_records
INNER JOIN users ON users.id = all_records.user_id
GROUP BY
    all_records.user_id,
    users.NAME,
    users.IMAGE
LIMIT 15

查询应该做的是获取对 (4) 表执行操作的唯一用户(请原谅表名称的更改)。即使使用LIMIT 15,它仍然运行缓慢,因为我认为它仍然可以读取所有 4 个表。我这样做是对的还是有什么方法可以优化它?

供参考:我正在使用 postgres 并使用 rails 但在 find_by_sql 中执行它。

编辑

本地 postgres:9.0.5; heroku postgres:9.1

【问题讨论】:

正如我在 IRC 上的回应 - 运行解释分析,并显示输出。最好粘贴在explain.depesz.com。 这是解释中显示的内容explain.depesz.com/s/3uZ3 @index: 这只是EXPLAIN。我们需要EXPLAIN ANALYZE 首先,总是,您的 PostgreSQL 版本是什么?其次,必不可少:您想要任意 15 行选择、真正随机 选择还是所有 行?第一种情况是迄今为止最便宜的。 解释表明 tt 没有“读取所有 4 个表” - 即它不对它们进行 seq 扫描。它使用索引扫描从所有 4 个表中读取数据。如果您不需要来自所有 4 个表的数据,为什么将它们包含在查询中?另外 - 说明 analyze 是关键,在某些情况下,版本信息也会有所帮助。 【参考方案1】:

照原样回答您的问题:“获取 15 行任意行”。那应该非常快。

SELECT u.id, u.name, u.image
FROM  (
   SELECT id
   FROM  (
      SELECT user_id AS id
      FROM   comments
      WHERE  commentable_id   = #object.id
      AND    commentable_type = '#object.class.to_s'

      UNION ALL
      SELECT user_id
      FROM   hello
      WHERE  helloable_id   = #object.id
      AND    helloable_type = '#object.class.to_s'

      UNION ALL
      SELECT user_id
      FROM   foo
      WHERE  fooable_id     = #object.id
      AND    fooable_type   = '#object.class.to_s'

      UNION ALL
      SELECT user_id
      FROM   bar
      WHERE  barable_id     = #object.id
      AND    barable_type   = '#object.class.to_s'
      ) AS a
   GROUP  BY id
   LIMIT  15
   ) b
JOIN   users u USING (id)

如果您运行的是 PostgreSQL 9.1 或更高版本,则可以简化为 GROUP BY id,假设 users.id 是主键。但我采取了更激进的方法。

我拉起GROUP BYLIMIT 一个查询级别,希望能够在基表上实现更快的索引扫描。使用LIMIT 15 而没有ORDER BY 则不应发生顺序扫描。 Postgres 可以从索引顶部读取元组,并在达到限制时立即停止。 与此密切相关的案例类似:Way to try multiple SELECTs till a result is available? 只有在这里 Postgres 从索引中读取元组。

might 使用 LEFT JOIN users 而不是 JOIN(而不是我的额外子查询级别)可以达到相同的效果,因为 JOIN 可能会从结果中删除行并禁用更简单的查询计划。

为了获得完美的性能,你有像

这样的索引
CREATE INDEX comments_mult_idx
ON comments (commentable_id, commentable_type, user_id)

在所有 4 张桌子上。 user_id 必须是最后一列。 Here's why.

【讨论】:

太棒了!谢谢。所以我在这个查询上做了一个explain analyze 并使用相同的对象进行挖掘,我从我的和你的得到Total runtime: 12.493 msTotal runtime: 1.663 ms。可以使用来自explain analyzeTotal runtime 来确定查询的速度吗? @index: 是的,EXPLAIN ANALYZE 为您提供了 Postgres 为您的查询(不包括网络开销)花费的实时时间(而不仅仅是 EXPLAIN,它只显示计划者的估计)。您可能需要运行几次以使缓存饱和。第二次或第三次运行通常会快一些。 Read the manual for details! 谢谢!这工作完美。 :) 你能解释一下我做了什么让查询这么慢吗?是因为join吗? 这个查询可以实现更简单的计划(就像我在上面和链接的答案中试图解释的那样)。比较两者的EXPLAIN ANALYZE 的输出应该会提供更多详细信息。

以上是关于优化查询以从不同的表中获取唯一(用户)记录的主要内容,如果未能解决你的问题,请参考以下文章

需要 Python 3.7 中的 Mysql 查询以从具有列 (table_no, is_new) 的表中选择记录

MySQL查询优化从大表中获取8-10条记录

如何优化限制查询以便从庞大的表中更快地访问数据?

从 2 个表中获取记录数 - 一对多关系

单个查询从具有不同列的多个表中获取记录

获取最后一组不同的记录