如何让AI设计数据库

本文属于《从 0 到 1 AI 产品出海知识库》中的「AI编程工作流实战」章节。

数据库设计错了,后面改起来很痛苦。字段类型选错、关系没建好、索引漏掉——这些问题在开发初期可能不显眼,一旦数据量上来、业务复杂度增加,就会变成性能瓶颈甚至数据一致性问题。传统做法是 DBA 或后端工程师花几天时间画 ER 图、写 DDL、反复评审,但对于独立开发者和小型团队来说,这套流程太重了。

AI 在数据库设计领域的介入,恰好能解决这个问题。你可以用自然语言描述业务需求,让 AI 帮你完成实体识别、关系建模、Schema 生成、迁移脚本编写,甚至索引优化。但 AI 生成不等于「直接可用」——你需要一套可靠的流程来约束和验证 AI 的输出。

本文会系统介绍:如何用 AI 完成从数据需求到可用 Schema 的完整流程,涵盖 Prisma、Drizzle、Supabase 等工具的实践,以及你在 AI 生成之后必须自己把关的关键决策点。

AI 设计数据库的完整流程

让 AI 设计数据库,不是打开聊天窗口说一句「帮我建一个用户表」就完事。一个可靠的过程分为五个阶段:

流程图画布 · 115%
Mermaid 流程图加载中...

第一阶段:业务需求梳理

这一步 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 的质量,取决于你输入的需求描述质量。以下是一个经过实践检验的数据需求描述模板:

要素说明示例
实体名称英文单数名词,PascalCaseUserOrderCourse
属性列表字段名、类型、约束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 生成场景下的表现各不相同。

维度PrismaDrizzleSupabase (SQL)
Schema 定义方式schema.prisma DSLTypeScript 代码SQL DDL
AI 生成友好度高,DSL 语法固定中,TS 代码变体多高,SQL 标准语法
类型安全自动生成类型编译时类型推导需配合代码生成
迁移工具内置 prisma migratedrizzle-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 reset

prisma migrate dev 会根据 schema.prisma 的变化自动生成 SQL 迁移文件,存放在 prisma/migrations/ 目录下。每次生成的迁移文件你应该 commit 到 Git,方便回溯和团队协作。

Drizzle 的迁移流程

# 生成迁移
npx drizzle-kit generate
 
# 应用迁移
npx drizzle-kit migrate
 
# 查看迁移状态
npx drizzle-kit status

Drizzle 的迁移脚本是纯 SQL 文件,结构更透明,但你需要自己管理迁移顺序。

AI 生成迁移的注意事项

AI 生成迁移脚本时,容易犯以下错误:

  1. 直接修改字段类型:PostgreSQL 中从 TEXT 改到 INT 需要 USING 子句,AI 经常漏掉。
  2. 忽略现有数据:添加 NOT NULL 约束时如果表中已有数据,需要指定 DEFAULT 值或先填充数据。
  3. 缺少回滚方案:好的迁移应该有对应的 down 脚本,虽然 Prisma 不自动生成 down migration,但你应该知道出问题时怎么回退。
  4. 大表加索引阻塞写入:生产环境应该用 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 已提交版本控制,迁移文件可回溯

参考资料