【问题标题】:When does Postgresql do partition pruning with JOIN columns?Postgresql 何时使用 JOIN 列进行分区修剪?
【发布时间】:2020-01-28 23:39:20
【问题描述】:

我在 Postgres 11 数据库中有两个表:

client table
--------
client_id    integer
client_name  character_varying

file table
--------
file_id      integer
client_id    integer
file_name    character_varying

client表不分区,file表按client_id分区(按list分区)。当新客户端插入客户端表时,触发器会为文件表创建一个新分区。 文件表有一个外键约束,在 client_id 上引用了客户端表。

当我执行这条 SQL(其中 c.client_id = 1)时,一切似乎都很好:

explain  
select *
from client c
join file f using (client_id)
where c.client_id = 1;

使用分区剪枝,只扫描分区file_p1:

Nested Loop  (cost=0.00..3685.05 rows=100001 width=82)
  ->  Seq Scan on client c  (cost=0.00..1.02 rows=1 width=29)
        Filter: (client_id = 1)
  ->  Append  (cost=0.00..2684.02 rows=100001 width=57)
        ->  Seq Scan on file_p1 f  (cost=0.00..2184.01 rows=100001 width=57)
              Filter: (client_id = 1)

但是当我使用诸如“where c.client_name = 'test'”之类的where子句时,数据库会扫描所有分区并且无法识别,client_name“test”等于client_id 1:

explain  
select *
from client c
join file f using (client_id)
where c.client_name = 'test';

执行计划:

Hash Join  (cost=1.04..6507.57 rows=100001 width=82)
  Hash Cond: (f.client_id = c.client_id)
  ->  Append  (cost=0.00..4869.02 rows=200002 width=57)
        ->  Seq Scan on file_p1 f  (cost=0.00..1934.01 rows=100001 width=57)
        ->  Seq Scan on file_p4 f_1  (cost=0.00..1934.00 rows=100000 width=57)
        ->  Seq Scan on file_pdefault f_2  (cost=0.00..1.00 rows=1 width=556)
  ->  Hash  (cost=1.02..1.02 rows=1 width=29)
        ->  Seq Scan on client c  (cost=0.00..1.02 rows=1 width=29)
              Filter: ((name)::text = 'test'::text)

所以对于这个 SQL,文件表中的所有分区都会被扫描。

那么每个选择都应该使用表分区所在的列吗?数据库不能偏离分区修剪标准吗?


编辑: 添加一些信息:

过去,我大部分时间都在使用 Oracle 数据库。

执行计划会是这样的

  1. 对具有客户端名称的客户端表进行全表扫描以找出 client_id。
  2. 对文件表执行“PARTITION LIST”访问,其中 SQL Developer 声明 PARTITION_START = KEY 和 PARTITION_STOP = KEY 以指示在计算执行计划时不知道确切的分区,但访问只会对列表进行分区数,根据客户端表中的 client_id 计算得出。

这也是我在 Postgresql 中所期望的。

【问题讨论】:

  • 查询计划是在执行查询之前创建的,因此不能基于查询结果。换句话说,在查询运行之前,规划器无法知道client_name = 'test' 表示client_id = 1
  • 感谢您的评论,我添加了一些额外的信息,我希望这些信息来自 Oracle 数据库。

标签: postgresql partitioning database-partitioning pruning


【解决方案1】:

The documentation 表示可以进行动态分区修剪

(...) 在实际执行查询计划期间。也可以在此处执行分区修剪以使用仅在实际查询执行期间已知的值来删除分区。这包括来自子查询的值和来自执行时参数的值,例如来自参数化嵌套循环连接的值。

如果我理解正确,它适用于准备好的语句或带有子查询的查询,这些子查询提供分区键值作为参数。使用explain analyse查看动态剪枝(我的样本数据包含三个分区中的一百万行):

explain analyze
select *
from file
where client_id = (
    select client_id
    from client
    where client_name = 'test');

Append  (cost=25.88..22931.88 rows=1000000 width=14) (actual time=0.091..96.139 rows=333333 loops=1)
  InitPlan 1 (returns $0)
    ->  Seq Scan on client  (cost=0.00..25.88 rows=6 width=4) (actual time=0.040..0.042 rows=1 loops=1)
          Filter: (client_name = 'test'::text)
          Rows Removed by Filter: 2
  ->  Seq Scan on file_p1  (cost=0.00..5968.66 rows=333333 width=14) (actual time=0.039..70.026 rows=333333 loops=1)
        Filter: (client_id = $0)
  ->  Seq Scan on file_p2  (cost=0.00..5968.68 rows=333334 width=14) (never executed)
        Filter: (client_id = $0)
  ->  Seq Scan on file_p3  (cost=0.00..5968.66 rows=333333 width=14) (never executed)
        Filter: (client_id = $0)
Planning Time: 0.423 ms
Execution Time: 109.189 ms

请注意,对分区 p2 和 p3 的扫描是 never executed

回答您的确切问题,问题中描述的带有连接的查询中的分区修剪尚未在 Postgres 中实现(还没有?)

【讨论】:

  • 非常感谢!这完全回答了我的问题。使用子查询和执行解释分析相结合是关键。
猜你喜欢
  • 2020-05-07
  • 2011-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
相关资源
最近更新 更多