【问题标题】:postgresql - join between two large tables takes very longpostgresql - 两个大表之间的连接需要很长时间
【发布时间】:2018-09-26 06:56:32
【问题描述】:

我确实有两个相当大的表,我需要在它们之间进行日期范围连接。不幸的是,查询需要 12 多个小时。我正在使用在 docker 中运行的 postgresql 10.5,最大。 5GB 内存和多达 12 个 CPU 内核可用。

基本上,在左表中,我确实有一个设备 ID 和一个日期范围列表(从 = Timestamp,到 = ValidUntil)。然后我想加入右表,其中包含所有设备的测量值(传感器数据),以便我只获得位于日期范围之一(来自左表)内的传感器数据。查询:

select
    A.*,
    B."Timestamp" as "PressureTimestamp",
    B."PropertyValue" as "Pressure"
from A
inner join B
    on  B."EquipmentId" =  A."EquipmentId"
    and B."Timestamp"   >= A."Timestamp"
    and B."Timestamp"   <  A."ValidUntil"

不幸的是,这个查询只使用了一个核心,这可能是它运行如此缓慢的原因。有没有办法重写查询以便并行化?

索引:

create index if not exists A_eq_timestamp_validUntil on public.A using btree ("EquipmentId", "Timestamp", "ValidUntil");
create index if not exists B_eq_timestamp on public.B using btree ("EquipmentId", "Timestamp");

表格:

-- contains 332,000 rows
CREATE TABLE A (
    "EquipmentId" bigint,
    "Timestamp" timestamp without time zone,
    "ValidUntil" timestamp without time zone
)
WITH ( OIDS = FALSE )

-- contains 70,000,000 rows
CREATE TABLE B
(
    "EquipmentId" bigint,
    "Timestamp" timestamp without time zone,
    "PropertyValue" double precision
)
WITH ( OIDS = FALSE )

执行计划(解释...输出):

Nested Loop  (cost=176853.59..59023908.95 rows=941684055 width=48)
  ->  Bitmap Heap Scan on v2_pressure p  (cost=176853.16..805789.35 rows=9448335 width=24)
        Recheck Cond: ("EquipmentId" = 2956235)
        ->  Bitmap Index Scan on v2_pressure_eq  (cost=0.00..174491.08 rows=9448335 width=0)
              Index Cond: ("EquipmentId" = 2956235)"
  ->  Index Scan using v2_prs_eq_timestamp_validuntil on v2_prs prs  (cost=0.42..5.16 rows=100 width=32)
        Index Cond: (("EquipmentId" = 2956235) AND (p."Timestamp" >= "Timestamp") AND (p."Timestamp" < "ValidUntil"))

更新 1: 根据 cmets 修复了索引,这大大提高了性能

【问题讨论】:

  • 您似乎在这些表上没有任何索引。在b("Timestsamp") 上试一试,在a("Timestamp", "ValidUntil") 上试一试
  • 无关,但是:你真的应该避免那些可怕的带引号的标识符。他们的麻烦比他们的价值要多得多
  • Postgres 通常只使用一个索引。您已经在三个不同的列上定义了三个单独的索引。您可能想尝试一个 composite 索引,该索引涉及连接中涉及的部分或全部列。
  • 使用索引改善了看起来的情况(测试仍在运行),但它仍然只使用 1 个核心。有没有办法以某种方式编写查询,以便 pgsql 可以并行执行查询?
  • 你可以尝试显式hash join,也许这有助于执行计划中的堆扫描。

标签: sql postgresql performance


【解决方案1】:

索引修正是解决缓慢的首要手段,但它只会在一定程度上有所帮助。鉴于您的表很大,我建议您尝试 Postgres Partition 。它有一些来自 postgres 的内置支持。

但您需要有一些过滤器/分区标准。我在您的查询中看不到任何 where 子句,因此无法建议。可能你可以试试设备ID。这也有助于实现并行性。

【讨论】:

  • 我尝试使用 EquipmentId 作为分区键(导致 13 个分区)。但是检查执行计划 postgres 基本上收集了所有分区的数据,然后将它们全部连接到另一个表(我没有分区)。也许我会再次尝试使用相同的分区键为 A 和 B 使用分区表。问题还在于我不再能够创建索引,例如在时间戳上。
  • 这就是分区的副作用。分区标准必须是您的 where 子句的一部分,例如使用“IN”并且上面应该有一个索引,然后只有它会扫描特定分区而不是所有分区。这就是为什么根据您的应用程序明智地选择分区标准很重要的原因。您应该能够在分区表上创建索引。我建议阅读更多内容并尝试使用它,因为它不是直截了当的。
