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:
-
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)
-
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.
-
Crea una vista che in pratica si limita a delegare alla nostra tabella principale:
create or replace view myTable as select * from myTable_master;
-
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.
-
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
$$;