用 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

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

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

小夜