Amazon DynamoDB
开发人员指南 (API 版本 2012-08-10)
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本地二级索引

一些应用程序只需要使用基表的主键查询数据。但是,在某些情况下,替代的排序键会很有帮助。要为应用程序提供可供选择的排序键,您可以对 Amazon DynamoDB 表创建一个或多个本地二级索引,并对这些索引发出 QueryScan 请求。

场景:使用本地二级索引

例如,请考虑在 创建表并加载样本数据 中定义的 Thread 表。此表对应用程序(如 AWS 开发论坛)十分有用。下表显示了此表中项目的组织方式,(并未显示所有属性。)


                包含论坛名称列表、主题、最后发布时间和回复数的 Thread 表。

DynamoDB 连续存储具有相同分区键值的所有项目。在该示例中,给定了特定 ForumName 时,Query 操作可立刻找该论坛的所有话题。在具有相同分区键值的一组项目中,这些项目按排序键值进行排序。如果查询中还提供了排序键 (Subject),则 DynamoDB 可以缩小所返回结果的范围,例如,返回“S3”论坛中 Subject 以字母“a”开头的所有话题。

某些请求可能需要提供更为复杂的数据访问模式。例如:

  • 哪些论坛话题的查看次数和回复次数最多?

  • 特定论坛中的哪个话题的消息数量最多?

  • 特定论坛在特定时期内发布了多少个话题?

要回答这些问题,只使用 Query 操作是不够的。您必须 Scan 整个表。如果表中有数百万个项目,则该操作会占用大量预配置读取吞吐量,并且需要很长时间才能完成。

但是,您可以在非键属性上指定一个或多个本地二级索引,例如 RepliesLastPostDateTime

local secondary index 为给定分区键值维护一个替代排序键。local secondary index还包含其基表的部分或全部属性的副本。您在创建表时指定哪些属性投影到local secondary index。local secondary index中的数据通过与基表相同的分区键来组织,但使用不同的排序键。这样,您就可以跨这个不同的维度高效访问数据项目。为了提高查询或扫描灵活性,您可以为每个表创建最多 5 个local secondary index。

假设应用程序需要查找过去三个月内发布的所有话题。如果不使用local secondary index,应用程序必须 Scan 整个 Thread 表,并舍弃指定时间范围之外的所有文章。如果使用local secondary index,则 Query 操作可以将 LastPostDateTime 用作排序键,快速查找数据。

下图显示了名为 LastPostIndex 的local secondary index的工作方式。请注意,分区键与 Thread 表的分区键相同,但排序键为 LastPostDateTime


                包含论坛名称列表、主题和最后发布时间的 LastPostIndex 表。

每个local secondary index都必须符合以下条件:

  • 分区键与其基表的分区键相同。

  • 排序键仅由一个标量属性构成。

  • 基表的排序键投影到索引中,并在其中用作非键属性。

在本例中,分区键名称为 ForumName,local secondary index 的排序键名称为 LastPostDateTime。此外,基表(在本示例中为 Subject)的排序键值投影到索引中,但不是索引键的一部分。如果应用程序需要基于 ForumNameLastPostDateTime 的列表,则可以对 LastPostIndex 发出 Query 请求。查询结果会按 LastPostDateTime 排序,并且可按升序或降序返回。查询还可以应用键条件,例如只返回 LastPostDateTime 在特定时间范围内的项目。

每个local secondary index都自动包含其基表的分区键和排序键;您可以选择将非键属性投影到索引中。当您查询索引时,DynamoDB 便可高效地检索这些已投影的属性。在查询local secondary index时,该查询还可以检索 投影到该索引中的属性。DynamoDB 会自动从基表提取这些属性,但会有更长的延迟且预配置吞吐量成本较高。

对于任何local secondary index,您可以针对每个不同的分区键值存储多达 10GB 数据。这一数字包括基表中的所有项目以及索引中具有相同分区键值的所有项目。有关更多信息,请参阅项目集合

属性投影

通过 LastPostIndex,应用程序可以使用 ForumNameLastPostDateTime 作为查询标准。但是,要检索任何附加属性,DynamoDB 必须对 Thread 表执行额外的读取操作。这些额外的读取被称为抓取,会增加查询所需的预配置吞吐总量。

