跳到主要内容
版本:2.x

关于表分区

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 KEYUNIQUE 约束的要求,这可能会影响键的选择。
    • 考虑数据删除需求;设计分区时,应将要一起删除的数据放在单个分区中,以便快速分离。
  • 分区数量

    • 分区过少:可能导致索引过大、数据局部性差以及缓存命中率低。
    • 分区过多:可能导致查询规划时间更长,以及在规划和执行期间内存消耗更高。
  • 未来增长

    • 考虑数据可能如何变化。如果按客户 LIST 分区,如果客户数量显著增长怎么办?使用合理数量的分区进行 HASH 分区可能更健壮。
  • 子分区

    • 对于划分预计会比其他分区更大的分区很有用。
    • 分区键中使用多列的范围分区是另一种选择。
    • 这两者都容易导致分区过多,因此请保持克制。
  • 开销

    • 查询规划器通常能很好地处理多达数千个分区的层次结构,前提是典型查询允许裁剪到少量分区。
    • 当裁剪后保留更多分区时,规划时间和内存使用会增加,特别是对于 UPDATEDELETE 命令。
    • 如果许多会话接触许多分区,服务器内存消耗会显著增长,因为每个分区的元数据会加载到每个会话的本地内存中。
  • 工作负载类型

    • 数据仓库任务可能比 OLTP 类型的工作负载受益于更多的分区。在数据仓库中,规划时间通常不如执行时间关键。
  • 彻底测试

    • 尽早做出决策,因为对大型表重新分区速度很慢。
    • 模拟预期的工作负载以优化分区策略。不要假设分区越多越好,反之亦然。

限制

请注意 Apache Cloudberry 中分区表的以下限制:

  • 分区表的每个级别最多可以有 32,767 个分区。
  • 不支持对复制表(使用 DISTRIBUTED REPLICATED 创建的表)进行分区。
  • Cloudberry 查询优化器(GPORCA,默认)不支持统一的多级分区表。对此类表的查询将使用基于 Postgres 的规划器。
  • 如果叶分区是外部表或外表,gpbackup 工具不会备份该叶分区的数据。
  • 要在分区表上创建唯一约束或主键约束:
    • 分区键不得包含任何表达式或函数调用。
    • 约束的列必须包含所有分区键列。这是因为各个索引在其分区内强制执行唯一性,并且分区结构本身必须保证不同分区之间没有重复项。
  • 无法创建跨越整个分区表的排他约束;此类约束只能放置在单个叶分区上。
  • 不允许在同一分区层次结构中混合临时关系和永久关系。如果分区表是永久的,则其所有分区必须是永久的;对于临时表(所有成员必须属于同一会话)也是如此。
  • 继承差异
    • 分区不能包含父表中不存在的列。创建后不能向分区添加列,也不能附加具有不同列结构的表。
    • 分区表的 CHECKNOT NULL 约束始终由其所有分区继承。不能在分区表上创建标记为 NO INHERITCHECK 约束。如果父表中存在相同的 NOT NULL 约束,则不能删除分区列上的 NOT NULL 约束。
    • 仅在分区表上添加或删除约束(例如,ALTER TABLE ONLY ...)只有在没有分区的情况下才支持。一旦存在分区,使用 ONLY 进行约束将导致错误。
    • 尝试对分区表使用 TRUNCATE ONLY 将始终返回错误,因为分区表本身不包含数据。
  • 外部表或外表叶分区
    • 在不可写的外部/外部分区中修改数据(INSERTDELETEUPDATETRUNCATE)或没有权限将出错。
    • 如果涉及更新外部/外部分区,COPY 无法将数据复制分区表。
    • 从外部/外表分区 COPY 会出错,除非指定了 IGNORE EXTERNAL PARTITIONS(它会跳过它们)。要从具有外部/外表叶分区的分区表进行 COPY,请使用 SQL 表达式(例如,COPY (SELECT * from my_table) TO stdout)
    • VACUUM 命令会跳过外部表和外表分区。