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


19

我想按日期范围对具有1M +行的表进行分区。通常如何做到这一点而又不需要太多的停机时间或冒着丢失数据的风险?这是我正在考虑的策略,但可以提出一些建议:

  1. 现有表是主表,子表继承自该表。随着时间的流逝,数据会从主数据移到子数据,但是一段时间后,某些数据将在主表中,而某些数据将在子表中。

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


1
这是我的想法:如果表具有datetime列->创建新的master +新的child->将新数据插入NEW + OLD(例如:datetime = 2015-07-06 00:00:00)->从OLD复制到NEW base on time列(其中:datetime <2015-07-06 00:00:00)->重命名表->将插入更改为NEW否则->为在主数据库上插入/更新(插入/更新新数据-创建“分区触发器” >移至子级,因此会将新数据插入到子级中)->更新master,触发器会将数据移至子级。
Luan Huynh

@Innnh,因此建议您选择第二个选项,但是一旦复制完数据,请删除旧表并重命名新表,使其具有与旧表相同的名称。那正确吗?
伊万·阿普比

将新表重命名为旧表,但是您应该保留旧表,直到完全可以使用新的流分区表为止。
Luan Huynh

2
对于仅有的几百万行,我认为实际上没有必要进行分区。为什么您认为需要它?您要解决什么问题?
a_horse_with_no_name

1
@EvanAppleby DELETE FROM ONLY master_table是解决方案。
dezso 2015年

Answers:


21

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

  1. 创建新的主表。

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. 创建从母版继承的子代。

    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);
    
    ...
  3. 将所有历史数据复制到新的主表

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  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;
  6. 重命名表,以便new_master成为生产数据库。

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  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;
  8. 添加触发器,以便在INSERTS上调用该函数

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. 将约束排除设置为“开”

    SET constraint_exclusion = on;
  10. 在生产数据库上重新启用UPDATES和INSERTS

  11. 设置触发器或cron,以便创建新分区并更新功能,以将新数据分配给正确的分区。参考本文以获得代码示例

  12. 删除old_master_backup


1
不错的文章。如果这实际上会使您的查询更快,那将很有趣。1000万行对于分区我还不算很多。我想知道您的性能下降是否vacuum是由于“闲置”会话而未能赶上或被阻止。
a_horse_with_no_name 2015年

@a_horse_with_no_name,到目前为止,还没有做出查询显著更好:(我使用Heroku的具有自动真空设置,它似乎为这个大表,每天要发生会不会显得更成寿。
埃文艾波比

不应该在步骤3和5中插入表new_master并让postgresql选择正确的子表/分区吗?
pakman

@pakman直到第7步才添加分配正确的孩子的功能
Evan Appleby

4

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

因此,类似以下内容的方法可以做到。

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.