postgresql Postgres分区(按周)

nle07wnf  于 2023-04-20  发布在  PostgreSQL
关注(0)|答案(4)|浏览(197)

我可以想象按日期(特别是日志)进行表分区是一种广泛使用的方法,但我无法找到一个很好的答案来解决我的问题。
我想按周创建一个表分区(记录的数量太大,以至于无法按月创建)。按周创建的原因是我需要一个算法的数据,该算法将查找日期作为该过程的一部分。
我的问题是,我希望它创建分区考虑到一周,并使用“典型”的方法,我将不得不手动创建它。

CREATE TABLE measurement_y2013w01 (
CHECK ( logdate >= DATE '2013-01-07' AND logdate < DATE '2013-01-14' ) 
    ) INHERITS (measurement);

    CREATE TABLE measurement_y2006w02 (
CHECK ( logdate >= DATE '2013-01-14' AND logdate < DATE '2013-01-21' ) 
    ) INHERITS (measurement);

...

但我希望它是自动创建的,我不希望每周都要一个一个地创建分区。
我的命名规则是yyyyywWW,用于命名分区或启动数据YYYYMMDD。
我想在插入时检查分区使用这样的东西:

SELECT
    nmsp_parent.nspname AS parent_schema,
    parent.relname      AS parent,
    nmsp_child.nspname  AS child,
    child.relname       AS child_schema
FROM pg_inherits
    JOIN pg_class parent        ON pg_inherits.inhparent = parent.oid
    JOIN pg_class child         ON pg_inherits.inhrelid   = child.oid
    JOIN pg_namespace nmsp_parent   ON nmsp_parent.oid  = parent.relnamespace
    JOIN pg_namespace nmsp_child    ON nmsp_child.oid   = child.relnamespace

如果分区不在那里,则在插入之前创建它,但是考虑到插入的记录的数量,这看起来效率很低。
我的另一个选择是让一个外部进程每周运行一次来创建这个分区,但我试图避免这种情况。
有没有一个更有效的解决方案,我错过了,这是用来,例如,每月检查?

vqlkdk9b

vqlkdk9b1#

好了,让我们创建一个函数来处理这个问题!

CREATE OR REPLACE FUNCTION create_partition_measurement( DATE, DATE )
returns void AS $$
DECLARE
create_query text;
BEGIN
    FOR create_query IN SELECT

        'CREATE TABLE measurement_' || TO_CHAR( d, 'YYYY_WW' ) || ' (
        CHECK ( EXTRACT(YEAR FROM logdate) = EXTRACT(YEAR FROM TIMESTAMP ''' || d || ''') AND EXTRACT(WEEK FROM logdate) = EXTRACT(WEEK FROM TIMESTAMP ''' || d || ''') )
        ) INHERITS (measurement);'

        FROM generate_series( $1, $2, '1 week' ) AS d LOOP

        EXECUTE create_query;

    END LOOP;
END;
$$
language plpgsql;

有了这个,你现在可以调用类似

SELECT create_partition_measurement ('2015/02/08','2015/03/01');

并创建分区。自动化的第一步完成。
我在自己的数据库中使用以下测试表测试了所有这些:

CREATE TABLE measurement (id INT NOT NULL PRIMARY KEY, id_user INT NOT NULL, logdate TIMESTAMP NOT NULL);

使用上面的函数创建分区后,我能够:

  • 将数据插入正确的分区;
  • 尝试将一周的数据插入另一周的分区时出错;
  • 自动创建分区数周,和;
  • 如果我尝试创建一个已经存在一个星期的分区,会得到一个错误。

应该够了吧=)
现在,关于自动化创建过程。我使用一个简单的cron脚本每个月为我调用这个函数,并使用几个监控脚本来确保一切正常工作。cron看起来像这样:

0 0 1 * * /var/lib/postgresql/create_partitions.sh

脚本将使用当前日期和当前日期+ 1个月运行命令。它看起来像这样:

