用 database-design:让 AI Agent 按规范设计表关系、索引、约束与 ORM

前言

用 Cursor、Claude Code、Codex 这类 AI 编程工具搭后端时,业务代码往往写得很快,一到数据库建模就容易露馅:表之间没有外键、多对多直接塞数组、TIMESTAMPTIMESTAMPTZ 混用、外键不建索引、删除父记录时子表变成孤儿行。本地数据量小看不出来,上线后才会以慢查询、约束冲突、脏数据的方式暴露。

问题通常不在「会不会写 CREATE TABLE」,而在缺少一套可重复的设计流程。模型训练数据里什么风格都有,Agent 很容易交出「能跑、但不规范」的 schema。database-design 就是把实体抽取、关系选型、约束、索引和 ORM 映射写成一份可复用的 SKILL.md,让助手在设计库表时按步骤走,而不是凭印象猜。

本文按官方 SKILL.md 与 awesome-cursor-skills 仓库说明核实后,介绍它是什么、覆盖哪些规则、怎么安装、怎么用。

这是什么

database-design 收录在 spencerpauly/awesome-cursor-skills 的「Planning & Architecture」分类里,仓库许可为 CC0-1.0。目录里目前只有一个文件:resources/database-design/SKILL.md

官方 frontmatter 描述是:

Design database schemas — tables, relationships, indexes, constraints, and ORM setup. Covers relational design, normalization, and common patterns.

也就是:根据需求设计关系型数据库 schema,覆盖表、关系、索引、约束,以及 ORM 配置;同时包含范式化与常见建模模式。

它遵循 Agent Skills 通用格式,可在 Cursor、Claude Code、Codex CLI 等支持该标准的工具中使用。Skill 正文第一句就把任务定死了:从需求出发设计数据库 schema。frontmatter 里还有 user-invocable: true,在支持斜杠命令的工具中可以用 /database-design 显式调用;Cursor 官方文档则说明,也可以在 Agent 对话里输入 / 按技能名搜索。

仓库地址:https://github.com/spencerpauly/awesome-cursor-skills/tree/main/resources/database-design

核心功能:六步工作流 + 约束清单

官方 SKILL.md 把设计过程拆成六步,后面再附最佳实践、常见模式和几条原则。下面按原文结构说明。

1. 识别实体

从需求里抽出核心实体(名词),例如 Users、Teams、Projects、Tasks、Comments。每个实体对应一张表。这一步看起来简单,但能避免 Agent 一上来就把「用户和资料」揉进同一张宽表,或者把评论做成 JSON 字段。

2. 定义关系

Skill 用一张表把四种关系落到具体实现,而不是只写「关联一下」:

关系 实现方式
一对一 外键加唯一约束,或直接嵌进同一张表
一对多 外键放在「多」的那一侧
多对多 中间表(junction / join table)
自引用 外键指向同一张表(例如 parent_id

多对多必须走中间表,这一点写得很明确。AI 常见的「在 users 里塞 project_ids UUID[]」并不在这份清单里。

3. 设计表结构

官方给了一段 PostgreSQL 风格的示例,主键用 UUID,时间用 TIMESTAMPTZ,外键带 ON DELETE

CREATE TABLE users (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email       TEXT NOT NULL UNIQUE,
  name        TEXT NOT NULL,
  avatar_url  TEXT,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE projects (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name        TEXT NOT NULL,
  owner_id    UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

这段示例同时示范了几件事:可选列才允许 NULLavatar_url),自然键加 UNIQUEemail),以及删除用户时级联删除其项目。

4. 套用最佳实践

主键:

  • 分布式系统或对外暴露的 ID,用 UUID
  • 仅内部使用的 ID,可用 SERIAL / BIGSERIAL(连接更快)。

时间戳:

  • 表上始终加 created_atupdated_at
  • 使用带时区的 TIMESTAMPTZ,不要用 TIMESTAMP

命名:

  • 表名:复数 snake_case(usersproject_members);
  • 列名:单数 snake_case(user_idcreated_at);
  • 索引:idx_<table>_<columns>(例如 idx_users_email)。

约束:

  • 除非确实可选,否则一律 NOT NULL
  • 自然键(邮箱、slug、外部 ID)加 UNIQUE
  • 外键必须写清 ON DELETE 行为(CASCADESET NULLRESTRICT);
  • 枚举或取值范围用 CHECK

5. 加索引

官方示例覆盖三类索引:普通过滤列、唯一查找、复合查询模式。

-- 经常按它过滤或排序的列
CREATE INDEX idx_projects_owner_id ON projects(owner_id);

-- 唯一查找
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- 常见查询组合
CREATE INDEX idx_tasks_project_status ON tasks(project_id, status);

该建索引的情况: 外键(几乎总是)、WHERE 列、ORDER BY 列、JOIN 条件。

不该建索引的情况: 很小的表(Skill 原文写的是少于 1000 行)、低基数列(布尔、只有三四个取值的 status)、几乎不会被查询的列。

这里有一个容易忽略的点:示例里 users.email 已经在建表时声明了 UNIQUE,后面又给了 CREATE UNIQUE INDEX idx_users_email。PostgreSQL 上 UNIQUE 约束本身会建唯一索引,Agent 若原样两处都写,可能重复。使用时按目标数据库的约束语义核对即可,不必把示例每一行都当成必须执行的迁移。

6. ORM 配置

Skill 给出了 Prisma 和 Drizzle 两份对照,重点是把数据库里的 snake_case 映射到应用层的 camelCase。

Prisma:

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String
  projects  Project[]
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
  @@map("users")
}