假如您要在网页中填充一个列表,其中包含“S3”中的所有话题和每个话题的回复数量,这些回复按最后一次回复日期/时间排序,并且最新的回复排在最前面。要填充此列表,您将需要以下属性:

  • Subject

  • Replies

  • LastPostDateTime

查询这些数据并避免提取操作的最有效方法是将 Replies 属性从表投影到local secondary index中,如下图所示。


                包含论坛名称列表、最后发布时间、主题和回复的 LastPostIndex 表。

投影 是从表复制到二级索引中的属性集。表的分区键和排序键始终投影到索引中;您可以投影其他属性以支持应用程序的查询要求。当您查询索引时,Amazon DynamoDB 可以访问投影中的任何属性,就像这些属性是在它们自己的表中一样。

创建 二级索引 时,您需要指定投影到索引中的属性。DynamoDB 提供了三个不同选项来实现这一目的:

  • KEYS_ONLY - 索引中的每个项目仅包含表分区键和排序键值以及索引键值。KEYS_ONLY 选项生成尽可能小的 二级索引。

  • INCLUDE - 除 KEYS_ONLY 中描述的属性外,二级索引 还包括您指定的其他非键属性。

  • ALL - 二级索引 包含源表中的所有属性。由于所有表数据均在索引中得以复制,因此 ALL 投影生成尽可能大的 二级索引。

在上图中,非键属性 Replies 投影到了 LastPostIndex。应用程序可以查询 LastPostIndex 而不是完整的 Thread 表,从而使用 SubjectRepliesLastPostDateTime 填充网页。如果请求任何其他非键属性,DynamoDB 就需要从 Thread 提取这些属性。

从应用程序的角度看,从基表提取其他属性的操作是自动和透明的,因此无需重新编写应用程序逻辑。不过请注意,这种提取会大幅降低使用local secondary index的性能优势。

您在选择要投影到local secondary index中的属性时,必须在预置吞吐量成本和存储成本之间做出权衡:

  • 如果只需要访问少量属性,同时尽可能降低延迟,就应考虑仅将键属性投影到local secondary index。索引越小,存储索引所需的成本越少,并且写入成本也会越少。如果您偶尔需要抓取某些属性,预置吞吐量的成本可能会超过长期存储这些属性的成本。

  • 如果您的应用程序频繁访问某些非键属性,就应考虑将这些属性投影到local secondary index。local secondary index的额外存储成本会抵消频繁执行表扫描的成本。

  • 如果需要频繁访问大多数非键属性,则可以将这些属性(甚至是整个基表)投影到local secondary index。这会为您带来最大限度的灵活性和最低的预配置吞吐量占用,因为无需进行提取。但是,您的存储会增加,在投影所有属性时甚至会翻倍。

  • 如果您的应用程序并不会频繁查询表,但必须要对表中的数据执行大量写入或更新操作,就应考虑投影 KEYS_ONLY。这是最小的local secondary index,但仍可用于查询活动。

创建 本地二级索引

要在表上创建一个或多个local secondary index,请使用 CreateTable 操作的 LocalSecondaryIndexes 参数。表的本地二级索引是在创建表时创建的。当您删除表时,同时也会删除表中的所有本地二级索引。

您必须指定一个非键属性作为local secondary index的排序键。您选择的属性必须是标量 StringNumberBinary。不允许使用其他标量类型、文档类型和集合类型。有关数据类型的完整列表,请参阅 数据类型

重要

对于具有本地二级索引的表,每个分区键值都有 10GB 的大小上限。只要任意分区键值的总大小不超过 10GB,具有本地二级索引的表就可以存储任何数量的项目。有关更多信息,请参阅项目集合大小限制

您可将任意数据类型的属性投影到local secondary index,这包括标量、文档和集。有关数据类型的完整列表,请参阅 数据类型

查询本地二级索引

在 DynamoDB 表中,每个项目的复合的分区键值和排序键值必须是唯一的。但在local secondary index中,排序键值对于指定的分区键值不必是唯一的。如果local secondary index中有多个项目具有相同的排序键值,Query 操作将返回分区键值相同的所有项目。在响应中,匹配项目不按任何特定的顺序返回。

