优化 PostgreSQL 中的 JOIN -> GROUP BY 查询:所有索引都已经存在

Posted

技术标签:

【中文标题】优化 PostgreSQL 中的 JOIN -> GROUP BY 查询:所有索引都已经存在【英文标题】:Optimize JOIN -> GROUP BY query in PostgreSQL: all indexes are already there 【发布时间】:2020-03-20 10:49:35 【问题描述】:

关于 SO 至少有几个类似(但不完全相同)的问题。在这些问题中,查询性能的问题在于缺少索引或过多的谓词。

但我的情况很简单明了:3 个表,每个都引用另一个。 每个引用的表格行都有 BTree 索引。以下是表格:

CREATE TABLE region(
   id serial PRIMARY KEY,
   title VARCHAR (50) NOT NULL
);

CREATE TABLE unit(
   id serial PRIMARY KEY,
   region_id INT NOT NULL REFERENCES region(id)
);

CREATE TABLE unit_usage(
   id serial PRIMARY KEY,
   title VARCHAR (50) NOT NULL,
   unit_id INT NOT NULL REFERENCES unit(id)
);

CREATE INDEX ON unit ((region_id));
CREATE INDEX ON unit_usage ((unit_id));
CREATE INDEX ON unit_usage ((title));

unit_usage 表中有 300 000 000+ 行,unit 表中有 50 000 000+ 行,region 表中有 65 000+ 行。 我想要的是查询每个 unit_usageregions 计数。像这样的:

WITH x AS
(
 select u.region_id as region_id, t.title as title
 from unit_usage t join unit u
 on t.unit_id = u.id
)
SELECT title, count(region_id) as found_in_regions
FROM x GROUP BY title;

这里'the DBFiddle。

此查询运行大约 5 分钟。这太多了——我的限制是大约 10 秒。 我试过的:

重新塑造查询,如:

select u.region_id, t.title, count(t.id) 
from unit_usage t join unit u
on t.unit_id = u.id group by u.region_id, t.title;

相同的执行时间。

设置 enable_hashjoin = off;我已经摆脱了 Hash Join 和 Seq Scan 之一,但这不会影响执行时间

【问题讨论】:

是OLAP还是OLTP? 这是一个 OLAP 部分 查询不可能扫描+3亿行并在10秒内执行。您应该创建带有聚合的附加表或将聚合列添加到单元表中并使用计数。并在您的 ETL 期间计算此聚合。那么你的查询就会变成一张表的简单全扫描。 看起来是这样,但至少我会很高兴有一半的时间。 您的两个查询似乎并不相同,因为在第一个查询中您按标题分组,在第二个查询中按标题和 region_id 分组。在group by中拥有title这么重要吗?或者您可以按 region_id 分组? 【参考方案1】:

此查询与您的第二个查询具有相同的结果。它可以更快,因为要加入的行更少:

with uu as (
  select u.unit_id, u.title, count(*) cnt
    from unit_usage u
   group by u.unit_id, u.title
)
select u.region_id, uu.title, sum(cnt)
  from uu
    inner join unit u
      on uu.unit_id = u.id
 group by u.region_id, uu.title

这个索引可能对这个查询有好处(无论有没有索引都更好测试):

create index unit_usage_ix on unit_usage(unit_id, title);

【讨论】:

【参考方案2】:

我会首先尝试让逻辑正确。如果您想计算不同区域的数量,那么我希望:

我想要的是查询每个 unit_usage 的区域数。

select u.id, count(distinct u.region_id) 
from unit_usage uu join
     unit u
     on t.unit_id = u.id
group by u.id;

这不会加快查询速度。但至少它应该返回正确的结果。如果是这样,那你就可以开始考虑如何改正了。

【讨论】:

对不起,我在写这个示例时犯了一个错误。我在两个主题/小提琴中都修复了 SQL。 SQL(固定)是正确的,它提供了所需的结果:与 unit_usage 中的每个单独标题相关的区域。 count(distinct u.region_id) 将始终等于 1,因为您按主键进行分组 @Sergey94 。 . .我不明白你的评论。 u.region_idunit_usage 上的主键无关。 看看你的group by uu.id。您正在按 unit_usage 的主键进行分组。这就是 count 总是等于 1 的原因。 @Sergey94 。 . .谢谢。

以上是关于优化 PostgreSQL 中的 JOIN -> GROUP BY 查询:所有索引都已经存在的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL——查询优化——整理计划树

PostgreSQL——查询优化——整理计划树

PostgreSQL——查询优化——整理计划树

PostgreSQL——查询优化——整理计划树

PostgreSQL 中的 SQL JOIN - WHERE 子句中的执行计划与 ON 子句中的不同

Postgresql中的Inner Join子查询导致错误