为何PostgreSQL没有聚集索引?解读两大数据库的设计差异
前言
高效的数据检索是数据库管理的基石, PostgreSQL和SQL Server都能提供强大的数据访问方法以支持各种工作负载方面表现出色。然而,它们的实现方式存在显著差异,反映了各自独特的设计理念和使用场景。 在这篇文章中将介绍PostgreSQL提供的各种数据访问方法,其中包括一个非常独特的特点:PostgreSQL不支持聚集索引。这一根本性的差异对于理解PostgreSQL与SQL Server在数据存储和检索上的不同方式至关重要。
顺序扫描
任何数据库系统的核心都离不开最简单的数据访问方法,就是扫描表中的所有行。 PostgreSQL 通过顺序扫描(Sequential Scan)来实现这一点,它逐行读取表中的每一行。 虽然对于大型数据集而言,这看起来可能效率不高,但在特定场景下,它往往是最实际的选择。 当处理小型表时,使用索引的开销通常超过其带来的好处,因此顺序扫描非常有效。 此外,当查询需要表中大量行时,例如需要查询超过50%的数据行时候,顺序扫描可以通过最小化随机I/O来优于索引扫描。 SQL Server采用了一种类似的技术,称为表扫描(Table Scan),它逐行读取整个表。无论是PostgreSQL还是SQL Server都依赖其查询优化器来决定何时应选择表扫描而不是使用索引扫描。例如,在没有合适索引的情况下,或者查询涉及广泛的过滤条件时,优化器将选择全表扫描。尽管顺序扫描和表扫描有时被批评为较慢,但它们依然是数据库处理特定工作负载时必不可少的工具。
在PostgreSQL中,所有的表默认存储在堆结构(Heap)中,这意味着行没有固定的顺序。PostgreSQL中没有聚集索引的概念,这意味着顺序扫描通常会访问以任意顺序存储的行。
索引扫描
在PostgreSQL中,索引扫描(Index Scan)是一个基本的查询执行方法,它使用索引来高效地检索符合特定查询条件的行。 当执行索引扫描时,PostgreSQL会遍历索引结构(B树)来查找满足查询条件的行的位置(元组指针)。这些指针将引导PostgreSQL定位到堆表中的相应行,进而检索完整的行数据。 在PostgreSQL中,索引扫描的关键之处在于,它对堆表的查找操作是作为索引扫描的一部分内部执行的。因此,PostgreSQL 执行计划将索引扫描显示为一个单独的操作,它封装了索引遍历和随后从堆表中检索行数据这两个步骤。
与此不同,SQL Server的执行计划明确区分了这两个步骤。在SQL Server中,索引查找(Index Seek)操作负责遍历索引以找到匹配的行。当索引不包含查询所需的所有列时,SQL Server会引入一个单独的操作,对于聚集索引表是键查找(Key Lookup),对于堆表则是RID查找(RID Lookup)。这些查找操作会直接从基础表中获取额外的列。通过分离这些步骤,SQL Server的执行计划提供了一个更加清晰详细的视图,展示查询如何访问数据,包括索引遍历和数据行检索的成本和行为。 这种执行计划表示的差异凸显了不同的设计理念。 PostgreSQL将堆查找集成到索引扫描操作中,呈现一个简化的执行计划。然而,这也可能掩盖索引扫描中堆访问部分的具体成本。另一方面,SQL Server明确分离提供了对查询执行过程更为详细的洞察。例如,当SQL Server的执行计划中包含键查找时,立刻可以看出索引缺少一些必需的列,这可以帮助数据库管理员通过创建覆盖索引来消除查找操作。这种透明度对于识别和解决复杂查询中的性能瓶颈特别有帮助。
位图索引扫描与位图堆扫描
对于具有多个条件或过滤器的查询,PostgreSQL经常使用位图堆扫描(Bitmap Heap Scan),这是一种将索引访问的精确性与批量读取的高效性相结合的混合方法。 在执行此类查询时,PostgreSQL首先使用相关的索引构建一个位图,即匹配查询条件的行的压缩表示。与逐行访问不同,位图使PostgreSQL能够批量获取这些行从而减少随机磁盘 I/O。这种方法对于必须同时评估多个条件的大型表特别有用,例如按客户年龄和地点进行过滤。位图扫描Bitmap Scan也分为两个阶段,第一个阶段是Bitmap Index Scan,第二个阶段是Bitmap Heap Scan。Bitmap Heap Scan采用Bitmap Index Scan生成的bitmap(或者经过 BitmapAnd 和 BitmapOr 节点通过一系列位图集操作后,生成的bitmap)来查找相关数据。位图的每个page可以是精确的(直接指向tuple的)或有损的(指向包含至少一行与查询匹配的page)。
SQL Server并没有直接等同于位图堆扫描的操作,但它在并行查询执行计划中使用位图过滤(Bitmap Filtering)。位图堆扫描在处理需要多个索引扫描的查询时尤其具有优势,因为它将这些操作合并为一个更高效的过程。这种方法突显了 PostgreSQL 在动态优化复杂查询方面的独特能力。通过平衡顺序访问和索引访问的优点,位图堆扫描架起了精确性与高效性之间的桥梁,使其在分析和报告工作负载中变得不可或缺。
仅索引扫描
在PostgreSQL中,仅索引扫描(Index-Only Scans)是一种查询执行特性,允许数据完全从索引中检索,跳过对堆表的访问。 当查询只涉及索引中的列时,这种方法是可行的。在进行仅索引扫描时,PostgreSQL直接从索引的叶节点中获取数据,从而显著减少了I/O操作并提高了查询性能,特别适用于读密集型工作负载。例如,如果一个查询仅检索客户的姓名和电子邮件,并且这些列是索引的一部分,那么数据库完全避免了访问堆表的开销。在SQL Server中,类似的概念是通过覆盖索引(Covering Indexes)来实现的,在索引定义中包含了额外的列(超出索引键列的部分)。这些额外的列被称为包含列(Included Columns),它们允许 SQL Server 直接从索引中检索所有所需的数据,而无需执行键查找(Key Lookup)或 RID 查找(RID Lookup)。
并行查询执行
随着数据集的增大和查询变得更加复杂,采用并行处理对于保持性能至关重要。 PostgreSQL支持并行查询执行,允许多个工作进程分担和处理大规模的工作负载。例如,并行扫描将一个大型表分成多个分段,每个工作进程同时扫描其中的一部分。这种方法能够显著减少资源密集型操作的查询时间。SQL Server也支持执行计划中的并行处理,使用并行扫描(Parallel Scan)和合并流(Gather Streams)等操作符,将工作负载分配并合并到多个工作线程中。SQL Server的并行查询引擎与其优化器紧密集成,通常能为事务型OLTP和分析型OLAP工作负载生成高效的执行计划。
聚集索引的作用
PostgreSQL和SQL Server之间最显著的区别之一是PostgreSQL不支持聚集索引。 在SQL Server中,聚集索引定义了表中行的物理顺序。这可以显著提高范围查询或返回按排序顺序排列行的查询的性能,因为数据已经根据索引键物理排序。 在PostgreSQL中,所有表都以堆(heap)形式存储,这意味着行没有特定的存储顺序。虽然PostgreSQL提供了一个名为CLUSTER的命令,可以基于索引物理重新排序表,但这个操作不是动态的,必须手动执行。此外,CLUSTER创建的排序不会随着行的插入、更新或删除而保持。 PostgreSQL的这种设计选择优先考虑灵活性,而不是聚集索引可能带来的性能提升。 通过保持表的无序,PostgreSQL 允许多个索引并存,这对于大量数据写入的场景或者说写多读少的场景非常有利。
总结
PostgreSQL 和 SQL Server 中的数据访问方法展示了各自系统的优势和优先事项。PostgreSQL 的灵活性,例如位图堆扫描、仅索引扫描,使其成为开发者在查询执行上寻求精确控制的强大选择。 然而,PostgreSQL 不支持聚集索引是其与 SQL Server 的一个关键区别。另一方面,SQL Server 使用聚集索引来提供表行的物理排序,这可以显著有利于范围查询和排序操作。 这种结构性差异体现了两个系统的不同哲学:PostgreSQL 倾向于适应性,而 SQL Server 强调紧密集成的优化。理解这些差异能够帮助数据库专业人员做出明智的决策,并针对每个平台的独特优势优化查询。

本文版权归作者所有,未经作者同意不得转载。