# 05 · 数据库规范 | 项 | 内容 | | --- | --- | | 文档版本 | V1.0(2026-11-02) | | 依据 | 技术架构 §5(数据模型)、§8.3 / §8.4(ADR) | | 适用范围 | `prisma/`、所有 SQL、数据迁移脚本 | | 强制级别 | 强制 | --- ## 1 命名与主键 | 对象 | 规则 | 示例 | | --- | --- | --- | | 表 | 复数 snake_case | `media_tasks`、`brief_card_index` | | 列 | snake_case | `customer_id`、`created_at` | | 主键 | `id`,**应用侧生成 ULID**,`char(26)` | `01JA8Z...` | | 外键 | `<单数实体>_id` | `script_id`、`customer_id` | | 布尔 | `is_` / `has_` 前缀 | `is_archived` | | 时间 | `_at` 后缀,`timestamptz` | `reviewed_at` | | 金额 / 成本 | `_cost`、`_amount`,`numeric(18,6)` | `unit_cost` | | JSON | `_json` 或语义名(`params`、`snapshot`) | `output_params` | | 索引 | `idx_<表>_<列>`;唯一 `uq_*`;部分 `idx_*_partial` | `idx_media_tasks_customer_created` | - `DB-01` **禁止库内生成主键**(不用 `gen_random_uuid()` / `uuidv7()`);主键统一由应用生成 ULID,便于排序与分片。 - `DB-02` 每张业务表必须含 `created_at`、`updated_at`;需要软删的加 `deleted_at`。 - `DB-03` Prisma 模型用 PascalCase + `@@map` / `@map` 映射到 snake_case,**禁止**直接在库里用 camelCase 列名。 --- ## 2 客户隔离(最高优先级) - `DB-04` 所有业务表 `customer_id NOT NULL` + 外键;同时冗余 `industry_id` 便于按行业聚合。 - `DB-05` Repository 方法必须接收 `ScopeContext` 并在 `where` 中注入 `customer_id`;**禁止**「先查全量再在内存过滤」(PRD R-04)。 - `DB-06` 唯一约束必须包含 `customer_id`(如客户内脚本编号唯一),跨客户唯一只允许编码类字段。 - `DB-07` 跨客户写操作(跨客户复用授权)必须走显式授权表并写审计,**禁止**在业务 SQL 里放宽 `customer_id`。 - `DB-08` P1 起启用 PG 行级安全(RLS)兜底;启用前必须在 staging 验证「范围外查询返回 0 行」。 --- ## 3 字段与类型 | 场景 | 规则 | | --- | --- | | 枚举 | 优先字典表 + 数据行;表内保存 `*_snapshot`(名称快照),避免字典变更影响历史 | | Prisma enum | 仅用于**永不扩展**的封闭集合,且需迁移评审说明 | | 动态字段 | JSONB + `schema_version`;可检索字段物化到 `brief_card_index`(D-03) | | 大文本 | `text`;禁止 `varchar(255)` 装富文本 | | 时长 | 毫秒整数(`duration_ms`),禁止小数秒 | | 比例 | `numeric(6,4)`;禁止 `float` 存比率 | | 标签 | 关联表或 `text[]`(仅查询简单场景);**禁止** JSONB 存关联 ID(PRD R-25) | | 文件 | 只存对象存储 key(`storage_key`)+ 元数据,**禁止存二进制或外链签名 URL** | | 密码 / 密钥 | 只存哈希 / 密文;AI 供应商密钥用 AES-256-GCM 加密列 | - `DB-09` JSONB 上默认不建 GIN 索引;仅在确有按字段查询且 `EXPLAIN` 证明需要时建。 - `DB-10` 禁止 `SELECT *` 返回含大 JSONB 的列表;查询必须显式 `select`。 --- ## 4 索引与查询 - `DB-11` 所有外键列建索引。 - `DB-12` 列表页统一复合索引:`(customer_id, created_at DESC, id DESC)`;游标分页基于 `(created_at, id)`(技术架构 §5.7)。 - `DB-13` 唯一约束配合软删用**部分索引**:`WHERE deleted_at IS NULL`。 - `DB-14` 禁止无条件的 `COUNT(*)` 大表统计;计数走预聚合表或 `EXPLAIN` 验证代价。 - `DB-15` 禁止 OFFSET 深翻页;分页一律游标。 - `DB-16` 禁止 N+1:列表查询用 `include`/`select` 显式取关联,或批量 `IN` 查询。 - `DB-17` 批量写入单批 ≤ 1000 行,超出分片;`createMany` 需注意不触发单条钩子。 - `DB-18` 慢查询(> 200ms)必须记录到日志并带 `trace_id`,纳入每周巡检。 --- ## 5 迁移(Prisma Migrate) - `DB-19` 迁移文件命名 `YYYYMMDDHHmmss_<动词>_<对象>`,如 `20261102153000_add_material_specs`。 - `DB-20` **禁止修改已合并的迁移文件**;修正用新迁移。 - `DB-21` 采用 **expand → migrate → contract** 三步;**禁止**同一次发布内「删字段 + 改代码」(技术架构 §4.6)。 - `DB-22` 生产迁移必须幂等、可重入;大表加列用可空列或带默认值的在线加列,禁止锁表长事务。 - `DB-23` 迁移前必须备份,并在 staging 演练;回滚脚本随迁移一起提交(`down` 说明)。 - `DB-24` `prisma db push` 仅限本地探索,**禁止**用于任何共享环境。 - `DB-25` 迁移涉及数据回填时,回填脚本独立于 schema 迁移,可重复执行。 --- ## 6 事务与并发 - `DB-26` 事务边界在 Service 层;跨表写必须同事务。 - `DB-27` 事务内**禁止外部 IO**(AI 调用、HTTP、对象存储、ffmpeg)。 - `DB-28` 编号 / 序号类唯一值(脚本编号、批次号)必须用**原子取号**(`UPDATE ... RETURNING` 或序列表 + 行锁),禁止「先查后写」。 - `DB-29` 状态迁移用条件更新(`WHERE status = 'pending'`)避免并发覆盖;受影响行数为 0 视为冲突并返回 409。 - `DB-30` 避免长事务(> 5s);批量任务状态更新按批提交。 --- ## 7 删除与保留 | 数据 | 策略 | | --- | --- | | 业务实体 | 软删 `deleted_at`;被引用的资产**只能停用不能删除**(PRD R-02) | | 审计 / 账本 / 任务事件 | **append-only**,禁止 UPDATE / DELETE | | 任务临时产物 | `temp` 桶 30 天自动清理 | | 导出包 | `exports` 桶 3 天 | | 归档 | `archive` 桶 ≥ 12 个月 | | 客户数据删除 | 只按 PRD 约定的保留期与流程执行,**禁止**开发者自行清理 | - `DB-31` 硬删只允许:临时文件、失败任务产物、测试数据;且必须有清理任务与日志。 - `DB-32` 审计表权限:应用账号仅 `INSERT` + `SELECT`,无 `UPDATE` / `DELETE` 授权。 --- ## 8 检查清单 - [ ] 表 / 列 snake_case,主键 ULID,`created_at` / `updated_at` 齐全 - [ ] 业务表有 `customer_id` + 外键 + 冗余 `industry_id` - [ ] Repository 接收 `ScopeContext`,SQL 带客户维度 - [ ] 列表查询走复合索引 + 游标分页,无 `SELECT *`、无 N+1 - [ ] 迁移 expand/contract,未改历史迁移,staging 已演练 - [ ] 事务内无外部 IO;编号原子取号;状态迁移带条件 - [ ] 审计 append-only;临时产物有清理策略 - [ ] 新增 JSONB 字段有 `schema_version` 与检索策略