如何在postgres中对现有表进行分区?

Eva*_*eby 38 postgresql optimization partitioning

我想按日期范围对 100 万行以上的表进行分区。这通常是如何在不需要大量停机时间或冒丢失数据的风险的情况下完成的?以下是我正在考虑的策略,但欢迎提出建议:

  1. 现有的表是主表,子表是从它继承而来的。随着时间的推移,将数据从主表移动到子表,但会有一段时间,其中一些数据在主表中,一些在子表中。

  2. 创建一个新的主表和子表。在子表中创建现有表中的数据副本(因此数据将驻留在两个位置)。一旦子表拥有最新数据,更改所有插入以指向新的主表并删除现有表。

Eva*_*eby 33

由于 #1 需要在活动生产环境中将数据从 master 复制到 child,因此我个人选择了 #2(创建新 master)。这可以防止在原始表正在积极使用时对其造成干扰,如果有任何问题,我可以轻松删除新主表而不会出现问题并继续使用原始表。以下是执行此操作的步骤:

  1. 创建新的主表。

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建从 master 继承的孩子。

    CREATE TABLE child_2014 (
        CONSTRAINT pk_2014 PRIMARY KEY (id),
        CONSTRAINT ck_2014 CHECK ( dt_created < DATE '2015-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2014 ON child_2014 (dt_created);
    
    CREATE TABLE child_2015 (
        CONSTRAINT pk_2015 PRIMARY KEY (id),
        CONSTRAINT ck_2015 CHECK ( dt_created >= DATE '2015-01-01' AND dt_created < DATE '2016-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2015 ON child_2015 (dt_created);
    
    ...
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将所有历史数据复制到新的主表

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 暂时暂停对生产数据库的新插入/更新

  5. 将最新数据复制到新的主表

    INSERT INTO child_2015 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created >= '01/01/2015'::date AND dt_created < '01/01/2016'::date;
    
    Run Code Online (Sandbox Code Playgroud)
  6. 重命名表,以便 new_master 成为生产数据库。

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
    
    Run Code Online (Sandbox Code Playgroud)
  7. 将 INSERT 语句的函数添加到 old_master,以便将数据传递到正确的分区。

    CREATE OR REPLACE FUNCTION fn_insert() RETURNS TRIGGER AS $$
    BEGIN
        IF ( NEW.dt_created >= DATE '2015-01-01' AND
             NEW.dt_created < DATE '2016-01-01' ) THEN
            INSERT INTO child_2015 VALUES (NEW.*);
        ELSIF ( NEW.dt_created < DATE '2015-01-01' ) THEN
            INSERT INTO child_2014 VALUES (NEW.*);
        ELSE
            RAISE EXCEPTION 'Date out of range';
        END IF;
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
    
    Run Code Online (Sandbox Code Playgroud)
  8. 添加触发器以便在插入时调用函数

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
    
    Run Code Online (Sandbox Code Playgroud)
  9. 将约束排除设置为 ON

    SET constraint_exclusion = on;
    
    Run Code Online (Sandbox Code Playgroud)
  10. 在生产数据库上重新启用 UPDATES 和 INSERTS

  11. 设置触发器或 cron 以便创建新分区并更新函数以将新数据分配给正确的分区。代码示例参考这篇文章

  12. 删除 old_master_backup


小智 7

有一个名为 pg_pathman ( https://github.com/postgrespro/pg_pathman )的新工具可以自动为您执行此操作。

所以像下面这样的事情就可以了。

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
Run Code Online (Sandbox Code Playgroud)

  • 注意:该项目不再处于开发阶段:`pg_pathman ...不会被移植到 13 及更高版本...这里不会发生新的开发` (5认同)