Dynamodb 的3字段复合主键(唯一项)

我正在尝试创建一个表,以便在 DynamoDB 中存储发票行项目。假设项目由 CompanyCodeInvoiceNumberLineItemId、金额和其他项目细节定义。

一个唯一的项目是由前3个属性的组合定义的。这些属性中的任何两个对于不同的项目都是相同的。我应该选择什么作为散列属性和范围属性?

83490 次浏览

我相信您已经发现,主键(hash + range)的属性不能超过两个。因此,根据要执行的查询类型和数据大小,您可以以不同的方式构造表。

(针对上面提到的查询类型进行了优化: 只有 CompanyCode和所有3)

适用于中小型数据集的最佳溶胶:

  • 哈希键: CompanyCode
  • 仅使用 CompanyCode和 然后根据其他两个属性筛选结果

大型数据集的最佳解决方案:

  • 哈希键: CompanyCode
  • 射程键: InvoiceNumber + LineItemId
  • 这允许您只对索引进行查询,但是表结构非常丑陋

我相信 @ georgeaf99提供的第一个选项不会起作用,因为如果你这样做,那么 CompanyCode在表中必须是唯一的。因此,每个公司只允许有一个项目。我认为第二个解决方案是唯一可行的方法。

您可以使用 CompanyCode作为散列键,然后所有其他字段组合起来使项目唯一(在本例中为 InvoiceNumberLineItemId)需要以某种方式为 合并成一个值(例如与字段分隔符连接) ,这将是您的范围键。不幸的是,这有点难看,但这就是像 DynamoDB 这样的 NoSQL 数据库的本质。但是,它将允许您以正确的唯一性成功地存储记录。在返回读取记录时,如果不想将合并后的字段解析回它的各个部分,那么必须为 InvoiceNumberLineItemID添加额外的单独字段。

如果每个公司没有大量的发票,那么只能通过 Hash 键进行查询,并在客户端进行过滤。如果每个公司有大量的发票,并且只需要查询单个发票的项目,那么我将在 CompanyCode 和 InvoiceNumber 上创建一个辅助索引。

一些介绍

为了提高效率,我建议采用完全不同的设计。对于 NoSQL 数据库(DynamoDB 也不例外) ,我们总是需要首先考虑访问模式。此外,如果可能的话,我们应该努力将所有数据放在同一个表和几个索引中。从 OP 和他的评论来看,有两种访问模式:

  1. 对于 X 公司,获得完整的发票 Y (包括所有项目或项目范围)[基于此 评论]
  2. 获取 X 公司的所有发票[基于此 评论]

我们现在想知道什么是好的主键?转换为问题什么是一个好的分区键(PK) ,什么是一个好的排序键(SK) ,我们需要创建哪些辅助索引,以及什么类型(本地或全局) ?一些提醒:

  • 主键可以位于一列或复合列上
  • 组合主键由分区键和排序键组成
  • 分区键用作散列函数的输入,散列函数将确定项的分区
  • 排序键也可以是复合的,这允许我们在 DynamoDB 中建模一对多关系,如注释链接 https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html所示
  • 在表或索引上创建查询时,始终需要在分区键上使用’=’运算符
  • 在排序键上查询范围时,您可以选择 KeyConditionExpression,它提供 排序运算符集和其中的所有内容(其中之一是函数 begins_with (a, substr))
  • 如果需要进一步细化 Query 结果(对投影属性进行过滤) ,还允许使用 FilterExpression
  • 本地辅助索引(LSI)与原始表具有相同的分区键但不同的排序键,并且根据替代的排序键组织,为您提供不同的数据视图
  • 全局辅助索引(GSI)与原始表具有不同的分区键和排序键,并提供完全不同的数据视图
  • 所有具有相同分区键的项都存储在一起,对于复合主键,按排序键值排序。如果集合大小超过10GB,DynamoDB 将按排序键拆分分区。

回到模特行业

很明显,我们正在处理多个需要建模并放入同一个表中的实体。为了满足 Partition Key 在表中是唯一的条件,CompanyCode作为一个天然的 Partition Key 出现——所以我将确保它是唯一的。如果没有,那么您需要问自己如何建模第二种访问模式?

假设我们已经在 CompanyCode上建立了唯一性,让我们简化一下,假设它是以电子邮件的形式出现的(或者可以是域或者仅仅是代码,但是我将使用电子邮件进行演示)。

  • 公司和发票之间的关系总是1: 多。
  • 发票和项目之间的关系总是1: many。

我建议设计如下图所示: Proposed design in DynamoDB

  • PK 是 CompanyCode,SK 是 InvoiceNumber,可以存储该公司发票的所有属性。
  • 没有什么阻止我也添加记录的 SK 是 Customer,它允许我存储关于公司的所有属性。
  • 使用 GSI1,我们将创建反向查找,其中 GSI1PK 是我的表 SK (InvoiceNumber) ,而我的 GSI1SK 是我的表 PK (CompanyCode)。
  • 我使用相同的表存储行项目与 PK 是 LineItemId和 SK 是 CompanyCode(仍然唯一)
  • 对于实体项目,我的 GSI1PK 仍然是 InvoiceNumber,我的 GSI1SK 是 LineItemId,它是表 PK,因此它与发票实体项目相同。

现在支持的访问模式如下:

  • 如果我想得到 X 公司的发票 Y 和所有的项目(访问模式1) : 查询表中的 CompanyCode=X和使用 KeyConditionExpression与排序键 InvoiceNumber上的 =操作符。如果我想得到所有的项目绑定到该发票,我将项目 Items属性使用 ProjectionExpression
  • 通过检索之前查询 X 公司和 Y 发票的所有项目,我现在可以在表上运行 BatchGetItem API 调用(使用我唯一的复合键 LineItemId+CompanyCode) ,以获取属于该特定客户的特定发票的所有项目。(这与 BatchGetItem API的一些限制有关)
  • 为了支持访问模式2,我将在 PK 上使用 CompanyCode=X进行查询,在 SK 上使用 KeyConditionExpressionbegins_with (a, substr)函数/操作符进行查询,以便只获取 X 公司的发票,而不是该公司的元数据。这将给我所有的发票给定的公司/客户。
  • 此外,使用以上 GSI1,对于任何给定的 InvoiceNumber,我可以轻松地选择属于该特定发票的所有行项目。记住: 全局辅助索引中的键值不需要是唯一的-因此在我的 GSI 1中,我可以很容易地得到发票 _ 1-> (商品 _ 1,商品 _ 2) ,然后再得到另一个商品 _ 1-> (商品 _ 1,商品 _ 2) ,但是在 GSI 中,两个商品之间的区别在于 SK (它将与不同的 CompanyCode相关联(但是为了演示的目的,我使用了发票 _ 1和商品 _ 2)。