创建分区表
-
创建父表,如果父表上定义了约束,子表会继承,因此除非是全局约束,否则不应该在表上定义约束,另外,父表不应该写入数据
CREATE TABLE log_ins(id serial, user_id int4, create_time timestamp(0) without time zone)
-
通过 INHERITS 方式创建继承表,也称之为子表或分区,子表的字段定义应该和父表保持一致
-
给所有子表创建约束,只有满足约束条件的数据才能写入对应分区,注意分区约束至范围不要有重叠。
create TABLE log_ins_history(CHECK(create_time < '2020-01-01')) INHERITS(log_ins); create TABLE log_ins_202001(CHECK(create_time >= '2020-01-01' and create_time < '2020-02-01')) INHERITS(log_ins); create TABLE log_ins_202002(CHECK(create_time >= '2020-02-01' and create_time < '2020-03-01')) INHERITS(log_ins); ...
-
给所有子表创建索引,由于继承操作不会集成父表上的索引,因此索引需要手工创建。
CREATE INDEX idx_his_ctime ON log_ins_history USING btree(create_time); CREATE INDEX idx_log_ins_202001_ctime ON log_ins_202001 USING btree(create_time); CREATE INDEX idx_log_ins_202002_ctime ON log_ins_202002 USING btree(create_time); ...
-
在父表上定义 INSERT、DELETE、UPDATE 触发器,将 SQL 分发到对应分区。
CREATE OR REPLACE FUNCTION log_ins_insert_trigger() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF (NEW.create_time < '2020-01-01') THEN INSERT INTO log_ins_history VALUES (NEW.*); ELSIF (NEW.create_time > '2020-01-01' and NEW.create_time < '2020-02-01') THEN INSERT INTO log_ins_201701 VALUES (NEW.*); ELSIF (NEW.create_time > '2020-02-01' and NEW.create_time < '2020-03-01') THEN INSERT INTO log_ins_201702 VALUES (NEW.*); ... ELSE RAISE EXCEPTION 'create_time out of range. Fix the log_ins_insert_trigger() function!'; END IF; RETURN NULL; END; $function$
-
启用 constraint_exclusion 参数,如果这个参数设置成 off,则父表上的 SQL 性能会降低。
使用分区表
在实际生产过程中,对于传统分区表分区方式,不建议应用访问父表,而是直接访问子表。
constraint_exclusion 参数
-
ON:所有表都通过约束优化查询
-
OFF:所有表都不通过约束优化查询
-
PARTITION:只有继承表和 UNION ALL 子查询通过检索约束来优化查询;
添加分区
-
创建分区
CREATE TABLE log_ins_202101(LIKE log_ins INCLUDING ALL)
-
添加约束
ALTER TABLE log_ins_202101 AND CONSTRAINT log_ins_202101_create_time_check CHECK(create_time >= '2021-01-01' and create_time < '2021-02-01')
-
更新触发器
-
将分区继承父表
ALTER TABLE log_ins_202101 INHERIT log_ins
删除分区
DROP TABLE log_ins_202001; ALTER TABLE log_ins_202001 NO INHERIT log_ins;
查看分区
-
\d
\d log_in
-
SELECT
SELECT nmsp_parent.nspname AS parent_schema, parent.relname AS parent, nmsp_child.nspname AS child_schema, child,relname AS child_table FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace WHERE parent.relname = 'log_ins'
-
统计
SELECT nspname, relname, count(*) AS partition_num FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid JOIN pg_inherits i ON c.oid = i.inhparent WHERE c.relhassubclass AND c.relkind in ('r','p') GROUP BY 1, 2 ORDER BY partition_num DESC
注意事项
-
当往父表插入数据时,需事先在父表上创建路由函数和触发器,数据才会根据分区键路由规则插入到对应分区中,目前仅支持范围分区和列表分区。
-
分区表上的索引、约束需要使用单独的命令创建,目前没有办法一次性自动在所有分区上创建索引、约束。
-
父表和子表允许单独定义主键,因此父表和子表可能存在重复的主键记录,目前不支持在分区表上定义全局主键。
-
UPDATE 时不建议更新分区键数据,特别是会使数据从一个分区移动到另一个分区的场景,可通过更新触发器实现,但会带来管理上的成本。
-
性能方面:传统分区表根据非分区键查询相比普通表性能差距较大,因为这种场景下分区会扫描所有分区;根据分区键查询相比普通表性能有小幅降低,而查询分区表子表性能相比普通表略有提升;