如何在插入特定分区之前减少检查数量?

时间:2019-05-17 12:28:09

标签: postgresql triggers partitioning

数据库中的表根据数据的时间戳(即tablename_y2019w20)按周进行分区。但是,当引入分区时,postgres开始占用太多的CPU时间。

通过运行

收集CPU使用情况统计信息

SELECT substring(query, 1, 50) AS short_query, round(total_time::numeric, 2) AS total_time, calls, rows, round(total_time::numeric / calls, 2) AS avg_time, round((100 * total_time / sum(total_time::numeric) OVER ())::numeric, 2) AS percentage_cpu FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20;

表明瓶颈是触发函数(见下文)中的语句SELECT NOT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=tablename)每次插入都会执行。

例如,每秒插入18个元素的EXPLAIN ANALYZE看起来像这样:

Planning time: 0.787 ms
 Trigger before_insert_data_trigger: time=253.374 calls=18
 Execution time: 254.161 ms

但是,由于我们每周只需要创建一次分区表,因此大多数情况下所有语句都是无用的。但是我无法事先从postgres外部创建分区。

是否可以安排在postgres中创建分区,例如仅在每个星期日进行创建?

这里是函数和各自的触发器:

CREATE OR REPLACE FUNCTION data_insert_child_date()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
                DECLARE
                    match data."timestamp"%TYPE;
                    checks TEXT;
                    tablename_parent text := "data";
                    tablename text;
                BEGIN
                    IF NEW."timestamp" IS NULL THEN
                        tablename := tablename_parent||'_null';
                        checks := '"timestamp" IS NULL';
                    ELSE
                         match := DATE_TRUNC('week', NEW."timestamp");
                        tablename := tablename_parent||'_' || TO_CHAR(NEW."timestamp", '"y"IYYY"w"IW');
                        checks := '"timestamp" >= ''' || match || ''' AND "timestamp" < ''' || (match + INTERVAL '1 week') || '''';
                    END IF;

                    IF NOT EXISTS(
                        SELECT 1 FROM information_schema.tables WHERE table_name=tablename)
                    THEN
                        BEGIN
                            EXECUTE 'CREATE TABLE part.' || tablename || ' (
                                CHECK (' || checks || '),
                                LIKE "data" INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES
                            ) INHERITS (part."'||tablename_parent||'");

                        ';
                        EXCEPTION WHEN duplicate_table THEN
                            -- pass
                        END;
                    END IF;

                    EXECUTE 'INSERT INTO part.' || tablename || ' VALUES (($1).*);' USING NEW;
                    RETURN NEW;
                END;
            $function$;

和触发器

CREATE TRIGGER before_insert_data_trigger BEFORE INSERT
    ON data
    FOR EACH ROW
    EXECUTE PROCEDURE data_insert_child_date();

1 个答案:

答案 0 :(得分:0)

时间表?

您只能使用cron或pgagent之类的外部工具进行调度,并且在需要此分区之前必须这样做,或者确保在创建cronjob或pgagent作业(或任何其他方法)之前,没有任何东西可以插入数据。就像您提到的那样,假设您无法预先创建分区。但是随后您仍然必须为所有表创建它们,或者以某种方式找出需要它们的表(例如将当天的数据插入到父表中,然后在一天或一周结束时将其移至分区)。

仅在星期日检查

您可以将分区检查限制为仅星期日:

IF extract(dow FROM current_date) = 7 /*maybe = 0 in US*/ AND NOT EXISTS(..

但是,如果由于某种原因(星期天没有数据,服务器关闭)在星期天不执行该触发器,那么一旦星期一到来,您就不会在该特定星期进行分区。

优化分区检查

如果您不必使用information_schema.tables,则可以使检查更快。在其中有〜100000行(索引,表,视图等)的目录上,检查我的计算机上是否存在一张表大约需要100毫秒,而对pg_catalog.pg_class进行的同一检查直接需要不到1毫秒。

--pg 9.5 and up
SELECT 1 FROM pg_class
WHERE relname = 'table_name'
  AND relnamespace = 'schema_name'::regnamespace;

--pg 9.4
SELECT 1 FROM pg_class
WHERE relname = 'table_name'
  AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_name');

我做什么

我个人认为我会每隔几个月就提前创建几个分区。这就是我目前的雇主所拥有的。 当然,最好升级到PG 11,但是我知道这可能不是一个选择。