[英]PostgreSQL partitioning with joined table - partition constraint not used in query plan
我在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
)包含每個point
和slice
組合的行,每個組合都有一個復合鍵。 我想只在其中一個鍵列partition_date
,它是slice
一部分。 當然,我的子表上的檢查約束不能直接包含它,所以我包含與該partition_date
對應的所有slice.id
值的范圍,如下所示:
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)
實現它的唯一方法是使查詢動態化:
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
傳遞,它將不會干擾查詢。
這個方案不會起作用。 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。 它可能能夠從相關統計數據中找出它,如果它保留它們,但表統計數據只是近似值,而不是分區所需的證明 。
我懷疑你需要對數據進行非規范化,如果你想通過它進行分區,則在每個data
行中復制slice.partition_date
。 您可以嘗試確保不要使它們不同步,或者(我要做的)在slice(id, partition_date)
上創建UNIQUE
約束slice(id, partition_date)
然后將data
分區中的FOREIGN KEY
引用添加到slice
,從而使確保它們不會以一些額外的索引維護和插入成本為代價而失去同步。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.