带有联接表的PostgreSQL分区-查询计划中未使用分区约束

带有联接表的PostgreSQL分区-查询计划中未使用分区约束,sql,performance,postgresql,database-design,partitioning,Sql,Performance,Postgresql,Database Design,Partitioning,我在PostgreSQL 9.2中有一个大表,我已经将其分区为。好。。。几乎!我的实际分区键不在分区表本身中,而是在一个联接表中,如下所示(简化): 要分区的表(数据)包含点和切片的每个组合的行,每个组合都有一个复合键。我只想在一个关键列上对它进行分区,partition\u date,它是切片的一部分。当然,我的子表上的check约束不能直接包含它,因此我将包含与该分区日期对应的所有slice.id值的范围,如下所示: ALTER TABLE data_part_123 ADD CONSTRA

我在PostgreSQL 9.2中有一个大表,我已经将其分区为。好。。。几乎!我的实际分区键不在分区表本身中,而是在一个联接表中,如下所示(简化):

要分区的表(
数据
)包含
切片
的每个组合的行,每个组合都有一个复合键。我只想在一个关键列上对它进行分区,
partition\u date
,它是
切片的一部分。当然,我的子表上的check约束不能直接包含它,因此我将包含与该分区日期对应的所有
slice.id
值的范围,如下所示:

ALTER TABLE data_part_123 ADD CONSTRAINT ck_data_part_123
    CHECK (slice_id >= 1234 AND slice_id <= 1278);
Aggregate  (cost=539243.88..539243.89 rows=1 width=0)
  ->  Hash Join  (cost=8.88..510714.02 rows=11411945 width=0)
        Hash Cond: (d.slice_id = s.id)
        ->  Append  (cost=0.00..322667.41 rows=19711542 width=4)
              ->  Seq Scan on data d  (cost=0.00..0.00 rows=1 width=4)
              ->  Seq Scan on data_part_123 d  (cost=0.00..135860.10 rows=8299610 width=4)
              ->  Seq Scan on data_part_456 d  (cost=0.00..186807.31 rows=11411931 width=4)
        ->  Hash  (cost=7.09..7.09 rows=143 width=4)
              ->  Seq Scan on slice s  (cost=0.00..7.09 rows=143 width=4)
                    Filter: (partition_date = '2013-07-23 00:00:00'::timestamp without time zone)
我可以在查询计划中看到,它仍然扫描所有子表。我尝试过用几种方法重写查询,包括CTE和sub-select,但都没有用

有没有办法让计划者“理解”我的分区方案?我真的不想在
data
表中复制分区键数百万次

查询计划如下所示:

ALTER TABLE data_part_123 ADD CONSTRAINT ck_data_part_123
    CHECK (slice_id >= 1234 AND slice_id <= 1278);
Aggregate  (cost=539243.88..539243.89 rows=1 width=0)
  ->  Hash Join  (cost=8.88..510714.02 rows=11411945 width=0)
        Hash Cond: (d.slice_id = s.id)
        ->  Append  (cost=0.00..322667.41 rows=19711542 width=4)
              ->  Seq Scan on data d  (cost=0.00..0.00 rows=1 width=4)
              ->  Seq Scan on data_part_123 d  (cost=0.00..135860.10 rows=8299610 width=4)
              ->  Seq Scan on data_part_456 d  (cost=0.00..186807.31 rows=11411931 width=4)
        ->  Hash  (cost=7.09..7.09 rows=143 width=4)
              ->  Seq Scan on slice s  (cost=0.00..7.09 rows=143 width=4)
                    Filter: (partition_date = '2013-07-23 00:00:00'::timestamp without time zone)

这个计划行不通<代码>约束\u排除
既简单又愚蠢。它必须能够通过在规划期间检查查询来证明查询不能触及某些分区以排除它们

此时不支持在查询执行期间排除分区。Pg提供的基本分区支持有很大的改进空间,执行时间约束排除只是可以使用工作的领域之一

您的应用程序将需要了解分区及其约束,并且只需要在所需分区的并集上显式联接


在这种情况下,我甚至不确定PostgreSQL如何能够实现您想要的功能。我猜您希望它通过连接上的复合关键点投影约束,断言由于查询指定了
s.partition\u date='2013-07-23'
,并且使用
s.partition\u date='2013-07-23'
对所有切片id进行查询时,会在
slice\u id>=1234和slice\u id范围内找到它们,因此实现该查询的唯一方法是使其成为动态的:

create function select_from_data (p_date date)
returns setof data as $function$

declare
    min_slice_id integer,
    max_slice_id integer;

begin
    select min(slice_id), max(slice_id)
    into min_slice_id, max_slice_id
    from slice
    where partition_date = p_date;

return query execute
    $dynamic$
        select *
        from data
        where slice_id between $1 and $2
    $dynamic$
    using min_slice_id, max_slice_id;

end;
$function$ language plpgsql;
这将为给定日期构建具有适当切片范围的查询,并在运行时规划查询,此时规划器将获得检查确切分区所需的信息

要使函数更通用,而不丧失计划员在运行时获取信息的能力,请使用过滤器中的
或参数为null
构造

create function select_from_data (
    p_date date,
    value_1 integer default null,
    value_2 integer default null
)
returns setof data as $function$

declare
    min_slice_id integer,
    max_slice_id integer;

begin
    select min(slice_id), max(slice_id)
    into min_slice_id, max_slice_id
    from slice
    where partition_date = p_date;

return query execute
    $dynamic$
        select *
        from data
        where
            slice_id between $1 and $2
            and (some_col = $3 or $3 is null)
            and (another_col = $4 or $4 is null)
    $dynamic$
    using min_slice_id, max_slice_id, value_1, value_2;

end;
$function$ language plpgsql;

现在,如果某个参数被传递为
null
,它将不会干扰查询。

感谢您的详细解释,这是有意义的。正如你所说,关键是约束只在计划时间内被考虑-我没有意识到这一点。一个有趣的想法+1,但我不确定我是否会这样做。这只考虑了右分区,当从中选择所有行时,它更快。但是,当添加进一步的过滤器(在其他列上)时,速度要慢得多。我相信这是因为plpgsql函数对优化器来说是不透明的,所以它会从函数中返回分区中大约500万行,然后对它们进行过滤。@EM您到底是如何添加进一步的过滤器的?函数内部?如果是这样,说明你是如何做到的,因为这可能会对规划者的愿景产生破坏性影响。请注意,您可以筛选函数返回的集合:
select*from select\u from\u data('2013-07-23'::date),其中另一个\u列=某个\u值
Yes,我在函数外部进行了筛选,如您的示例中所示-这是比较慢的。函数内部的过滤应该很快,但这当然需要为每个查询编写一个单独的函数(或将SQL作为字符串传递给函数)。@EM我使用函数的通用版本进行了更新,但我仍然希望看到您使用其
explain
输出尝试的查询。我无法确定这是可怕的还是可怕的,但无论如何+1。