在PostgreSQL 16 / PolarDB-PG 16中,如何在分区表的父表上并发创建索引?
我一直在使用 PolarDB for PostgreSQL 14 (PolarDB-PG 14),在其中我能够在分区表的 父表 上运行 CREATE INDEX CONCURRENTLY,并且没有出错。
最近,我升级到了 PolarDB-PG 16,发现同样的语句在以下错误信息下失败:
ERROR: cannot create index on partitioned table "measurement" concurrently
我在开源的PostgreSQL 16上使用 dbfiddle 测试了这一行为,结果相同。
下面是一个最小可复现的示例:
DROP TABLE IF EXISTS measurement;
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
-- create partitions
CREATE TABLE measurement_y2020
PARTITION OF measurement
FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');
CREATE TABLE measurement_y2021
PARTITION OF measurement
FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
-- Try to create index concurrently on the parent table
CREATE INDEX CONCURRENTLY measurement_logdate_idx ON measurement (logdate);
-- View the index
\d+ measurement_logdate_idx
在PolarDB-PG 14中,这条命令可以成功执行,但在PostgreSQL / PolarDB-PG 16中,则会得到上面的错误。
问题:
在PostgreSQL 16(或PolarDB-PG 16)中,是否有办法在分区表的 父表 上以并发方式创建索引?
解决方案
父表其实并不是真正的表。你对它所做的任何操作,实际上都会被路由到承载实际数据的底层分区,因此默认对它执行 create index on the_parent 时,会尝试级联到所有分区。
你可以 create index on only the_parent 但这会得到一个在为所有底层分区创建并附加完整的索引集合之前保持无效的索引。
实际上可以在很大程度上并发地创建整个索引结构:你可以使用 dblink 打开并行会话,在其中的每一个会话里同时从内部创建分区索引。
db<>fiddle演示
create index ptpx on ONLY your_partitioned_table_parent(v);
create extension if not exists dblink;
create temp table target_partitions on commit preserve rows
as
select dblink_connect(concat_ws('_',child.name,'ptpx',row_number()over()),'')
,concat_ws('_',child.name,'ptpx',row_number()over()) as new_index_name
,child.schema as child_schema
,child.name as child
from pg_inherits as i
cross join lateral pg_get_object_address('table', array[i.inhrelid::regclass::text],'{}')as a
cross join lateral pg_identify_object(a.classid, a.objid, a.objsubid) as child
where i.inhparent = 'your_schema.your_partitioned_table_parent'::regclass;
select dblink_exec( new_index_name--my connections are named the same as indices
,format($q$ CREATE INDEX CONCURRENTLY %1$I ON %2$I.%3$I(v); $q$
,new_index_name
,child_schema
,child ))
from target_partitions;
select dblink_disconnect(new_index_name)
from target_partitions;
由于不能对同一个父索引并发执行多个 alter 命令,使用一个 PL/pgSQL loop 就足以动态地逐一将它们附加。
do $d$
declare n text;
begin
for n in select new_index_name from target_partitions loop
execute format($q$ ALTER INDEX ptpx ATTACH PARTITION %1$I; $q$
,new_index_name);
end loop;
end $d$ language plpgsql;
这类做法也是文档在 5.12.2.2. Partition Maintenance 中所提出的,即讨论该限制的部分:
然而,在分区表上创建新索引时有一个限制,那就是不能使用
CONCURRENTLY限定符,这可能导致长时间锁定。为避免这种情况,你可以对分区表使用CREATE INDEX ON ONLY,这将创建一个标记为无效的新索引,阻止对现有分区的自动应用。相反,可以随后在每个分区上分别创建索引,使用CONCURRENTLY,并通过ALTER INDEX ... ATTACH PARTITION将它们附加到父表上的分区索引。等到所有分区的索引都附加到父索引后,父索引将自动标记为有效。
在PL/pgSQL例程中,concurrently 选项会被拒绝,即使在动态构造的SQL语句中使用也一样,这也是需要像这样的变通办法的原因。