DynamoDB 中的单表设计
从 SQL 过来,本能是每种实体一张表:customers、orders、order_items。在 DynamoDB 里,这个本能通常是错的。一张存储每一种实体、靠重载的键前缀来区分的单表,能让你在一次 Query 里就取回一个父项及其所有子项——没有连接,没有 N+1。
什么是 DynamoDB 中的单表设计?
单表设计把每一种实体——客户、订单、订单项——都存进一张 DynamoDB 表,靠重载的和排序键前缀来区分。因为键是围绕你的访问模式而非你的实体来设计的,一个父项及其所有子项住在一个里,一次 Query 就能取回——没有连接,没有 N+1 读取。
这个思路
挑选通用的键名(PK、SK),把实体类型编码进取值里:
| PK | SK | attributes |
|---|---|---|
| CUSTOMER#42 | PROFILE | name, email, plan |
| CUSTOMER#42 | ORDER#2026-001 | total, status |
| CUSTOMER#42 | ORDER#2026-002 | total, status |
现在一次 Query PK = "CUSTOMER#42" 在单次计费读取里返回资料以及每一条订单。SK begins_with "ORDER#" 把它收窄到只剩订单。
从视觉上看,重载的条目在一个下堆叠成一个:
一次读取分区,就把客户和每一条订单一起交回。
重载的 GSI
同样的技巧在索引上也管用。给条目放上通用的 GSI1PK/GSI1SK,单个 就能服务多种访问模式,取决于每个条目往那些属性里写了什么:
| PK | SK | GSI1PK | GSI1SK |
|---|---|---|---|
| ORDER#001 | METADATA | STATUS#OPEN | 2026-01-04 |
| ORDER#002 | METADATA | STATUS#OPEN | 2026-01-05 |
现在 Query GSI1 WHERE GSI1PK = "STATUS#OPEN" 按日期列出打开的订单——一种基础表回答不了的模式。另一种实体可以带着自己的含义复用 GSI1(例如 CATEGORY#books)。一个索引,多种查询。
多对多:邻接表
对于关系(一个用户在多个团队里、一个团队有多个用户),把这条边写两遍、把 id 对调:PK=USER#1, SK=TEAM#9 和 PK=TEAM#9, SK=USER#1。查询任一侧都能列出另一侧——这是 DynamoDB 对连接表的替身。
何时不该用单表
它不是免费的。一张重载的表更难推理、更难演进,也对分析不友好。如果你的访问模式确实未知或不断变化,或者数据主要是分析性的,那么分开的表(或另一种存储)反而更明智。单表在模式已知且高频时取胜。
错误形态的代价
建模成分开的表,会迫使你用一次 Scan 或客户端连接来把一个客户重新拼起来,而那就是 Scan 坑。先给访问模式建模,然后设计键,让每一种模式都成为一次 Query。(至于那个你从未建模过的临时跨实体问题,DynoTable 的 SQL Workbench 会在客户端运行 JOIN——探索不必等一次重新建模。)
先用免费的单表设计工具勾勒设计本身——它会把你的访问模式清单变成一份带示例项和成本提示的 PK/SK/GSI 方案。用项大小与容量计算器估算这些条目每次读取的成本,并试用 DynoTable 浏览一个单表模式,把重载的集合并排看个明白。