您可以使用最终一致性读取查询local secondary index,也可以使用强一致性读取。要指定您想使用的一致性类型,请使用 Query 操作的 ConsistentRead 参数。对local secondary index执行的强一致性读取总是会返回最新的已更新值。如果查询需要提取基表中的其他属性,那么这些属性会根据索引保持一致。

考虑一下 Query 返回的以下数据,它从特定论坛的讨论话题中请求数据。

{ "TableName": "Thread", "IndexName": "LastPostIndex", "ConsistentRead": false, "ProjectionExpression": "Subject, LastPostDateTime, Replies, Tags", "KeyConditionExpression": "ForumName = :v_forum and LastPostDateTime between :v_start and :v_end", "ExpressionAttributeValues": { ":v_start": {"S": "2015-08-31T00:00:00.000Z"}, ":v_end": {"S": "2015-11-31T00:00:00.000Z"}, ":v_forum": {"S": "EC2"} } }

在此查询中:

  • DynamoDB 访问 LastPostIndex,以使用 ForumName 分区键找到“EC2”的索引项目。具有此键的所有索引项目都彼此相邻存储,以实现快速检索。

  • 在此论坛内,DynamoDB 使用此索引查找符合指定 LastPostDateTime 条件的键。

  • 由于 Replies 属性投影到了索引中,因此 DynamoDB 可以在不额外占用任何预配置吞吐量的情况下检索此属性。

  • Tags 属性未投影到索引,因此 DynamoDB 必须访问 Thread 表才能提取此属性。

  • 系统会返回按 LastPostDateTime 排序的结果。索引条目按分区键值排序,然后按排序键值排序,并且 Query 按它们的存储顺序将其返回。(您可以使用 ScanIndexForward 参数按降序返回结果。)

由于 Tags 属性未投影到local secondary index,因此 DynamoDB 必须额外占用读取容量单位才能从基表中抓取此属性。如果您需要经常运行此查询,则应将 Tags 投影到 LastPostIndex 以避免从表中提取。但是,如果您仅需要偶尔访问 Tags,则将 Tags 投影到索引中的附加存储成本就可能不划算。

扫描本地二级索引

您可以使用 Scan 检索local secondary index中的全部数据。您必须在请求中提供基表名称和索引名称。通过 Scan,DynamoDB 可读取索引中的全部数据并将其返回到应用程序。您还可以请求仅返回部分数据并放弃其余数据。为此,请使用 FilterExpression API 的 Scan 参数。有关更多信息,请参阅 Scan 的筛选表达式

项目写入和本地二级索引

DynamoDB 会自动保持所有本地二级索引与其各自的基表同步。应用程序绝不会直接向索引中写入内容。但是,您有必要了解 DynamoDB 如何维护这些索引。

创建local secondary index时,您需要指定一个属性作为索引的排序键。同时也指定了该属性的数据类型。这就意味着,无论您何时向基表中写入项目,只要项目定义了索引键属性,它的类型就必须匹配索引键架构的数据类型。在 LastPostIndex 的情况下,索引中的 LastPostDateTime 排序键定义为 String 数据类型。如果您尝试向 Thread 表添加项目并为 LastPostDateTime(例如 Number),DynamoDB 会因数据类型不匹配而返回 ValidationException

基表中的项目和local secondary index中的项目之间无需具有一对一的关系。事实上,此行为对很多应用程序很有好处。

相较于索引数量较少的表,拥有较多本地二级索引的表会产生较高的写入活动成本。有关更多信息,请参阅 本地二级索引的预配置吞吐量注意事项

重要

对于具有本地二级索引的表,每个分区键值都有 10GB 的大小上限。只要任意分区键值的总大小不超过 10GB,具有本地二级索引的表就可以存储任何数量的项目。有关更多信息,请参阅项目集合大小限制

本地二级索引的预配置吞吐量注意事项

当您在 DynamoDB 中创建表时,要根据表的预期工作负载预置读取和写入容量单位。该工作负载包括表的本地二级索引中的读取和写入活动。

要查看预配置吞吐容量的当前费率,请参阅 Amazon DynamoDB 定价

读取容量单位

当您查询local secondary index时,所占用的读取容量单位数量取决于数据的访问方式。

与表查询一样,索引查询可以使用最终一致性读取,也可以使用强一致性读取,具体取决于 ConsistentRead 的值。一次强一致性读取占用一个读取容量单位,一次最终一致性读取占用半个读取容量单位。因此,如果选择最终一致性读取,您就可以减少读取容量单位费用。

