在 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
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

在上面的示例中,主键是一列 – .如果主键由单个列组成,则称为简单主键。pet_chip_id

对于上表,请执行查询:

SELECT * from heartrate_v1 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23;
  • 1.

如果我们想通过查询我们的数据,但同时也要通过查询我们的数据,会发生什么?也就是说,如果我们的查询是: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.

在这种情况下,上述查询将不起作用。我们可以将主键定义为包含多个列,在这种情况下,它称为复合(或复合)键。创建下表:

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
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

并插入一些数据:

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);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

输入分区键和群集键

在上面显示的情况下,主键的第一部分称为分区键(在上面的示例中),第二部分称为群集键()。pet_chip_idtime

主键由两部分组成:

  • 分区键负责跨节点分发数据。它确定哪个节点将存储给定的行。它可以是一列或多列。

在 ScyllaDB(或 Cassandra)中使用主键、分区键和群集键_2d

  • 群集键负责对分区中的行进行排序。它可以是零列或多列。

现在,根据时间执行我们之前看到的查询:

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';
  • 1.

此外,我们之前遇到过一个问题,即无论时间如何,宠物都只能记录一个心率值。现在,我们定义了作为主键一部分的时间,每个主键(是 和 的组合)都可以有一个心率值。heartrate_v1pet_chip_idtime

读取同一只宠物的数据:

SELECT * from heartrate_v2 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23 ;
  • 1.

我们可以看到与前面的例子相反。这一次,该值没有被覆盖。

具有多列的分区键和群集键

正如我们刚才所看到的,分区键和聚类键都可以包含多个列,例如,如果我们的查询是:

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
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

创建上表,然后插入一些数据:

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');
  • 1.

在本例中,分区键包括两列:和 ,聚类键为 。请记住,每个查询都必须包含分区键中定义的所有列。pet_chip_idtimepet_name

现在尝试执行此查询:

SELECT * from heartrate_v3 WHERE pet_chip_id = 123e4567-e89b-12d3-a456-426655440b23;
  • 1.

它失败,因为没有给出整个分区键。

现在试试这个查询:

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';
  • 1.

当给出完整的分区键时,它会成功。

同样,如果我们希望每个分区都基于 但希望能够根据 和 进行查询: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.

可以定义(执行此操作):

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
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

注意:

  • 如果聚类分析键(和上面的示例中)中有多个列,则这些列的顺序定义聚类排序。对于给定的分区,所有行在 ScyllaDB 中按聚类顺序进行物理排序。此顺序决定了可以在此分区上有效运行的选择查询。pet_nameheart_rate
  • 在此示例中,排序首先是 by,然后是 。pet_nameheart_rate
  • 除了“分区键”列之外,查询还可能包括“群集键”。如果它确实包含“聚类分析键”列,则必须按照定义的顺序使用它们。