表分区

来自 PostgreSQL 维基
跳转到导航跳转到搜索

背景

现状

从 PostgreSQL 10 开始,我们拥有声明式分区。使用它,有专门的语法来创建范围和列表 *分区* 表及其分区。PostgreSQL 11 解决了在 PostgreSQL 中使用分区表时存在的一些限制,例如无法在分区父表上创建索引、行级触发器等。PostgreSQL 11 还添加了哈希分区。

如果需要实现一些自定义分区标准(除了声明式分区原生支持的范围和列表方法),或者如果认为声明式分区表的限制会阻碍,则仍然可以使用旧的分区方法。详情请参见 PostgreSQL 分区。有一些第三方插件可以简化(手动)任务/触发器等,请参见本页底部。虽然 PostgreSQL 10 中的声明式分区减少了许多手动步骤,但此类第三方插件仍然提供核心系统不提供的功能。

请参阅各种博客,它们描述了新的声明式分区和旧的基于继承的实现。

已解决的问题

  • SELECT、UPDATE、DELETE(在 8.2 中): 它们可以使用约束排除来处理。
  • TRUNCATE(在 8.4 中): 父表的 TRUNCATE 将扩展到子表。
  • ANALYZE(在 9.0 中): ANALYZE 用于计算具有子类的表的统计信息
  • MAX()/MIN()(在 9.1 中): 更智能的分区检测。
  • NO INHERIT 约束(在 9.2 中)使定义仅在父级上的约束成为可能,这样它将始终被排除在外;声明式分区始终在没有任何额外配置的情况下排除父级
  • 插入到父分区表中的元组会自动路由到叶分区(PostgreSQL 10)
  • 添加了执行器阶段分区修剪或更快的子表修剪或并行分区处理(PostgreSQL 11)
  • 哈希分区(PostgreSQL 11)
  • 导致行从一个分区移动到另一个分区的 UPDATE(PostgreSQL 11)
  • 将元组路由到作为外部表的分区(PostgreSQL 11)
  • 分区表上的本地分区索引,如果键包含分区键,可以是 UNIQUE(PostgreSQL 11)
  • 分区表上的 FOREIGN KEY 引用常规表(PostgreSQL 11)
  • 一个“万能”/“回退”/“默认”分区(PostgreSQL 11)
  • 分区父表上的行触发器子集(PostgreSQL 11)
  • 分区父表现在可以在外键关系中被引用(PostgreSQL 12)

限制(截至 PostgreSQL 12 的声明式分区)

  • 不支持使用专用命令“拆分”或“合并”分区
  • 不支持自动创建分区(例如,对于未覆盖的值)

项目目标概述

列表讨论

可能的方向

Oracle 风格

允许用户声明他们对分区表的意图。即,声明分区键是什么以及每个分区覆盖了哪些范围或值。

我认为这意味着两种新的关系类型。一个“元表”,它像一个视图一样,没有附加的 filenode。它还将包含关于分区键的一些元数据,但没有视图定义,它将类似于嵌套表结构中的父表。另一个将是“分区”,它将是与表不同的命名空间,并将包含有关它覆盖的分区键值的信息。

优点

  • 使自动处理插入变得更加合理,因为结构是显式的,不需要进行逻辑推断。
  • 更防呆,即你不能设置毫无意义的约束组合。
  • 与其他数据库和 DBA 预期一致。

缺点

  • 灵活性较差,你无法设置任意非传统结构,例如在父表中保存一些数据或在某些子表中保存额外的列。

背景


DB2 风格

DB2 在 CREATE TABLE 语句中使用修饰符子句进行分区。它在同一个实现中包含了分片的一种原生形式

CREATE TABLE 语句中的子句 DB2 功能名称
DISTRIBUTE BY HASH DPF - 数据库分区功能
ORGANIZE BY DIMENSION MDC - 多维集群
PARTITION BY RANGE TP - 表分区

