分布式 SQL 数据库会将应用程序数据存储在多个节点上,从存储和计算的角度提高了可扩展性。这种分布意味着某些应用程序请求,包括 JOIN 操作和聚合,可能跨多个数据库节点,可能导致数据在网络中的传输。
为了减轻网络延迟对整体应用程序性能的影响,一些数据库支持共置和交错表格。这种优化技术允许将子表格记录与其父行一起存储。因此,在执行查询时,某些需要查询父子记录的请求可能会更快,因为数据库节点在数据传输过程中的负载较小。
然而,像任何优化技术一样,共置和交错表格也有其优缺点。让我们深入了解这些表格类型,以更好地理解它们的好处和权衡。
定义
共置表格(Colocated Tables)和交错表格(Interleaved Tables)是数据库中的优化技术,用于改善数据存储和查询性能。
共置表格(Colocated Tables)
共置表格是一种优化方法,允许将子表格(child table)的记录与其父表格(parent table)的行一起存储在相同的物理位置或相邻的位置。它通过将相关数据放置在同一节点或相近节点上来提高查询效率,尤其对于需要频繁进行父子表格数据关联的查询来说,可以减少网络传输和提升查询性能。
交错表格(Interleaved Tables)
交错表格是共置表格的一种特殊类型。与共置表格不同的是,交错表格会在同一物理表结构和数据库文件中将子行与其父行物理地相邻存储,而不是分开存储。逻辑上,这些子表格和父表格仍然是不同的表格,但物理上它们的数据行被交错存储,这有助于更快地执行联合查询和减少数据查找的次数。
总的来说,这些优化技术旨在提高数据库查询效率,减少数据传输,特别是针对需要频繁进行父子表格数据关联的查询,通过优化数据存储和组织来改善性能。
共置表格
举个例子: 设想一个虚构公司开发电子商务应用程序,允许各种商家在线销售商品。
该应用程序包括两个关键表格,跟踪 Customers(客户)和他们的 Orders(订单)(注意,颜色用于区分来自不同客户的订单)。
公司预计每天会有数百万活跃客户,并决定使用一个包含 3 个节点的分布式数据库集群以增强可扩展性和可用性。
在开发阶段,数据库均匀地分布了所有应用程序记录到集群中,确保每个节点存储了可比较量的数据并处理了类似的读写工作负载。
在一个架构委员会会议期间,公司决定尝试共置表格功能,该功能允许将子表格记录与父表格的行一起存储。
结果,Order(子表格)与 Customer(父表格)共置,并且数据库开始在相同节点上将订单与其客户记录一起存储。
团队选择了实验表共置技术来评估一些请求的性能改进,这些请求需要在 Customer 和 Order 表格之间进行 JOIN 操作。例如,当应用程序需要计算客户的总支出时,该请求将在特定的数据库节点上执行。
公司确实观察到了一些查询的性能改进。
交错表格
在电子商务服务的开发阶段,公司探索了几个分布式 SQL 数据库。其中一个数据库提供了交错表格的支持,促使团队也尝试了这种表格类型。
交错表格是共置表格的一种特定类别。与共置表格类似,交错表格将子表格记录与其父表格记录一起存储。但与经典共置表格不同,经典共置表格在物理表格结构中将子和父记录存储在不同的物理表格结构中,交错表格则在同一表格结构和数据库文件中 物理地 将子行放置在其父行旁边(逻辑上,Customer 和 Order 仍然是不同的表格)。
通过这种存储级别的优化,公司发现了与之前经典共置表格测试的相同查询的额外性能增加。这种增加是因为客户及其订单存储在内存和磁盘中的同一页面上,需要更少的查找。
共置和交错表格权衡
随着时间的推移,公司接近电子商务应用程序的预生产测试阶段,出现了新的挑战。
当团队加载一个生产级数据集并开始负载测试时,他们遇到了共置表格的第一个权衡 — 数据和负载倾斜。此外,由于新的业务需求,工程团队遇到了这些表格特定的另一个权衡 — 选择最佳的父表格进行共置。
数据和负载倾斜权衡
数据倾斜是指集群中的一个节点开始存储比其他节点显着更多的应用程序记录。这种不平衡通常导致负载倾斜,因为过载的节点必须处理更多的应用程序请求并处理更大的数据量。
在早期开发阶段,团队已经注意到某些客户比其他客户更频繁地购物。这种客户行为导致某些数据库节点存储的订单比其他节点多。例如,第二个节点最终存储了比第一和
第三个节点合计更多的订单(5 > 2 + 2 = 4)。
当公司开始在使用生产级数据集的预生产环境中进行测试时,这些数据和负载倾斜问题变得更加明显。虽然某些客户在系统中只有几十个订单,但其他购物频率更高的客户则积累了数百甚至数千个通过应用程序处理的订单。
这些经常购物的顾客是某些节点存储和处理更多数据的主要原因。
据我所知,针对这种特定的数据倾斜问题没有简洁的解决方案。共置表格将子记录映射到存储父记录的节点,并且每个父记录一次只能映射到一个数据库节点。
因此,为了将特定客户的订单分布到多个节点,必须有多个标识该客户的记录。例如,通过在 Customer 主键 (id, bucket) 中添加 bucket ID,你可以为经常购买者创建多个记录 — Customer (1, 1), Customer (1, 2), Customer (1, 3) 等。然后,可以将客户的订单添加到特定的桶中,从而允许数据库将它们分布到多个节点。然而,这种方法使应用程序逻辑变得更加复杂,需要决定桶的数量和每个新订单的合适桶。
最佳父表格权衡
共置表格的第二个权衡与选择最合适的父表格进行共置有关。
实际上,电子商务应用程序的数据库架构要复杂得多,涵盖了许多关系和依赖关系。
例如,尽管客户最初打算购买一件商品,但通常会最终在购物车中有几个产品。因此,一个订单可能包括两个或更多已购买的产品。
假设公司引入了 SoldProducts 表格来跟踪所有客户订单中销售的产品。类似于 Orders 表格,决定将这个新表格与 Customer 表格共置。
选择这种共置策略是有原因的。应用程序经常需要执行请求,这些请求连接了 Customer、Order 和 SoldProduct 表格的数据。例如 — 客户在最近一个月内购买了什么产品?
因此,订单和已售产品的记录现在都与保持其客户记录的数据库节点一起存储。
然而,在预生产测试的中途,来自业务团队的新要求出现了。他们的目标是优先考虑一个专门用于商家和公司物流以及业务增长部门的微服务。该服务旨在使商家和公司能够跟踪和预测各种产品的需求和可用性。通过实施此功能,公司可以通过向具有相似喜好的客户推荐热门产品来提高销售,而商家则可以主动补充产品。
工程团队决定将表 Product(子表格)记录与 Merchant 表格(父表格)的行共置。
然而,从性能的角度来看,这种方法证明是不够的,因为许多查询需要连接 Merchant、Product 和 SoldProduct 表格的数据。这样一个查询的示例是 — 最近 12 小时内购买的最热门产品是什么?
从技术上讲,将已售产品记录存储在相应的商家附近是可行的。但有一个复杂问题 — SoldProduct 记录已经与 Customer 表格共置,以满足另一个微服务的要求。
这是最佳父表格权衡的一个例子。例如,SoldProducts 表格一次只能与一个父表格共置。确定哪种共置策略从长远来看是最好的通常是一项困难的任务。
总结
像每一种优化技术一样,表共置和交错都有其优点和权衡。要确保共置不是你的用例的过早优化,首先在没有共置的情况下运行应用程序工作负载。检查查询执行计划,并应用避免需要共置表格的优化。有时,你只需要创建适当的索引,为 JOIN 操作启用批处理,为页面缓冲区提供更多内存等 — 在看到执行计划之前你永远不会知道。但是,如果什么都不起作用,那么考虑共置表格,确保它们的权衡不会对长期产生不良影响。