【解决方案2】:
-- \i tmp.sql

CREATE TABLE A
        ( equipmentid bigint NOT NULL
        , ztimestamp timestamp without time zone NOT NULL
        , validuntil timestamp without time zone NOT NULL
        , PRIMARY KEY (equipmentid,ztimestamp)
        , UNIQUE (equipmentid,validuntil) -- mustbeunique, since the intervals dont overlap
        ) ;

-- contains 70,000,000 rows
CREATE TABLE B
        ( equipmentid bigint NOT NULL
        , ztimestamp timestamp without time zone NOT NULL
        , propertyvalue double precision
        , PRIMARY KEY (equipmentid,ztimestamp)
        ) ;

INSERT INTO B(equipmentid,ztimestamp,propertyvalue)
SELECT i,t, random()
FROM generate_series(1,1000) i
CROSS JOIN generate_series('2018-09-01','2018-09-30','1day'::interval) t
        ;


INSERT INTO A(equipmentid,ztimestamp,validuntil)
SELECT equipmentid,ztimestamp, ztimestamp+ '7 days'::interval
FROM B
WHERE date_part('dow', ztimestamp) =0
        ;

ANALYZE A;
ANALYZE B;

EXPLAIN
SELECT
    A.*,
    B.ztimestamp AS pressuretimestamp,
    B.propertyvalue AS pressure
FROM A
INNER JOIN B
    ON  B.equipmentid =  A.equipmentid
    AND B.ztimestamp   >= A.ztimestamp
    AND B.ztimestamp   <  A.validuntil
    WHERE A.equipmentid=333 -- I added this, the plan in the question also has a r estriction on Id
        ;

以及由此产生的计划:


SET
ANALYZE
ANALYZE
                                                 QUERY PLAN                                                 
------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.34..21.26 rows=17 width=40)
   ->  Index Scan using a_equipmentid_validuntil_key on a  (cost=0.17..4.34 rows=5 width=24)
         Index Cond: (equipmentid = 333)
   ->  Index Scan using b_pkey on b  (cost=0.17..3.37 rows=3 width=24)
         Index Cond: ((equipmentid = 333) AND (ztimestamp >= a.ztimestamp) AND (ztimestamp < a.validuntil))
(5 rowSET

这是我当前的random_page_cost=1.1;设置

设置为4.0后,我得到了和OP一样的方案:


SET
                                                                     QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=35.13..54561.69 rows=1416136 width=40) (actual time=1.391..1862.275 rows=225540 loops=1)
   ->  Bitmap Heap Scan on aa2  (cost=34.71..223.52 rows=1345 width=24) (actual time=1.173..5.223 rows=1345 loops=1)
         Recheck Cond: (equipmentid = 5)
         Heap Blocks: exact=9
         ->  Bitmap Index Scan on aa2_equipmentid_validuntil_key  (cost=0.00..34.38 rows=1345 width=0) (actual time=1.047..1.048 rows=1345 loops=1)
               Index Cond: (equipmentid = 5)
   ->  Index Scan using bb2_pkey on bb2  (cost=0.42..29.87 rows=1053 width=24) (actual time=0.109..0.757 rows=168 loops=1345)
         Index Cond: ((equipmentid = 5) AND (ztimestamp >= aa2.ztimestamp) AND (ztimestamp < aa2.validuntil))
 Planning Time: 3.167 ms
 Execution Time: 2168.967 ms
(10 rows)

【讨论】:

  • 我已经复制了您的创建表语句,从原始问题中添加了我的索引语句,然后将我的实际数据复制到表中。但是,我的执行计划与您的不同:pastebin.com/vTPSkiNi
  • 表名注意事项:v2_test_prs = A & v2_test_pressure = B
  • 1) 行数估计值似乎相当高,您是否(在插入后)对表运行分析? 2)为什么要添加额外的索引? PK+UNIQUE 将提供足够的索引能力。 3)ID 的分布是否非常不平衡? ID的基数低吗? (不可能,因为它是一个大整数)
  • 是的,我确实在两个表上都运行了“分析”。不同的设备在两个表中都有不同数量的条目:imgur.com/a/UylqcFC。我还主要使用连接来过滤来自 B 的条目。我希望 A 中的每个条目在表 B 中有 30-45 个匹配条目。另外两个索引不应该受到伤害,对吧(预计存储空间会增加)?似乎 pg 实际上正在使用它们。
  • 当我SET random_page_cost=4; 时,我得到的计划与您的计划相同,这是默认设置。尝试将其设置得更低,(至 ~1.2)
猜你喜欢
  • 2013-12-19
  • 1970-01-01
  • 1970-01-01
  • 2020-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多