如何让AI设计数据库
本文属于《从 0 到 1 AI 产品出海知识库》中的「AI编程工作流实战」章节。
数据库设计错了,后面改起来很痛苦。字段类型选错、关系没建好、索引漏掉——这些问题在开发初期可能不显眼,一旦数据量上来、业务复杂度增加,就会变成性能瓶颈甚至数据一致性问题。传统做法是 DBA 或后端工程师花几天时间画 ER 图、写 DDL、反复评审,但对于独立开发者和小型团队来说,这套流程太重了。
AI 在数据库设计领域的介入,恰好能解决这个问题。你可以用自然语言描述业务需求,让 AI 帮你完成实体识别、关系建模、Schema 生成、迁移脚本编写,甚至索引优化。但 AI 生成不等于「直接可用」——你需要一套可靠的流程来约束和验证 AI 的输出。
本文会系统介绍:如何用 AI 完成从数据需求到可用 Schema 的完整流程,涵盖 Prisma、Drizzle、Supabase 等工具的实践,以及你在 AI 生成之后必须自己把关的关键决策点。
AI 设计数据库的完整流程
让 AI 设计数据库,不是打开聊天窗口说一句「帮我建一个用户表」就完事。一个可靠的过程分为五个阶段:
第一阶段:业务需求梳理
这一步 AI 做不了,只能你来。你需要回答几个核心问题:
- 系统里有哪些核心业务对象?
- 每个对象有哪些属性?哪些是必填的?
- 对象之间是什么关系?一对一、一对多、还是多对多?
- 有哪些业务约束?比如唯一性、级联删除、默认值?
把这些想清楚,后面的 AI 生成才会准确。否则 AI 只能猜,而猜错的概率很高。
第二阶段:实体与属性识别
把业务需求翻译成数据模型语言。一个实体对应一张表,实体的属性对应表的字段。这个阶段你需要为每个字段确定:
- 字段名(英文,snake_case 或 camelCase 取决于你的 ORM 约定)
- 数据类型(String、Int、Boolean、DateTime、JSON 等)
- 是否可空
- 默认值
- 是否需要索引
你可以直接把业务描述丢给 AI,让它提取实体和属性。但更稳妥的做法是你先列一个草稿,再让 AI 补充和校验。
第三阶段:关系建模
关系是数据库设计中最容易出错的部分。三种基本关系类型:
- 一对一(1:1):一个用户对应一个个人资料
- 一对多(1:N):一个用户有多条订单
- 多对多(M:N):学生和课程之间,需要中间表
AI 在识别显式关系时表现不错,但对隐式关系(比如「用户的地址」是独立实体还是用户表的字段)常常做出武断判断。你需要明确告诉 AI 你的业务意图。
第四阶段:Schema 生成
关系模型确定后,让 AI 生成目标 ORM 的 Schema 代码。这一步是目前 AI 工具链最成熟的环节,Prisma、Drizzle、Supabase 都已有良好的 AI 辅助支持。
第五阶段:迁移与验证
Schema 生成后,通过 Migration 工具同步到数据库,然后验证数据完整性、查询性能和约束是否符合预期。验证不通过就回到前面的阶段调整。
数据需求描述的标准格式
AI 生成 Schema 的质量,取决于你输入的需求描述质量。以下是一个经过实践检验的数据需求描述模板:
| 要素 | 说明 | 示例 |
|---|---|---|
| 实体名称 | 英文单数名词,PascalCase | User、Order、Course |
| 属性列表 | 字段名、类型、约束 | email: String, unique, required |
| 关系描述 | 明确关系类型和目标实体 | User 1:N Order(一个用户有多条订单) |
| 业务约束 | 唯一性、级联规则、默认值 | Order 删除时不影响 User |
| 索引需求 | 高频查询字段 | Order.userId 需要索引 |
| 软删除 | 是否需要逻辑删除 | User 使用 isDeleted 软删除 |
一个完整的描述示例:
实体:User
- id: Int, 自增主键
- email: String, 唯一, 必填
- name: String, 必填
- role: Enum(ADMIN, USER), 默认 USER
- createdAt: DateTime, 自动生成
- isDeleted: Boolean, 默认 false
实体:Post
- id: Int, 自增主键
- title: String, 必填
- content: String
- published: Boolean, 默认 false
- authorId: Int, 关联 User
- createdAt: DateTime, 自动生成
关系:
- User 1:N Post(一个用户有多篇文章,删除 User 时级联删除 Post)
索引:
- Post.authorId 需要索引
- Post.title 需要全文搜索索引
把这个格式的描述给 AI,生成的 Schema 准确率远高于随意的一段话。
Schema 生成工具对比
目前主流的数据库 Schema 方案有三个阵营:Prisma、Drizzle 和 Supabase(直接使用 SQL)。它们在 AI 生成场景下的表现各不相同。
| 维度 | Prisma | Drizzle | Supabase (SQL) |
|---|---|---|---|
| Schema 定义方式 | schema.prisma DSL | TypeScript 代码 | SQL DDL |
| AI 生成友好度 | 高,DSL 语法固定 | 中,TS 代码变体多 | 高,SQL 标准语法 |
| 类型安全 | 自动生成类型 | 编译时类型推导 | 需配合代码生成 |
| 迁移工具 | 内置 prisma migrate | drizzle-kit | 内置迁移或 supabase cli |
| 关系表达 | @relation 指令,直观 | 代码声明,灵活 | 外键约束,标准 |
| 学习曲线 | 低 | 中 | 需要 SQL 基础 |
| 适用场景 | 快速原型、中小型项目 | 需要精细控制的项目 | 需要 Supabase 生态时 |
| AI Prompt 示例 | 「生成 Prisma Schema」 | 「用 Drizzle 定义表」 | 「写 CREATE TABLE SQL」 |
Prisma 的 AI 生成实践
Prisma 的 Schema 语法是声明式的 DSL,结构固定、模式清晰,非常适合 AI 生成。你可以直接在 prompt 中指定实体和关系,AI 输出的 schema.prisma 通常可以直接使用:
model User {
id Int @id @default(autoincrement())
email String @unique
name String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
isDeleted Boolean @default(false)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
createdAt DateTime @default(now())
@@index([authorId])
}
enum Role {
ADMIN
USER
}Prisma 官方也提供了 AI 集成支持,包括 MCP 服务器和 GitHub Copilot 插件,让 AI 工具能直接读取你的 Schema 上下文来生成更准确的代码。参考 Prisma AI 文档 了解细节。
Drizzle 的 AI 生成实践
Drizzle 用 TypeScript 代码定义 Schema,好处是和业务代码在同一个语言生态里,坏处是写法变体多,AI 有时会生成不符合你项目风格的代码。使用前最好给 AI 提供一个示例文件作为参考:
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').unique().notNull(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
isDeleted: boolean('is_deleted').default(false).notNull(),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
})Supabase 的 AI 生成实践
Supabase 底层是 PostgreSQL,直接用 SQL 定义 Schema。AI 对 SQL 的理解最深,生成的 DDL 通常最可靠。而且 Supabase 自带 Row Level Security(RLS),可以在 Schema 里直接定义权限策略:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
is_deleted BOOLEAN DEFAULT false NOT NULL
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT false NOT NULL,
author_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
);
CREATE INDEX idx_posts_author_id ON posts(author_id);关系设计的最佳实践
关系设计是数据库的核心,也是最容易出问题的地方。以下是几条经过验证的原则:
| 原则 | 说明 | 反面案例 |
|---|---|---|
| 优先用外键约束 | 数据库层面的约束比应用层校验更可靠 | 只在代码里检查 user_id 是否存在 |
| 明确级联规则 | 删除父记录时子记录怎么办?CASCADE、SET NULL 还是 RESTRICT? | 不设置 onDelete,默认行为因数据库而异 |
| 多对多必须用中间表 | 不要试图用 JSON 数组存储关系 | 在 user 表里存 course_ids: JSON |
| 外键字段加索引 | 关联查询的性能取决于索引 | 觉得数据量小就不加索引 |
| 避免循环引用 | A 引用 B,B 又引用 A,会导致迁移和删除死锁 | category 表自引用时不设好终止条件 |
| 命名一致性 | 同一概念在所有表里用同一个字段名 | 有的表叫 user_id,有的叫 author_id |
外键约束的三种策略
选择哪种级联策略,取决于你的业务语义:
- CASCADE:父记录删除时,子记录一起删除。适用于强依赖关系,比如用户和用户的 session。
- SET NULL:父记录删除时,子记录的外键字段设为 NULL。适用于弱关联,比如文章的作者被删除后文章仍保留。
- RESTRICT / NO ACTION:如果有子记录存在,禁止删除父记录。适用于不能丢失数据的场景,比如订单和订单明细。
在给 AI 的描述中,明确写出每种关系的级联策略,不要让 AI 自己决定。
数据库迁移与版本管理
Schema 定义好之后,需要通过迁移(Migration)同步到数据库。迁移的本质是一组 SQL 变更脚本,记录数据库结构从版本 A 到版本 B 的变化。
迁移的核心原则
- 迁移脚本必须可审查:不要盲信 AI 生成的迁移 SQL,每条 DDL 都要看懂。
- 生产环境禁止破坏性变更:DROP TABLE、DROP COLUMN、修改字段类型这些操作在生产环境极其危险,必须拆分到多步迁移中。
- 保持迁移的幂等性:同一个迁移脚本跑两次,结果应该和跑一次相同。
- 版本化管理:迁移脚本纳入 Git,和代码版本一一对应。
Prisma 的迁移流程
# 生成迁移(开发环境)
npx prisma migrate dev --name add_posts_table
# 部署迁移(生产环境)
npx prisma migrate deploy
# 重置数据库(仅开发环境)
npx prisma migrate resetprisma migrate dev 会根据 schema.prisma 的变化自动生成 SQL 迁移文件,存放在 prisma/migrations/ 目录下。每次生成的迁移文件你应该 commit 到 Git,方便回溯和团队协作。
Drizzle 的迁移流程
# 生成迁移
npx drizzle-kit generate
# 应用迁移
npx drizzle-kit migrate
# 查看迁移状态
npx drizzle-kit statusDrizzle 的迁移脚本是纯 SQL 文件,结构更透明,但你需要自己管理迁移顺序。
AI 生成迁移的注意事项
AI 生成迁移脚本时,容易犯以下错误:
- 直接修改字段类型:PostgreSQL 中从
TEXT改到INT需要USING子句,AI 经常漏掉。 - 忽略现有数据:添加 NOT NULL 约束时如果表中已有数据,需要指定 DEFAULT 值或先填充数据。
- 缺少回滚方案:好的迁移应该有对应的 down 脚本,虽然 Prisma 不自动生成 down migration,但你应该知道出问题时怎么回退。
- 大表加索引阻塞写入:生产环境应该用
CREATE INDEX CONCURRENTLY,AI 通常不会主动考虑这一点。
案例:从需求到 Schema 的完整实践
案例一:SaaS 订阅管理系统
业务需求:用户注册后可以订阅不同套餐,每个套餐有多个价格档位(月付/年付),需要记录订阅历史和支付记录。
实体识别:
User:
- id, email, name, createdAt
Plan:
- id, name, description, features(JSON)
Price:
- id, planId, amount, currency, interval(MONTH/YEAR)
Subscription:
- id, userId, planId, priceId, status, currentPeriodStart, currentPeriodEnd
Payment:
- id, subscriptionId, amount, currency, status, paidAt
关系设计:
- User 1:N Subscription
- Plan 1:N Price
- Plan 1:N Subscription
- Price 1:N Subscription
- Subscription 1:N Payment
关键决策:
- Price 独立为实体而非 Plan 的字段,因为一个 Plan 有多个价格档位
- Subscription 记录当前和历史,status 字段区分 ACTIVE、CANCELED、PAST_DUE
- Payment 关联 Subscription 而非 User,因为支付是订阅维度的
AI 生成 Prompt 示例:
用 Prisma Schema 定义以下数据模型:
- User: id(Int自增), email(String唯一), name(String), createdAt(DateTime)
- Plan: id, name, description, features(JSON)
- Price: id, planId(关联Plan), amount(Decimal), currency(String), interval(Enum: MONTH/YEAR)
- Subscription: id, userId(关联User), planId(关联Plan), priceId(关联Price), status(Enum: ACTIVE/CANCELED/PAST_DUE), currentPeriodStart(DateTime), currentPeriodEnd(DateTime)
- Payment: id, subscriptionId(关联Subscription), amount(Decimal), currency(String), status(Enum: SUCCESS/FAILED/PENDING), paidAt(DateTime)
关系约束:
- User 删除时,Subscription 设为 SET NULL(保留历史记录)
- Plan 删除时,Price 级联删除
- Subscription 删除时,Payment 级联删除
- Subscription.userId, Subscription.planId, Subscription.priceId 加索引
- Payment.subscriptionId 加索引
案例二:AI 漫剧创作平台
业务需求:用户可以创建项目(剧本),每个项目下有多个角色、场景和分镜。角色可以出现在多个场景中,分镜引用角色和场景。
实体识别:
Project:
- id, title, description, userId, createdAt, updatedAt
Character:
- id, name, description, avatarUrl, projectId
Scene:
- id, title, description, order, projectId
Shot:
- id, sceneId, description, imageUrl, order, cameraAngle
CharacterScene(中间表):
- characterId, sceneId
关系设计:
- Project 1:N Character
- Project 1:N Scene
- Scene 1:N Shot
- Character M:N Scene(通过 CharacterScene 中间表)
关键决策:
- Character 和 Scene 是多对多关系,一个角色可以出现在多个场景,一个场景可以有多个角色
- Shot 只属于一个 Scene,不直接关联 Character(角色通过 Scene 间接关联)
- Scene 和 Shot 都用 order 字段控制排序
- Character 的 avatarUrl 可为空,因为初期可能没有头像
常见陷阱与避坑指南
| 陷阱 | 表现 | 解决方案 |
|---|---|---|
| 过度规范化 | 为了「范式」拆出过多表,查询时需要大量 JOIN | 合理反规范化,读多写少的场景可以冗余字段 |
| 忽略软删除 | 物理删除后数据丢失无法恢复 | 核心业务表加 isDeleted 字段 |
| 时间字段不统一 | 有的用 timestamp,有的用 timestamptz | 统一使用 timestamptz(带时区) |
| ID 类型不一致 | 有些表用自增 Int,有些用 UUID | 项目初期统一选择一种,推荐 UUID v7(有序) |
| 枚举用字符串存储 | 数据库里存 "ACTIVE" 字符串,改枚举值困难 | ORM 层面用 enum,数据库层面用 Int 或 String 均可 |
| AI 遗漏审计字段 | 没有 createdAt、updatedAt | 所有表统一加审计字段 |
检查清单
在用 AI 生成数据库 Schema 后,逐项检查以下内容:
- 所有实体都有唯一主键
- 所有外键关系都有对应的索引
- 级联删除规则已明确设定,不是依赖默认行为
- 必填字段设置了 NOT NULL 约束
- 枚举类型的取值覆盖了所有业务场景
- 时间字段统一使用带时区的类型(timestamptz)
- 软删除字段(如 isDeleted)有默认值
- 审计字段(createdAt、updatedAt)存在于所有业务表
- 多对多关系使用了中间表,而非 JSON 数组
- 迁移脚本已经在开发环境测试通过
- 生产环境的迁移不包含破坏性变更(或已拆分为安全步骤)
- Schema 已提交版本控制,迁移文件可回溯
参考资料
- Prisma AI 官方文档 — Prisma 与 AI 工具的集成指南,包括 MCP 服务器和 Copilot 插件。
- Prisma Schema 概览 — Prisma Schema 的完整语法参考。
- Experimenting With Generative AI For Database Design — Mike Sparr 关于用生成式 AI 做数据库设计的实践文章。
- Drizzle ORM 文档 — Drizzle ORM 的官方文档,包含 Schema 定义和迁移指南。
- Supabase 数据库设计指南 — Supabase 的数据库设计与 RLS 安全策略文档。
- AI-Powered Database Schema Design (YouTube) — 关于 AI 辅助 PostgreSQL Schema 设计的视频讲解。
- Structa - Prisma Schema Generator with AI — 用自然语言生成 Prisma Schema 的可视化工具。
- ChartDB — 可视化数据库 Schema 设计工具,支持 ER 图和 DDL 导出。