DynamoDB单表存储多实体方案咨询:是否需改用多表?
问题背景
我正在开发一款以DynamoDB为数据库的应用,涉及四类实体:用户、组织、项目、脚本,实体关系如下:
- 组织包含多个项目
- 项目或组织可包含多个脚本
- 用户仅属于一个组织,一个组织可拥有多个用户
- 脚本可归属项目或组织,且需存储版本以支持回滚
应用需要支持的查询场景:
- 获取某组织下所有用户
- 获取某组织下所有项目
- 获取某项目下所有脚本
- 按ID查询脚本
- 按脚本ID查询所有版本
- 按脚本ID查询最新版本
- 按组织ID查询组织
- 按项目ID查询项目
- 按脚本ID查询脚本
现有单表设计
我计划创建名为Entities的单表,表结构如下:
| pk | sk | type (gsi1-pk) | parent (gsi1-sk) |
|---|---|---|---|
| root#org-1 | info | org | root |
| org-1#proj-1 | info | project | org-1 |
| proj-1#script-1 | info | script | proj-1 |
| proj-1#script-1 | rev_latest | script_revision | script-1 |
| proj-1#script-1 | rev_1 | script_revision | script-1 |
| proj-1#script-1 | rev_2 | script_revision | script-1 |
| org-1#user-1 | info | user | org-1 |
| org-1#user-2 | info | user | org-1 |
设计思路:
- 通过
pk和sk可查询指定ID的实体 - 针对“通过父对象ID查询子对象”的场景,创建GSI进行查询
pk中保留父对象前缀是为防止误更新,尽管parent字段已包含该信息
疑问
请问是否改用多表存储更合适?出于成本考虑,我希望避免使用Scan操作,理想状态下仅通过Query操作获取数据。
回答
不用改多表,你的单表设计已经能覆盖所有查询需求,完全符合DynamoDB单表设计的最佳实践,还能避免多表带来的额外成本和复杂度。
为什么单表更合适?
完全满足查询需求:
- 按ID查询单个实体(组织/项目/脚本):直接用
pk = [实体前缀#ID]+sk = info做Query,精准定位。 - 查询父对象下的子实体(比如组织下的用户/项目、项目下的脚本):用GSI,
gsi1-pk设为实体类型(如user/project/script),gsi1-sk设为父ID,直接Query就能拿到所有子实体,完全不用Scan。 - 脚本版本相关查询:
- 查所有版本:
pk = [归属前缀#script-ID]+sk begins_with rev_,Query即可获取所有版本条目。 - 查最新版本:直接用
pk = [归属前缀#script-ID]+sk = rev_latest,一步到位。
- 查所有版本:
- 按ID查询单个实体(组织/项目/脚本):直接用
成本优势:
- 单表只需要维护一套读写容量,多表会分散容量,可能导致个别表容量闲置而另一些表容量不足,反而增加成本。
- 所有查询都用Query,完全避开Scan的高成本和性能问题,符合你的成本控制需求。
数据一致性与维护性:
- 单表内的关联数据操作(比如创建项目时关联组织)可以在一个事务内完成,多表则需要跨事务,复杂度更高。
pk带父前缀的设计很实用,能有效防止误操作(比如不会把其他组织的项目ID当成当前组织的来更新),比单纯依赖parent字段更安全。
可以优化的小细节
- 脚本的
pk设计:如果脚本可能从项目移到组织(或者反过来),当前pk带归属前缀的话,移动时需要修改pk,这会导致旧数据变成孤儿条目。可以考虑把脚本的pk改成script#script-1,然后用额外字段存储归属信息(比如owner_type和owner_id),同时调整GSI的设计来支持按归属查询脚本。不过如果你的业务里脚本归属不会变更,当前设计就没问题。 - 脚本最新版本可以不用单独存
rev_latest,而是把版本号设为时间戳格式(比如rev_202405201230),然后用Query加ScanIndexForward = false取第一条就是最新版本,这样能减少一条条目,节省存储。
总的来说,你的单表设计已经很成熟,完全能支撑业务需求,没必要改用多表。
内容的提问来源于stack exchange,提问作者ed1t
相关产品推荐
相关产品推荐