这些子句可以以任何组合使用来实现所需的效果。(参见 https://www.ibm.com/developerworks/data/library/techarticle/dm-0608mcinerney/)

- DPF 拆分为“数据库分区”(我们称之为分片)。“每个数据库分区都有自己的计算资源集,包括 CPU 和存储。在 DPF 环境中,每个表行根据 CREATE TABLE 语句中指定的分发键分发到一个数据库分区。当处理查询时,请求会被拆分,因此每个数据库分区处理它负责的行。”

- MDC 使具有跨多个维度相似值的行能够在磁盘上物理地聚集在一起。这种聚类允许对典型的分析查询进行有效的 I/O。例如,所有 Product='car'、Region='East' 以及 SaleMonthYear='Jan09' 的行都可以存储在同一个存储位置,称为块。

- TP 是我们所知的“范围分区”或“列表分区”,其实现方式与 Postgres 当前的方式非常相似:“用户可以手动定义每个数据分区,包括要包含在该数据分区中的值的范围。”(而 MDC 会自动为其分配存储空间)。“每个 TP 分区都是一个独立的数据库对象(与其他表是单个数据库对象不同)。因此,TP 支持将数据分区附加到 TP 表或从 TP 表分离数据分区。分离的分区将成为一个普通表。此外,如果需要,每个数据分区都可以放在它自己的表空间中。”

关键点似乎是,所有这三种功能都是相互正交的,可以在创建表时添加,也可以在以后添加。此外,分片被视为一等公民,并得到数据库的直接支持。我认为我们可以利用 postgres_fdw 的演进版本(加上从 pg_shard 和/或 PL/Proxy 借用的一些代码)来实现这一点。


MQTs(物化查询表)---我们称之为物化视图---也直接受分区影响(显然,也受分片影响)。

语法示例

CREATE TABLE orders(id INT, shipdate DATE, …)
PARTITION BY RANGE(shipdate)
(
   STARTING '1/1/2006' ENDING '12/31/2006' 
   EVERY 3 MONTHS
)

按间隔自动分区非常有用……


CREATE TABLE orders(id INT, shipdate DATE, …)
PARTITION BY RANGE(shipdate)
(
   PARTITION q4_05 STARTING MINVALUE,
   PARTITION q1_06 STARTING '1/1/2006',
   PARTITION q2_06 STARTING '4/1/2006',
   PARTITION q3_06 STARTING '7/1/2006',
   PARTITION q4_06 STARTING '10/1/2006' 
   ENDING ‘12/31/2006'
)

这等同于“VALUES LESS THAN”(技术上是 VALUES GREATER THAN)并且包含一个限制

分区操作语法(这里,添加)也很好

ALTER TABLE orders
   ATTACH PARTITION q1_07
   STARTING '01/01/2007'
   ENDING   '03/31/2007'
   FROM TABLE neworders


参考资料

MySQL 风格

相当基础,支持 RANGE、LIST 和 HASH

CREATE TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)
   ENGINE=INNODB
   PARTITION BY HASH( MONTH(tr_date) )
   PARTITIONS 6;

参考资料

基于触发器

首次尝试使用触发器来支持自动分区。

  • 避免使用需要 'CREATE LANGUAGE' 的特定语言,例如 pgpsql
  • C 触发器的性能比 pgpsql 快 4 到 5 倍
  • 当所有行都通过触发器从主表路由到子表时,insert/copy 返回 0 行
  • 链式触发器允许在行不匹配任何分区的情况下进行可调整的行为:添加错误触发器,移动到溢出表,动态创建新分区
  • constraint_exclusion 在预处理语句中效果不佳。如果条件是变量,则可能将 CHECK 转换为一次性过滤器计划节点。

正在进行的积极工作

语法

分区语法在 "分区语法" 和 "第二个版本" 中提出。语法类似于 OracleMySQL。另请参阅 Todo#Administration (简化创建分区表的功能)。

-- create partitioned table and child partitions at once.
CREATE TABLE parent (...)
PARTITION BY [ RANGE | LIST ] ( key ) [ opclass ]
[ (
     PARTITION child
       {
           VALUES LESS THAN { ... | MAXVALUE } -- for RANGE
         | VALUES [ IN ] ( { ... | DEFAULT } ) -- for LIST
       }
       [ WITH ( ... ) ] [ TABLESPACE tbs ]
     [, ...]
  ) ] ;

