前言¶
用 Cursor、Claude Code、Codex 这类 AI 编程工具搭后端时,业务代码往往写得很快,一到数据库建模就容易露馅:表之间没有外键、多对多直接塞数组、TIMESTAMP 和 TIMESTAMPTZ 混用、外键不建索引、删除父记录时子表变成孤儿行。本地数据量小看不出来,上线后才会以慢查询、约束冲突、脏数据的方式暴露。
问题通常不在「会不会写 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()
);
这段示例同时示范了几件事:可选列才允许 NULL(avatar_url),自然键加 UNIQUE(email),以及删除用户时级联删除其项目。
4. 套用最佳实践¶
主键:
- 分布式系统或对外暴露的 ID,用
UUID; - 仅内部使用的 ID,可用
SERIAL/BIGSERIAL(连接更快)。
时间戳:
- 表上始终加
created_at和updated_at; - 使用带时区的
TIMESTAMPTZ,不要用TIMESTAMP。
命名:
- 表名:复数 snake_case(
users、project_members); - 列名:单数 snake_case(
user_id、created_at); - 索引:
idx_<table>_<columns>(例如idx_users_email)。
约束:
- 除非确实可选,否则一律
NOT NULL; - 自然键(邮箱、slug、外部 ID)加
UNIQUE; - 外键必须写清
ON DELETE行为(CASCADE、SET NULL、RESTRICT); - 枚举或取值范围用
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_type、entity_id、action、actor_id、payload; - 标签: 中间表(如
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 的规则,这里至少应出现 users、projects、tasks、comments,标签走 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、再按需反范式」写成团队约定,而不是每次口头提醒。
使用限制¶
- 这是指令包,不是迁移工具。 目录里没有
scripts/,不会连库、不会跑prisma migrate。它只约束 Agent「怎么设计」,执行仍要你确认。 - 示例偏 PostgreSQL。
UUID、gen_random_uuid()、TIMESTAMPTZ都是 Postgres 写法。MySQL / SQLite 需要自行替换类型与函数,Skill 没有提供其它方言模板。 - ORM 只给了 Prisma 和 Drizzle。 其它框架不要当成官方支持。
- 覆盖的是关系建模,不是运维调优。 连接池、RLS、慢查询诊断不在这份 Skill 里。如果项目跑在 Supabase / Neon 上,需要另装对应的数据库最佳实践 Skill。
- 「少于 1000 行不建索引」是启发式规则。 生产表增长很快,不能把这句话理解成永远不给小表加外键索引。
- 多态关联被明确降级。 需要「评论既可以挂任务也可以挂文档」时,优先拆开外键,而不是一上来
entity_type+entity_id。 - 输出仍需人工 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