Drizzle:

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
});

原文没有展开 SQLAlchemy、TypeORM、Ent 等其它 ORM。项目如果用的不是 Prisma / Drizzle,可以把 SQL 层规则留下,ORM 层按自己的栈改写,不要指望这份 Skill 自动生成其它框架的模型文件。

常见模式与原则

Skill 另外列了五类高频模式:

  • 软删除:deleted_at TIMESTAMPTZ,而不是物理删行;
  • 审计日志: 单独的 audit_events 表,字段包括 entity_typeentity_idactionactor_idpayload
  • 标签: 中间表(如 task_tags),用 task_id + tag_id
  • 树 / 层级: parent_id 自引用,或物化路径(/1/4/7/);
  • 多态关联:entity_type + entity_id;原文明确写了 能不用就不用,优先分开的外键

最后几条原则同样具体:

  • 先按第三范式(3NF)设计,只有测到性能问题后再反范式;
  • 不要存派生数据,除非有缓存和失效策略;
  • 状态字段用数据库枚举或 CHECK,不要用自由文本;
  • 设计时始终考虑「删除父记录之后会发生什么」。

安装与启用

该 Skill 只有一份 SKILL.md,安装方式是把文件放到 Agent 会扫描的技能目录里。awesome-cursor-skills 仓库说明:把现成的 SKILL.md 复制进 .cursor/skills/ 即可。

Cursor

根据 Cursor 官方文档,Skill 会从以下路径自动发现:

路径 作用域
.cursor/skills/ 项目级
.agents/skills/ 项目级
~/.cursor/skills/ 用户级(全局)
~/.agents/skills/ 用户级(全局)

为兼容 Claude 与 Codex,Cursor 还会加载 .claude/skills/.codex/skills/ 以及对应的用户目录。项目级推荐安装:

cd your-project

mkdir -p .cursor/skills/database-design
curl -o .cursor/skills/database-design/SKILL.md \
  https://raw.githubusercontent.com/spencerpauly/awesome-cursor-skills/main/resources/database-design/SKILL.md

也可以直接打开目录手动复制:

https://github.com/spencerpauly/awesome-cursor-skills/tree/main/resources/database-design

装好后,打开 Cursor 侧边栏 Customize → Skills,应能在 Agent Decides 区域看到 database-design。需要手动触发时,在 Agent 对话输入 / 搜索技能名。

Claude Code

Claude Code 的技能目录以官方文档为准:

范围 路径
个人(所有项目) ~/.claude/skills/<skill-name>/SKILL.md
当前项目 .claude/skills/<skill-name>/SKILL.md
mkdir -p .claude/skills/database-design
curl -o .claude/skills/database-design/SKILL.md \
  https://raw.githubusercontent.com/spencerpauly/awesome-cursor-skills/main/resources/database-design/SKILL.md

之后可用 /database-design 显式调用,或在「设计表结构 / 写 Prisma schema」这类请求下让 Claude 按描述自动匹配。

Codex CLI

Codex 会扫描仓库内的 .agents/skills(从当前工作目录一直到仓库根)以及用户目录 $HOME/.agents/skills。项目级可以这样放:

mkdir -p .agents/skills/database-design
curl -o .agents/skills/database-design/SKILL.md \
  https://raw.githubusercontent.com/spencerpauly/awesome-cursor-skills/main/resources/database-design/SKILL.md

