DynamoDB 关系建模(多对多)

19 篇文章 0 订阅
18 篇文章 1 订阅

前一阵在Stack Overflow 上看到一篇关于DynamoDB 建模的回答,回答很好。所以这里整理一篇文章回顾该回答,指导以后的设计。

1 Dynamodb 基础概念

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

2 一对一

我们可以为护照和持有人建模以证明这种关系。一本护照只能有一个主人,一个人只能拥有一本护照。
我们有两个表,其中一个表应该有一个外键。

护照表:
分区键:PassportId

╔════════════╦═══════╦════════════╗
║ PassportId ║ Pages ║   Issued   ║
╠════════════╬═══════╬════════════╣
║ P1         ║    15 ║ 11/03/2009 ║
║ P2         ║    18 ║ 09/02/2018 ║
╚════════════╩═══════╩════════════╝

护照持有人表:
分区键:PersonId

╔══════════╦════════════╦══════╗
║ PersonId ║ PassportId ║ Name ║
╠══════════╬════════════╬══════╣
║ 123      ║ P1         ║ Jane ║
║ 234      ║ P2         ║ Paul ║
╚══════════╩════════════╩══════╝

这样建立表之后我们可以通过personId找到对应PassportId ,但这里耶存在问题,如果知道PaasportId,是没有办法找到对应的PersonId的,一对一要实现正反向均有效的情况的话需要做一定的处理。所以护照持有人表上建立一个以PassportId为主键全局二级索引GSI即可。

护照持有人表GSI:
分区键:PassportId

╔════════════╦══════════╦══════╗
║ PassportId ║ PersonId ║ Name ║
╠════════════╬══════════╬══════╣
║ P1         ║ 123      ║ Jane ║
║ P2         ║ 234      ║ Paul ║
╚════════════╩══════════╩══════╝

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

3 一对多

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


宠物表:
分区键:PetId

╔═══════╦═════════╦════════╗
║ PetId ║ OwnerId ║ Type   ║
╠═══════╬═════════╬════════╣
║ P1    ║ O1      ║ Dog    ║
║ P2    ║ O1      ║ Cat    ║
║ P3    ║ O2      ║ Rabbit ║
╚═══════╩═════════╩════════╝

宠物拥有者表:
分区键:OwnerId

╔═════════╦════════╗
║ OwnerId ║ Name   ║
╠═════════╬════════╣
║ O1      ║ Angela ║
║ O2      ║ David  ║
╚═════════╩════════╝

我们把外键放在很多表中。如果我们反过来这样做,并将PetIds放在Owner表中,则一个所有者项必须有一组PetId,这将变得很复杂。

如果我们想找一个宠物的主人,它很容易。我们可以做一个GetItem来返回Pet Item,它会告诉我们拥有者是谁。但另一种方式是更难 - 如果我们有一个OwnerId,他们拥有哪些宠物?为了节省我们不必在Pet表上进行扫描,我们将GSI添加到Pet表中。

宠物表拥有者ID GSI
分区键:OwnerId

╔═════════╦═══════╦════════╗
║ OwnerId ║ PetId ║ Type   ║
╠═════════╬═══════╬════════╣
║ O1      ║ P1    ║ Dog    ║
║ O1      ║ P2    ║ Cat    ║
║ O2      ║ P3    ║ Rabbit ║
╚═════════╩═══════╩════════╝

如果我们有一个OwnerId并且我们想找到他们的宠物,我们可以在宠物拥有者ID GSI上执行查询。例如,对所有者O1的查询将返回具有PetId P1和P2的项目。

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

另外,GSI不需要投影与基表相同的所有属性。如果您仅使用GSI进行查找,则可能仅投影希望GSI键属性。(PS:省空间)

3 多对多

在DynamoDB中有三种主要方式来建模多对多关系。每个人都有优点和缺点。
我们可以使用医生和患者的例子来建模这种关系。医生可以有很多患者,患者可以有很多医生。
PS:AWS 官方其实有所推荐,但那种推荐方式真的是比较恶心。

3.1 辅助表

一般来说,这是首选的方法,我们的想法是创建没有关系引用的“普通”基表。然后关系引用进入辅助表(每种关系类型一个辅助表 - 在这种情况下只是医生 - 患者)。


医生表:
分区键:DoctorId

╔══════════╦═══════╗
║ DoctorId ║ Name  ║
╠══════════╬═══════╣
║ D1       ║ Anita ║
║ D2       ║ Mary  ║
║ D3       ║ Paul  ║
╚══════════╩═══════╝

病人表:
分区键:PatientId

╔═══════════╦═════════╦════════════╗
║ PatientId ║ Name    ║ Illness    ║
╠═══════════╬═════════╬════════════╣
║ P1        ║ Barry   ║ Headache   ║
║ P2        ║ Cathryn ║ Itchy eyes ║
║ P3        ║ Zoe     ║ Munchausen ║
╚═══════════╩═════════╩════════════╝

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   ║
╚══════════╩═══════════╩══════════════╝

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   ║
╚═══════════╩══════════╩══════════════╝

有三个表,DoctorPatient辅助表是有趣的一个。
DoctorPatient基表主键必须是唯一的,因此我们创建了DoctorId(分区键)和PatientId(排序键)的复合键。
我们可以使用DoctorId对DoctorPatient基表执行查询,以获取Doctor所具有的所有患者
我们可以使用PatientId对DoctorPatient GSI 执行查询,以获取与患者相关的所有医生

