PostgreSQL 16/PolarDB-PG 16中如何在分区表父表上并发创建索引?
PostgreSQL 16/PolarDB-PG 16中如何在分区表父表上并发创建索引?
你遇到的这个问题其实是PostgreSQL 16之后官方特意调整的行为——PG14及之前的版本因为实现逻辑的原因,允许直接在分区表父表上用CREATE INDEX CONCURRENTLY,但PG16开始官方收紧了这个限制。毕竟分区表的索引本质是每个子分区独立维护的,父表只是逻辑上的聚合,在父表层面做并发建索引很难协调所有分区的并发流程,容易出现一致性问题,所以直接禁止了这种操作。
不过别担心,我们可以用「分而治之」的方式达到同样的无锁(低锁)建索引效果,下面是具体的解决方案:
方案一:手动给每个分区并发建索引,再关联父表
这是最直接的方式,思路是先给每个子分区单独执行CREATE INDEX CONCURRENTLY(这样不会阻塞分区的读写),然后在父表上创建分区索引(这个操作只是元数据层面的关联,速度极快,几乎不锁表)。
具体代码步骤:
-- 1. 逐个给分区创建并发索引 CREATE INDEX CONCURRENTLY measurement_y2020_logdate_idx ON measurement_y2020 (logdate); CREATE INDEX CONCURRENTLY measurement_y2021_logdate_idx ON measurement_y2021 (logdate); -- 2. 在父表上创建分区索引(无需加CONCURRENTLY,因为只是关联已有的分区索引) CREATE INDEX measurement_logdate_idx ON measurement (logdate);
执行完成后,你可以用\d+ measurement查看,父表的measurement_logdate_idx会自动关联所有分区的对应索引,查询时会正常使用这个索引,而且整个过程中分区的读写操作不会被长时间阻塞。
方案二:自动遍历所有分区并发建索引(适合分区较多的场景)
如果你的分区表有很多子分区,手动逐个执行太麻烦,可以用PL/pgSQL写个小脚本自动处理:
DO $$ DECLARE partition_rec RECORD; BEGIN -- 遍历所有属于measurement父表的子分区 FOR partition_rec IN ( SELECT inhrelid::regclass AS partition_name FROM pg_inherits JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_class parent ON pg_inherits.inhparent = parent.oid WHERE parent.relname = 'measurement' ) LOOP -- 给每个分区动态创建并发索引 EXECUTE format( 'CREATE INDEX CONCURRENTLY %I ON %I (logdate)', partition_rec.partition_name || '_logdate_idx', partition_rec.partition_name ); END LOOP; END $$; -- 最后关联父表索引 CREATE INDEX measurement_logdate_idx ON measurement (logdate);
这个脚本会自动找出所有属于measurement的子分区,逐个创建并发索引,之后再关联父表,全程无需手动干预。
注意事项
- 父表创建索引时不要加CONCURRENTLY:因为这个操作只是把已有的分区索引关联到父表,不需要扫描数据,速度极快,也不会锁表。
- 新增分区的后续处理:如果之后给父表新增了子分区,需要手动给新分区创建对应的并发索引,然后执行
ALTER INDEX measurement_logdate_idx ATTACH PARTITION measurement_y2022_logdate_idx;把新分区的索引关联到父表;或者在创建新分区时用INDEXES子句自动创建索引(但如果要并发的话还是得单独执行CREATE INDEX CONCURRENTLY)。 - 唯一索引的情况:如果要创建唯一分区索引,需要确保索引包含分区键(你的例子中
logdate就是分区键,刚好符合要求),同样按照上述步骤,先给分区建唯一并发索引,再在父表创建唯一分区索引即可。
为什么PG16禁止直接在父表用CONCURRENTLY?
简单来说,分区表的索引是「分区级」的,每个分区的索引都是独立的数据库对象。在父表上执行CREATE INDEX CONCURRENTLY需要协调所有分区的并发建索引流程,这个过程逻辑复杂,容易出现死锁、数据不一致等问题,所以PG16官方直接禁止了这种操作,转而推荐「先分区并发建索引,再关联父表」的更安全的方式。