对于只请求索引键和投影属性的索引查询,DynamoDB 计算预置读取活动的方式与查询表的方式一样。唯一不同的是,本次计算基于索引条目的大小,而不是基表中项目的大小。读取容量单位的数量就是返回的所有项目的所有投影属性大小之和,该结果会向上取整到 4 KB 的下一个倍数。有关 DynamoDB 如何计算预配置吞吐量使用情况的更多信息,请参阅管理预置表的吞吐量设置

对于读取属性未投影到local secondary index的索引查询,除从索引中读取投影属性外,DynamoDB 还需要从基表中提取这些属性。当您在 Select 操作的 ProjectionExpressionQuery 参数中包含未投影属性时,就会发生这些抓取操作。提取操作会让查询响应有更长时间的延迟,同时还会增加预配置吞吐量成本:除了前述对local secondary index执行的读取操作外,您还需为提取的每个基表项目支付读取容量单位的费用。此费用源自读取表中的每个完整项目,而不仅仅是源自所请求的属性。

Query 操作返回的结果大小上限为 1 MB。这包括所有属性名称的大小和所返回的所有项目的值。但是,如果对local secondary index执行的 Query 操作导致 DynamoDB 从基表中获取项目属性,结果中数据的大小上限可能会更低。在本示例中,结果大小是以下内容之和:

  • 索引中匹配项目的大小,向上取整到 4 KB 的下一个倍数。

  • 基表中每个匹配项目的大小,其中每个项目都分别向上取整到 4 KB 的下一个倍数。

如果使用此公式,Query 操作返回的结果大小上限仍为 1 MB。

例如,假如有这样一个表,其中每个项目的大小为 300 字节。该表中有一个local secondary index,但是每个项目只有 200 字节投影到索引。现在,假如对此索引执行 Query 操作,此查询会需要对每个项目执行表获取操作,并且查询返回 4 个项目。DynamoDB 计算以下内容之和:

  • 索引中匹配项目的大小:200 字节 × 4 个项目 = 800 字节;这一数字会向上取整到 4 KB。

  • 基表中每个匹配项目的大小:(300 字节,向上取整到 4 KB)× 4 个项目 = 16 KB。

因此,结果中的数据大小总共为 20 KB。

写入容量单位

当您在表中添加、更新或删除项目时,更新本地二级索引会占用表的预配置写入容量单位。一次写入操作的总预配置吞吐量成本是对表执行的写入操作以及更新本地二级索引所占用的写入容量单位之和。

向local secondary index写入项目的成本取决于多个因素:

  • 如果您向定义了索引属性的表中写入新项目,或更新现有的项目来定义之前未定义的索引属性,只需一个写入操作即可将项目放置到索引中。

  • 如果对表执行的更新操作更改了索引键属性的值(从 A 更改为 B),就需要执行两次写入操作,一次用于删除索引中之前的项目,另一次用于将新项目放置到索引中。 

  • 如果索引中已有某一项目,而对表执行的写入操作删除了索引属性,就需要执行一次写入操作删除索引中旧的项目投影。

  • 如果更新项目前后索引中没有此项目,此索引就不会额外产生写入成本。

所有这些因素都假定索引中每个项目的大小小于或等于 1 KB 这一项目大小(用于计算写入容量单位)。如果索引条目大于这一大小,就会占用额外的写入容量单位。您可以考虑查询需要返回的属性类型并仅将这些属性投影到索引中,从而最大程度地减少写入成本。

本地二级索引的存储注意事项

当应用程序向表中写入项目时,DynamoDB 会自动将适当的属性子集复制到应包含这些属性的所有本地二级索引。您的 AWS 账户需要支付在基表中存储项目以及在表的任何本地二级索引中存储属性的费用。

索引项目所占用的空间大小就是以下内容之和:

  • 基表的主键 (分区键和排序键) 的大小 (按字节计算)

  • 索引键属性的大小(按字节计算)

  • 投影的属性(如果有)的大小(按字节计算)

  • 每个索引项目 100 bytes 的开销

要预估local secondary index的存储要求,您可以预计索引中项目的平均大小,然后乘以基表中的项目数。