Codex 文档说明可用 $ 提及技能,或运行 /skills 查看已发现列表。若新文件没有出现,重启一次 Codex。

典型用法示例

Skill 原文没有单独列出提示词模板,但任务定义很清楚:根据需求设计 schema。下面几类请求与工作流直接对应;Agent 应按六步产出 SQL,而不是只丢一张「看起来像表」的草稿。

场景一:从需求出第一版库表

/database-design

做一个团队项目管理工具:用户可以创建项目,项目下有任务,任务可以评论、打标签。
请先识别实体和关系,再给出 PostgreSQL 建表语句,外键要写 ON DELETE。

按 Skill 的规则,这里至少应出现 usersprojectstaskscomments,标签走 task_tags 中间表,而不是在任务表里塞文本数组。

场景二:补索引和约束

下面是现有的 projects / tasks 表,请按 database-design 的索引规则检查:
哪些外键和 WHERE 列该建索引,哪些低基数列不要建。

Skill 要求外键几乎都建索引,同时避免给布尔列、取值很少的 status 滥建。复合索引示例是 idx_tasks_project_status ON tasks(project_id, status),对应「按项目过滤再按状态筛」这种常见查询。

场景三:SQL 落到 ORM

把刚才的 users / projects schema 写成 Prisma 和 Drizzle。
列名在数据库里保持 snake_case,应用层用 camelCase。

对照官方片段,Prisma 侧应出现 @map("created_at")@@map("users"),Drizzle 侧时间戳应带 { withTimezone: true },与「只用 TIMESTAMPTZ」这条规则对齐。

场景四:删除策略与软删除

用户删除账号时,项目要一起删,但任务评论希望保留审计痕迹。
请按 Skill 里的 ON DELETE 和软删除模式给出方案。

原文要求设计时始终考虑父记录删除;软删除用 deleted_at,审计则单独建 audit_events。Agent 应把 CASCADE / SET NULL / RESTRICT 写进外键,而不是只在应用代码里 DELETE FROM ...

适用场景与注意事项

适合谁用

  • 用 AI 助手从零搭 Web / SaaS 后端,需要第一版关系模型的人;
  • 已有草稿 schema,想让 Agent 按清单补外键、索引、命名和时间戳的人;
  • 技术栈落在 PostgreSQL + Prisma 或 Drizzle 的项目(与官方示例最贴近);
  • 希望把「先 3NF、再按需反范式」写成团队约定,而不是每次口头提醒。

使用限制

  1. 这是指令包,不是迁移工具。 目录里没有 scripts/,不会连库、不会跑 prisma migrate。它只约束 Agent「怎么设计」,执行仍要你确认。
  2. 示例偏 PostgreSQL。 UUIDgen_random_uuid()TIMESTAMPTZ 都是 Postgres 写法。MySQL / SQLite 需要自行替换类型与函数,Skill 没有提供其它方言模板。
  3. ORM 只给了 Prisma 和 Drizzle。 其它框架不要当成官方支持。
  4. 覆盖的是关系建模,不是运维调优。 连接池、RLS、慢查询诊断不在这份 Skill 里。如果项目跑在 Supabase / Neon 上,需要另装对应的数据库最佳实践 Skill。
  5. 「少于 1000 行不建索引」是启发式规则。 生产表增长很快,不能把这句话理解成永远不给小表加外键索引。
  6. 多态关联被明确降级。 需要「评论既可以挂任务也可以挂文档」时,优先拆开外键,而不是一上来 entity_type + entity_id
  7. 输出仍需人工 Review。 例如 ON DELETE CASCADE 会物理删子行,和软删除、审计日志可能冲突;Agent 按条文生成后,删除策略仍要对照产品语义改。

小结

AI 写业务代码可以「先能跑再说」,数据库 schema 通常没有这个余地:缺一次外键、错一种删除行为、漏一组索引,后面补迁移的成本会高很多。database-design 把实体识别、关系落地、约束、索引和 ORM 映射收成一份短的 SKILL.md,安装成本就是复制一个文件。

它解决的不是「Agent 会不会写 SQL」,而是「写出来的库表有没有统一规矩」。对已经把 Cursor / Claude Code / Codex 用于日常开发的人来说,遇到「帮我设计表结构」时先装上这一条,比事后对着生产慢查询再补课要省事。

官方 Skill 地址:

https://github.com/spencerpauly/awesome-cursor-skills/tree/main/resources/database-design

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