前言¶
用 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