【问题标题】:Optimizing multiple joins优化多个连接
【发布时间】:2011-01-06 09:52:47
【问题描述】:

我正在尝试找出一种方法来加快一个特别繁琐的查询,该查询按日期在几个表中聚合一些数据。下面是完整的(丑陋的)查询以及EXPLAIN ANALYZE,以显示它是多么可怕。

如果有人可以偷看一下,看看他们是否能发现任何重大问题(这很可能,我不是 Postgres 人),那就太棒了。

就这样吧。查询是:

SELECT 
 to_char(p.period, 'DD/MM/YY') as period,
 coalesce(o.value, 0) AS outbound,
 coalesce(i.value, 0) AS inbound
FROM (
 SELECT
  date '2009-10-01' + s.day 
  AS period 
  FROM generate_series(0, date '2009-10-31' - date '2009-10-01') AS s(day)
) AS p 
LEFT OUTER JOIN(
 SELECT
  SUM(b.body_size) AS value, 
  b.body_time::date AS period 
 FROM body AS b 
 LEFT JOIN 
  envelope e ON e.message_id = b.message_id 
 WHERE 
  e.envelope_command = 1 
  AND b.body_time BETWEEN '2009-10-01' 
  AND (date '2009-10-31' + INTERVAL '1 DAY') 
 GROUP BY period 
 ORDER BY period
) AS o ON p.period = o.period
LEFT OUTER JOIN( 
 SELECT 
  SUM(b.body_size) AS value, 
  b.body_time::date AS period 
 FROM body AS b 
 LEFT JOIN 
  envelope e ON e.message_id = b.message_id 
 WHERE 
  e.envelope_command = 2 
  AND b.body_time BETWEEN '2009-10-01' 
  AND (date '2009-10-31' + INTERVAL '1 DAY') 
 GROUP BY period 
 ORDER BY period
) AS i ON p.period = i.period 

EXPLAIN ANALYZE 可以在这里找到:on explain.depesz.com

感谢任何 cmets 或问题。

干杯

【问题讨论】:

  • 如果您发布了对该查询重要的架构部分,以及您想要达到的目标的简短英文描述,将会很有帮助。

标签: sql postgresql optimization join


【解决方案1】:

以 Craig Young 的 suggestions 为基础,这里是修改后的查询,我正在处理的数据集运行时间约为 1.8 秒。这比原来的 ~2.0s 略有改进,对 Craig 的约 22s 的改进是巨大的。

SELECT
    p.period,
    /* The pivot technique... */
    SUM(CASE envelope_command WHEN 1 THEN body_size ELSE 0 END) AS Outbound,
    SUM(CASE envelope_command WHEN 2 THEN body_size ELSE 0 END) AS Inbound
FROM
(
    /* Get days range */
    SELECT date '2009-10-01' + day AS period
    FROM generate_series(0, date '2009-10-31' - date '2009-10-01') AS day
) p
    /* Join message information */
    LEFT OUTER JOIN
    (
        SELECT b.body_size, b.body_time::date, e.envelope_command
        FROM body AS b 
            INNER JOIN envelope e ON e.message_id = b.message_id 
        WHERE
            e.envelope_command IN (2, 1)
            AND b.body_time::date BETWEEN (date '2009-10-01') AND (date '2009-10-31')
    ) d ON d.body_time = p.period
GROUP BY p.period
ORDER BY p.period

【讨论】:

  • 你有关于 Body.Body_time 的索引吗?是集群还是不集群?索引中还包含哪些其他列?这些列在索引中的顺序是什么?
  • body.body_time 上有一个索引(见下文),正文表中没有其他列。索引就是CREATE INDEX body_time_idx ON body USING btree (body_time);
  • 正如我在回答中指出的那样;必须从两个角度进行优化。选择索引,并以优化器考虑索引的方式编写查询。 注意 您实际上不应该通过查询提示“告诉”优化器使用特定索引;您应该让优化器根据表统计信息做出决定。您需要创建索引、运行查询并检查执行计划以查看优化器做了什么。执行计划中最重要的信息是:优化器选择了哪些索引? (续...)
  • (...继续) 如果您的查询的特定版本没有使用您期望的索引,您需要尝试找出原因。同样,即使计划确实使用了您期望的索引,但仍然需要“太长时间”,您也需要再次找出原因。这让我想到了您将在执行计划中寻找的第二条信息:每个步骤处理了多少数据,以及完成了什么样的处理?您可以尝试以下索引:正文:(body_time,message_id,body_size)或(message_id,body_time,body_size)。另外,如果它的选择性足够,试试信封:(envelope_command)
  • 感谢克雷格的持续投入。我现在要标记这个问题已经解决了,因为你帮了很大的忙,还有其他更大的鱼要炸。干杯。
【解决方案2】:

在优化查询时总是要考虑两件事:

  • 可以使用哪些索引(您可能需要创建索引)
  • 查询的编写方式(您可能需要更改查询以使查询优化器能够找到适当的索引,并且不会重复读取数据)

