摘要:
随着企业级应用数据量的爆炸式增长,传统单机数据库在性能、扩展性与高可用性方面面临严峻挑战。GaussDB作为华为推出的高性能、高可用、高安全的分布式关系型数据库,通过将数据分布到多个计算节点上,实现了水平扩展能力。然而,其性能表现高度依赖于合理的数据分布策略。其中,分部键(Distribution Key)、索引(Index)与分区(Partitioning)的设计是影响系统吞吐量、查询延迟与负载均衡的核心因素。本文深入探讨GaussDB中三者的设计原理与最佳实践,结合真实业务场景,提出一套系统化的协同设计方法论,旨在为开发者与架构师提供可落地的指导方案,最大化发挥GaussDB的分布式优势。
关键词:
GaussDB;分布式数据库;分部键;索引设计;分区策略;数据分布;查询优化
1. 引言
GaussDB基于Shared-Nothing架构,数据被切分并存储在多个DN(Data Node)节点上,CN(Coordinator Node)负责SQL解析、执行计划生成与结果汇总。在这种架构下,数据的物理分布直接决定了查询的执行路径。不当的分部键选择可能导致严重的数据倾斜或跨节点JOIN操作,引发“木桶效应”;不合理的索引设计会增加写入开销并降低查询效率;而分区策略若与访问模式不匹配,则无法有效缩小扫描范围。
本文将系统性地分析分部键、索引与分区在GaussDB中的作用机制,并结合实际开发需求,提出设计原则、评估方法与优化策略。
2. GaussDB核心组件与数据分布机制
2.1 组件概述



2.2 分布式数据分布方式

2.2.1 分布方式概述
分布式集群采用水平分表的方式,将业务数据表的元
组/行打散存储到各个节点内。
通过全并行数据处理技术和快速定位到数据存储位置
等手段可极大提升数据库性能

3. 分部键(Distribution Key)设计方法
3.1分部键的概述
分部键决定了数据在DN节点间的分布方式,是分布式数据库性能的基石。数据均匀分布,可以防止数据在部分DN上集中分布,从而导致因存储倾斜造成集群有效容量下降。通过选择合适的分布列,可以避免数据倾斜。GaussDB分布式数据库的表中,应必须指定合适的分布键,如果不指定分部键,系统会优先以表的主键作为分部键,如果该表没有主键则会默认以第一个字段作为分部键,因此可能诱发数据倾斜的问题。
GaussDB目前提供REPLICATION、HASH、Range和List四种表分布策略。REPLICATION分布会在每个节点保留一份相同的完整的数据表。HASH分布会根据所提供的分布键值将表数据分布到多个节点中。范围(Range)和列表(List)根据分布列的取值落入满足一定范围或者具体值的对应目标节点。
表的分布方式及使用场景如下:

