作者:James Blackwood-Sewell 和 Carlota Soto
PostgreSQL 是世界上最流行的关系型数据库之一。其丰富的功能集和开源特性使其成为各种规模企业的热门选择。但是,随着您的数据增长,您可能会遇到一些挑战,尤其是在处理大型表时。
解决这些问题的一种流行策略是表分区。在本文中,我们将探讨何时以及为何您应该考虑 **Postgres 分区**(尤其是大型表),分享一些最佳实践和常见陷阱,如果您决定对数据进行分区。
表分区是一种数据库设计技术,将一个大型表分成多个更小、更易于管理的部分,称为分区。每个分区都是一个子表,它包含数据的子集,每行数据只存在于一个分区中。分区可以拥有自己的索引,但任何唯一索引(包括主键)都必须包含用于对表进行分区的列。
这种技术在管理大型表时可以提供显著的优势。通过将一个大型表分解成更小的分区,您可以从改进的查询性能、优化后的索引大小以及更有效的数据维护操作中获益,从而使您的大型数据库更敏捷、响应更快。
但分区并非万能药:有时它不会对您有任何好处。它甚至可能降低您的性能,我们将在后面看到。
PostgreSQL 支持三种分区策略
**范围分区**:这种策略非常适合时间序列数据或递增序列(可能是 BIGINT 主键),您根据值的范围对数据进行分区(例如,按天或键的数量)。
**列表分区**:当您要根据列中的特定值(例如,国家或部门)对数据进行分区时,可以使用列表分区。
**哈希分区**:当没有明确的分区键时,哈希分区非常适用,因为它根据哈希函数将数据均匀地分布到各个分区中。
PostgreSQL 中分区的一个重要特点是它不支持“全局索引”:无法跨分区创建索引。相反,当跨分区查询时,PostgreSQL 将评估 WHERE
子句,如果对分区列有约束,它将使用这些约束来排除与查询无关的数据分区。
此时,您可能已经清楚地认识到,如果您要在生产环境中实施分区,则需要到位自动化来创建和维护分区,尤其是当分区表预计将持续接收数据时。有多种方法可以实现这一点,最常见的方法如下
您可以编写 SQL 过程/函数来检查未来几天/周/月所需的分区是否存在,使用 cron(在类 Unix 系统上)或任务计划程序(在 Windows 上)定期运行脚本。
您可以使用专门为此目的的 PostgreSQL 扩展,最流行的是 pg_partman(可能需要与调度程序配合使用)。
您可以使用 Timescale,其超表具有内置的自动分区功能(将分区创建和维护简化为一条命令)。
因此,是的,分区可能非常强大,但它绝非适用于所有用例。决定是否对 PostgreSQL 表进行分区并不严格基于绝对的表大小,而是基于各种与表大小相互作用的因素。在实施分区之前,必须评估数据库的特性和要求。
一般来说,如果您遇到以下情况之一(或多项),您应该开始考虑分区
**您拥有大型表。**正如我们提到的,表的大小并不是决定您是否能从分区中获益的唯一因素;话虽如此,如果您拥有大型表(从数千万行到数十亿行),您可能会从分区中获益。
**您的数据摄取率很高。**即使当前表大小不庞大,高数据摄取率也可能表明表在不久的将来会大幅增长。为了实施分区策略,最好在影响性能和维护操作之前预先管理这种增长。
您开始注意到查询性能下降。
如果您的查询开始变慢,尤其是那些只应触及数据子集的查询,分区也可能会有所帮助。即使您的表较小,由于数据的复杂性和查询的复杂性,这也可能是真的。例如,当您的日常查询包含基于特定范围或条件的搜索时,分区可以显著提高查询性能。假设您正在处理时间序列数据:按日期进行分区可以帮助您快速检索特定时间段内的记录,而无需扫描整个表。
**您正在处理维护开销。**随着表的大小增加,维护操作(如 VACUUM
、ANALYZE
和索引)可能需要更长时间,并且可能开始影响您的运营效率。分区可以简化这些操作,因为您可以专注于独立维护较小的分区,从而减少对数据库整体性能的影响。
**您正在管理数据保留策略。**如果您的数据集具有内置的过期机制,即定期清除较旧的数据,则分区可以使这些操作更有效率。删除旧分区比删除行快得多,资源消耗也少得多。
您想使用更少的内存。如果您想用有限的内存操作,分区可能对您有所帮助,因为较小的索引和数据块更适合内存,并提高缓存命中率。 在大多数情况下,这也会提高性能。
在某些情况下,引入分区可能会适得其反,您可能希望考虑其他优化,例如调整索引或查询
您的表很小,并且您没有以高速度进行数据导入。
如果您的表不大,并且不太可能大幅增长,分区将增加不必要的复杂性,而不会带来明显的益处。
您刚开始使用 PostgreSQL。实施和维护分区表会引入一定程度的操作和架构复杂性:管理此类表将变得更具挑战性,因此请确保您已准备好开始 - 或者使用 Timescale 之类的托管服务为您管理。
您的数据访问模式一致。如果您的查询通常在整个表中一致地访问数据,而不是专注于特定子集(例如最近的数据、数据范围、特定类别等),那么您的查询性能实际上可能会变差。
频繁的完整扫描。如果大多数查询没有在 WHERE
子句中使用分区键,您每次都会扫描每个分区。 这将很慢,并且随着分区数量的增加,速度会越来越慢。
但是,如果您决定这样做,以下是一些有关如何正确执行它的建议
选择合适的分区大小。这是您在实施分区时首先想到的设计问题之一:您理想的分区大小是多少? 答案是,这取决于情况 - 您应该在过大和过小之间取得平衡。 虽然 PostgreSQL 可以处理大量分区,但分区过多会增加规划时间,并可能对查询时间产生负面影响。 同时,如果您的分区过大,那么您将无法使用范围来排除数据,并且分区修剪的有效性将降至最低。
保持分区大小一致。 尽量保持分区大小相对一致,确保维护任务和查询性能在各个分区之间保持一致。
选择合适的分区键。选择与您的查询模式相符的键。 例如,如果大多数查询按日期筛选,那么时间戳或日期列将是理想的分区键。
提前创建分区。确保提前创建未来时期的分区(例如未来的几天或几个月),以便数据导入不会中断。 虽然您可以使用默认分区来捕获孤立的记录,但在实践中,这会引入维护负担,并且性能不佳。
利用数据保留策略来维护旧的分区。 例如,如果您按时间进行分区,并且数据的使用寿命有限,请定期安排任务来删除或归档旧分区。
优化您的查询。如果您特别关注优化查询性能,请务必分析并了解查询执行计划,以验证是否只扫描了必要的分区。
将分区正确地放置在不同的存储介质上。
如果您使用表空间将分区放置在不同的存储引擎(例如 EBS 或 S3)上,请确保频繁访问的分区位于更快的存储上,而较旧或较少访问的分区可以位于更慢、更便宜的存储上。
同样,请务必避免以下常见错误
过度分区。 创建许多小分区很诱人,但这行不通 - 您将遇到查询规划和管理方面的挑战。
低效的索引。 避免在您的分区上创建不必要的索引。 只索引经常过滤或连接的列。
未优化的查询模式。跨越多个分区或不在 WHERE
子句中使用分区键的查询的性能可能会下降。 确保大多数查询都针对分区方案进行了优化。
分区不足。如果您插入没有分区可供存放的数据,它将被拒绝或存储在 DEFAULT
分区中。 请确保您在空闲时间预先创建分区(因为这会锁定您的表),或者使用扩展程序在运行时创建新分区。
监视磁盘使用情况(分区需要额外的空间)。如果您创建了大量分区,尤其是在不同的表空间或磁盘上,请监视磁盘使用情况,以避免出现磁盘空间不足的问题。
对 PostgreSQL 表进行分区可以成为生产数据库中的一大利器,尤其是当您的表开始变大、数据导入量不断增长或查询性能开始下降时。 但是,分区不是一种万能的解决方案:在决定实施之前,请考虑它是否适合您的用例。
如果您决定按时间对大型 PostgreSQL 表进行分区,请 查看 Timescale,它完全自动化了分区的创建和维护:您可以像操作常规 PostgreSQL 表一样操作您的表,而 Timescale 在后台处理分区。 您将获得额外的性能,但不会带来额外的负担。
想要继续阅读? 了解更多有关 确定最佳 Postgres 分区大小 的信息。