【问题标题】:Postgres partition by weekPostgres 按周分区
【发布时间】:2013-04-17 00:20:13
【问题描述】:

我可以想象按日期(尤其是日志)的表分区是一种广泛使用的东西,但我无法为我的问题找到一个好的答案。

我想按周创建一个表分区(记录的数量很大,以使其每月一次)。它是每周一次的原因是我需要算法的数据,该算法将寻找日期作为过程的一部分。

我的问题是我希望它创建考虑到一周的分区并使用“典型”方法我必须手动创建它。像这样的。

    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 用于分区的命名或开始 datadYYYYMMDD。

我想在插入时检查分区是这样的:

 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

如果分区不存在,则在插入之前创建它,但考虑到插入的记录数量,这会效率低下。

我的另一种选择是每周运行一个外部进程来创建此分区,但我试图避免这种情况。

是否有我缺少的更有效的解决方案,例如用于每月检查?

【问题讨论】:

标签: sql postgresql partitioning week-number


【解决方案1】:

好的,让我们自己创建一个函数来处理它!

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,或帮助触发器完成所有这些工作,请告诉我。

【讨论】:

    【解决方案2】:

    您可以使用 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

    【讨论】:

    • +1 因为它对大多数步骤都很有用。如果它们不存在,仍然不能完全回答创建分区的主要部分。
    【解决方案3】:

    如果有帮助,我编写了一个 postgres 触发器来创建一个按天自动分区的表。创建继承表会自动发生。要按周进行分区,您必须更改日期->字符串映射,仅此而已。

    https://github.com/bitdivine/pg_day_partitions

    【讨论】:

      猜你喜欢
      • 2022-10-21
      • 2015-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-24
      • 1970-01-01
      • 1970-01-01
      • 2019-10-01
      相关资源
      最近更新 更多