3.2 分布方式设计原则
3.2.1对于系统配置表、数据字典表等数据规模较小,且插入更新十分低频的表,要求采用REPLICATION分布。此种分布方式如果数据量较大会造成空间膨胀,DML性能下降的负面影响。
3.2.2数据量较大,更新频率较高的表,必须进行数据分片,要求采用HASH分布策略,分布键必须是主键中的一个或多个字段。
3.2.3 用户需要自定义分布规则的场景,可以使用Range和List分布策略。
例如:以地市或者国家作为list分部键
--定义一个表,使用LIST分布。 CREATE TABLE warehouse_d4 ( W_WAREHOUSE_SK INTEGER NOT NULL, W_WAREHOUSE_ID CHAR(16) NOT NULL, W_WAREHOUSE_NAME VARCHAR(20) , W_WAREHOUSE_SQ_FT INTEGER , W_STREET_NUMBER CHAR(10) , W_STREET_NAME VARCHAR(60) , W_STREET_TYPE CHAR(15) , W_SUITE_NUMBER CHAR(10) ,
W_CITY VARCHAR(60) ,
W_COUNTY VARCHAR(30) ,
W_STATE CHAR(2) ,
W_ZIP CHAR(10) , W_COUNTRY VARCHAR(20) , W_GMT_OFFSET DECIMAL(5,2) )DISTRIBUTE BY LIST(W_COUNTRY) ( SLICE s1 VALUES ('USA') DATANODE dn1, SLICE s2 VALUES ('CANADA') DATANODE dn2, SLICE s3 VALUES ('UK') DATANODE dn3, SLICE s4 VALUES (DEFAULT) DATANODE dn4 );
例如:以部门ID或者仓库ID作为range分部键
--定义一个表,使用RANGE分布。
CREATE TABLE warehouse_d3 ( W_WAREHOUSE_SK INTEGER NOT NULL, W_WAREHOUSE_ID CHAR(16) NOT NULL, W_WAREHOUSE_NAME VARCHAR(20) , W_WAREHOUSE_SQ_FT INTEGER , W_STREET_NUMBER CHAR(10) , W_STREET_NAME VARCHAR(60) , W_STREET_TYPE CHAR(15) , W_SUITE_NUMBER CHAR(10) ,
W_CITY VARCHAR(60) ,
W_COUNTY VARCHAR(30) ,
W_STATE CHAR(2) ,
W_ZIP CHAR(10) , W_COUNTRY VARCHAR(20) , W_GMT_OFFSET DECIMAL(5,2) )DISTRIBUTE BY RANGE(W_WAREHOUSE_ID) ( SLICE s1 VALUES LESS THAN (10) DATANODE dn1, SLICE s2 VALUES LESS THAN (20) DATANODE dn2, SLICE s3 VALUES LESS THAN (30) DATANODE dn3, SLICE s4 VALUES LESS THAN (MAXVALUE) DATANODE dn4 );
3.3 HASH分布方式分布键设计原则
3.3.1 应该比较离散,以便数据能在各个DN上均匀分布。避免数据倾斜所产生的I/O等负载集中在部分DN上,影响整体查询性能
3.3.2 在满足第一条原则的情况下,尽量不要选取在查询中存在常量过滤条件的字段作为分布键
3.3.3 在满足前两条原则的情况,尽量选择查询中在关联条件的列为分布键。当关联条件作为分布键时,join任务的相关数据都分布在DN本地,将极大减少DN之间的数据流动代价。
3.3.4 分布键不建议超过3列,列数过多将带来较高的计算开销
3.3.5 分布键使用的列长度不宜超过128,过长会带来较高的计算开销
3.3.6 分布键值一旦插入不允许更新(UPDATE),如需更新需删除后插入
3.4 实际设计步骤
3.4.1 分析业务查询模式:
统计高频查询的WHERE/JOIN字段。
3.4.2 评估候选列基数:
SELECT COUNT(DISTINCT col) FROM table;
3.4.3 模拟分布:
对候选键计算哈希分布,预估倾斜度。
3.4.4 权衡写入与查询:
避免为优化查询牺牲写入性能。
4. 分区(Partitioning)设计方法
4.1 分区概述
数据库表分区是一种数据库优化的技术,可以将大表物理上分割成多个较小的部分以提高查询性能。每个部分称为一个分区,这些分区可以存储在不同的存储设备上。
优点:
1)提高查询性能:通过减少扫描的数据量使查询性能有显著地提升。
2)优化存储:通过把不同分区存放到不同的存储介质上,来平衡性能和成本。
3)增加可维护性:分区表的维护操作(数据清理、重建索引)可以从分区的粒度来进行,减少对整个系统的影响。
4)提高并发性:分区表可以提高并发性,因为多个分区可以并行处理。例如,多个查询可以同时访问不同的分区,而不会相互干扰。
缺点:
1)内存资源占用:分区表使用内存大致为(分区数 * 3 / 1024)MB,当分区数太多导致内存不足时,会间接导致性能急剧下降。
2)分区策略复杂性:制定和实施合适的分区策略需要技术知识和经验。如果分区策略选择不当,可能会导致数据分布不均衡,进一步影响性能。
3)备份恢复的复杂性:虽然可以单独备份和恢复分区,但这也意味着需要更细致的备份策略和管理工作。
4.2 分区类型与适用场景
4.2.1提高查询性能:表的数据量大,且具有某些特性的数据在其中某个场景中经常会用到,可以通过减少查询时扫描数据量来提高性能。如:经常以月、季度、年为单位做分析的表。
4.2.2平衡性能和成本:表的数据量过大,需要将冷数据(不常访问的数据)移动到低成本存储,而将热数据(频繁访问的数据)保留在高性能存储上。
4.2.3大数据量表管理:表的数据量过大,需要在多个存储介质上存储的场景。
4.3 设计策略

