我在PostgreSQL 9.2中有一个大表,我将其分区为described in the manual。好吧......差不多!我的真实分区键不在分区表本身中,而是在连接表中,如下所示(简化):
-- 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
)包含point
和slice
的每个组合的行,每个组合都有一个复合键。我想在仅partition_date
的一部分关键列slice
上对其进行分区。当然,我的子表的检查约束不能直接包含它,所以我包括与slice.id
对应的所有partition_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)
答案 0 :(得分:4)
实现它的唯一方法是使查询动态化:
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
传递,则不会干扰查询。
答案 1 :(得分:3)
这个方案不会起作用。 constraint_exclusion
简单而愚蠢。它必须能够通过在规划期间检查查询来证明查询无法触及某些分区以排除它们。
目前不支持在查询执行期间排除分区。 Pg提供的基本分区支持有很大的改进空间,执行时约束排除只是可以使用工作的领域之一。
您的应用程序需要了解分区及其约束,并且需要显式连接仅需要的分区的联合。
在这种情况下,我不确定PostgreSQL甚至可以做你想做的事情。我想你希望它通过连接上的复合键来设置约束,断言由于查询指定了s.partition_date = '2013-07-23'
,并且对s.partition_date = '2013-07-23'
的所有切片ID的查询在slice_id >= 1234 AND slice_id <= 1278
范围内找到它们然后只扫描分区data_part_123
。
问题在于在规划时 PostgreSQL完全不知道s.partition_date = '2013-07-23
对应于特定范围的切片ID。它可能能够从相关统计数据中找出它,如果它保留它们,但表统计数据只是近似值,而不是分区所需的证明。
我怀疑您需要稍微对数据进行非规范化,如果您希望按每个slice.partition_date
行进行分区,则需要复制data
。您可以尝试确保不要让它们不同步,或者(我要做的)在UNIQUE
上创建slice(id, partition_date)
约束,然后从分区中添加FOREIGN KEY
引用data
slice
到{{1}},从而确保他们无法以不同的索引维护和插入费用为代价。