在 ScyllaDB 和其他 NoSQL 数据库中,数据模型基于查询,而不仅仅是围绕域实体。在创建数据模型时,我们同时考虑了概念数据模型和应用程序工作流:哪些用户将执行哪些查询以及执行频率。
在 ScyllaDB 和其他宽列数据库(例如 Apache Cassandra)中进行数据建模的主要目标之一是快速返回结果。为此,您需要:
均匀的数据分布:数据应均匀分布在群集中,以便每个节点保存大致相同的数据量。ScyllaDB 根据分区键的哈希值确定哪个节点应存储数据。因此,选择合适的分区键至关重要。稍后会详细介绍。
若要最大程度地减少读取查询中访问的分区数,请执行以下操作: 为了加快读取速度,理想情况下,我们会将读取查询中所需的所有数据存储在单个表中。虽然跨表复制数据是可以的,但就性能而言,如果读取查询所需的数据位于一个表中,则更好。
你不应该关注的事情:
避免数据重复:为了获得高效的读取,我们有时必须复制数据。本课稍后将详细介绍此内容和非规范化。在后面的课程中,我们将学习如何在某些情况下使用二级索引避免重复。
最小化写入次数:ScyllaDB中的写入不是免费的,但它们非常高效且“便宜”。ScyllaDB 针对高写入吞吐量进行了优化。读取虽然仍然非常快,但通常比写入更昂贵,并且更难微调。我们通常会准备增加写入次数以提高读取效率。请记住,表的数量也会影响一致性。
在本教程中,您将使用一个名为 4Paws Clinic 的兽医诊所的示例。在这家诊所,每只入院的动物都有一个连接的心率监测器,每五秒钟记录一次心率和其他重要信息。
什么是主键?
主键在表中定义。它是用于标识行的一列或多列。所有表都必须包含主键的定义。例如,请考虑以下表:
1 CREATE TABLE heartrate_v1 ( 2 pet_chip_id uuid, 3 time timestamp, 4 heart_rate int, 5 PRIMARY KEY (pet_chip_id) 6 );
在上面的示例中,主键是一列 – .如果主键由单个列组成,则称为简单主键。pet_chip_id
对于上表,请执行查询:
SELECT * from heartrate_v1 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23;
如果我们想通过查询我们的数据,但同时也要通过查询我们的数据,会发生什么?也就是说,如果我们的查询是:pet_chip_idtime
SELECT * from heartrate_v1 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 AND time >='2019-03-04 07:01:00' AND time <='2019-03-04 07:02:00';
在这种情况下,上述查询将不起作用。我们可以将主键定义为包含多个列,在这种情况下,它称为复合(或复合)键。创建下表:
1 CREATE TABLE heartrate_v2 ( 2 pet_chip_id uuid, 3 time timestamp, 4 heart_rate int, 5 PRIMARY KEY (pet_chip_id, time) 6 );
并插入一些数据:
INSERT INTO heartrate_v2(pet_chip_id, time, heart_rate) VALUES (123e4567-e89b-12d3-a456-426655440b23, '2019-03-04 07:01:05', 100); INSERT INTO heartrate_v2(pet_chip_id, time, heart_rate) VALUES (123e4567-e89b-12d3-a456-426655440b23, '2019-03-04 07:01:10', 90); INSERT INTO heartrate_v2(pet_chip_id, time, heart_rate) VALUES (123e4567-e89b-12d3-a456-426655440b23, '2019-03-04 07:01:50', 96); INSERT INTO heartrate_v2(pet_chip_id, time, heart_rate) VALUES (123e4567-e89b-12d3-a456-426655440b23, '2019-04-04 07:01:50', 99);
输入分区键和群集键
在上面显示的情况下,主键的第一部分称为分区键(在上面的示例中),第二部分称为群集键()。pet_chip_idtime
主键由两部分组成:
分区键负责跨节点分发数据。它确定哪个节点将存储给定的行。它可以是一列或多列。
群集键负责对分区中的行进行排序。它可以是零列或多列。
现在,根据时间执行我们之前看到的查询:
SELECT * from heartrate_v2 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 AND time >='2019-03-04 07:01:00' AND time <='2019-03-04 07:02:00';
此外,我们之前遇到过一个问题,即无论时间如何,宠物都只能记录一个心率值。现在,我们定义了作为主键一部分的时间,每个主键(是 和 的组合)都可以有一个心率值。heartrate_v1pet_chip_idtime
读取同一只宠物的数据:
SELECT * from heartrate_v2 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 ;
我们可以看到与前面的例子相反。这一次,该值没有被覆盖。
具有多列的分区键和群集键
正如我们刚才所看到的,分区键和聚类键都可以包含多个列,例如,如果我们的查询是:
SELECT * from heartrate_v3 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 AND time ='2019-03-04 07:01:00' AND pet_name = 'Duke'; 1. 我们可以按如下方式定义该表: 1 CREATE TABLE heartrate_v3 ( 2 pet_chip_id uuid, 3 time timestamp, 4 heart_rate int, 5 pet_name text, 6 PRIMARY KEY ((pet_chip_id, time), pet_name) 7 );
创建上表,然后插入一些数据:
INSERT INTO heartrate_v3(pet_chip_id, time, heart_rate, pet_name) VALUES (123e4567-e89b-12d3-a456-426655440b23, '2019-03-04 07:01:10', 90, 'Duke');
在本例中,分区键包括两列:和 ,聚类键为 。请记住,每个查询都必须包含分区键中定义的所有列。pet_chip_idtimepet_name
现在尝试执行此查询:
SELECT * from heartrate_v3 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23;
它失败,因为没有给出整个分区键。
现在试试这个查询:
SELECT * from heartrate_v3 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 AND time ='2019-03-04 07:01:10' AND pet_name = 'Duke';
当给出完整的分区键时,它会成功。
同样,如果我们希望每个分区都基于 但希望能够根据 和 进行查询:pet_chip_idpet_nameheart_rate
SELECT * from heartrate_v4 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 AND pet_name = 'Duke' AND heart_rate = 100;
可以定义(执行此操作):
1 CREATE TABLE heartrate_v4 ( 2 pet_chip_id uuid, 3 time timestamp, 4 heart_rate int, 5 pet_name text, 6 PRIMARY KEY (pet_chip_id, pet_name, heart_rate) 7 );
注意:
如果聚类分析键(和上面的示例中)中有多个列,则这些列的顺序定义聚类排序。对于给定的分区,所有行在 ScyllaDB 中按聚类顺序进行物理排序。此顺序决定了可以在此分区上有效运行的选择查询。pet_nameheart_rate
在此示例中,排序首先是 by,然后是 。pet_nameheart_rate
除了“分区键”列之外,查询还可能包括“群集键”。如果它确实包含“聚类分析键”列,则必须按照定义的顺序使用它们。