4.3.1 范围分区表:将数据基于范围映射到不同的分区。范围由创建分区表时指定的分区键决定。常用于时间序列数据,例如按日期、月份或年份进行分区。
4.3.2 列表分区表:根据分区键的值将数据分配到不同的分区,分区中包含的键值由创建分区表时指定。适用于分类明确的数据,例如订单状态、设备类型或地区代码。
4.3.3 哈希分区表:将数据根据哈希算法映射到不同分区中,分区个数在创建分区表时指定。适用于需要均匀分布数据以平衡负载的场景。
4.3.4分区与分部键协同
理想情况:分部键与分区键一致(如 user_id 既做分部键又做哈希分区键),实现双层过滤。
让分布键(Distribute Key)和分区键(Partition Key)保持一致,或者至少让它们协同设计,是一种非常推荐的最佳实践。这样做可以最大化查询性能,特别是对于大规模数据的关联(JOIN)和聚合操作,尽量减少节点间数据重分布,当分区键和分布键不一致的情况,有跨节点的情况下,应优先考虑分布键。
分布键(DISTRIBUTE BY HASH):决定数据在节点间如何分布,目标是避免跨节点JOIN。
分区键(PARTITION BY RANGE/LIST):决定数据在节点内如何组织,目标是高效查询和管理。
5. 索引设计方法
5.1 索引概述
索引是数据库中用于加速数据检索的一种数据结构,通过创建指向表中数据的指针,使得数据库可以快速定位和访问特定的行,而无需扫描整个表。索引类似于书籍的目录,帮助用户快速定位所需要的内容,从而显著提高查询性能。虽然索引可以提高数据访问速度,但同时也增加了插入、更新、和删除操作的处理时间。所以是否要为表创建索引、索引建立在哪些字段上,是创建索引前必须要考虑的问题。
5.2 索引类型选择
5.2.1按索引列数将索引分类
单列索引:仅在一个列上建立索引
多列索引:多列索引又称为组合索引。一个索引中包含多个列,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用
5.2.2 按索引使用方法将索引分类:
全文索引:提供了查询可读性文档的能力,并且通过查询相关度将结果进行排序
唯一索引:列值或列值组合唯一的索引。建表时会在主键上自动建立唯一索引
函数索引(表达式索引):建立在函数基础之上的索引
分区索引:在表的分区上独立创建的索引,在删除某个分区时不影响该表的其他分区索引的使用
全局二级索引:分布式集群,允许用户定义与基表分布不一致的索引,从而实现基表非分布列查询的单节点计划和基表非分布列上的unique/主键约束
5.2.3 根据索引数据的分布方式以及对查询、维护操作的不同,分为全局索引和本地索引
本地索引 (Local Index):是与表的分区(或分布)结构对齐的索引。每个表分区(或每个节点上的数据)都有一个独立的索引片段(Index Partition),这些片段共同构成完整的索引。

优点
维护成本低,支持高效的分区管理。
索引损坏只影响单个分区,风险隔离。
适合大表分区场景。
缺点
跨多个分区的范围查询可能需要访问多个索引片段。
全局索引 (Global Index):是独立于表的分区结构的单一索引。它跨越所有表分区(或所有节点),形成一个统一的索引结构。

