如何尽可能透明地将现有的Postgres表迁移到分区表?

yan*_*kee 5 sql postgresql database-partitioning postgresql-10

我在postgres-DB中有一个现有表。为了演示,它是这样的:

create table myTable(
    forDate date not null,
    key2 int not null,
    value int not null,
    primary key (forDate, key2)
);

insert into myTable (forDate, key2, value) values
    ('2000-01-01', 1, 1),
    ('2000-01-01', 2, 1),
    ('2000-01-15', 1, 3),
    ('2000-03-02', 1, 19),
    ('2000-03-30', 15, 8),
    ('2011-12-15', 1, 11);
Run Code Online (Sandbox Code Playgroud)

但是,与这几个值相比,myTable实际上是巨大的,并且还在不断增长。我正在从该表中生成各种报告,但是目前我98%的报告使用一个月,其余查询的工作时间甚至更短。通常,我的查询使Postgres对这个巨大的表进行表扫描,而我正在寻找减少问题的方法。表分区似乎完全适合我的问题。我可以将桌子分成几个月。但是,如何将现有表转换为分区表?该手册明确指出:

无法将常规表转换为分区表,反之亦然

因此,我需要开发自己的迁移脚本,该脚本将分析当前表并将其迁移。需求如下:

  • 在设计时,myTable覆盖的时间框架是未知的。
  • 从该月的第一天到该月的最后一天,每个分区应覆盖一个月。
  • 该表将无限期增长,因此对于要生成多少个表,我没有理智的“停止值”
  • 结果应该尽可能透明,这意味着我想尽可能少地使用现有代码。在最佳情况下,这感觉就像是一张普通表,我可以在其中插入或选择它们而没有任何特殊之处。
  • 可以接受数据库停机以进行迁移
  • 与纯Postgres相处而无需在服务器上安装任何插件或其他东西的情况是高度首选的。
  • 数据库是PostgreSQL 10,无论如何迟早都会升级到较新版本,因此如果有帮助,则可以选择此选项

我该如何迁移要分区的表?

yan*_*kee 9

在Postgres 10中引入了“声明性分区”,它可以减轻您的大量工作,例如使用巨大的if / else语句重定向到正确的表来生成触发器或规则。Postgres现在可以自动执行此操作。让我们从迁移开始:

  1. 重命名旧表并创建新的分区表

    alter table myTable rename to myTable_old;
    
    create table myTable_master(
        forDate date not null,
        key2 int not null,
        value int not null
    ) partition by range (forDate);
    
    Run Code Online (Sandbox Code Playgroud)

这几乎不需要任何解释。重命名旧表(在数据迁移后,我们将其删除),并为分区获得一个主表,该表与原始表基本相同,但没有索引)

  1. 创建一个可以根据需要生成新分区的函数:

    create function createPartitionIfNotExists(forDate date) returns void
    as $body$
    declare monthStart date := date_trunc('month', forDate);
        declare monthEndExclusive date := monthStart + interval '1 month';
        -- We infer the name of the table from the date that it should contain
        -- E.g. a date in June 2005 should be int the table mytable_200506:
        declare tableName text := 'mytable_' || to_char(forDate, 'YYYYmm');
    begin
        -- Check if the table we need for the supplied date exists.
        -- If it does not exist...:
        if to_regclass(tableName) is null then
            -- Generate a new table that acts as a partition for mytable:
            execute format('create table %I partition of myTable_master for values from (%L) to (%L)', tableName, monthStart, monthEndExclusive);
            -- Unfortunatelly Postgres forces us to define index for each table individually:
            execute format('create unique index on %I (forDate, key2)', tableName);
        end if;
    end;
    $body$ language plpgsql;
    
    Run Code Online (Sandbox Code Playgroud)

稍后将派上用场。

  1. 创建一个基本上只委托给我们的主表的视图:

    create or replace view myTable as select * from myTable_master;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建规则,以便在插入规则时,我们不仅会更新分区表,还会在需要时创建一个新分区:

    create or replace rule autoCall_createPartitionIfNotExists as on insert
        to myTable
        do instead (
            select createPartitionIfNotExists(NEW.forDate);
            insert into myTable_master (forDate, key2, value) values (NEW.forDate, NEW.key2, NEW.value)
        );
    
    Run Code Online (Sandbox Code Playgroud)

当然,如果您还需要updatedelete,则还需要一个简单明了的规则。

  1. 实际迁移旧表:

    -- Finally copy the data to our new partitioned table
    insert into myTable (forDate, key2, value) select * from myTable_old;
    
    -- And get rid of the old table
    drop table myTable_old;
    
    Run Code Online (Sandbox Code Playgroud)

现在,表的迁移已完成,无需知道需要多少个分区,并且视图myTable也将完全透明。您可以像以前一样简单地从表中插入并选择,但是从分区中可以获得性能上的好处。

请注意,仅需要视图,因为分区表不能具有行触发器。如果您可以随时createPartitionIfNotExists从代码中手动调用,那么就不需要该视图及其所有规则。在这种情况下,您需要在迁移过程中手动添加分区als:

do
$$
declare rec record;
begin
    -- Loop through all months that exist so far...
    for rec in select distinct date_trunc('month', forDate)::date yearmonth from myTable_old loop
        -- ... and create a partition for them
        perform createPartitionIfNotExists(rec.yearmonth);
    end loop;
end
$$;
Run Code Online (Sandbox Code Playgroud)

  • 这似乎是一个很好的答案,但是如果旧表通过外键关系被其他表引用呢?我想 fk 必须更新以指向新表 - 这足够了吗? (2认同)
  • @ babis21:是的,当然。但是FK支持受到限制:Postgre 10:[不支持引用分区表的外键,也不支持从分区表到其他表的外键引用。](https://www.postgresql.org/docs/10/ddl (-partitioning.html#DDL-PARTITIONING-DECLARATIVE-LIMITATIONS)对于Postgre 11有所改善:[不支持引用分区表的外键。(支持从分区表到其他表的外键引用。)](https://www.postgresql.org/docs/11/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-LIMITATIONS) (2认同)