PostgreSQL:传统分区表

创建分区表

  1. 创建父表,如果父表上定义了约束,子表会继承,因此除非是全局约束,否则不应该在表上定义约束,另外,父表不应该写入数据

    CREATE TABLE log_ins(id serial,
        user_id int4,
        create_time timestamp(0) without time zone)
  2. 通过 INHERITS 方式创建继承表,也称之为子表或分区,子表的字段定义应该和父表保持一致

  3. 给所有子表创建约束,只有满足约束条件的数据才能写入对应分区,注意分区约束至范围不要有重叠。

    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);
    ...
  4. 给所有子表创建索引,由于继承操作不会集成父表上的索引,因此索引需要手工创建。

    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);
    ...
  5. 在父表上定义 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$
  6. 启用 constraint_exclusion 参数,如果这个参数设置成 off,则父表上的 SQL 性能会降低。

使用分区表

在实际生产过程中,对于传统分区表分区方式,不建议应用访问父表,而是直接访问子表。

constraint_exclusion 参数

  • ON:所有表都通过约束优化查询

  • OFF:所有表都不通过约束优化查询

  • PARTITION:只有继承表和 UNION ALL 子查询通过检索约束来优化查询;

添加分区

  1. 创建分区

    CREATE TABLE log_ins_202101(LIKE log_ins INCLUDING ALL)
  2. 添加约束

    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')
  3. 更新触发器

  4. 将分区继承父表

    ALTER TABLE log_ins_202101 INHERIT log_ins

删除分区

DROP TABLE log_ins_202001;
ALTER TABLE log_ins_202001 NO INHERIT log_ins;

查看分区

  1. \d

    \d log_in
  2. 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'
  3. 统计

    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

注意事项

  1. 当往父表插入数据时,需事先在父表上创建路由函数和触发器,数据才会根据分区键路由规则插入到对应分区中,目前仅支持范围分区和列表分区。

  2. 分区表上的索引、约束需要使用单独的命令创建,目前没有办法一次性自动在所有分区上创建索引、约束。

  3. 父表和子表允许单独定义主键,因此父表和子表可能存在重复的主键记录,目前不支持在分区表上定义全局主键。

  4. UPDATE 时不建议更新分区键数据,特别是会使数据从一个分区移动到另一个分区的场景,可通过更新触发器实现,但会带来管理上的成本。

  5. 性能方面:传统分区表根据非分区键查询相比普通表性能差距较大,因为这种场景下分区会扫描所有分区;根据分区键查询相比普通表性能有小幅降低,而查询分区表子表性能相比普通表略有提升;

请使用浏览器的分享功能分享到微信等