如何在DynamoDB中建模一对一,一对多和多对多关系

F_S*_*O_K 8 amazon-dynamodb

在DynamoDB中建模这些关系的最佳方法是什么?

  • 一对一关系
  • 一对多关系
  • 多对多关系

F_S*_*O_K 15

我已经多次看到这个问题的变体,以为我会写一个问答集。

DynamoDB关键基础

在阅读本文之前,您应该了解:

  • 每个DynamoDB表都有一个唯一的主键
  • 主键必须由分区键组成,并且可以有选择地具有排序键。同时具有分区键和排序键的主键是复合键
  • 的GetItem请求返回一个且唯一一个使用它的唯一主键项。
  • 一个查询做了快速查找,必须指定一个且只有一个分区键。它可以返回多个项目。
  • 一个扫描评估表中的每个项目,并可能返回基于滤波器参数的一个子集。在某些情况下,扫描是正确的选择,但如果使用不正确,则扫描速度会很慢且成本很高。
  • GSILSI之间的区别以及它们的用途。

一对一


我们可以为护照和人物建模以证明这种关系。一本护照只能有一个所有者,一个人只能拥有一本护照。

该方法非常简单。我们有两个表,其中一个表应具有外键。

护照表:

分区键:PassportId

???????????????????????????????????
? PassportId ? Pages ?   Issued   ?
???????????????????????????????????
? P1         ?    15 ? 11/03/2009 ?
? P2         ?    18 ? 09/02/2018 ?
???????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

护照夹表:

分区键:PersonId

????????????????????????????????
? PersonId ? PassportId ? Name ?
????????????????????????????????
? 123      ? P1         ? Jane ?
? 234      ? P2         ? Paul ?
????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

请注意,PersonId没有出现在护照表中。如果这样做,我们将在两个地方拥有相同的信息(哪些护照属于哪个人)。如果表对谁拥有哪本护照没有达成一致意见,这将导致额外的数据更新,并可能导致某些数据质量问题。

但是,我们缺少一个用例。我们可以通过PersonId轻松查找一个人,并找到他们拥有的护照。但是,如果我们有一个PassportId,并且我们需要找到谁拥有它,该怎么办?在当前模型中,我们需要在“护照持有人”表上执行“ 扫描 ”。如果这是常规用例,则我们不希望使用扫描。为了支持GetItem,我们可以简单地将GSI添加到Passport持有人表中:

护照持有人表GSI:

分区键:PassportId

????????????????????????????????
? PassportId ? PersonId ? Name ?
????????????????????????????????
? P1         ? 123      ? Jane ?
? P2         ? 234      ? Paul ?
????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

现在,我们可以非常快速且廉价地使用PassportId或PersonId查找关系。

还有其他建模方法。例如,您可以有一个“普通” Passport表和没有外键的Person表,然后有第三个辅助表,可以简单地将PassortIds和PersonIds映射在一起。在这种情况下,我认为这不是最干净的设计,但是,如果您喜欢它,则该方法没有错。请注意,它们是“多对多关系”部分中辅助关系表的示例。


一对多


我们可以为宠物和主人建模以证明这种关系。宠物只能有一个主人,但主人可以有很多宠物。

该模型看起来与一对一模型非常相似,因此我将只关注这种差异。

宠物桌:

分区键:PetId

????????????????????????????
? PetId ? OwnerId ? Type   ?
????????????????????????????
? P1    ? O1      ? Dog    ?
? P2    ? O1      ? Cat    ?
? P3    ? O2      ? Rabbit ?
????????????????????????????
Run Code Online (Sandbox Code Playgroud)

所有者表:

分区键:OwnerId

????????????????????
? OwnerId ? Name   ?
????????????????????
? O1      ? Angela ?
? O2      ? David  ?
????????????????????
Run Code Online (Sandbox Code Playgroud)

我们将外键放在表中。如果我们采用另一种方法,并将PetIds放在Owner表中,则一个Owner Item将必须具有一组PetId,这将使管理变得复杂。

如果我们想找出宠物的主人,这很容易。我们可以做一个GetItem来返回宠物物品,它告诉我们主人是谁。但是另一种方法则更难-如果我们拥有OwnerId,他们拥有哪些宠物?为了节省我们必须在Pet表上执行扫描的过程,我们改为向Pet表中添加GSI。

宠物桌GSI

分区键:OwnerId

????????????????????????????
? OwnerId ? PetId ? Type   ?
????????????????????????????
? O1      ? P1    ? Dog    ?
? O1      ? P2    ? Cat    ?
? O2      ? P3    ? Rabbit ?
????????????????????????????
Run Code Online (Sandbox Code Playgroud)