优点
对非分区键的查询性能好。
索引结构单一,查询路径简单。
缺点
分区维护操作代价高(需重建索引)。
索引损坏可能影响整个表的查询。
在分布式环境下,全局索引的维护和查询可能涉及跨节点操作。
5.2.4 按照数据库引擎不同分为:
ASTORE存储引擎支持的索引类型:btree(行存表缺省值)、gin、gist。USTORE存储引擎支持的索引类型:ubtree、ugin。
取值范围:
btree:btree索引使用一种类似于B+树的结构来存储数据的键值,通过这种结构能够快速的查找索引。btree适合支持比较查询以及范围查询。
ubtree:仅供ustore表使用的多版本btree索引,索引页面上包含事务信息,并能自主回收页面。ubtree索引默认开启insertpt功能。
ugin:通用倒排索引,一种仅供Ustore表使用的GIN索引。
gin:通用倒排索引,可以处理包含多个键的值(例如数组)。
gist:适用于几何和地理等多维数据类型和集合数据类型。目前支持的数据类型包括box、point、poly、circle、tsvector、tsquery和range。
5.3 设计原则
5.3.1经常执行查询的字段作为索引。
5.3.2在连接条件上创建索引,对于存在多字段连接的查询,建议在这些字段上建立组合索引
5.3.3 where子句的过滤条件字段上(尤其是范围条件)。
5.3.4在经常出现在order by、group by和distinct后的字段
5.3.5 分区表索引分为LOCAL索引与GLOBAL索引,LOCAL索引与某个具体分区绑定,而GLOBAL索引则对应整个分区表
5.3.6 基表为HASH分布时,若创建不包含基表分布键的主键或唯一索引,需要使用全局二级索引(CREATE GLOBAL INDEX),若创建包含基表分布键的主键或唯一索引,需要使用普通索引(CREATE INDEX),单DN部署形式下,使用全局二级索引或者普通索引均可创建成功;当基表为除HASH分布以外的其他分布形式时,主键或唯一索引只能使用普通索引(CREATE INDEX),即索引键必须包含基表分布键
5.3.7 索引自身也占用存储空间、消耗计算资源,创建过多的索引将对数据库性能造成负面影响(尤其影响数据导入的性能,建议在数据导入后再建索引)。
5.3.8 如果基表是HASH/RANGE/LIST分布,则创建唯一索引时必须包含基表的分布键,且不能含有表达式
5.3.9 如果表达式索引中调用的是用户自定义函数,按照表Owner权限执行表达式索引函数
5.3.10 不支持XML类型数据作为普通索引、UNIQUE索引、GLOBAL索引、LOCAL索引、部分索引
5.3.11 UGIN索引目前仅支持Ustore存储引擎,仅支持单列索引
5.3.12 GIN、GiST索引仅支持Astore存储引擎
5.3.13 由于B-tree索引有结构性约束,需要保证单个页面有至少三个索引元组,因此有最长索引长度的约束。B-tree和UB-tree索引单个索引元组最长长度为2696字节,但是由于Ustore的UB-tree索引中有事务信息,所以其单个索引元组实际的最长长度为2688字节。用户对超过这个长度的字段创建索引会报错,同样地,从Astore表迁移到Ustore表后对应的索引也存在创建失败的风险
5.3.14 组合索引最左匹配原则:如果查询条件包含了组合索引的一列或者多列,那么组合索引的最左侧开始的连续列需要与查询条件匹配
5.4 索引失效的情况
5.4.1 使用 NOT IN、!=、NOT EXISTS 等否定条件
5.4.2 使用 OR 连接条件且部分列无索引
5.4.3 在索引列上使用 LIKE 且以通配符开头
5.4.4 数据类型不匹配导致隐式转换
5.4.5 复合索引未遵循最左前缀原则
5.4.6查询结果占全表数据比例过高
5.4.7 IS NULL / IS NOT NULL 查询(特定情况)
5.4.8 统计信息过期或缺失(ANALYZE TABLE table1;)
6. 协同设计方法论
6.1 设计流程图
业务需求分析
↓
识别核心表与访问模式(读/写比例、高频查询)
↓
选择分部键:优先JOIN字段、高频过滤列、高基数
↓
设计分区策略:按时间/地域等维度拆分大表
↓
添加索引:基于查询条件创建覆盖索引,避免回表
↓
压测验证:检查数据倾斜、执行计划、QPS/延迟
↓
迭代优化
6.2 典型场景案例
6.2.1场景:电商平台订单系统
表结构:orders(order_id, user_id, shop_id, create_time, status, amount)
查询模式:
- 用户查订单:WHERE user_id = ?
- 商家查订单:WHERE shop_id = ?
- 运营按时间统计:WHERE create_time BETWEEN ? AND ?
6.2.2设计决策:
1. 分部键:user_id(用户查询最频繁)
2. 分区键:RANGE(create_time) 按时间分区
3. 索引:
- idx_shop_time:(shop_id, create_time) 支持商家查询
- idx_status_time:(status, create_time) 支持状态统计
4. JOIN优化:订单与用户表均以 user_id 为分部键,JOIN高效。
7. 结论
在GaussDB分布式数据库中,分部键、索引与分区的设计并非孤立行为,而是一个需要协同优化的系统工程。分部键决定数据的物理分布与查询的并行效率,分区策略管理数据的生命周期与访问局部性,索引则加速特定访问路径。实际开发中,应以业务查询模式为核心,遵循高基数、高频率、低倾斜的原则选择分部键;采用时间或业务维度进行分区;并基于查询条件构建最小化、覆盖式的索引。通过压测验证与持续监控,可构建出高性能、可扩展的分布式数据架构。
1万+

被折叠的 条评论
为什么被折叠?



