我在postgres-DB中有一个现有表。为了演示,它是这样的:
create table myTable(
forDate date not null,
key2 int not null,
value int not null,
primary key (forDate, key2)
);
insert into myTable (forDate, key2, value) values
('2000-01-01', 1, 1),
('2000-01-01', 2, 1),
('2000-01-15', 1, 3),
('2000-03-02', 1, 19),
('2000-03-30', 15, 8),
('2011-12-15', 1, 11);
然而,与这几个值相反,myTable
实际上是巨大的,并且在不断增长。我正在从该表中生成各种报告,但是目前我98%的报告使用一个月,其余查询的工作时间甚至更短。通常,我的查询导致Postgres对这个巨大的表进行表扫描,我正在寻找减少问题的方法。 Table partitioning似乎完全适合我的问题。我可以将桌子分成几个月。但是,如何将现有表转换为分区表?手册明确指出:
不可能将常规表转换为分区表,反之亦然
因此,我需要开发自己的迁移脚本,该脚本将分析当前表并将其迁移。需要如下:
myTable
涵盖的时间范围是未知的。如何迁移要分区的表?
答案 0 :(得分:4)
在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
$$;
答案 1 :(得分:0)
建议使用一个视图进行主表访问,执行上述步骤,在此创建新的分区表。完成后,将视图指向新的分区表,然后进行迁移,最后弃用旧表。