如果我们有OwnerId并希望找到其Pets,则可以在Pet表GSI上执行查询。例如,对所有者O1的查询将返回带有PetId P1和P2的项目。

您可能会在这里注意到一些有趣的东西。一个主键对于一个表必须是唯一的。这仅适用于基表。GSI主键(在本例中仅为GSI分区键)不必唯一

在DynamoDB表中,每个键值必须唯一。但是,全局二级索引中的键值不必唯一

附带说明,GSI不需要投影所有与基表相同的属性。如果仅将GSI用于查找,则可能只希望投影GSI键属性。


多对多


在DynamoDB中,可以通过三种主要方法来建模多对多关系。每个都有优点和缺点。

我们可以使用“医生和患者”的示例来建立这种关系的模型。医生可以有很多病人,而病人可以有很多医生。


多对多-选项1-辅助表


通常,这是我首选的方法,这就是为什么要优先考虑的原因。这个想法是创建没有关系引用的“普通”基表。然后将关系引用放入辅助表(每种关系类型一个辅助表-在这种情况下,仅是Doctors-Patients)。

医生桌:

分区密钥:DoctorId

????????????????????
? DoctorId ? Name  ?
????????????????????
? D1       ? Anita ?
? D2       ? Mary  ?
? D3       ? Paul  ?
????????????????????
Run Code Online (Sandbox Code Playgroud)

病床

分区键:PatientId

????????????????????????????????????
? PatientId ? Name    ? Illness    ?
????????????????????????????????????
? P1        ? Barry   ? Headache   ?
? P2        ? Cathryn ? Itchy eyes ?
? P3        ? Zoe     ? Munchausen ?
????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

DoctorPatient表(辅助表)

分区密钥:DoctorId

排序键:PatientId

???????????????????????????????????????
? DoctorId ? PatientId ? Last Meeting ?
???????????????????????????????????????
? D1       ? P1        ? 01/01/2018   ?
? D1       ? P2        ? 02/01/2018   ?
? D2       ? P2        ? 03/01/2018   ?
? D2       ? P3        ? 04/01/2018   ?
? D3       ? P3        ? 05/01/2018   ?
???????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

DoctorPatient表GSI

分区键:PatientId

排序键:DoctorId

???????????????????????????????????????
? PatientId ? DoctorId ? Last Meeting ?
???????????????????????????????????????
? P1        ? D1       ? 01/01/2018   ?
? P2        ? D1       ? 02/01/2018   ?
? P2        ? D2       ? 03/01/2018   ?
? P3        ? D2       ? 04/01/2018   ?
? P3        ? D3       ? 05/01/2018   ?
???????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

一共有三个表,DoctorPatient辅助表是其中一个有趣的表。

DoctorPatient基表主键必须唯一,因此我们创建了DoctorId(分区键)和PatientId(排序键)的复合键。

我们可以使用DoctorId在DoctorPatient基表上执行查询,以获取Doctor拥有的所有患者。

我们可以使用PatientId在DoctorPatient GSI上执行查询,以获取与患者关联的所有Doctors。

这种方法的优势在于表的清晰分隔以及将简单业务对象直接映射到数据库的能力。它不需要使用更高级的功能(例如集合)。

必须协调一些更新,例如,如果您删除“患者”,则还需要注意删除DoctorPatient表中的关系。但是,与其他一些方法相比,引入数据质量问题的机会很小。

这种方法的潜在缺点是需要3个表。如果要为表供应吞吐量,则表越多,分配容量就越薄。但是,有了新的按需功能,就不必担心了。


多对多-选项2-外键集


这种方法仅使用两个表。

医生桌:

分区密钥:DoctorId

?????????????????????????????????
? DoctorId ? PatientIds ? Name  ?
?????????????????????????????????
? D1       ? P1,P2      ? Anita ?
? D2       ? P2,P3      ? Mary  ?
? D3       ? P3         ? Paul  ?
?????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

患者表:

分区键:PatientId

??????????????????????????????????
? PatientId ? DoctorIds?  Name   ?
??????????????????????????????????
? P1        ? D1       ? Barry   ?
? P2        ? D1,D2    ? Cathryn ?
? P3        ? D2,D3    ? Zoe     ?
??????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

此方法涉及将关系作为一组存储在每个表中。

要查找医生的患者,我们可以在Doctor表上使用GetItem来检索Doctor项目。然后,将PatientId作为一组存储在Doctor属性中。

