表分区
背景
现状
从 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 的声明式分区)
- 不支持使用专用命令“拆分”或“合并”分区
- 不支持自动创建分区(例如,对于未覆盖的值)
项目目标概述
列表讨论
- (2005-05) 表分区,第一部分
- (2007-03) 自动创建分区
- (2007-04) Re: 自动分区补丁 - WIP 版本 1
- (2008-01) 使用段可见性映射的动态分区
- (2008-01) 命名分区与未命名分区
- (2008-01) 分区的存储模型
- (2008-01) 声明式分区语法
- (2008-10) 自动分区补丁讨论
- (2009-03) 分区功能
- (2009-05) 未来版本的 PG 中是否支持透明表分区?
- (2009-07) 关于自动 DML 路由和显式分区子命令的评论
- (2009-10) 自动分区的补丁
- (2009-11) 分区的语法
- (2009-11) 对 COPY 的分区支持
- (2010-01) 分区语法
- (2010-07) 规划器在非平凡分区数量下的可扩展性
- (2011-07) 新分区 WAS: 仅检查分区父级的约束?
- (2014-08) 关于分区
- (2015-02) 分区 WIP 补丁
- (2015-08) 声明式分区
- (2016-08) 声明式分区 - 另一种方法
可能的方向
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
参考资料
- https://www.ibm.com/developerworks/data/library/techarticle/dm-0608mcinerney/
- http://www.ibm.com/developerworks/data/library/techarticle/dm-0605ahuja2/
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 转换为一次性过滤器计划节点。
正在进行的积极工作
语法
分区语法在 "分区语法" 和 "第二个版本" 中提出。语法类似于 Oracle 和 MySQL。另请参阅 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
- 项目主页
- 这是一个扩展,它自动执行基于时间和序列的分区(基本上执行间隔分区,为您设置正确的触发器)。
- 处理设置、对现有数据进行分区、删除不需要的子表以及撤消分区。