如果表包含的某个项目未定义特定属性,但是该属性定义为索引排序键,则 DynamoDB 不会将该项目的任何数据写入索引中。

项目集合

注意

此部分仅与具有本地二级索引的表相关。

在 DynamoDB 中,项目集合 是表及其所有本地二级索引中具有相同分区键值的项目的任意组合。在本部分使用的示例中,Thread 表的分区键是 ForumNameLastPostIndex 的分区键也是 ForumName。所有具有相同 ForumName 的表和索引项目都是同一个项目集合的一部分。例如,在 Thread 表和 LastPostIndex local secondary index中,论坛 EC2 有一个项目集合,论坛 RDS 有另一个项目集合。

下表显示了论坛 S3 的项目集合。

在此表中,项目集合由 ThreadLastPostIndex 中的所有项目构成,其中 ForumName 分区键值为“S3”。如果此表有其他本地二级索引,那么这些索引中索引的 ForumName 等于“S3”的任何项目都是此项目集合的一部分。

您可以在 DynamoDB 中使用以下任意一个操作返回有关项目集合的信息:

  • BatchWriteItem

  • DeleteItem

  • PutItem

  • UpdateItem

每个操作都支持 ReturnItemCollectionMetrics 参数。如果您将此参数设置为 SIZE,就可以查看索引中每个项目集合的大小的相关信息。

以下是对 Thread 表执行 UpdateItem 操作后输出的示例,其中 ReturnItemCollectionMetrics 设置为 SIZE。项目更新后,ForumName 值为“EC2”,因此输出包括有关此项目集合的信息。

{ ItemCollectionMetrics: { ItemCollectionKey: { ForumName: "EC2" }, SizeEstimateRangeGB: [0.0, 1.0] } }

SizeEstimateRangeGB 对象显示此项目集合的大小介于 0 到 1 GB 之间。DynamoDB 会定期更新这一大小估值,因此下次修改项目时,这一数字可能会有所不同。

项目集合大小限制

任意项目集合的大小上限均为 10GB。此限制不适用于没有本地二级索引的表。只有具有一个或多个本地二级索引的表受影响。

如果项目集合的大小超出 10GB 的限制,则 DynamoDB 返回 ItemCollectionSizeLimitExceededException,并且您无法向该项目集合添加更多项目,也不能增加该项目集合中项目的大小。(仍允许执行可减小项目集合大小的读写操作。) 您仍然可以向其他项目集合中添加项目。

要减少项目集合的大小,您可以执行以下任意一项操作:

  • 删除包含相关分区键值的非必要项目。当您从基表中删除这些项目时,DynamoDB 还会删除具有相同分区键值的所有索引条目。

  • 通过删除属性或减少属性的大小来更新项目。如果这些属性投影到任何本地二级索引,DynamoDB 还会减少对应的索引条目的大小。

  • 创建具有相同分区键和排序键的新表,然后将旧表中的项目移动到新表中。如果您不经常访问表中的历史数据,这也是一个好办法。您还可以考虑将这些历史数据存档到 Amazon Simple Storage Service (Amazon S3)。

项目集合的总大小减小到 10GB 以下后,您就能重新添加具有相同分区键值的项目。

我们建议您最好对应用程序进行检测,从而监测项目集合的大小。实现这一目的的一种方式就是,无论何时使用 BatchWriteItemDeleteItemPutItemUpdateItem,都将 ReturnItemCollectionMetrics 参数设置为 SIZE。您的应用程序应检查输出中的 ReturnItemCollectionMetrics 数据元,并在项目集合超出用户定义的限制(例如 8 GB)时记录错误消息。如果将限制设置在 10GB 以下,系统就会为您提供预警系统,让您能及时了解项目集合的大小即将达到所设置的限制并采取相应的措施。

项目集合和分区

每个项目集合的表和索引数据都存储在一个分区中。请参照 Thread 表示例,具有相同 ForumName 属性的所有基表和索引项目都存储在同一分区中。“S3”项目集合存储在一个分区中,“EC2”存储在另一个分区中,“RDS”存储在第三个分区中。

设计应用程序时,您应当确保表数据在不同的分区键值间均匀分布。对于具有本地二级索引的表,您的应用程序不应在单一分区的一个项目集合内创建读写活动“热点”。