PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como migrar uma tabela Postgres existente para uma tabela particionada da forma mais transparente possível?


No Postgres 10, o "Particionamento Declarativo" foi introduzido, o que pode aliviar muito o trabalho, como gerar gatilhos ou regras com enormes instruções if/else redirecionando para a tabela correta. O Postgres pode fazer isso automaticamente agora. Vamos começar com a migração:

  1. Renomeie a tabela antiga e crie uma nova tabela particionada
    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);
    

Isso dificilmente deve exigir qualquer explicação. A tabela antiga é renomeada (após a migração de dados vamos excluí-la) e obtemos uma tabela mestre para nossa partição que é basicamente a mesma da nossa tabela original, mas sem índices)

  1. Crie uma função que possa gerar novas partições conforme precisamos delas:
    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;
    

Isso será útil mais tarde.

  1. Crie uma visão que basicamente delegue para nossa tabela mestra:
    create or replace view myTable as select * from myTable_master;
    

  2. Crie uma regra para que, quando inserirmos na regra, não apenas atualizemos a tabela particionada, mas também criemos uma nova partição, se necessário:
    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)
        );
    

Claro, se você também precisar de update e delete , você também precisa de uma regra para aqueles que devem ser diretos.

  1. Na verdade, migre a tabela antiga:
    -- 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;
    

Agora a migração da tabela está completa sem que houvesse a necessidade de saber quantas partições são necessárias e também a visão myTable será absolutamente transparente. Você pode simplesmente inserir e selecionar a partir dessa tabela como antes, mas pode obter o benefício de desempenho do particionamento.

Observe que a exibição só é necessária, pois uma tabela particionada não pode ter gatilhos de linha. Se você conseguir chamar createPartitionIfNotExists manualmente sempre que necessário do seu código, você não precisa da visualização e todas as suas regras. Nesse caso, você precisa adicionar as partições também manualmente durante a migração:
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
$$;