【问题标题】:Postgres: why LEFT JOIN affects to query plan?Postgres:为什么 LEFT JOIN 会影响查询计划?
【发布时间】:2017-11-22 10:31:58
【问题描述】:

我有 PostgreSQL 9.5.9 和两个表:table1 和 table2

 Column   |              Type              |                 Modifiers                 
------------+--------------------------------+-------------------------------------------
 id         | integer                        | not null
 status     | integer                        | not null
 table2_id  | integer                        | 
 start_date | timestamp(0) without time zone | default NULL::timestamp without time zone
Indexes:
    "table1_pkey" PRIMARY KEY, btree (id)
    "table1_start_date" btree (start_date)
    "table1_table2" btree (table2_id)
Foreign-key constraints:
    "fk_t1_t2" FOREIGN KEY (table2_id) REFERENCES table2(id)


 Column |          Type           |            Modifiers            
--------+-------------------------+---------------------------------
 id     | integer                 | not null
 name   | character varying(2000) | default NULL::character varying
Indexes:
    "table2_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "table1" CONSTRAINT "fk_t1_t2" FOREIGN KEY (table2_id) REFERENCES table2(id)

table2 只包含 3 行; table1 包含大约 400000 行,其中只有一半在 table_2_id 列中有一些值。

当我从按 start_date 列排序的 table1 中选择一些值时,查询速度足够快,因为有效地使用了 table1_start_date 索引:

SELECT t1.*
FROM table1 AS t1 
ORDER BY t1.start_date DESC
LIMIT 25 OFFSET 150000;

解释分析结果

   Limit  (cost=7797.40..7798.70 rows=25 width=20) (actual time=40.994..41.006 rows=25 loops=1)
   ->  Index Scan Backward using table1_start_date on table1 t1  (cost=0.42..20439.74 rows=393216 width=20) (actual time=0.078..36.251 rows=150025
 loops=1)
 Planning time: 0.097 ms
 Execution time: 41.033 ms

但是当我添加 LEFT JOIN 以从 table2 中获取值时,查询变得非常慢:

SELECT t1.*, t2.*
FROM table1 AS t1
LEFT JOIN table2 AS t2 ON t2.id = t1.table2_id
ORDER BY t1.start_date DESC
LIMIT 25 OFFSET 150000;

解释分析结果:

 Limit  (cost=33690.80..33696.42 rows=25 width=540) (actual time=191.282..191.320 rows=25 loops=1)
   ->  Nested Loop Left Join  (cost=0.57..88317.50 rows=393216 width=540) (actual time=0.028..184.537 rows=150025 loops=1)
         ->  Index Scan Backward using table1_start_date on table1 t1  (cost=0.42..20439.74 rows=393216 width=20) (actual time=0.018..35.196 rows=
150025 loops=1)
         ->  Index Scan using table2_pkey on table2 t2  (cost=0.14..0.16 rows=1 width=520) (actual time=0.000..0.001 rows=1 loops=150025)
               Index Cond: (id = t1.table2_id)
 Planning time: 0.210 ms
 Execution time: 191.357 ms

为什么查询时间从 32ms 增加到 191ms?据我了解,LEFT JOIN 不会影响结果。因此,我们可以先从 table1 中选择 25 行(LIMIT 25),然后从 table2 中连接行,查询的执行时间应该不会显着增加。没有一些棘手的条件会破坏索引等的使用。

我不完全理解第二个查询的 EXPLAIN ANALYZE,但似乎 postgres 分析器决定“执行连接然后过滤”而不是“过滤然后连接”。这样查询太慢了。有什么问题?

【问题讨论】:

  • ,而不是字段。
  • 作为一个 LEFT 连接并且事实上你没有选择 table2 的任何列,PostgreSQL 认为你不需要 table2 并简单地忽略它。
  • 感谢您的通知,我已编辑问题。第一个问题没有join。
  • 如果您只对执行速度感兴趣,您可以尝试将 t2 上的左连接替换为标量子查询。
  • 不,在这种情况下,子查询对我来说是显而易见的解决方案。其实我有兴趣了解 psql 查询分析器。

标签: sql postgresql sql-execution-plan


【解决方案1】:

它只是不知道该限制应该应用于table1 而不是连接结果,因此它获取最少需要的行,即150025,然后在table2 上执行150025 次循环。如果您对 table1 进行限制的子选择并将 table2 加入该子选择,您应该得到您想要的。

SELECT t1.*, t2.*
FROM (SELECT *
        FROM table1
       ORDER BY start_date DESC
       LIMIT 25 OFFSET 150000) AS t1
LEFT JOIN table2 AS t2 ON t2.id = t1.table2_id;

【讨论】:

  • 嗯。对我来说,很清楚应该将限制应用于 table1:1)因为使用了 LEFT JOIN(而不是 INNER JOIN)2)因为我们使用了 table1 中的外键,所以每个 table1 行可以与 0 或 1 个 table2 行连接。这样连接结果不会影响所选行的数量。是不是?分析器不够聪明吗? :)
  • @ox160d05d OFFSET+LIMIT 应用于完整查询的结果。 (这就是它在语法中位于 ORDER BY 和 GROUP BY 之后的原因)
  • @ox160d05d 这不是一对一的关系,所以 JOIN(甚至 LEFT)可以改变结果的行数。考虑一下,如果你翻转表格,你会得到多少行,右边是table2,只有几行,左连接table1,有数千个匹配的行。它会只返回几行还是几千行?
  • 感谢您的回答。 “table2 join table1”的情况很清楚 - 我们不知道将连接多少行,所以我们强制先连接然后限制。但是在“table1 join table2”的情况下,我们知道,LEFT JOIN 不会影响行数(table2_id 是外键,它与 table2.id 相关,它是主键和唯一键table2) 实际上是 1 到 0..1 的关系
  • 是的,你是对的。我想我当时没有答案。我想它只是不够优化。
猜你喜欢
  • 1970-01-01
  • 2019-11-13
  • 2016-04-02
  • 2020-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 2021-06-01
相关资源
最近更新 更多