4

我在 PostgreSQL 9.2 中有一个大表,我按照手册中的描述进行了分区。嗯……差不多!我真正的分区键不在分区表本身中,而是在连接表中,如下所示(简化):

-- millions to tens of millions of rows
CREATE TABLE data
(
  slice_id integer NOT NULL,
  point_id integer NOT NULL,
  -- ... data columns ...,
  CONSTRAINT pk_data PRIMARY KEY (slice_id, point_id),
  CONSTRAINT fk_data_slice FOREIGN KEY (slice_id) REFERENCES slice (id)
  CONSTRAINT fk_data_point FOREIGN KEY (point_id) REFERENCES point (id)
)

-- hundreds to thousands of rows
CREATE TABLE slice
(
  id serial NOT NULL,
  partition_date timestamp without time zone NOT NULL,
  other_date timestamp without time zone NOT NULL,
  int_key integer NOT NULL
  CONSTRAINT pk_slice PRIMARY KEY (id)
)

-- about 40,000 rows
CREATE TABLE point
(
  -- ... similar to "slice" ...
)

要分区的表 ( ) 包含和data的每个组合的行,每个组合都有一个复合键。我只想在一个键列上对其进行分区,它是. 当然,我的子表上的检查约束不能直接包含它,所以我包含与that 对应的所有值的范围,如下所示:pointslicepartition_datesliceslice.idpartition_date

ALTER TABLE data_part_123 ADD CONSTRAINT ck_data_part_123
    CHECK (slice_id >= 1234 AND slice_id <= 1278);

这一切都适用于插入数据。但是,查询不使用上述 CHECK 约束。例如。

SELECT *
FROM data d
JOIN slice s ON d.slice_id = s.id
WHERE s.partition_date = '2013-07-23'

我可以在查询计划中看到这仍然会扫描所有子表。我尝试以多种方式重写查询,包括 CTE 和子选择,但这并没有帮助。

有什么办法可以让规划者“理解”我的分区方案?我真的不想在data表中复制分区键数百万次。

查询计划如下所示:

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)
4

2 回答 2

5

实现它的唯一方法是使查询动态化:

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;

这将使用给定日期的适当切片范围构建查询,并将在运行时计划它,此时计划程序将拥有检查确切分区所需的信息。

为了使函数更通用而不失去规划器在运行时获取信息的能力,请使用or parameter is 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它不会干扰查询。

于 2013-07-23T11:34:06.003 回答
3

这个方案是行不通的。constraint_exclusion简单而愚蠢。它必须能够通过在计划期间检查查询来证明查询不能触及某些分区以排除它们。

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

您的应用程序将需要了解分区及其约束,并且需要明确加入仅需要的分区的联合。

在这种情况下,我不确定 PostgreSQL 怎么能做你想做的事。我猜你希望它通过连接上的复合键来投射约束,断言由于查询指定s.partition_date = '2013-07-23'并且对所有切片 ID 的查询s.partition_date = '2013-07-23'在范围内找到它们,那么应该只扫描slice_id >= 1234 AND slice_id <= 1278分区。data_part_123

问题在于,在规划时PostgreSQL 完全不知道s.partition_date = '2013-07-23对应于特定范围的切片 ID。如果保留它们,它可能能够从相关统计信息中找出它,但表统计信息只是近似值,而不是分区所需的证明。

我怀疑您需要对数据进行一些非规范化处理,如果您希望按它进行分区,则slice.partition_date在每一行中复制。data您可以尝试确保不让它们不同步,或者(我会做的)创建一个UNIQUE约束,然后从into的分区slice(id, partition_date)添加一个FOREIGN KEY引用,从而确保它们不会以不同步为代价一些额外的索引维护和插入成本。dataslice

于 2013-07-23T10:48:10.047 回答