为啥在greenplum中,分区表使用nestedloop join,而非分区表使用hash join

Posted

技术标签:

【中文标题】为啥在greenplum中,分区表使用nestedloop join,而非分区表使用hash join【英文标题】:Why in greenplum, partitioned table uses nestedloop join, while non-partitioned table uses hash join为什么在greenplum中,分区表使用nestedloop join,而非分区表使用hash join 【发布时间】:2018-06-04 07:09:14 【问题描述】:

我创建了两个表(A,B),有 100 列,相同的 DDL,除了 B 已分区

CREATE TABLE A (
  id integer, ......, col integer,
  CONSTRAINT A_pkey PRIMARY KEY (id))
WITH (OIDS = FALSE)
TABLESPACE pg_default
DISTRIBUTED BY (id);

CREATE TABLE B (
  id integer, ......, col integer,
  CONSTRAINT B_pkey PRIMARY KEY (id))
WITH (OIDS = FALSE)
TABLESPACE pg_default
DISTRIBUTED BY (id)
PARTITION BY RANGE(id) 
  (START (1) END (2100000) EVERY (500000), 
   DEFAULT PARTITION extra 
  );

并将相同的数据(2000000行)导入A和B。然后我分别用A和B执行sql:

UPDATE A a SET a.col = c.col from C c where c.id = a.id
UPDATE B b SET b.col = c.col from C c where c.id = b.id

结果A过了一分钟就成功了,但是B花了很长时间,最后出现内存错误:

ERROR:  Canceling query because of high VMEM usage.

于是我查看了两个sql的EXPLAIN,发现A使用了Hash Join,而B使用了Nested-Loop Join

分区表使用嵌套循环连接有什么原因吗? greenplum在存储百万数据时是否不需要使用表分区?

【问题讨论】:

【参考方案1】:

您正在做一些不推荐的事情,这可以解释为什么您会看到嵌套循环。

    一般避免使用 UPDATE 语句。该行的旧版本以及该行的新版本保留在磁盘上。因此,如果您更新整个表,您实际上是在使用它的磁盘上的物理大小加倍。 我从未见过用于分区表的堆表。您应该主要在 Greenplum 中使用 Append Only 表,尤其是在较大的表(例如分区表)上。 您正在按分配键进行分区。这是不推荐的,而且根本没有好处。您是否打算按一系列 ID 进行过滤?这很不寻常。如果是这样,请将分发密钥更改为其他内容。 我认为 Pivotal 禁用了在分区表上创建主键的功能。有一次,这是不允许的。我完全不鼓励您创建任何主键,因为它只会占用空间并且优化器通常不会使用它。

修复这些项目后,我无法重现您的嵌套循环问题。我也在使用 5.0.0 版本。

    drop table if exists a;
    drop table if exists b;
    drop table if exists c;
    CREATE TABLE A 
    (id integer, col integer, mydate timestamp)
    WITH (appendonly=true)
    DISTRIBUTED BY (id);

    CREATE TABLE B 
    (id integer, col integer, mydate timestamp)
    WITH (appendonly=true)
    DISTRIBUTED BY (id)
    PARTITION BY RANGE(mydate) 
      (START ('2015-01-01'::timestamp) END ('2018-12-31'::timestamp) EVERY ('1 month'::interval), 
       DEFAULT PARTITION extra 
      );

    create table c
    (id integer, col integer, mydate timestamp)
    distributed by (id);

    insert into a
    select i, i+10, '2015-01-01'::timestamp + '1 day'::interval*i
    from generate_series(0, 2000) as i
    where '2015-01-01'::timestamp + '1 day'::interval*i < '2019-01-01'::timestamp;

    insert into b
    select i, i+10, '2015-01-01'::timestamp + '1 day'::interval*i
    from generate_series(0, 2000) as i
    where '2015-01-01'::timestamp + '1 day'::interval*i < '2019-01-01'::timestamp;

    insert into c
    select i, i+10, '2015-01-01'::timestamp + '1 day'::interval*i
    from generate_series(0, 2000) as i
    where '2015-01-01'::timestamp + '1 day'::interval*i < '2019-01-01'::timestamp;


    explain UPDATE A a SET col = c.col from C c where c.id = a.id;
    /*
    "Update  (cost=0.00..862.13 rows=1 width=1)"
    "  ->  Result  (cost=0.00..862.00 rows=1 width=34)"
    "        ->  Split  (cost=0.00..862.00 rows=1 width=30)"
    "              ->  Hash Join  (cost=0.00..862.00 rows=1 width=30)"
    "                    Hash Cond: public.a.id = c.id"
    "                    ->  Table Scan on a  (cost=0.00..431.00 rows=1 width=26)"
    "                    ->  Hash  (cost=431.00..431.00 rows=1 width=8)"
    "                          ->  Table Scan on c  (cost=0.00..431.00 rows=1 width=8)"
    "Settings:  optimizer_join_arity_for_associativity_commutativity=18"
    "Optimizer status: PQO version 2.42.0"
    */

    explain UPDATE B b SET col = c.col from C c where c.id = b.id;
    /*
    "Update  (cost=0.00..862.13 rows=1 width=1)"
    "  ->  Result  (cost=0.00..862.00 rows=1 width=34)"
    "        ->  Split  (cost=0.00..862.00 rows=1 width=30)"
    "              ->  Hash Join  (cost=0.00..862.00 rows=1 width=30)"
    "                    Hash Cond: public.a.id = c.id"
    "                    ->  Table Scan on a  (cost=0.00..431.00 rows=1 width=26)"
    "                    ->  Hash  (cost=431.00..431.00 rows=1 width=8)"
    "                          ->  Table Scan on c  (cost=0.00..431.00 rows=1 width=8)"
    "Settings:  optimizer_join_arity_for_associativity_commutativity=18"
    "Optimizer status: PQO version 2.42.0"

    */

【讨论】:

非常感谢您的帮助。现在我明白了。

以上是关于为啥在greenplum中,分区表使用nestedloop join,而非分区表使用hash join的主要内容,如果未能解决你的问题,请参考以下文章

使用 gp_segment_id 作为分区列从 greenplum 中并行获取数据

外部分区表 greenplum

greenplum 分区优化

在 Greenplum DB [大数据] 上选择分区策略的更好实践

Greenplum 中的多级分区

为啥greenplum不能在不同的模式中创建相同的表名