使用 plpgsql 脚本批量插入/删除行

Insert/delete rows in batches with plpgsql script

提问人:Bylaw 提问时间:3/29/2023 最后编辑:Bylaw 更新时间:3/29/2023 访问量:162

问:

我需要对具有 ~400M 条记录的表进行分区。由于数据库资源、WAL 大小等原因,它有几个陷阱和挫折。经过不同的方法,解决方案似乎是这样的:

  • 为作用域中的每个月创建子表,表示将来的分区
  • 在同一事务中将相关数据从父级插入到子级,并从父级中删除
  • 停止继承,将以前的子表作为分区附加到默认分区的新表

这样一来,停机时间几乎为零,因为查询原始(父)表将返回数据,即使它已经在子表中。插入/删除部分是通过脚本完成的,该脚本批量循环访问表。所有这些都在本地工作(数据要少得多),但实际表存在问题。

不知何故,在某些批处理中,删除的行要少得多,即使我用于收集批处理的公共表表达式对于两个命令都是相同的。由于这个原因,以及我出于某些原因多次运行脚本的事实,子项中存在重复的行(不幸的是,其中没有约束,因此可能会发生这种情况)。

现在,我需要对其进行重复数据删除,然后从父行中删除已迁移的行。即使在那之后,我也必须解决删除问题,这是我的实际问题:可能是什么问题?这两个命令是否以某种方式相互干扰?这是一个问题吗

起初,我认为只有日志具有欺骗性,但看到所有重复的数据表明,删除的行确实较少。

提前致谢!

脚本:(稍微简化一点,实际脚本也会循环几个月,但包括基本要素)

DO $$
    DECLARE
        batch_size INTEGER := 10000;
        off_set INTEGER := 0;
        rows_inserted INTEGER;
        rows_deleted INTEGER;
    BEGIN
        SELECT MIN(id) INTO off_set FROM ONLY parent_table WHERE inserttime >= ('2023-01-01') AND inserttime < ('2023-02-01');
        LOOP
            WITH parent_table_cte AS (
                SELECT * FROM parent_table
                WHERE inserttime >= ('2023-01-01')
                  AND inserttime < ('2023-02-12')
                  AND id >= off_set
                ORDER BY id ASC
                LIMIT batch_size
            )
            INSERT INTO child_table SELECT * FROM parent_table_cte;
            GET DIAGNOSTICS rows_inserted = row_count;
            RAISE info 'Inserted [%] rows into [child_table]!', rows_inserted;

            WITH parent_table_cte AS (
                SELECT * FROM pod.parent_table
                WHERE inserttime >= ('2023-01-01')
                  AND inserttime < ('2023-02-12')
                  AND id >= off_set
                ORDER BY id ASC
                LIMIT batch_size
            )
            DELETE FROM ONLY pod.parent_table USING parent_table_cte
            WHERE parent_table.id = parent_table_cte.id;
            GET DIAGNOSTICS rows_deleted = row_count;
            RAISE info 'Deleted [%] rows from [parent_table] in the range of (2023-01-01) - (2023-02-01)!', rows_deleted;

            IF rows_inserted = 0 THEN
                RAISE info e'No more rows to insert from range (2023-01-01) - (2023-02-01) into table: [child_table]\n';
                EXIT;
            END IF;

            RAISE info e'[%] rows were inserted and [%] were deleted in the batch ending at offset [%]\n', rows_inserted, rows_deleted, off_set + rows_inserted;

            COMMIT;

            off_set := off_set + batch_size;

            -- PERFORM pg_sleep(5);
        END LOOP;
    END;
$$;

更新

现在,当我浏览我的问题时,我注意到 CTE 不仅从父项中进行选择,这意味着子项也在查询中。这可能是问题所在,我会尽快检查。

PostgreSQL 继承 plpgsql 分区 postgres-14

评论

0赞 Frank Heikens 3/29/2023
您能否共享当前表、原始表、继承表和分区的 DDL?而这 4 亿条记录,以 GB 为单位的大小是多少?
0赞 Bylaw 3/29/2023
感谢您的回复!我更新了问题,但我认为我应该写一个实际的答案并将其标记为解决方案。我在那里解释了这个问题。

答:

0赞 Bylaw 3/29/2023 #1

当我再次浏览我的问题时,我发现了问题: CTE 使用父级的 SELECT 命令,而不使用 ONLY 关键字。这样,子表也会被查询。虽然删除只发生在父表中,但批处理也将包含来自父表和子表的数据,因此删除量正在减少,脚本变得越来越慢,因为子表一直在增长。