关于表分区
Apache Cloudberry 允许您通过表分区将大型表逻辑地划分为更小、更易于管理的部分。这可以显著提高查询性能,因为查询优化器可以只扫描必要的数据,而不是整个表。分区是一种逻辑划分;它不会改变表数据在段上的物理分布。
有关如何创建和管理分区表,请参阅创建和管理分区表。
为什么使用表分区
分区可以提供多项优势,特别是对于超大型表(例如,超出数据库服务器物理内存的表):
- 提高查询性能:当大部分频繁访问的行位于单个分区或少量分区中时,查询运行速度会快得多。分区就像索引的更高层级,使得索引的关键部分更可能驻留在内存中。
- 高效扫描:当查询或更新访问单个分区的很大一部分时,对该分区的顺序扫描可能比使用索引更高效,因为使用索引会涉及对整个表的随机访问读取。
- 更快的数据管理:通过添加或删除分区,可以快速执行批量加载和删除操作。
DROP TABLE
分区或ALTER TABLE DETACH PARTITION
等操作比批量DELETE
操作快得多,并且避免了VACUUM
的开销。 - 数据分层:通过管理分区,可以将很少使用的数据迁移到更便宜、更慢的存储介质。
分区方法
Apache Cloudberry 支持以下表分区方法:
- 范围分区:表被划分为由一个或多个键列定义的“范围”。分配给不同分区的值范围之间没有重叠。例如,您可以按日期范围或业务对象标识符范围进行分区。每个范围的下限是包含的,上限是独占的。
- 列表分区:表通过明确列出每个分区中出现的键值进行分区。例如,按销售区域或产品线进行分区。
- 哈希分区:表通过为每个分区指定模数和余数进行分区。每个分区都包含其分区键的哈希值除以模数后产生指定余数的行。
这些方法可以组合成多级分区设计,例如,在第一级使用日期范围分区,并在子级使用列表分区。(源文档中的一个示例图说明了使用日期范围和列表分区的多级设计)。
分区表结构
在 Apache Cloudberry 中对表进行分区时:
- 您定义的主表(分区表)是一个“虚拟”表;它本身不存储数据。
- 实际数据存储在各个分区中,这些分区是与分区表关联的普通表。每个分区根据其分区边界存储数据的子集。
- 插入到分区表中的行会根据分区键列自动路由到正确的分区。如果某行的分区键被更新,使其不再符合其原始分区的边界,Apache Cloudberry 将尝试将其移动到正确的分区。
- 您在使用
CREATE TABLE
命令中的PARTITION BY
子句创建表时定义分区。这将创建一个顶级根分区表,它可以有一个或多个级别的子表(分区)。紧邻子表上方的表是它的父表。叶分区位于层次结构的底部并保存数据;同一层次结构中的叶分区可以位于不同的深度。 - Apache Cloudberry 会自动为每个分区创建不同的分区约束,限制它可以包含的数据。查询优化器使用这些约束来确定要为给定查询扫描哪些分区。
- 分区本身可以定义为分区表,从而导致子分区。
- 所有分区必须与其父分区具有相同的列,但它们可以拥有自己的不同索引、约束和默认值。
- 分区也可以是外部表或外表,但您必须确保其内容满足分区规则。外表还有其他限制。
决定分区策略
考虑以下问题以确定分区是否是可行的策略:
- 表是否足够大? 分区有利于包含数百万或数十亿记录的大型事实表。对于小表,管理开销可能超过收益。
- 您是否遇到不令人满意的性能? 如果查询速度低于预期,则应考虑分区。
- 您的查询谓词是否有可识别的访问模式? 查找在
WHERE
子句中一致使用的列。例如,如果查询经常按日期过滤,日期分区设计可能是有益的。如果按区域访问,请考虑列表分区。 - 您的数据仓库是否维护历史数据窗口? 如果您需要保留特定时期的数据(例如,最近十二个月),分区(例如,按月)允许您轻松删除最旧的分区并添加新分区。
- 数据是否可以分成大致相等的部分? 选择尽可能均匀地划分数据的标准。如果分区具有大致相等的记录,则查询性能可以与分区数量成比例地提高(假设设计支持查询条件)。
- 避免过度分区:创建过多分区可能会降低管理和维护任务(如清理、段恢复和集群扩展)的速度。
- 确保分区消除:除非查询优化器可以根据查询谓词消除分区,否则分区不会提高查询性能。扫描每个分区的查询将运行得更慢。务必检查
EXPLAIN
计划。 - 多级分区注意事项:对多级分区要小心,因为分区文件的数量会非常快速地增长,这可能会影响系统可管理性。例如,按 1000 天和 1000 个城市进行分区会创建一百万个分区。如果表有 100 列并且是列式存储,这可能意味着每个段有 1 亿个文件。考虑使用带有位图索引的单级分区作为替代方案,并始终测试性能。
分区设计的最佳实践
精心设计分区非常重要,否则可能会对查询规划和执行性能产生负面影响。
-
分区键选择:
- 选择最常出现在查询
WHERE
子句中的列,以启用分区裁剪。 - 考虑
PRIMARY KEY
或UNIQUE
约束的要求,这可能会影响键的选择。 - 考虑数据删除需求;设计分区时,应将要一起删除的数据放在单个分区中,以便快速分离。
- 选择最常出现在查询
-
分区数量:
- 分区过少:可能导致索引过大、数据局部性差以及缓存命中率低。
- 分区过多:可能导致查询规划时间更长,以及在规划和执行期间内存消耗更高。
-
未来增长:
- 考虑数据可能如何变化。如果按客户
LIST
分区,如果客户数量显著增长怎么办?使用合理数量的分区进行HASH
分区可能更健壮。
- 考虑数据可能如何变化。如果按客户
-
子分区:
- 对于划分预计会比其他分区更大的分区很有用。
- 分区键中使用多列的范围分区是另一种选择。
- 这两者都容易导致分区过多,因此请保持克制。
-
开销:
- 查询规划器通常能很好地处理多达数千个分区的层次结构,前提是典型查询允许裁剪到少量分区。
- 当裁剪后保留更多分区时,规划时间和内存使用会增加,特别是对于
UPDATE
和DELETE
命令。 - 如果许多会话接触许多分区,服务器内存消耗会显著增长,因为每个分区的元数据会加载到每个会话的本地内存中。
-
工作负载类型:
- 数据仓库任务可能比 OLTP 类型的工作负载受益于更多的分区。在数据仓库中,规划时间通常不如执行时间关键。
-
彻底测试:
- 尽早做出决策,因为对大型表重新分区速度很慢。
- 模拟预 期的工作负载以优化分区策略。不要假设分区越多越好,反之亦然。
限制
请注意 Apache Cloudberry 中分区表的以下限制:
- 分区表的每个级别最多可以有 32,767 个分区。
- 不支持对复制表(使用
DISTRIBUTED REPLICATED
创建的表)进行分区。 - Cloudberry 查询优化器(GPORCA,默认)不支持统一的多级分区表。对此类表的查询将使用基于 Postgres 的规划器。
- 如果叶分区是外部表或外表,
gpbackup
工具不会备份该叶分区的数据。 - 要在分区表上创建唯一约束或主键约束:
- 分区键不得包含任何表达式或函数调用。
- 约束的列必须包含所有分区键列。这是因为各个索引在其分区内强制执行唯一性,并且分区结构本身必须保证不同分区之间没有重复项。
- 无法创建跨越整个分区表的排他约束;此类约束只能放置在单个叶分区上。
- 不允许在同一分区层次结构中混合临时关系和永久关系。如果分区表是永久的,则其所有分区必须是永久的;对于临时表(所有成员必须属于同一会话)也是如此。
- 继承差异:
- 分区不能包含父表中不存在的列。创建后不能向分区添加列,也不能附加具有不同列结构的表。
- 分区表的
CHECK
和NOT NULL
约束始终由其所有分区继承。不能在分区表上创建标记为NO INHERIT
的CHECK
约束。如果父表中存在相同的NOT NULL
约束,则不能删除分区列上的NOT NULL
约束。 - 仅在分区表上添加或删除约束(例如,
ALTER TABLE ONLY ...
)只有在没有分区的情况下才支持。一旦存在分区,使用ONLY
进行约束将导致错误。 - 尝试对分区表使用
TRUNCATE ONLY
将始终返回错误,因为分区表本身不包含数据。
- 外部表或外表叶分区:
- 在不可写的外部/外部分区中修改数据(
INSERT
、DELETE
、UPDATE
、TRUNCATE
)或没有权限将出错。 - 如果涉及更新外部/外部分区,
COPY
无法将数据复制到分区表。 - 从外部/外表分区
COPY
会出错,除非指定了IGNORE EXTERNAL PARTITIONS
(它会跳过它们)。要从具有外部/外表叶分区的分区表进行COPY
,请使用 SQL 表达式(例如,COPY (SELECT * from my_table) TO stdout)
。 VACUUM
命令会跳过外部表和外表分区。
- 在不可写的外部/外部分区中修改数据(