postgresql Postgres:创建分区需要花费大量时间

nhn9ugyo  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(1)|浏览(184)

我有一个基于timestamp(daily)分区的表。该表也有一个默认分区。我们有一个cron作业,它创建新的空分区并删除旧分区。
由于一些错误,这个cron作业没有运行,我们最终在default分区中有很多数据。现在新分区的创建需要很多时间。我找到了以下解释
每当你创建一个新的分区时,它必须扫描默认分区,以确保默认分区中没有一行属于新创建的分区。
我理解这一点,并认为在默认分区上的分区键(时间戳)上创建一个索引会加快新分区的添加。但这也不起作用,它仍然需要大量的时间来添加新分区。
为什么索引没有帮助?下面的查询是即时的

select count(*) from partitioned_table_default where timestamp > (now() + interval '1 days');

字符串
编辑:我检查了pg_stat_activity来查看这个查询在做什么,它将等待事件显示为DataFileRead。我还看到来自数据库的IO数量激增。这表明它仍然在从默认分区阅读数据。
任何帮助是高度赞赏!

fae0ux8s

fae0ux8s1#

你可以在src/backend/partitioning/partbounds.c中看到check_default_partition_contents()中的相关代码:

/*
 * check_default_partition_contents
 *
 * This function checks if there exists a row in the default partition that
 * would properly belong to the new partition being added.  If it finds one,
 * it throws an error.
 */
void
check_default_partition_contents(Relation parent, Relation default_rel,
                                 PartitionBoundSpec *new_spec)
{
   [...]

    /*
     * If the existing constraints on the default partition imply that it will
     * not contain any row that would belong to the new partition, we can
     * avoid scanning the default partition.
     */
    if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
    {
        ereport(DEBUG1,
                (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
                                 RelationGetRelationName(default_rel))));
        return;
    }

字符串
因此,如果默认分区上有一个约束,意味着默认分区中没有任何行违反新的约束,则不需要扫描该分区。

/*
     * Scan the default partition and its subpartitions, and check for rows
     * that do not satisfy the revised partition constraints.
     */
    if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        all_parts = find_all_inheritors(RelationGetRelid(default_rel),
                                        AccessExclusiveLock, NULL);
    else
        all_parts = list_make1_oid(RelationGetRelid(default_rel));

    foreach(lc, all_parts)
    {
        [...]

        estate = CreateExecutorState();

        /* Build expression execution states for partition check quals */
        partqualstate = ExecPrepareExpr(partition_constraint, estate);

        econtext = GetPerTupleExprContext(estate);
        snapshot = RegisterSnapshot(GetLatestSnapshot());
        tupslot = table_slot_create(part_rel, &estate->es_tupleTable);
        scan = table_beginscan(part_rel, snapshot, 0, NULL);

        /*
         * Switch to per-tuple memory context and reset it for each tuple
         * produced, so we don't leak memory.
         */
        oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));

        while (table_scan_getnextslot(scan, ForwardScanDirection, tupslot))
        {
            econtext->ecxt_scantuple = tupslot;

            if (!ExecCheck(partqualstate, econtext))
                ereport(ERROR,
                        (errcode(ERRCODE_CHECK_VIOLATION),
                         errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
                                RelationGetRelationName(default_rel)),
                         errtable(default_rel)));

            ResetExprContext(econtext);
            CHECK_FOR_INTERRUPTS();
        }

        MemoryContextSwitchTo(oldCxt);
        table_endscan(scan);
        UnregisterSnapshot(snapshot);
        ExecDropSingleTupleTableSlot(tupslot);
        FreeExecutorState(estate);

        if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
            table_close(part_rel, NoLock);  /* keep the lock until commit */
    }
}


否则,PostgreSQL会在默认分区上执行顺序扫描(table_beginscan()/table_scan_getnextslot()/table_endscan())。
PostgreSQL甚至没有尝试使用索引扫描。我不认为这背后有什么深层次的原因,它只是没有实现。
解决方法是在默认分区NOT VALID上创建一个检查约束,就像修改后的分区约束一样。然后您可以使用ALTER TABLE ... VALIDATE CONSTRAINT ...来验证它,而无需锁定表。添加新分区后,再次删除约束。
我的一般建议是,如果您计划稍后添加更多分区,请不要创建默认分区。

相关问题