PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come migrare una tabella Postgres esistente in una tabella partizionata nel modo più trasparente possibile?

In Postgres 10 è stato introdotto il "partizionamento dichiarativo", che può sollevarti da una buona quantità di lavoro come la generazione di trigger o regole con enormi istruzioni if/else che reindirizzano alla tabella corretta. Postgres può farlo automaticamente ora. Iniziamo con la migrazione:

  1. Rinomina la vecchia tabella e crea una nuova tabella partizionata

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

Questo non dovrebbe richiedere alcuna spiegazione. La vecchia tabella viene rinominata (dopo la migrazione dei dati la cancelleremo) e otteniamo una tabella principale per la nostra partizione che è sostanzialmente la stessa della nostra tabella originale, ma senza indici)

  1. Crea una funzione in grado di generare nuove partizioni di cui abbiamo bisogno:

    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;
    

Questo tornerà utile in seguito.

  1. Crea una vista che in pratica si limita a delegare alla nostra tabella principale:

    create or replace view myTable as select * from myTable_master;
    
  2. Crea una regola in modo che quando inseriamo la regola, non solo aggiorneremo la tabella partizionata, ma creeremo anche una nuova partizione, se necessario:

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

Naturalmente, se hai bisogno anche di update e delete , hai anche bisogno di una regola per quelli che dovrebbero essere diretti.

  1. In realtà migra la vecchia tabella:

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

Ora la migrazione della tabella è completa senza che ci fosse bisogno di sapere quante partizioni sono necessarie e anche la vista myTable sarà assolutamente trasparente. Puoi semplicemente inserire e selezionare da quella tabella come prima, ma potresti ottenere il vantaggio in termini di prestazioni dal partizionamento.

Si noti che la vista è necessaria solo perché una tabella partizionata non può avere trigger di riga. Se riesci a chiamare createPartitionIfNotExists manualmente ogni volta che è necessario dal tuo codice, non hai bisogno della vista e di tutte le sue regole. In questo caso è necessario aggiungere le partizioni anche manualmente durante la migrazione:

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