gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 03:06:19 26 4
gpt4 key购买 nike

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

  • 一对一关系
  • 一对多关系
  • 多对多关系
  • 最佳答案

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

    DynamoDB关键基础

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

  • 每个DynamoDB表都有一个唯一的primary key
  • 主键必须由partition key组成,并且可以选择具有sort key。同时具有分区键和排序键的主键是复合键
  • GetItem请求使用其唯一的主键仅返回一项。
  • Query进行快速查找,并且必须指定一个且只有一个分区键。它可以返回多个项目。
  • Scan评估表中的每个项目,并可以基于过滤器参数返回一个子集。在某些情况下,扫描是正确的选择,但如果使用不正确,则扫描速度会很慢且成本很高。
  • 全局二级索引(GSI)具有与基本表不同的分区键。认为它有点像两个表(基本表和GSI)保持同步。根据使用情况,GSI可能会使基本表的成本增加一倍。
  • 本地二级索引(LSI)具有与基表相同的分区键,但具有不同的排序键。可以将它视为对基表数据进行排序的另一种方式,但只能在分区键中进行。 LSI不会花费您任何费用。


  • 一对一

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

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

    护照表:

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

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

    护照持有人表格GSI:

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

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

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

    一对多

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

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

    宠物桌:

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

    所有者表:

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

    我们将外键放在多表中。如果我们反过来做,并将PetIds放在Owner表中,则一个Owner Item将必须具有一组PetId,这将使管理变得复杂。

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

    宠物桌GSI

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

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

    您可能会在这里注意到一些有趣的东西。一个主键对于一个表必须是唯一的。这仅适用于基表。 GSI主键,在本例中为 GSI partition key, does not have to be unique

    In a DynamoDB table, each key value must be unique. However, the key values in a global secondary index do not need to be unique



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

    多对多

    在DynamoDB中,有三种主要的模型多对多关系模型。每个都有优点和缺点。

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

    多对多-选项1-辅助表

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

    医生表:

    分区密钥: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基表上执行 Query,以获取Doctor拥有的所有患者。

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

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

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

    编辑:DynamoDB现在支持 Transactions,允许您将多个更新协调到跨多个表的单个原子事务中。

    这种方法的潜在缺点是需要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表上使用GetItem来检索Doctor项目。然后,将PatientId作为一组存储在Doctor属性中。

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

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

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

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

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

    AWS先前将其称为 Adjacency List pattern。它通常更称为 Graph databaseTriple Store

    我以前在AWS Adjancey List Pattern上添加了 answered this question,这似乎帮助了一些人理解了它。

    而且,AWS最近有一个演讲,谈到了这种模式 here

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

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

    分区键:Key1

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

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

    分区键:Key2

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

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

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

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

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

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

    关于amazon-dynamodb - 如何在DynamoDB中建模一对一,一对多和多对多关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55152296/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com