

# 在 DynamoDB 中建模关系数据的最佳实践
<a name="bp-relational-modeling"></a>

本节提供了在 Amazon DynamoDB 中对关系数据建模的最佳实践。首先，我们介绍传统的数据建模概念。然后，我们将介绍使用 DynamoDB 相对于传统关系数据库管理系统的优势 – 它如何消除对 JOIN 操作的需求并减少开销。

然后，我们将解释如何设计可高效扩展的 DynamoDB 表。最后，我们提供一个如何在 DynamoDB 中对关系数据进行建模的示例。

**Topics**
+ [传统的关系数据库模型](#SQLtoNoSQL.relational-modeling2)
+ [DynamoDB 如何消除对 JOIN 操作的需求](#bp-relational-modeling-joins)
+ [DynamoDB 事务如何消除写入进程的开销](#bp-relational-modeling-transactions)
+ [在 DynamoDB 中为关系数据建模的初始步骤](bp-modeling-nosql.md)
+ [在 DynamoDB 中为关系数据建模的示例](bp-modeling-nosql-B.md)

## 传统的关系数据库模型
<a name="SQLtoNoSQL.relational-modeling2"></a>

传统关系数据库管理系统（RDBMS）在规范化关系结构中存储数据。关系数据模型的目标是（通过规范化）减少数据重复，支持引用完整性并减少数据异常。

以下模式是通用订单输入应用程序的关系数据模型示例。该应用程序支持一种人力资源模式，此模式为理论制造商的运营和业务支持系统提供大力支持。

![\[示例 RDBMS 架构。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/RDBMS.png)


作为一种非关系数据库服务，与传统的关系数据库管理系统相比，DynamoDB 具有许多优势。

## DynamoDB 如何消除对 JOIN 操作的需求
<a name="bp-relational-modeling-joins"></a>

RDBMS 使用结构查询语言（SQL）将数据返回到应用程序。由于数据模型的规范化，此类查询通常需要使用 `JOIN` 运算符来合并来自一个或多个表的数据。

例如，要生成按照可发运每个项目的所有仓库库存数量排序的采购订单项目列表，可以对上述架构发出下面的 SQL 查询。

```
SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC
```

这种 SQL 查询可提供用于访问数据的灵活 API，但需要大量处理。查询中的每个联接都会增加查询的运行时复杂性，因为每个表的数据都必须暂存，然后汇集才能返回结果集。

可能影响查询运行时间的其它因素包括表的大小以及所联接的列是否有索引。上述查询对多个表发起复杂查询，然后对结果集进行排序。

消除对 `JOINs` 的需求是 NoSQL 数据建模的核心所在。这就是我们构建 DynamoDB 来支持 Amazon.com 的原因，也是 DynamoDB 在任何规模都能提供一致性能的原因。考虑到 SQL 查询和 `JOINs` 的运行时复杂性，RDBMS 的性能在大规模情况下并不稳定。随着客户应用程序的增长，这会导致性能问题。

虽然规范化数据确实减少了存储到磁盘的数据量，但影响性能的最受限制的资源通常是 CPU 时间和网络延迟。

DynamoDB 旨在消除 `JOINs`（并鼓励数据非规范化）和优化数据库架构，通过对某项目的单个请求来完全回答应用程序查询，从而最大限度地减少这两种限制。这些特性使 DynamoDB 能够在任何规模上提供个位数、毫秒级的性能。这是因为对于常见的访问模式，无论数据大小如何，DynamoDB 操作的运行时复杂度都是恒定的。

## DynamoDB 事务如何消除写入进程的开销
<a name="bp-relational-modeling-transactions"></a>

另一个可能减慢 RDBMS 速度的因素是利用事务写入到规范化模式。如示例所示，大多数在线事务处理 (OLTP) 应用程序使用的关系数据结构存储在 RDBMS 中时，必须分解并分布在多个逻辑表中。

因此，需要一个符合 ACID 的事务框架，避免应用程序尝试读取正在写入的对象时可能发生的争用情况和数据完整性问题。此类事务框架与关系架构相结合，会大幅增加写入进程的开销。

在 DynamoDB 中实现事务可以防止 RDBMS 中发现的常见扩展问题。为此，DynamoDB 将事务作为单个 API 调用发出，并限制该单个事务中可以访问的项目数量。长时间运行的事务可能会由于长时间或永久锁定数据而导致操作问题，因为事务从不会关闭。

为了防止在 DynamoDB 中出现此类问题，使用以下两个不同的 API 操作实现事务：`TransactWriteItems` 和 `TransactGetItems`。这些 API 操作不具有 RDBMS 中常见的开始和结束语义。此外，DynamoDB 还有一个事务内 100 个项目的访问限制，以便以类似方式防止长时间运行的事务。要了解有关 DynamoDB 事务的更多信息，请参阅[处理事务](transactions.md)。

为此，如果业务需要低延迟响应高流量查询，采用 NoSQL 系统通常具有技术和经济意义。Amazon DynamoDB 可以避免这些限制，帮助解决问题。

RDBMS 的性能因为下列原因通常无法正常扩展：
+ 使用成本高昂的连接，重新组织需要的查询结果视图。
+ 规范化数据并存储在多个表中，需要多个查询以写入磁盘。
+ 通常产生 ACID 合规事务系统的性能成本。

DynamoDB 可以正常扩展的原因包括：
+ 架构灵活性支持 DynamoDB 在单个项目内存储复杂层次数据。
+ 复合键设计支持将相关项目一起存储在同一个表。
+ 事务是在单个操作中执行的。可以访问的项目数量限制为 100，以避免长时间运行的操作。

针对数据存储的查询变得简单得多，通常采用以下形式：

```
SELECT * FROM Table_X WHERE Attribute_Y = "somevalue"
```

与前面示例中的 RDBMS 相比，DynamoDB 可以更轻松返回请求的数据。

# 在 DynamoDB 中为关系数据建模的初始步骤
<a name="bp-modeling-nosql"></a>

**注意**  
NoSQL 设计需要不同于 RDBMS 设计的思维模式。对于 RDBMS，可以创建规范化数据模型，不考虑访问模式。以后出现新问题和查询要求后进行扩展。而在 Amazon DynamoDB 中，应先了解需要解决的问题，再开始设计架构。预先了解业务问题和应用程序使用案例是至关重要的。

要开始设计能够高效扩展的 DynamoDB 表，必须先采取几个措施，确定其需要的支持的运营和业务支持系统 (OSS/BSS) 所需的访问模式。
+ 对于新应用程序，查看有关活动和目标的用户案例。记录确定的各种使用案例，然后分析这些案例需要的访问模式。
+ 对于现有应用程序，分析查询日志以了解人们目前使用该系统的方式，以及键访问模式。

完成此过程后，应获得一个可能如下所示的列表。


**订单条目应用程序的访问模式**  

| 模式编号 | 访问模式说明 | 
| --- | --- | 
| 1 | 按员工 ID 查找员工详细信息 | 
| 2 | 按员工姓名查询员工详细信息 | 
| 3 | 查找员工电话号码 | 
| 4 | 查找客户电话号码 | 
| 5 | 获取客户在日期范围内的订单 | 
| 6 | 显示日期范围内的所有未结订单 | 
| 7 | 查看最近聘用的所有员工 | 
| 8 | 查找某个仓库中的所有员工 | 
| 9 | 获取某个产品在订单上的所有项目 | 
| 10 | 获取某个产品在所有仓库中的库存 | 
| 11 | 按客户代表获取客户 | 
| 12 | 按客户代表获取订单 | 
| 13 | 获取担任某个职位的员工 | 
| 14 | 按产品和仓库获取库存 | 
| 15 | 获取总产品库存 | 

实际应用程序的列表可能更长。这个列表代表生产环境中可能出现的查询模式复杂性的范围。

DynamoDB 架构设计的现代化方法使用面向聚合的原则，根据访问模式而不是严格的实体边界对数据进行分组。此方法考虑多种设计模式：
+ *单个表设计*：使用复合排序键、重载的全局二级索引以及相邻列表模式，在一个表中存储多种实体类型
+ *多表设计*：为具有独立操作特征和低访问相关性的实体使用单独的表，使用策略性 GSI 进行跨实体查询
+ *聚合设计*：在相关的数据始终一起访问时嵌入相关数据（订单 \$1 订单项目），或者为标识关系使用项目集合（产品 \$1 库存）

您应根据具体的访问模式、数据特征和运行要求来选择这些方法。可以使用这些元素构造数据，使得应用程序可以在表或索引上使用单个查询，检索对于给定访问模式所需的任何内容。

**注意**  
究竟是选择单表设计还是多表设计应视您的特定要求而定。当多个实体具有高访问相关性和相似的操作特征时，适合使用单表设计。当实体具有独立的操作要求、不同的访问模式或需要明确的操作边界时，则首选使用多表设计。本指南中的示例演示的是多表方法，其中采用了策略性聚合和逆规范化。

要使用 NoSQL Workbench for DynamoDB 来帮助可视化您的分区键设计，请参阅[使用 NoSQL Workbench 构建数据模型](workbench.Modeler.md)。

# 在 DynamoDB 中为关系数据建模的示例
<a name="bp-modeling-nosql-B"></a>

此示例介绍如何在 Amazon DynamoDB 中为关系数据建模。该 DynamoDB 表设计对应于[关系建模](bp-relational-modeling.md)中显示的关系订单条目架构。该设计没有使用单个相邻列表，而是使用多个专用表，提供了明确的操作边界，同时利用策略性 GSI 高效地服务所有访问模式。

该设计方法使用面向聚合的原则，根据访问模式而不是严格的实体边界对数据进行分组。其中涉及到几个关键设计决策，例如为具有低访问相关性的实体使用单独的表，在相关的数据始终一起访问时嵌入相关数据，以及为标识关系使用项目集合。

下表及其随附的索引支持关系订单条目架构：

## 员工表设计
<a name="employee-table-design"></a>

员工表将员工信息的每个项目作为单个实体进行存储，针对直接查找员工进行了优化，并通过策略性 GSI 支持多种查询模式。此表演示针对具有独立操作特征和低跨实体访问相关性的实体设计单独表的原则。

该表使用简单分区键（employee\$1id），不带排序键，因为每位员工都是一个不同的实体。通过四个 GSI，可以按照不同属性高效地执行查询：
+ *EmployeeByName GSI*：使用包含所有员工属性的 INCLUDE 投影来支持按姓名检索完整的员工详细信息，使用 employee\$1id 作为排序键来处理可能会出现的重名情况。
+ *EmployeeByWarehouse GSI*：使用仅包含基本属性（name、job\$1title、hire\$1date）的 INCLUDE 投影来尽可能降低存储成本，同时支持基于仓库的查询
+ *EmployeeByJobTitle GSI*：使用 INCLUDE 投影启用基于职位的查询，用于报告和组织分析
+ *EmployeeByHireDate GSI*：使用静态分区键值“EMPLOYEE”，将 hire\$1date 作为排序键，来实现针对最近招聘员工的高效日期范围查询。由于员工信息的添加/更新通常低于 1000 WCU，因此单个分区可以处理写入负载，而不会出现热分区问题


**员工表 – 基表结构**  

| employee\$1id（PK） | name | phone\$1numbers | warehouse\$1id | job\$1title | hire\$1date | entity\$1type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\$1001 | John Smith | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | EMPLOYEE | 
| emp\$1002 | Jane Doe | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | Associate | 2025-01-10 | EMPLOYEE | 
| emp\$1003 | Bob Wilson | ["\$11-555-0104"] | wh\$1pdx | Associate | 2025-06-20 | EMPLOYEE | 
| emp\$1004 | Alice Brown | ["\$11-555-0105"] | wh\$1pdx | 主管 | 2023-11-05 | EMPLOYEE | 
| emp\$1005 | Charlie Davis | ["\$11-555-0106"] | wh\$1sea | Associate | 2025-12-01 | EMPLOYEE | 


**EmployeeByName GSI – 支持员工姓名查询**  

| name（GSI-PK） | employee\$1id（GSI-SK） | phone\$1numbers | warehouse\$1id | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | --- | 
| Alice Brown | emp\$1004 | ["\$11-555-0105"] | wh\$1pdx | 主管 | 2023-11-05 | 
| Bob Wilson | emp\$1003 | ["\$11-555-0104"] | wh\$1pdx | Associate | 2025-06-20 | 
| Charlie Davis | emp\$1005 | ["\$11-555-0106"] | wh\$1sea | Associate | 2025-12-01 | 
| Jane Doe | emp\$1002 | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | Associate | 2025-01-10 | 
| John Smith | emp\$1001 | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | 


**EmployeeByWarehouse GSI – 支持仓库查询**  

| warehouse\$1id（GSI-PK） | employee\$1id（GSI-SK） | name | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | 
| wh\$1pdx | emp\$1003 | Bob Wilson | Associate | 2025-06-20 | 
| wh\$1pdx | emp\$1004 | Alice Brown | 主管 | 2023-11-05 | 
| wh\$1sea | emp\$1001 | John Smith | Manager | 2024-03-15 | 
| wh\$1sea | emp\$1002 | Jane Doe | Associate | 2025-01-10 | 
| wh\$1sea | emp\$1005 | Charlie Davis | Associate | 2025-12-01 | 


**EmployeeByJobTitle GSI – 支持职位查询**  

| job\$1title（GSI-PK） | employee\$1id（GSI-SK） | name | warehouse\$1id | hire\$1date | 
| --- | --- | --- | --- | --- | 
| Associate | emp\$1002 | Jane Doe | wh\$1sea | 2025-01-10 | 
| Associate | emp\$1003 | Bob Wilson | wh\$1pdx | 2025-06-20 | 
| Associate | emp\$1005 | Charlie Davis | wh\$1sea | 2025-12-01 | 
| Manager | emp\$1001 | John Smith | wh\$1sea | 2024-03-15 | 
| 主管 | emp\$1004 | Alice Brown | wh\$1pdx | 2023-11-05 | 


**EmployeeByHireDate GSI – 支持最近招聘员工查询**  

| entity\$1type（GSI-PK） | hire\$1date（GSI-SK） | employee\$1id | name | warehouse\$1id | 
| --- | --- | --- | --- | --- | 
| EMPLOYEE | 2023-11-05 | emp\$1004 | Alice Brown | wh\$1pdx | 
| EMPLOYEE | 2024-03-15 | emp\$1001 | John Smith | wh\$1sea | 
| EMPLOYEE | 2025-01-10 | emp\$1002 | Jane Doe | wh\$1sea | 
| EMPLOYEE | 2025-06-20 | emp\$1003 | Bob Wilson | wh\$1pdx | 
| EMPLOYEE | 2025-12-01 | emp\$1005 | Charlie Davis | wh\$1sea | 

## 客户表设计
<a name="customer-table-design"></a>

客户表通过对 account\$1rep\$1id 的策略性逆规范化来维护客户信息，从而实现高效的客户代表查询。这种设计选择以少量存储开销来提高查询性能，避免了联接客户数据和客户代表数据的需求。

该表使用列表属性来支持每个客户的多个电话号码，这体现了 DynamoDB 的架构灵活性。单个 GSI 可以实现客户代表工作流：
+ *CustomerByAccountRep GSI*：使用包含姓名和电子邮件属性的 INCLUDE 投影来支持客户代表的客户管理，而无需检索完整的客户记录


**客户表 – 基表结构**  

| customer\$1id（PK） | name | phone\$1numbers | 电子邮件 | account\$1rep\$1id | 
| --- | --- | --- | --- | --- | 
| cust\$1001 | Acme Corp | ["\$11-555-1001"] | contact@acme.com | rep\$1001 | 
| cust\$1002 | TechStart Inc | ["\$11-555-1002", "\$11-555-1003"] | info@techstart.com | rep\$1001 | 
| cust\$1003 | Global Traders | ["\$11-555-1004"] | sales@globaltraders.com | rep\$1002 | 
| cust\$1004 | BuildRight LLC | ["\$11-555-1005"] | orders@buildright.com | rep\$1002 | 
| cust\$1005 | FastShip Co | ["\$11-555-1006"] | support@fastship.com | rep\$1003 | 


**CustomerByAccountRep GSI – 支持客户代表查询**  

| account\$1rep\$1id（GSI-PK） | customer\$1id（GSI-SK） | name | 电子邮件 | 
| --- | --- | --- | --- | 
| rep\$1001 | cust\$1001 | Acme Corp | contact@acme.com | 
| rep\$1001 | cust\$1002 | TechStart Inc | info@techstart.com | 
| rep\$1002 | cust\$1003 | Global Traders | sales@globaltraders.com | 
| rep\$1002 | cust\$1004 | BuildRight LLC | orders@buildright.com | 
| rep\$1003 | cust\$1005 | FastShip Co | support@fastship.com | 

## 订单表设计
<a name="order-table-design"></a>

订单表使用垂直分区，为订单标题和订单项目使用单独的项目。这种设计可以按照产品高效地进行查询，同时将所有订单组成部分保持在同一个分区内，从而实现高效访问。每个订单包含多个项目：
+ *订单标题*：包含 PK=order\$1id、SK=order\$1id 的订单元数据
+ *订单项目*：PK=order\$1id、SK=product\$1id 的单独行项目，实现直接产品查询

**注意**  
这种垂直分区方法牺牲了嵌入式订单项目的简化性，换来的是查询灵活性的增强。每个订单项目都成为一个单独的 DynamoDB 项目，实现了按照产品高效地进行查询，同时将所有订单数据保存在同一个分区内，这样就能在单个请求中高效进行检索。

该表包括 account\$1rep\$1id（从客户表复制）的策略性逆规范化，无需查找客户即可直接查询客户代表。对于高吞吐量写入场景，OPEN 订单包括状态和分片属性，实现了跨多个分区的写入分片。

四个 GSI 支持不同的查询模式并具有优化的投影：
+ *OrderByCustomerDate GSI*：使用包含订单摘要和项目详细信息的 INCLUDE 投影，支持查询带有日期范围筛选的客户订单历史记录
+ *OpenOrdersByDate GSI（稀疏、分片）*：使用多属性分区键（状态 \$1 分片）和 5 个分片，将 5000 WPS（每秒写入次数）在分区之间进行分配（每个分区 1000 WPS，与 DynamoDB 的每个分区 1000 WCU 限制相匹配）。仅对 OPEN 订单（占总数的 20%）编制索引，这有助于降低 GSI 存储成本。需要在所有 5 个分片上并行执行查询，同时在客户端合并结果
+ *OrderByAccountRep GSI*：使用包含订单摘要属性的 INCLUDE 来支持客户代表工作流，而无需完整订单详细信息
+ *ProductInOrders GSI*：从 OrderItem 记录（PK=order\$1id，SK=product\$1id）创建，此 GSI 实现了查找包含某个特定产品的所有订单的查询。使用 INCLUDE 投影及订单上下文（customer\$1id、order\$1date、quantity）进行产品需求分析


**订单表 – 基表结构（垂直分区）**  

| PK | SK | customer\$1id | order\$1date | status | account\$1rep\$1id | quantity | 价格 | 分片 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| ord\$1001 | ord\$1001 | cust\$1001 | 2025-11-15 | 已关闭 | rep\$1001 |  |  |  | 
| ord\$1001 | prod\$1100 |  |  |  |  | 5 | 25.00 |  | 
| ord\$1002 | ord\$1002 | cust\$1001 | 2025-12-20 | OPEN | rep\$1001 |  |  | 0 | 
| ord\$1002 | prod\$1101 |  |  |  |  | 10 | 15.00 |  | 
| ord\$1003 | ord\$1003 | cust\$1002 | 2026-01-05 | OPEN | rep\$1001 |  |  | 2 | 
| ord\$1003 | prod\$1100 |  |  |  |  | 3 | 25.00 |  | 


**OrderByCustomerDate GSI – 支持客户订单查询**  

| customer\$1id（GSI-PK） | order\$1date（GSI-SK） | order\$1id | status | total\$1amount | order\$1items | 分片 | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\$1001 | 2025-11-15 | ord\$1001 | 已关闭 | 225.00 | [\$1product\$1id: "prod\$1100", qty: 5\$1] |  | 
| cust\$1001 | 2025-12-20 | ord\$1002 | OPEN | 150.00 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 0 | 
| cust\$1002 | 2026-01-05 | ord\$1003 | OPEN | 175.00 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 2 | 
| cust\$1003 | 2025-10-10 | ord\$1004 | 已关闭 | 250.00 | [\$1product\$1id: "prod\$1101", qty: 5\$1] |  | 
| cust\$1004 | 2026-01-03 | ord\$1005 | OPEN | 200.00 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 1 | 


**OpenOrdersByDate GSI（稀疏、分片）– 支持高吞吐量的未结订单查询**  

| status（GSI-PK-1） | shard（GSI-PK-2） | order\$1date（SK） | order\$1id | customer\$1id | account\$1rep\$1id | order\$1items | total\$1amount | 
| --- | --- | --- | --- | --- | --- | --- | --- | 
| OPEN | 0 | 2025-12-20 | ord\$1002 | cust\$1001 | rep\$1001 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\$1005 | cust\$1004 | rep\$1002 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\$1003 | cust\$1002 | rep\$1001 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 175.00 | 


**OrderByAccountRep GSI – 支持客户代表订单查询**  

| account\$1rep\$1id（GSI-PK） | order\$1date（GSI-SK） | order\$1id | customer\$1id | status | total\$1amount | 
| --- | --- | --- | --- | --- | --- | 
| rep\$1001 | 2025-11-15 | ord\$1001 | cust\$1001 | 已关闭 | 225.00 | 
| rep\$1001 | 2025-12-20 | ord\$1002 | cust\$1001 | OPEN | 150.00 | 
| rep\$1001 | 2026-01-05 | ord\$1003 | cust\$1002 | OPEN | 175.00 | 
| rep\$1002 | 2025-10-10 | ord\$1004 | cust\$1003 | 已关闭 | 250.00 | 
| rep\$1002 | 2026-01-03 | ord\$1005 | cust\$1004 | OPEN | 200.00 | 


**ProductInOrders GSI – 支持产品订单查询**  

| product\$1id（GSI-PK） | order\$1id（GSI-SK） | customer\$1id | order\$1date | quantity | 
| --- | --- | --- | --- | --- | 
| prod\$1100 | ord\$1001 | cust\$1001 | 2025-11-15 | 5 | 
| prod\$1100 | ord\$1003 | cust\$1002 | 2026-01-05 | 3 | 
| prod\$1101 | ord\$1002 | cust\$1001 | 2025-12-20 | 10 | 

## 产品表设计
<a name="product-table-design"></a>

产品表使用项目集合模式，将产品元数据和库存数据存储在同一个分区中。这种设计利用了商品与库存之间的标识关系，即没有父产品时就不会有库存。使用 PK=product\$1id 和 SK=product\$1id 作为产品元数据，并使用 SK=warehouse\$1id 来确定库存项目，这样就无需单独的库存表和 GSI，从而将成本降低了大约 50%。

这种模式可以针对某个产品高效地查询单个仓库中的库存（使用复合键 GetItem）和所有仓库中的库存（按分区键查询）。产品元数据项目中的 total\$1inventory 属性提供逆规范化聚合，用于快速查找总库存。


**产品表 – 基表结构（项目集合模式）**  

| product\$1id（PK） | warehouse\$1id（SK） | product\$1name | category | unit\$1price | inventory\$1quantity | total\$1inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\$1100 | prod\$1100 | Widget A | Hardware | 25.00 |  | 500 | 
| prod\$1100 | wh\$1sea |  |  |  | 200 |  | 
| prod\$1100 | wh\$1pdx |  |  |  | 150 |  | 
| prod\$1100 | wh\$1atl |  |  |  | 150 |  | 
| prod\$1101 | prod\$1101 | Gadget B | Electronics | 50.00 |  | 300 | 
| prod\$1101 | wh\$1sea |  |  |  | 100 |  | 
| prod\$1101 | wh\$1pdx |  |  |  | 200 |  | 

每个表都设计有特定的全局二级索引（GSI），用于高效地支持所需的访问模式。该设计使用面向聚合的原则和策略性逆规范化以及稀疏索引，来优化性能和成本。

主要设计优化包括：
+ *稀疏 GSI*：OpenOrdersByDate 仅对 OPEN 订单（占总数的 20%）编制索引，这有助于降低 GSI 存储成本
+ *项目集合模式*：产品表使用 PK=product\$1id、SK=warehouse\$1id 来存储库存，这样就无需使用单独的库存表
+ *Order \$1 OrderItems 聚合*：由于 100% 的访问相关性，作为单个项目嵌入
+ *策略性逆规范化*：订单表中复制了 account\$1rep\$1id 来实现高效查询

最后，您可以再次访问之前定义的访问模式。下表显示如何使用带有策略性 GSI 的多表设计，来高效支持各种访问模式。各模式会使用直接键查找或单个 GSI 查询，从而避免了昂贵的扫描操作，并可在任意规模下提供一致的性能。


| 序列号 | 访问模式 | 查询条件 | 
| --- | --- | --- | 
|  1  |  按员工 ID 查找员工详细信息  |  员工表：GetItem(employee\$1id="emp\$1001")  | 
|  2  |  按员工姓名查询员工详细信息  |  EmployeeByName GSI：Query(name="John Smith")  | 
|  3  |  查找员工电话号码  |  员工表：GetItem(employee\$1id="emp\$1001")  | 
|  4  |  查找客户电话号码  |  客户表：GetItem(customer\$1id="cust\$1001")  | 
|  5  |  获取客户在日期范围内的订单  |  OrderByCustomerDate GSI：Query(customer\$1id="cust\$1001", order\$1date BETWEEN "2025-01-01" AND "2025-12-31")  | 
|  6  |  显示日期范围内的所有未结订单  |  OpenOrdersByDate GSI：使用多属性 PK（status="OPEN" \$1 shard=0-4）、SK=order\$1date BETWEEN "2025-01-01" AND "2025-12-31" 并行查询 5 个分片，合并结果  | 
|  7  |  查看最近聘用的所有员工  |  EmployeeByHireDate GSI：Query(entity\$1type="EMPLOYEE", hire\$1date >= "2025-01-01")  | 
|  8  |  查找某个仓库中的所有员工  |  EmployeeByWarehouse GSI：Query(warehouse\$1id="wh\$1sea")  | 
|  9  |  获取某个产品在订单上的所有项目  |  ProductInOrders GSI：Query(product\$1id="prod\$1100")  | 
|  10  |  获取某个产品在所有仓库中的库存  |  产品表：Query(product\$1id="prod\$1100")  | 
|  11  |  按客户代表获取客户  |  CustomerByAccountRep GSI：Query(account\$1rep\$1id="rep\$1001")  | 
|  12  |  按客户代表获取订单  |  OrderByAccountRep GSI：Query(account\$1rep\$1id="rep\$1001")  | 
|  13  |  获取担任某个职位的员工  |  EmployeeByJobTitle GSI：Query(job\$1title="Manager")  | 
|  14  |  按产品和仓库获取库存  |  产品表：GetItem(product\$1id="prod\$1100", warehouse\$1id="wh\$1sea")  | 
|  15  |  获取总产品库存  |  产品表：GetItem(product\$1id="prod\$1100", warehouse\$1id="prod\$1100")  | 