DynamoDB单表架构设计咨询:个人健身训练系统场景
健身训练系统DynamoDB单表设计咨询
我正在深入学习NoSQL及DynamoDB相关知识,阅读资料和视频后仍对单表架构设计思路感到困惑,因此希望通过个人项目实践探索,我熟悉关系型数据库,现寻求方向指导。
我计划构建个人健身训练系统:用户注册企业后创建拥有该企业的用户账号(所有权可变更),企业可包含门店、客户、训练计划等。
实体定义
- Business:由
id唯一标识,包含设置信息,可关联多门店、教练、客户、动作、训练、课程 - Location:由
id唯一标识,包含设置信息,属于单个企业,可关联多用户、客户 - Trainer:由
email唯一标识,可关联多动作、训练、课程、客户 - Client:由
email唯一标识,可关联多动作、训练、课程、教练 - Exercise:由
id唯一标识,可关联多训练、课程、客户、教练、门店 - Workout:由
id唯一标识,可关联多课程、客户、教练、门店 - Program:由
id唯一标识,可关联多客户、教练、门店
访问模式
- 获取企业(按id)
- 获取企业设置(按id)
- 获取门店列表(按企业id)
- 获取单个门店(按门店id)
- 获取门店设置(按门店id)
- 获取教练列表(按企业id/企业+门店id)
- 获取单个教练(按教练id)
- 获取客户列表(按企业id/企业+门店id/企业+教练id)
- 获取动作列表(按企业id/企业+门店id/企业+教练id/动作id)
- 获取训练列表(按企业id/企业+门店id/企业+教练id/训练id)
- 获取课程列表(按企业id/企业+门店id/企业+教练id/课程id)
已设计的实体表
| Entity | PK | SK | GS1PK | GS1SK |
|---|---|---|---|---|
| BUSINESS | BUSINESS#id | BUSINESS#id | ||
| SETTINGS | BUSINESS#id | #SETTINGS | ||
| LOCATION | BUSINESS#id | LOCATION#id | ||
| SETTINGS | LOCATION#id | #SETTINGS | ||
| EXERCISE | EXERCISE#id | BUSINESS#id | ||
| WORKOUT | WORKOUT#id | BUSINESS#id | ||
| PROGRAM | PROGRAM#id | BUSINESS#id | ||
| USER | USER#id | USER#id | USER#email | USER#email |
| TRAINER | TRAINER#id | TRAINER#email | BUSINESS#id | TRAINER#id |
| CLIENT | CLIENT#id | CLIENT#email | BUSINESS#id | CLIENT#id |
困惑点
- 企业包含多门店,LOCATION应作为独立实体还是归属于BUSINESS(如
BUSINESS##LOCATION#)? - 门店有独立设置,SETTINGS应作为独立实体还是归属于LOCATION(如
LOCATION##SETTINGS)? - 用户模块将使用NextAuth(Auth.js)处理认证,其DynamoDB适配器要求遵循默认USER schema,是否应将TRAINER和CLIENT合并到USER实体,通过账号类型属性区分?
- 如何判断何时需要使用GSI或LSI?
不确定当前设计方向是否正确,期待专业建议。
针对困惑点的解答
1. LOCATION的归属与实体设计
在DynamoDB单表架构中,不需要纠结"归属"的关系定义,核心是围绕访问模式设计主键:
- 现有设计用
BUSINESS#id作为PK、LOCATION#id作为SK,能直接通过Query操作拉取指定企业下的所有门店,完全匹配「按企业id获取门店列表」的需求,这部分是合理的。 - 但现有设计无法直接通过门店id定位单个门店(因为门店的PK是所属企业id,可能存在不知道门店所属企业的场景),所以需要给LOCATION添加GSI:将GS1PK设为
LOCATION#id,GS1SK设为LOCATION#id,这样就能通过GSI的Get操作直接获取单个门店。 BUSINESS##LOCATION#这类嵌套写法没必要,用#分隔主键语义已足够清晰,保持PK/SK的语义明确即可。
2. SETTINGS的实体设计
同样围绕访问模式判断:
- 现有设计中,企业设置用
BUSINESS#id作为PK、#SETTINGS作为SK;门店设置用LOCATION#id作为PK、#SETTINGS作为SK,完全满足「按企业id获取设置」「按门店id获取设置」的需求,不需要将SETTINGS归属于LOCATION的主键结构中。 - 这种设计的优势是操作语义清晰:获取企业设置直接执行
Query PK=BUSINESS#XXX AND SK=#SETTINGS,获取门店设置执行Query PK=LOCATION#XXX AND SK=#SETTINGS,高效且易维护。
3. USER、TRAINER、CLIENT的合并问题
必须合并到USER实体,原因如下:
- NextAuth的DynamoDB适配器依赖固定的USER schema,如果单独维护TRAINER/CLIENT表,会导致认证系统与业务系统的用户数据割裂,后续同步、维护成本极高。
- 解决方案:在USER实体中新增
account_type属性(枚举值:OWNER/TRAINER/CLIENT),同时添加业务关联字段,比如business_id(关联所属企业)、location_id(关联所属门店)等。 - 这种设计既能符合NextAuth的要求,又能通过GSI实现各类列表查询:比如要按企业id查询教练列表,可创建GSI将GS1PK设为
BUSINESS#id,GS1SK设为TRAINER#user_id,直接Query即可。
4. GSI与LSI的使用判断
LSI(本地二级索引)使用场景
- 必须与基表PK相同,仅能在创建表时定义,最多支持5个。
- 适合同一主实体下的多维度排序/查询,比如企业下有门店、教练、客户等实体,想通过同一个PK(
BUSINESS#id)查询不同类型的实体,可通过LSI的SK区分,但LSI灵活性较低,目前更推荐使用GSI。
GSI(全局二级索引)使用场景
- 可独立定义PK和SK,支持随时添加,最多支持20个。
- 当访问模式需要以非基表PK的字段作为查询条件时,就需要GSI:
- 比如按门店id查询单个门店,基表中门店的PK是
BUSINESS#id,无法直接定位,需用GSI将LOCATION#id设为PK。 - 比如按企业+门店id查询教练列表,可创建GSI将GS1PK设为
BUSINESS#id#LOCATION#id,GS1SK设为TRAINER#user_id,直接Query即可获取目标数据。
- 比如按门店id查询单个门店,基表中门店的PK是
- 简单判断原则:如果基表的PK/SK组合无法满足访问模式,就使用GSI。
整体设计优化建议
- 统一实体主键格式:所有实体的PK采用
ENTITY_TYPE#ID格式,比如LOCATION的PK设为LOCATION#id,SK设为BUSINESS#id,这样既可以通过GSI按BUSINESS#id拉取门店列表,也能直接通过PK获取单个门店。 - 优化EXERCISE/WORKOUT/PROGRAM的设计:现有设计中它们的PK是自身id、SK是企业id,但访问模式需要按门店/教练查询这些实体,因此需添加GSI:比如给EXERCISE添加GSI,将GS1PK设为
BUSINESS#id#LOCATION#id,GS1SK设为EXERCISE#id,支持按企业+门店查询动作列表。 - 利用SK的排序能力:比如查询教练列表时,将SK设为
TRAINER#created_at#user_id,可直接按创建时间排序返回结果,无需额外处理。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

