8.1 KiB
8.1 KiB
DB Design & Migrations(数据库设计与迁移)|Plan
对应规范:
spec_kit/Personalized Reco/modules/db-design/spec.md前置确认(已对齐):
content_id:MySQL 自增主键;文案微调时 content_id 不变(更新同一条记录)。need_suitability/context_suitability:JSON 存储。risk_flags:选择更强扩展性的方案(本计划采用 关联表,利于索引与过滤)。- 安全池(L3):方式 A(
is_safe_pool)。- 迁移:使用 Alembic,目录放
server/alembic/;dev/pro 两套库均可运行同一套迁移。- 字符集:统一 utf8mb4。
1. 目标与交付物
1.1 目标
- 在“库为空”的前提下,落地推荐系统最小可用的数据模型。
- 保证后续推荐查询可实现:按画像条件召回、按 ID 批量查、按风险标记过滤、支持安全池兜底。
1.2 交付物
server/alembic/:Alembic 初始化目录、alembic.ini(或等价配置)、迁移脚本。- SQLAlchemy ORM 模型(建议放
server/app/db/models/)。 - 初始迁移:创建
contents、content_profiles、content_risk_flags(以及必要索引)。
2. 技术决策(V1)
2.1 表设计原则
- 分离主体与画像:
contents存文本与来源字段;content_profiles存画像字段(便于未来画像重算/回填)。 - JSON 存 suitability:
context_suitability、need_suitability用 JSON,保持结构与规则文档一致。 - risk_flags 关联表:用
content_risk_flags(content_id, flag),便于:- 快速 Hard Filter(
block_health_medical等) - 索引与统计(按 flag 计数)
- 兼容旧 flag 映射(在写入/读取层)
- 快速 Hard Filter(
- emotion_score 的 general 表示:用
NULL表示 general(与规则文档“可为 general”语义等价)。 - personalization_power:存为
TINYINT(0/5/10)或DECIMAL(2,1)(0/0.5/1)。本计划推荐TINYINT(更易索引/更省空间),应用层做映射:- 0 → 0.0
- 5 → 0.5
- 10 → 1.0
2.2 字符集与排序规则
- 数据库与表:
utf8mb4 - collation:建议
utf8mb4_0900_ai_ci(MySQL 8 默认更常见;若环境不同以实际为准,但必须 utf8mb4)
3. 表结构(V1 方案)
以下为“建议 schema”。实际字段名可调整,但语义必须严格对齐
句子文案打分规则。
3.1 contents(文案主体)
content_idBIGINT UNSIGNED PK AUTO_INCREMENTtextTEXT NOT NULLauthor_idVARCHAR(64) NULLtemplate_idVARCHAR(64) NULLcreated_atDATETIME NOT NULLupdated_atDATETIME NOT NULL
索引建议:
idx_contents_author_id(author_id)idx_contents_template_id(template_id)
3.2 content_profiles(内容画像)
content_idBIGINT UNSIGNED PK(FK → contents.content_id,ON DELETE CASCADE)stageENUM('general','expecting','parenting','unknown') NOT NULL DEFAULT 'general'emotion_scoreDECIMAL(3,2) NULL- 约定:NULL 表示 general
context_suitability_jsonJSON NOT NULLneed_suitability_jsonJSON NOT NULLpersonalization_powerTINYINT UNSIGNED NOT NULL DEFAULT 0- 约定:只允许 0/5/10
review_confidenceDECIMAL(3,2) NULL- 约定:NULL 由推荐模块按 0.7 兜底(对齐规则文档)
is_safe_poolBOOLEAN NOT NULL DEFAULT FALSEupdated_atDATETIME NOT NULL
索引建议:
idx_profiles_stage(stage)idx_profiles_personalization_power(personalization_power)idx_profiles_is_safe_pool(is_safe_pool)
说明:suitability 放 JSON 后,V1 可以先不做 JSON 路径索引;当候选量上来后再加“生成列/函数索引”做加速(见 6.2)。
3.3 content_risk_flags(风险标记,关联表)
idBIGINT UNSIGNED PK AUTO_INCREMENTcontent_idBIGINT UNSIGNED NOT NULL(FK → contents.content_id,ON DELETE CASCADE)flagVARCHAR(64) NOT NULLcreated_atDATETIME NOT NULL
约束与索引:
- UNIQUE:
uniq_content_flag(content_id, flag)(同一 content 不重复插同 flag) - 索引:
idx_flag(flag)(用于 Hard Filter 与统计) - 索引:
idx_content_id(content_id)(用于按内容批量取 flags)
命名约束(应用层强制,DB 可选):
- flag 必须以
unsafe_for_/block_/soft_开头(严格对齐规则文档)
4. Alembic 迁移落地步骤
4.1 依赖与目录
- 后端依赖:
alembic(加入server/requirements.txt,版本随项目统一管理) - 目录:
server/alembic/(包含env.py、versions/) - 连接串:复用现有
DATABASE_URL(mysql+aiomysql://...)
4.2 初始化与生成迁移(一次性)
alembic init alembic(在server/下)- 配置
env.py:- 从
app/core/config.py读取DATABASE_URL - 引入 ORM Base 与 models,启用 autogenerate
- 从
- 创建初始迁移:
alembic revision --autogenerate -m "init content tables"alembic upgrade head
4.3 dev/pro 一致性
- 迁移脚本保持同一套;通过不同环境的
DATABASE_URL指向mindfulness_dev或mindfulness。
5. 入库流程(写入契约)
5.1 一条文案最小入库数据
必须字段(V1 最小可用):
textcontent_profiles.stagecontent_profiles.emotion_score(可为 NULL 表示 general)content_profiles.context_suitability_json(必须包含 5 个 key:family/work/relationship/friends/health,值为 0/0.5/1)content_profiles.need_suitability_json(必须包含 5 个 key:emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance,值为 0/0.5/1)content_profiles.personalization_power(0/5/10)content_risk_flags(可为空集合,但若存在必须按命名规范)
强烈建议字段:
author_id、template_idreview_confidenceis_safe_pool(若要参与 L3 安全池)
5.2 写入策略
- 创建文案时:
- 先写
contents得到content_id(自增) - 再写
content_profiles(同 content_id) - 再批量写
content_risk_flags
- 先写
- 文案微调时(content_id 不变):
- 更新
contents.text与updated_at - 同步更新
content_profiles(若画像变更) - risk_flags 做“全量覆盖”或“差量更新”(plan 实现阶段定)
- 更新
6. 查询与性能规划
6.1 V1 查询策略(先可用)
- 候选召回:
- 先按
stage、personalization_power、is_safe_pool等可索引字段进行粗过滤 - 再在应用层结合 suitability JSON 与 risk_flags 做精过滤/打分
- 先按
- Hard Filter:
- 通过
content_risk_flagsjoin 或子查询排除指定 flags(如block_health_medical)
- 通过
- 批量查:
content_id IN (...)joincontent_profiles+ left joincontent_risk_flags
6.2 V1.1 性能增强(候选量上来后再做)
当候选池变大、应用层过滤成本上升时,优先做两类增强:
- 生成列/函数索引:为常用召回维度(例如 need/context 的某些 key)创建 generated columns(从 JSON_EXTRACT 取值并映射到 TINYINT),再加索引。
- 风险 flag 位图/派生列:对
block_health_medical等强规则增加派生布尔列(或维护冗余表),降低 join 成本。
7. 测试与验收(DB 子模块)
7.1 迁移验收
- 在全新库执行
alembic upgrade head成功。 - 执行
downgrade(若实现)可回滚(至少在开发环境可用)。
7.2 数据契约验收
插入一条最小文案记录后,能够查询并组装出推荐模块所需的 ContentProfile 字段集合:
content_id/text/stage/emotion_score/context_suitability/need_suitability/personalization_power/risk_flags
7.3 规则口径验收(写入侧)
- 写入
risk_flags时,若出现旧 flag(如block_stage_unknown):- 写入层需在入库前映射为新命名(或拒绝写入并提示)
- 推荐侧读取层不得再出现旧 flag 名称
8. 与其他子模块的接口约定
Content Repository只依赖本模块提供的表与字段语义,不依赖具体迁移实现细节。- 推荐引擎/打分模块对
review_confidence的缺省值假设(0.7)在 DB 缺失时依然成立。