环境
系统平台:Linux x86-64 Red Hat Enterprise Linux 7
版本:5.6.5
文档用途
某些情况下由于业务预估过多,导致开发把所有表都建立成了分区表,而实际上并不需要分区。本文用于将原生分区表快速转换为普通表。
详细信息
背景
某些情况下由于业务预估过多,导致开发把所有表都建立成了分区表,而实际上并不需要分区。
-
在高并发下分区表可能引入一定的优化器消耗
-
分区多,会导致会话relcache的内存增加,长连接+高并发+未使用的huge page可能触发OOM
注意:本文不考虑采用serial导致sequence和table挂钩、外键约束,如果分区表包含serial字段,序列属于partition table的情况,删除old table会导致序列被删除,并且新表的默认值也会被清理。
例子
数据准备
create database db1;
\c db1
create table tbl(id int, info text) partition by hash(id);
-- MODULUS子句用于指定除数,REMAINDER子句用于指定哈希值被除后的余数。
-- REMAINDER 子句需要指定一个小于 MODULUS 子句的值。
-- 如果指定的分区数量少于 MODULES 子句的值,将会导致无法插入某些数据,因为没有用于存储这些值的分区。
create table tbl0 partition of tbl for values with (modulus 2, remainder 0);
create table tbl1 partition of tbl for values with (modulus 2, remainder 1);
insert into tbl select generate_series(1, 10000), md5(random()::text);
create table ttbl(id int, info text) partition by hash(id);
create table ttbl0 partition of ttbl for values with (modulus 2, remainder 0);
create table ttbl1 partition of ttbl for values with (modulus 2, remainder 1);
insert into ttbl select generate_series(1, 10000), md5(random()::text);
create table "TTb" (id int, info text) partition by hash (id);
create table "TTb0" partition of "TTb" for values with (modulus 2, remainder 0);
create table "TTb1" partition of "TTb" for values with (modulus 2, remainder 1);
insert into "TTb" select generate_series(1,10000), md5(random()::text);
create schema s1;
set search_path=s1;
create table tbl (id int, info text) partition by hash (id);
create table tbl0 partition of tbl for values with (modulus 2, remainder 0);
create table tbl1 partition of tbl for values with (modulus 2, remainder 1);
insert into tbl select generate_series(1,10000), md5(random()::text);
create table ttbl (id int, info text) partition by hash (id);
create table ttbl0 partition of ttbl for values with (modulus 2, remainder 0);
create table ttbl1 partition of ttbl for values with (modulus 2, remainder 1);
insert into ttbl select generate_series(1,10000), md5(random()::text);
create table "TTb" (id int, info text) partition by hash (id);
create table "TTb0" partition of "TTb" for values with (modulus 2, remainder 0);
create table "TTb1" partition of "TTb" for values with (modulus 2, remainder 1);
insert into "TTb" select generate_series(1,10000), md5(random()::text);
create schema s2;
set search_path=s2;
create table tbl (id int, info text) partition by hash (id);
create table tbl0 partition of tbl for values with (modulus 2, remainder 0);
create table tbl1 partition of tbl for values with (modulus 2, remainder 1);
insert into tbl select generate_series(1,10000), md5(random()::text);
create table ttbl (id int, info text) partition by hash (id);
create table ttbl0 partition of ttbl for values with (modulus 2, remainder 0);
create table ttbl1 partition of ttbl for values with (modulus 2, remainder 1);
insert into ttbl select generate_series(1,10000), md5(random()::text);
create table "TTb" (id int, info text) partition by hash (id);
create table "TTb0" partition of "TTb" for values with (modulus 2, remainder 0);
create table "TTb1" partition of "TTb" for values with (modulus 2, remainder 1);
insert into "TTb" select generate_series(1,10000), md5(random()::text);
db1=# \dPt *.* List of partitioned tables Schema | Name | Owner | Parent name
--------+------+----------+------------- public | TTb | highgo | public | tbl | highgo | public | ttbl | highgo | s1 | TTb | highgo | s1 | tbl | highgo | s1 | ttbl | highgo | s2 | TTb | highgo | s2 | tbl | highgo | s2 | ttbl | highgo | (9 rows)
使用下面的方法来转换表:
begin;
create table new_tbl (like tbl including all);
insert into new_tbl select * from tbl;
alter table tbl rename to old_tbl;
alter table new_tbl rename to tbl; -- 如果有依赖,这个表有被依赖的对象时,会导致drop失败
end;
一次性转换所有分区表的操作如下:
-- 防止锁等待,等待直接回滚
-- 建议断开业务后连接操作
set lock_timeout='5s';
do language plpgsql $$
declare
v_nsp name; v_tbl name; v_exchange1 name := 'new'||md5(random()::text); v_exchange2 name := 'old'||md5(random()::text);begin for v_nsp, v_tbl in SELECT n.nspname as "Schema", c.relname as "Name"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_inherits inh ON c.oid = inh.inhrelid
WHERE c.relkind IN ('p','')
AND n.nspname !~ '^pg_toast'
ORDER BY "Schema", "Name" LOOP execute format('create table %I.%I (like %I.%I including all)', v_nsp, v_exchange1, v_nsp, v_tbl); execute format('insert into %I.%I select * from %I.%I', v_nsp, v_exchange1, v_nsp, v_tbl); execute format('alter table %I.%I rename to %I', v_nsp, v_tbl, v_exchange2); execute format('alter table %I.%I rename to %I', v_nsp, v_exchange1, v_tbl); -- 如果有依赖,以下语句将报错,导致整个事务回滚。 execute format('drop table %I.%I', v_nsp, v_exchange2); END loop; end;
$$;
-- 再查询分区表
db1=# \dPt *.* List of partitioned tables Schema | Name | Owner | Parent name
--------+------+-------+-------------
(0 rows)
如果分区表内的数据很多,这个可能会执行很久。
如果有依赖问题怎么办
先解决依赖,再执行如上
例如解决sequence的依赖问题,对每个分区表相关的sequence执行去除owner,改成新表:
alter sequecve 序列 owned by none;
以上do脚本改成如下:
do language plpgsql $$
declare
v_tbl_oid oid; v_nsp name; v_tbl name; v_exchange1 name := 'new'||md5(random()::text); v_exchange2 name := 'old'||md5(random()::text); v_seq_nsp name;
v_seq_name name;
begin
for v_tbl_oid,v_nsp,v_tbl in SELECT c.oid, n.nspname as "Schema", c.relname as "Name" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_catalog.pg_inherits inh ON c.oid = inh.inhrelid WHERE c.relkind IN ('p','') AND n.nspname !~ '^pg_toast' ORDER BY "Schema", "Name" LOOP execute format('create table %I.%I (like %I.%I including all)', v_nsp, v_exchange1, v_nsp, v_tbl); execute format('insert into %I.%I select * from %I.%I', v_nsp, v_exchange1, v_nsp, v_tbl); execute format('alter table %I.%I rename to %I', v_nsp, v_tbl, v_exchange2); execute format('alter table %I.%I rename to %I', v_nsp, v_exchange1, v_tbl); -- 如果有依赖,以下将报错,导致整个事务回滚. -- 处理serial类型依赖, 设置owned by为none
for v_seq_nsp, v_seq_name in
select n.nspname as "Schema", c.relname as "Name" from pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
where c.oid in
(select objid from pg_depend where refobjid=v_tbl_oid)
and relkind='S'
AND n.nspname !~ '^pg_toast' loop execute format('alter sequence %I.%I owned by none', v_seq_nsp, v_seq_name);
end loop;
execute format('drop table %I.%I', v_nsp, v_exchange2);
END loop;
end; $$;
1398

被折叠的 条评论
为什么被折叠?



