В Postgres 10 было представлено «Декларативное разбиение», которое может избавить вас от большой работы, такой как генерация триггеров или правил с огромными операторами if / else, перенаправляющими на правильную таблицу.Postgres может сделать это автоматически сейчас.Давайте начнем с миграции:
Переименуйте старую таблицу и создайте новую многораздельную таблицу
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);
Это вряд ли должно требовать каких-либо объяснений.Старая таблица переименовывается (после переноса данных мы ее удаляем), и мы получаем основную таблицу для нашего раздела, которая в основном совпадает с нашей исходной таблицей, но без индексов)
Создайте функцию, которая может генерировать новые разделы по мере необходимости:
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;
Это пригодится позже.
Создайте представление, которое в основном просто делегирует нашу основную таблицу:
create or replace view myTable as select * from myTable_master;
Создайте правило, чтобы при вставке в правило мы не просто обновлялииз разделенной таблицы, но при необходимости создайте новый раздел:
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)
);
Конечно, если вам также нужны update
и delete
, вам также нужно правило для техкоторый должен быть прямым.
Фактически перенесите старую таблицу:
-- 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;
Теперь миграция таблицы завершена без необходимости знать, сколько нужно разделов, а такжевид 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
$$;