要查找患者的医生,我们可以在“患者”表上使用GetItem来检索“患者”项目。然后,DoctorIds作为一组存储在Patient属性中。

这种方法的优势在于,业务对象和数据库表之间存在直接映射。只有两个表,因此,如果您使用预配置吞吐容量,则不需要将其分布得太窄。

这种方法的主要缺点是可能存在数据质量问题。如果将“患者”链接到“医生”,则需要协调两个更新,每个表一个。如果一次更新失败会怎样?您的数据可能不同步。

另一个缺点是在两个表中都使用了集。DynamoDB SDK设计用于处理集合,但是当涉及集合时,某些操作可能会很复杂。


多对多-选项3-图形架构


AWS先前将其称为“ 邻接列表”模式。它通常被称为Graph数据库Triple Store

我之前已经在AWS Adjancey List Pattern 上回答了这个问题,这似乎帮助了一些人理解它。

而且AWS最近有一个演讲,在这里谈论了很多关于这种模式的内容

该方法涉及将所有数据放在一张表中。

我只是绘制了一些示例行,而不是整个表:

分区键:Key1

排序键:Key2

??????????????????????????????????????????????????????????
? Key1    ? Key2    ? Name  ?   illness   ? Last Meeting ?
??????????????????????????????????????????????????????????
? P1      ? P1      ? Barry ? Headache    ?              ?
? D1      ? D1      ? Anita ?             ?              ?
? D1      ? P1      ?       ?             ? 01/01/2018   ?
??????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

然后需要一个GSI来反转密钥:

分区键:Key2

排序键:Key1

??????????????????????????????????????????????????????????
? Key2    ? Key1    ? Name  ?   illness   ? Last Meeting ?
??????????????????????????????????????????????????????????
? P1      ? P1      ? Barry ? Headache    ?              ?
? D1      ? D1      ? Anita ?             ?              ?
? P1      ? D1      ?       ?             ? 01/01/2018   ?
??????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

该模型在某些特定情况下具有一定的优势-在高度连接的数据中可以表现良好。如果您格式化好数据,则可以实现非常快速和可扩展的模型。灵活的是,您可以在表中存储任何实体或关系,而无需更新架构/表。如果您要配置吞吐能力,那么这将是高效的,因为所有吞吐能力可用于应用程序中的任何操作。

如果使用不当或不慎重考虑,此模型将遭受巨大的不利影响。

您将丢失业务对象和表之间的任何直接映射。这几乎总是导致无法阅读的意大利面条代码。即使执行简单的查询也可能会非常复杂。由于代码和数据库之间没有明显的映射,因此管理数据质量变得困难。我见过的大多数使用此方法的项目最终都会编写各种实用程序,其中一些实用程序本身就是产品,仅用于管理数据库。

另一个小问题是模型中每个项目的每个属性都必须存在于一个表中。这通常会导致一个包含数百列的表。本身这不是问题,但是尝试在具有这么多列的表上工作通常会引发一些简单的问题,例如难以查看数据。

简而言之,我认为AWS可能在一组文章中发布了本应该有用的文章,但是由于未能引入用于管理多对多关系的其他(更简单)概念,他们使很多人感到困惑。很明显,邻接列表模式可能很有用,但它不是在DynamoDB中建模多对多关系的唯一选择。如果它适用于您的情况(例如严重的大数据),请务必使用它,但如果不行,请尝试一种较简单的模型。

  • 这应该是社区Wiki并固定在最上面。 (3认同)
  • AWS文档指出`您应在DynamoDB应用程序中维护尽可能少的表。设计良好的大多数应用程序仅需要一张表。`映射多对多关系的最佳实践集中在一个表解决方案上,即邻接表模式。有什么看法吗? (2认同)
  • 另外,此处介绍的所有一对多和多对多解决方案都要求写入多个表和/或表中的项目,并且DynamoDB无法保证操作是原子的。使用NoSQL时必须有数据冗余,这会自动导致数据不一致。也许您的答案中应该更加强调这一点? (2认同)
  • 我发现这个答案对于学习数据库设计原理非常有帮助,但正如其他人确实评论的那样,这不是 DynamoDB 的推荐工作流程。我想我应该分享两个我发现有用的资源(对于相对初学者来说),让我了解最佳实践;这篇文章:https://www.alexdebrie.com/posts/dynamodb-single-table/ 和本教程:https://aws.amazon.com/getting-started/hands-on/design-a-database-for -a-mobile-app-with-dynamodb/ (2认同)