如何设计宽表主键
GeminiDB Cassandra是一款分布式数据引擎,其宽表引擎中的数据均按照主键进行分布。在执行查询时,若表中包含多列主键,系统将从最左侧的主键开始进行匹配。若主键设计不合理,可能导致系统无法有效利用主键分布,从而引发数据热点问题,影响查询性能。因此,在数据分区与查询过程中,主键的设计尤为关键。本文将介绍设计主键前需要考虑的关键问题,并提供相应的设计示例。
主键定义
Cassandra中的主键由分区键(Partition Key)和集群键(Clustering Key)组成,主键用于唯一标识一行数据。其中,分区键决定了数据分布到哪个Token范围内,而集群键则决定了数据在该分区内部的排序。
主键结构可表示为:
Primary Key(主键)= Partition Key(分区键)+ [Clustering Key(集群键,可选)]
主键的唯一性
在GeminiDB Cassandra中,相同的主键被视为同一条数据的多个版本。查询时只会返回最新版本的数据,因此,主键必须保证唯一性。
主键可以由一列或多列组合构成,用于唯一标识一行数据。以下是几种常见的主键设计示例:
- [userid]:主键仅有一列,没有集群键。每个用户仅有一条记录。
- [userid][orderid]:主键为两列组合,其中userid作为分区键,orderid作为集群键。每个用户可以有多个订单记录。
- [userid, orderid][timestamp]:主键为三列组合,userid和orderid共同构成复合分区键,timestamp作为集群键。每个用户下的订单可以包含多个不同时间戳的记录。
通过合理设计主键结构,可以有效提升数据的查询效率与组织逻辑。
基于主键的查询场景
主键的设计限制了数据的查询方式,一条SELECT查询语句可能对应两种查询方式。
- 根据完整的主键查询,例如:
SELECT * FROM table WHERE userid='abc' AND orderid=123;
该方式需要知道所有的主键列,即组成主键所有字段的值是确定的。
- 根据主键的范围查询,例如:
SELECT * FROM table WHERE userid = 'abc' AND orderid > 123 AND orderid < 456;
该方式需要指定第一列主键的范围,否则可能会导致查询超时或失败。
最佳设计示例:在有限的查询方式下如何实现复杂查询?以下方法可以帮您实现。
- 再新建一张表作为索引表。
- 查询条件给定非主键列范围,服务端会使用Filter过滤不需要的数据。
- 使用二级索引。
- 使用ORDER BY方法实现倒序(将新数据排在前面),例如:
SELECT * FROM table WHERE userid = 'abc' AND orderid > 123 AND orderid < 456 ORDER BY orderid DESC;
由于表字段原始顺序的倒序性能比正序性能差,如果大部分数据是倒序场景,可以体现在主键设计上,主键设计为[userid][orderid DESC]。
设计主键应考虑哪些因素
在设计主键时,需要综合考虑主键列值的长度和列的数量。
- 主键列值的长度:建议主键值尽量短小,优先使用固定长度的数据类型,如长整型(Long)。如果必须使用可变长度类型,建议将长度控制在2KB以内,这有助于降低存储成本,提升写入性能。
- 主键列的个数:主键列的数量越少,写入性能越高,同时能够减少存储开销。建议将主键的列数控制在1到3列之间,以取得性能和扩展性的良好平衡。
设计主键应该避免哪些情况
GeminiDB Cassandra是一个分布式数据库,数据按照主键进行分布。如果主键包含多列,数据库会按照“最左匹配”原则进行数据分布。为避免写入热点问题,建议您遵循以下最佳实践:
- 分区键应尽量分散:避免将大量数据写入同一个分区键下,以减少单个节点的负载压力。
- 避免使用共同前缀作为分区键:共同前缀会导致数据分布不均,容易引发热点问题。
- 避免使用具有有限前缀或枚举类型的字段作为第一列主键:例如order_type`这类字段值有限,容易引起数据倾斜,影响写入性能和均衡性。
主键精简
主键列越精简,数据量越小,查询和写入效率越高。
最佳实践示例:
- 使用Long或Int代替String,如将'2015122410' 替换为 Long(2015122410)。
- 用编码代替名称,如将'手机'替换为'sj'。
通过精简主键值,可有效减少存储开销并提升系统性能。
常见设计示例
以下是几种常见场景下的主键设计示例:
该类数据具有写入量大、按时间范围查询的特点。
场景一:查询某台机器、某个指标、在特定时间段内的数据。
业务需求:在监控大屏上展示某台机器的CPU使用率历史曲线。
设计建议:
- 主键结构:PRIMARY KEY ((hostname, metric_name), timestamp)
- 分区键:hostname + metric_name
- 集群键:timestamp
场景二:查询某台机器、某个指标、最新的N条数据。
业务需求:实时告警列表,查看最近10次错误日志。
设计建议:
- 主键结构:PRIMARY KEY ((hostname, metric_name), timestamp)
- 分区键:hostname + metric_name
- 集群键:timestamp(倒序)
通过合理设计主键结构,可以显著提升查询效率,尤其是在高并发、大数据量的场景中。
场景一:卖家后台,查询某卖家某段时间内的交易记录
场景二:买家App,查询某买家某段时间内的交易记录
设计建议:
- 主键结构:PRIMARY KEY ((buyer_id), timestamp, order_id)
- 分区键:buyer_id + order_id
- 集群键:timestamp (DESC), order_id
即使数据与上面“卖家表”完全一样,为了满足“按买家查询”的高性能,我们需要单独建立这张表。