一些观察:

  • 在加入日期之前,您正在执行日期操作。作为一般规则,这将阻止查询优化器使用索引,即使它存在。您应该尝试编写表达式,使索引列在表达式的一侧保持不变。

  • 您的子查询过滤到与generate_series 相同的日期范围。这是一个重复,它限制了优化器选择最有效优化的能力。我怀疑可能是为了提高性能而写入的,因为优化器无法在日期列上使用索引 (body_time)?

  • 注意:我们实际上非常希望在Body.body_time上使用索引

  • ORDER BY 在子查询中充其量是多余的。在最坏的情况下,它可能会强制查询优化器在加入之前对结果集进行排序;这对查询计划不一定有好处。而是仅在最后应用订购权以进行最终展示。

  • 在您的子查询中使用 LEFT JOIN 是不合适的。假设您对NULL 行为使用ANSI 约定(并且您应该这样做),任何outer 连接到envelope 都将返回envelope_command=NULL,因此这些将被条件@987654329 排除在外@。

  • 子查询oi 几乎相同,除了envelope_command 值。这迫使优化器扫描相同的基础表两次。您可以使用 数据透视表 技术将数据连接一次,然后将值拆分为 2 列。

尝试以下使用枢轴技术的方法:

SELECT  p.period,
        /*The pivot technique in action...*/
        SUM(
        CASE WHEN envelope_command = 1 THEN body_size
        ELSE 0
        END) AS Outbound,
        SUM(
        CASE WHEN envelope_command = 2 THEN body_size
        ELSE 0
        END) AS Inbound
FROM    (
        SELECT  date '2009-10-01' + s.day AS period
        FROM    generate_series(0, date '2009-10-31' - date '2009-10-01') AS s(day)
        ) AS p 
        /*The left JOIN is justified to ensure ALL generated dates are returned
          Also: it joins to a subquery, else the JOIN to envelope _could_ exclude some generated dates*/
        LEFT OUTER JOIN (
        SELECT  b.body_size,
                b.body_time,
                e.envelope_command
        FROM    body AS b 
                INNER JOIN envelope e 
                  ON e.message_id = b.message_id 
        WHERE   envelope_command IN (1, 2)
        ) d
          /*The expressions below allow the optimser to use an index on body_time if 
            the statistics indicate it would be beneficial*/
          ON d.body_time >= p.period
         AND d.body_time < p.period + INTERVAL '1 DAY'
GROUP BY p.Period
ORDER BY p.Period

编辑:添加了 Tom H 建议的过滤器。

【讨论】:

  • 充分利用 CASE 来限制连接。您可以通过信封命令 IN (1, 2) 添加到信封的约束
  • 啊是的,非常正确;这将提高性能,因为它将消除否则将被引入到聚合中的行,两列中的值都为零。 (即会消除根本不影响结果的行)
  • 使用这个建议的查询,执行时间从原来的 ~2 秒增加到 ~23 秒!当我有更多信息时,我会更新。
  • envelope_command IN (1, 2)AND b.body_time BETWEEN '2009-10-01' AND (date '2009-10-31' + INTERVAL '1 DAY') 之后添加一个额外的条件可以将时间缩短到~6.5 秒。结论:还有更多工作要做。
  • 在上述答案的基础上(或将其弄得一团糟),目前最高效的解决方案使用枢轴技术来节省迄今为止的双重连接和强制转换。在评论中发布太长,因此将在单独的答案中。
【解决方案3】:

几天前我卸载了我的 PostgreSQL 服务器,所以您可能不得不尝试一下,但希望这对您来说是一个好的开始。

关键是:

  1. 您不需要子查询 - 只需执行直接连接和聚合即可
  2. 您应该能够使用 INNER JOIN,它通常比 OUTER JOIN 性能更高

如果没有别的,我认为下面的查询更清楚一些。

我在查询中使用了日历表,但您可以在使用时将其替换为 generate_series。

此外,根据索引,最好将 body_date 与 >= 和 = date (time=midnight) AND body_date

SELECT
    CAL.calendar_date AS period,
    SUM(O.body_size) AS outbound,
    SUM(I.body_size) AS inbound
FROM
    Calendar CAL
INNER JOIN Body OB ON
    OB.body_time::date = CAL.calendar_date
INNER JOIN Envelope OE ON
    OE.message_id = OB.message_id AND
    OE.envelope_command = 1
INNER JOIN Body IB ON
    IB.body_time::date = CAL.calendar_date
INNER JOIN Envelope IE ON
    IE.message_id = IB.message_id AND
    IE.envelope_command = 2
GROUP BY
    CAL.calendar_date

【讨论】:

  • 一般而言,应该首选 INNER JOINS,但有时您需要一个集合中的所有行,即使连接集中没有匹配项也是如此。在我看来,OP 想要所有生成的日期,并使用 COALESCE 函数来确保如果没有匹配的行,Inbound 和 Outbound 将显示 0。因此,您对 Body 的加入应该是 LEFT OUTER 以满足该要求。不幸的是,(如果我没记错的话)Envelope 的内部连接仍然可以消除一些日期行。子查询用于减少围绕连接的混淆。但是,OP 在使用 LEFT JOINS 到 Envelope 时确实犯了错误。
  • 哎呀,你就在那儿,克雷格。你知道 PostgreSQL 是否支持嵌套 JOIN 吗?我的猜测(未经测试)是嵌套连接会比子查询执行得更好。
猜你喜欢
  • 2012-11-03
  • 2018-10-30
  • 2019-06-23
  • 2023-03-12
  • 2016-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
相关资源
最近更新 更多