这种方法的优点是表的清晰分离,以及将简单业务对象直接映射到数据库的能力。它不需要使用诸如集之类的更高级功能。

有必要协调一些更新,例如,如果删除患者,您还需要小心删除DoctorPatient表中的关系。然而,与其他一些方法相比,引入数据质量问题的可能性很低。也就是删除实体需要删除关系表。DynamoDB 支持事务操作可以处理这个问题。
这种方法的潜在弱点是它需要3个表。如果您使用吞吐量配置表,则表中的表越多,您就越需要扩展容量。然而,随着新的按需功能,这不是一个问题。

3.2 外键集

这种方法只使用两个表。

医生表:
分区键:DoctorId

╔══════════╦════════════╦═══════╗
║ DoctorId ║ PatientIds ║ Name  ║
╠══════════╬════════════╬═══════╣
║ D1       ║ P1,P2      ║ Anita ║
║ D2       ║ P2,P3      ║ Mary  ║
║ D3       ║ P3         ║ Paul  ║
╚══════════╩════════════╩═══════╝

病人表:
分区键:PatientId

╔═══════════╦══════════╦═════════╗
║ PatientId ║ DoctorIds║  Name   ║
╠═══════════╬══════════╬═════════╣
║ P1        ║ D1       ║ Barry   ║
║ P2        ║ D1,D2    ║ Cathryn ║
║ P3        ║ D2,D3    ║ Zoe     ║
╚═══════════╩══════════╩═════════╝

该方法涉及将关系存储为每个表中的集合。

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

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

这种方法的主要缺点是数据质量问题的可能性。如果您将患者链接到医生,您需要协调两个更新,每个表更新一个。如果一次更新失败会怎样?您的数据可能会不同步。(PS:
解决两表同步这里就需要引入DynamoDB事务**, DynamoDB的事务本身就意味着双倍的容量开销)。

另一个缺点是在两个表中使用集合(操作集合数据结构中的某些内容)。DynamoDB SDK旨在处理集合,但在涉及集合时,某些操作可能会很复杂。

3.3 图形模式

AWS先前已将此称为邻接列表模式。它通常被称为Graph数据库或Triple Store。这是AWS推荐的官方方案,基于NoSql的情况下,**该方法涉及将所有数据放在一个表中。**因为NoSql并不对每条数据类型都要求一致。

这是绘制了一些示例行而不是整个表:
分区键:Key1
排序键:键2

╔═════════╦═════════╦═══════╦═════════════╦══════════════╗
║ Key1    ║ Key2    ║ Name  ║   illness   ║ Last Meeting ║
╠═════════╬═════════╬═══════╬═════════════╬══════════════╣
║ P1      ║ P1      ║ Barry ║ Headache    ║              ║
║ D1      ║ D1      ║ Anita ║             ║              ║
║ D1      ║ P1      ║       ║             ║ 01/01/2018   ║
╚═════════╩═════════╩═══════╩═════════════╩══════════════╝

然后需要一个GSI来反转键:
分区键:Key2
排序键:键1

╔═════════╦═════════╦═══════╦═════════════╦══════════════╗
║ Key2    ║ Key1    ║ Name  ║   illness   ║ Last Meeting ║
╠═════════╬═════════╬═══════╬═════════════╬══════════════╣
║ P1      ║ P1      ║ Barry ║ Headache    ║              ║
║ D1      ║ D1      ║ Anita ║             ║              ║
║ P1      ║ D1      ║       ║             ║ 01/01/2018   ║
╚═════════╩═════════╩═══════╩═════════════╩══════════════╝

该模型在某些特定情况下具有一些优势 - 它可以在高度连接的数据中表现良好。如果您很好地格式化数据,则可以实现极其快速且可扩展的模型。它非常灵活,您可以在表中存储任何实体或关系,而无需更新架构/表。如果您正在配置吞吐量容量,则可以高效**,因为整个应用程序中的所有操作都可以使用所有吞吐量。**

如果使用不当或没有认真考虑,这个模型会有一些巨大的缺点。
您丢失了业务对象和表之间的任何直接映射。这几乎总是导致不可读的代码。执行甚至简单的查询都会感觉非常复杂。由于代码和数据库之间没有明显的映射,因此管理数据质量变得困难。设置为了管理数据库会编写相应的工具,因为完全没有数据库模型和代码无法建立直接的映射。

所以要明确,邻接列表模式可能很有用,但它不是在DynamoDB中建模多对多关系的唯一选择。一定要使用它,如果它适用于你的情况,如严重的大数据,但如果没有,尝试一个更简单的模型。PS:事实当时读DynamoDB官方多对多关系文档时感觉一阵懵逼,如果没有足够优秀健壮且具有前瞻性的数据库设计,而就采用这种邻接表形式存储数据,可能会导致数据库变得十分难以维护。而AWS官方本身并没有对该方案的这种情况进行详细说明,虽然邻接表性能会有所优势且节约资源。

个人偏向于前两种多对多方案。

4 参考链接

https://stackoverflow.com/questions/55152296/how-to-model-one-to-one-one-to-many-and-many-to-many-relationships-in-dynamodb

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值