PostgreSQL 似乎在简单的条件连接中创建了低效的计划

hed*_*yat 5 postgresql performance join execution-plan postgresql-9.3 postgresql-performance

考虑这两个查询:

SELECT
    t1.id, *
FROM
    t1
INNER JOIN
    t2 ON t1.id = t2.id
    where t1.id > -9223372036513411363;
Run Code Online (Sandbox Code Playgroud)

和:

SELECT
    t1.id, *
FROM
    t1
INNER JOIN
    t2 ON t1.id = t2.id
    where t1.id > -9223372036513411363 and t2.id > -9223372036513411363;
Run Code Online (Sandbox Code Playgroud)

注意:不是-9223372036513411363表中的最小值,并且条件将结果(从总行数 3.5 亿行)减少到 1700 万行。

就我个人而言,我希望 PostgreSQL 能够为这两个查询提供相同的计划,因为t1.id = t2.id自动意味着第二个条件。但不幸的是,PostgreSQL 正在创建两个不同的计划,第二个计划要好得多:

我非常喜欢第一个查询,因为我想从连接创建一个视图,并将 where 条件放在视图上的查询上,我在其中看到单个 id 列(我使用连接,USING因此单个 id 列在视图中可见) 。另外,我将连接两个以上的表,并且我不希望为每个连接添加这样的条件。

这种行为有什么原因吗?或者这是一个错误?有什么解决方法吗?

  • 替换ON t1.id = t2.idUSING (id)在两个查询中没有区别。
  • 这是 PostgreSQL 9.3
  • 实际返回行数为17,658,189
  • 分析已在表上运行。然而,PostgreSQL 的统计相关设置是其默认值。
  • 观察:查询 1 的解释对最终结果有一个很好的估计,但在查询 t2 时使用了一个很差的计划。对于第二个查询,t1 和 t2 的行数估计很好,但最终合并的估计大约是实际行数的一半。
  • id列是两个表中的主键。表大约有 350,000,000 行。t1 约为 20GiB,t2 约为 14GiB。
  • 替换INNER JOINLEFT OUTER JOIN产生类似的结果
  • 选择较少的行(通过增加 where 条件中的最小 ID 值)不会产生任何差异,直到行数变得太少,在这种情况下它会使用完全不同的计划。

我想要实现的目标

我有一个包含很多行的数据库,并且不断向其中插入新数据。我们希望为这些数据生成不同的报告,其中包括不同类型的查询,例如:搜索不同的数据、按列排序、聚合查询等。

在当前的设计中,我们没有 UPDATE 操作。目前,我正在尝试一种高度规范化的设计(基于 Anchor 建模和/或 6NF 所提倡的想法)。这种设计将广泛使用 JOIN 和 VIEW 来使数据库工作变得愉快,因此需要数据库能够有效地完成这些工作。

据我所知(基于这样的问题),PostgreSQL 似乎不太适合这种设计(大约有 11 个表和大量视图),并且似乎几乎总是比标准化程度较低的设计表现更差有一张或两张桌子,没有风景。我希望规划 JOIN 查询时出现的这个问题是我的错,但现在看来还不是这样。对于这个问题,我似乎应该忘记使用 VIEWS 并使用带有大量重复条件的详细查询,或者忘记使用 PostgreSQL 或此设计。

表格

实际的列数有点多,但它们与其他表没有任何关系,因此应该与本讨论无关:

CREATE TABLE t1
(
  id bigint NOT NULL DEFAULT nextval('ids_seq'::regclass),
  total integer NOT NULL,
  price integer NOT NULL,
  CONSTRAINT pk_t1 PRIMARY KEY (id)
)

CREATE TABLE t2
(
  id bigint NOT NULL,
  category smallint NOT NULL,
  CONSTRAINT pk_t2 PRIMARY KEY (id),
  CONSTRAINT fk_id FOREIGN KEY (id)
      REFERENCES t1 (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 3

那么它看起来像是优化器的盲点,您应该使用第二个查询。

当存在连接两个表的条​​件aba.id = b.id以及附加条件时a.id > @some_constant,优化器似乎使用“索引条件”来确定在a (id)索引上开始索引扫描的位置,但不会将其用于第二个索引b (id)

因此,添加(冗余)b.id > @some_constant可以使其产生稍微更有效的计划,b (id)同时也跳过索引的一部分。

这可以作为改进建议(如果还没有)发布到 Postgres 黑客组。


编辑后,我们知道有一个FOREIGN KEY限制。因此,编写查询的“自然”(等效)方法是:t2REFERENCES t1

SELECT
    -- whatever
FROM
    t2
  LEFT JOIN
    t1 ON t1.id = t2.id
WHERE t2.id > -9223372036513411363 ;
Run Code Online (Sandbox Code Playgroud)

您能尝试一下并告诉我们它产生的执行计划吗?有些转换仅适用于LEFT(外)连接,不适用于内连接。
不幸的是,这也不会产生任何不同的计划。


OP 在 Postgres 性能列表中发布了一个问题,我们可以在这里看到整个线程:PostgreSQL似乎在简单的条件连接中创建低效的计划以及David Rowley的回复,这证实了这是一个功能,尽管它已经被考虑过,尚未在优化器中实现:

是的,不幸的是,您已经做了唯一可以做的事情,那就是在查询中包含这两个条件。是否有一些特殊原因导致您不能只t2.id > ...在查询中写入条件?或者查询是由某些您无法控制的软件动态生成的?

我个人非常希望看到这方面的改进,甚至编写了一个补丁1来解决这个问题。我在提出解决方案时遇到的问题是,我无法报告有关有多少人受到此计划限制的影响的详细信息。我提出的补丁对许多查询的规划时间造成了非常小的影响,并且许多人认为没有在足够多的情况下应用它,因此值得放慢不可能受益的查询。我当然同意这一点,我对放慢查询规划没有兴趣,但同时理解这方面令人讨厌的糟糕优化。

尽管请记住我提出的补丁只是提案初稿。不用于生产用途。