-- add a partition key to a table.
ALTER TABLE parent PARTITION BY  [ RANGE | LIST ] ( key ) [ opclass ] [ (...) ] ;

-- create a new partition on a partitioned table.
CREATE PARTITION child ON parent VALUES ... ;

-- add a table as a partition.
ALTER TABLE parent ATTACH PARTITION child VALUES ... ;

-- Remove a partition as a normal table.
ALTER TABLE parent DETACH PARTITION child ;

内部表示

磁盘结构包含在“分区语法”补丁中。内存结构将在以后的补丁中提出。

磁盘结构

添加了一个新的系统表“pg_partition”。分区键存储在其中。

CREATE TABLE pg_catalog.pg_partition
(
   partrelid   oid    NOT NULL, -- partitioned table oid
   partopclass oid    NOT NULL, -- operator class to compare keys
   partkind    "char" NOT NULL, -- kind of partition: RANGE or LIST
   partkey     text,            -- partition key expression

   PRIMARY KEY (partrelid),
   FOREIGN KEY (partrelid)   REFERENCES pg_class (oid),
   FOREIGN KEY (partopclass) REFERENCES pg_opclass (oid)
)
WITHOUT OIDS ;

在 pg_inherits 中添加了一个新列“inhvalues”。每个分区的分区值存储在其中。

ALTER TABLE pg_class.pg_inherits ADD COLUMN inhvalues anyarray ;
  • RANGE 分区在 inhvalues 中有一个范围的上限。
  • LIST 分区在 inhvalues 中有一个包含多个元素的数组。
  • 溢出分区在 inhvalues 中有一个空数组。
  • 一个正常的继承表在 inhvalues 中有一个 NULL。

内存结构

一个缓存的分区列表按分区值排序,并存储在父表的 relcache 中。对分区的更改需要使父缓存失效,以确保缓存保持准确。

操作

INSERT

INSERT 触发器将被使用内存结构的专用元组路由功能替换。元组将以 O(log N) 的速度进行路由。它还解决了 INSERT 触发器中“0 行受影响”的问题。

SELECT、UPDATE、DELETE

CHECK 约束将继续使用一段时间。

它将使用内存结构进行改进;代替每个子表的 CHECK 约束,我们可以使用父表中的排序列表。约束排除可以按 O(log N) 顺序进行,而不是现在的 O(N)。

VACUUM、CLUSTER、REINDEX

我们目前没有扩展这些命令,但可能需要像 TRUNCATE 一样扩展它们。

未来改进

这些在 9.0 中很难修复,但应该在将来的版本中继续改进。

语法

  • 支持对现有分区进行 SPLIT 和 MERGE。另请参阅 Kedar 的补丁
  • 支持更新分区键和值。
  • 支持在现有分区之间添加分区。这需要 SPLIT 功能。
  • 支持子分区。
  • 支持 GIS 类型的一些分区类型。例如,“PARTITION BY GIST”将分区键作为 GiST 树存储在内存结构中。
  • 支持 HASH 分区。每个分区都可以在 SQL/MED 中是一个 FOREIGN TABLE。换句话说,它是 PL/Proxy 集成。
  • 支持 CREATE TABLE AS -- CREATE TABLE tbl PARTITION BY ... AS SELECT ...;

执行器

  • SELECT FOR SHARE/UPDATE 用于父表。
  • 在占位符中使用分区键的预备语句。
    • 一个想法是将检查约束转换为一次性过滤器 [1]
  • 跨多个分区的唯一约束,当每个分区在分区键集/超集上具有唯一索引时。
  • 在一般情况下跨多个分区的唯一约束(通常称为“全局索引”)。

规划器

  • 针对 min/max、LIMIT + ORDER BY、GROUP BY 对分区键进行优化。
  • 当约束排除与稳定或易失函数一起使用时进行优化。这是一个非常常见的情况,即分区键是时间戳,并与 now() 进行比较。
  • 两个分区表的联接优化。

第三方工具

PG Partition Manager

  • 项目主页
  • 这是一个扩展,它自动执行基于时间和序列的分区(基本上执行间隔分区,为您设置正确的触发器)。
  • 处理设置、对现有数据进行分区、删除不需要的子表以及撤消分区。