startDate=`date "+%Y/%m/%d"`
endDate=`date -u -d "+1 month -$(date +%d) days" "+%Y/%m/%d"
psql -U "$dbUser" -w -c "SELECT create_partition_measurement('$startDate','$endDate');"

如果您需要在表中包含索引、PK、FK,或者帮助使用触发器来使所有这些工作正常进行,请告诉我。

jbose2ul

jbose2ul2#

您可以使用date_trunc函数将数据值四舍五入到一周的第一天,分区命名可以使用年份和年份中的星期号YYWW

CREATE TABLE measurement_1301 (
    CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-07') )
    INHERITS (measurement);

CREATE TABLE measurement_1302 (
    CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-14') )
    INHERITS (measurement);

CREATE TABLE measurement_1303 (
    CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-21') )
    INHERITS (measurement);

-- Default partition:
CREATE TABLE measurement_default () INHERITS (measurement);

对于分区名称生成,使用to_char( logdate::date, 'YYWW'),如果您喜欢yyyyywWWto_char( logdate::date, '"y"YYYY"w"WW')
要检查现有的分区,您可以使用非常简单的查询:

SELECT relname FROM pg_class
WHERE relname ~ '^measurement_[0-9]{4}$'
ORDER BY RIGHT(relname,4) DESC

数据路由触发器插入到适当的分区中,如果给定周没有分区,则回退到默认值。

CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN

    IF to_char( NEW.logdate::date, 'YYWW') = '1301' THEN
        INSERT INTO measurement_1301 VALUES (NEW.*);
    ELSIF to_char( NEW.logdate::date, 'YYWW') = '1302' THEN
        INSERT INTO measurement_1302 VALUES (NEW.*);
    ELSIF to_char( NEW.logdate::date, 'YYWW') = '1303' THEN
        INSERT INTO measurement_1303 VALUES (NEW.*);
    -- ...
    ELSE
      INSERT INTO measurement_default VALUES (NEW.*);
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER measurement_insert_tr BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger()

您需要预先创建所有分区,或者您可以使用默认分区,并不时地重新分区存储在那里的数据,创建新的分区并调整插入触发器。
PS你可以在这里找到基于触发器的分区解决方案的脚本http://hg.nowitworks.eu/postgresql-triggers-and-partitions

dxpyg8gm

dxpyg8gm3#

如果有帮助的话,我写了一个postgres触发器来创建一个按天自动分区的表。创建继承的表是自动发生的。要按周分区,你必须更改day-〉stringMap,就是这样。
https://github.com/bitdivine/pg_day_partitions

js81xvg6

js81xvg64#

您可以创建一个插入触发器,在分区不存在时创建该分区:

CREATE OR REPLACE FUNCTION trg_measurement_partition()
    RETURNS trigger AS
$func$
DECLARE
    _tbl text := to_char(NEW.logdate, '"measurement_y"IYYY_"w"IW');
    _rec_date date := NEW.logdate::date;
    _min_date date := date_trunc('week', NEW.logdate)::date;
    _max_date date := date_trunc('week', NEW.logdate)::date + 7;
    _min_live date := date_trunc('week', NEW.logdate)::date - 1;
BEGIN
IF NOT EXISTS (
    SELECT 1
    FROM   pg_catalog.pg_class c
    JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
    WHERE  n.nspname = 'public'
    AND    c.relname = _tbl
    AND    c.relkind = 'r') THEN
    EXECUTE format('CREATE TABLE IF NOT EXISTS %I
                    (CHECK (logdate::date >= to_date(%L, ''yyyy-mm-dd'') AND
                            logdate::date <  to_date(%L, ''yyyy-mm-dd'')),
                        LIKE measurement INCLUDING INDEXES )
                        INHERITS (measurement)'
            , _tbl
            , to_char(_min_date, 'YYYY-MM-DD')
            , to_char(_max_date, 'YYYY-MM-DD')
            );
    IF (_rec_date >= _min_date) and (_rec_date < _max_date) THEN
        EXECUTE format('CREATE OR REPLACE VIEW v_measurement AS (
                            SELECT * FROM measurement
                            WHERE (logdate::date >= to_date(%L, ''yyyy-mm-dd'') AND
                                logdate::date <  to_date(%L, ''yyyy-mm-dd''))
                        )'
                , to_char(_min_live, 'YYYY-MM-DD')
                , to_char(_max_date, 'YYYY-MM-DD')
                );
    END IF;
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(_tbl) || ' VALUES ($1.*)'
USING NEW;
RETURN NULL;
END
$func$ LANGUAGE plpgsql SET search_path = public;

DROP TRIGGER IF EXISTS ins_measurement on measurement;
CREATE TRIGGER ins_measurement
BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE trg_measurement_partition();

触发器执行以下任务:
1.检查表是否存在(查询pg_catalog非常快)。
1.计算记录logdate的相应表的名称
1.如果该名称的分区表不存在,则为相应的周创建一个新的分区表(CREATE TABLE ... LIKE measurement INCLUDING INDEXES INHERITS (measurement)
1.为上周的数据创建或更新一个名为v_measurement的视图。
答案假设存在一个空的measurement表作为模板,并且logdate字段是timestamp。在该表中,最好为logdate字段创建一个索引,该字段的类型为:

drop index if exists measurement_logdate_part_idx;

CREATE UNIQUE INDEX measurement_logdate_part_idx
ON measurement((logdate::date) DESC NULLS LAST, id);

代码改编自另一个类似的stackoverflow响应。

相关问题