PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie kann eine vorhandene Postgres-Tabelle so transparent wie möglich in eine partitionierte Tabelle migriert werden?

In Postgres 10 wurde „Declarative Partitioning“ eingeführt, das Ihnen viel Arbeit abnehmen kann, wie das Generieren von Triggern oder Regeln mit riesigen if/else-Anweisungen, die auf die richtige Tabelle umleiten. Postgres kann dies jetzt automatisch tun. Beginnen wir mit der Migration:

  1. Benennen Sie die alte Tabelle um und erstellen Sie eine neue partitionierte Tabelle

    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);
    

Dies sollte kaum einer Erklärung bedürfen. Die alte Tabelle wird umbenannt (nach der Datenmigration löschen wir sie) und wir erhalten eine Haupttabelle für unsere Partition, die im Grunde dieselbe ist wie unsere ursprüngliche Tabelle, jedoch ohne Indizes)

  1. Erstellen Sie eine Funktion, die neue Partitionen nach Bedarf generieren kann:

    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;
    

Dies wird sich später als nützlich erweisen.

  1. Erstellen Sie eine Ansicht, die im Grunde nur an unsere Haupttabelle delegiert:

    create or replace view myTable as select * from myTable_master;
    
  2. Erstellen Sie eine Regel, damit wir beim Einfügen in die Regel nicht nur die partitionierte Tabelle aktualisieren, sondern bei Bedarf auch eine neue Partition erstellen:

    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)
        );
    

Natürlich, wenn Sie auch update benötigen und delete , brauchen Sie auch eine Regel für diejenigen, die einfach sein sollten.

  1. Migrieren Sie tatsächlich die alte Tabelle:

    -- 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;
    

Jetzt ist die Migration der Tabelle abgeschlossen, ohne dass Sie wissen müssten, wie viele Partitionen benötigt werden, und auch die Ansicht myTable wird absolut transparent sein. Sie können diese Tabelle wie zuvor einfach einfügen und auswählen, aber Sie können die Leistungsvorteile durch die Partitionierung erzielen.

Beachten Sie, dass die Ansicht nur benötigt wird, weil eine partitionierte Tabelle keine Zeilenauslöser haben kann. Wenn Sie mit dem Aufruf von createPartitionIfNotExists zurechtkommen manuell, wann immer nötig, aus Ihrem Code, Sie brauchen die Ansicht und alle ihre Regeln nicht. In diesem Fall müssen Sie die Partitionen während der Migration auch manuell hinzufügen:

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
$$;