Prisma 是最流行的 TypeScript ORM,它的 schema 优先方法使数据库建模变得直观且类型安全。无论你是在构建新项目还是从原始 SQL 迁移,理解 Prisma schema 语法、关系和查询都至关重要。本综合指南涵盖了从基本模型定义到软删除和多租户等高级模式的所有内容。
使用我们的免费工具即时将 SQL 转换为 Prisma Schema →
什么是 Prisma?
Prisma 是面向 Node.js 和 TypeScript 的下一代 ORM。与传统 ORM 将类映射到表不同,Prisma 使用 schema 优先方法,你在 .prisma 文件中定义数据模型,Prisma 从中生成完全类型安全的客户端。
- Prisma Schema — 声明式数据建模语言(
schema.prisma) - Prisma Client — 自动生成的、类型安全的 Node.js 和 TypeScript 查询构建器
- Prisma Migrate — 声明式迁移系统,保持数据库 schema 同步
- Prisma Studio — 查看和编辑数据库数据的图形界面
Prisma 消除了应用程序代码和数据库之间的阻抗不匹配。你可以获得完整的自动补全、编译时错误检查,不再需要为基本的 CRUD 操作编写原始 SQL。
// Install Prisma
npm install prisma --save-dev
npm install @prisma/client
// Initialize a new Prisma project
npx prisma init
// This creates:
// prisma/schema.prisma — your data model
// .env — database connection URLSchema 基础
每个 Prisma 项目都从 schema.prisma 文件开始。它包含三个主要块:datasource(数据源)、generator(生成器)和 model(模型)定义。
// prisma/schema.prisma
// 1. Datasource — where your data lives
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// 2. Generator — what client to generate
generator client {
provider = "prisma-client-js"
}
// 3. Model — your database tables
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}
enum Role {
USER
ADMIN
MODERATOR
}datasource 块配置数据库连接。generator 块告诉 Prisma 生成什么客户端。model 定义数据库表。
字段类型
Prisma 支持丰富的标量类型,映射到原生数据库列类型。理解这些类型对于正确设计 schema 至关重要。
| Prisma 类型 | TypeScript 类型 | PostgreSQL | MySQL |
|---|---|---|---|
String | string | text | varchar(191) |
Int | number | integer | int |
Float | number | double precision | double |
Boolean | boolean | boolean | tinyint(1) |
DateTime | Date | timestamp(3) | datetime(3) |
Json | JsonValue | jsonb | json |
Bytes | Buffer | bytea | longblob |
BigInt | bigint | bigint | bigint |
Decimal | Decimal | decimal(65,30) | decimal(65,30) |
Prisma 还支持 枚举(enum),特别适用于状态字段、角色和其他固定值列:
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
model Order {
id Int @id @default(autoincrement())
status OrderStatus @default(PENDING)
total Decimal
items Json // flexible JSON column
metadata Bytes? // binary data
createdAt DateTime @default(now())
}
// Using cuid() or uuid() for string IDs
model Product {
id String @id @default(cuid())
// or: id String @id @default(uuid())
name String
price Float
}字段修饰符与属性
字段修饰符和属性控制字段在数据库层面的行为。它们是良好 schema 设计的基础。
| 属性 | 用途 | 示例 |
|---|---|---|
? | 使字段可选(可为空) | bio String? |
[] | 使字段成为列表(数组) | tags String[] |
@id | 标记主键 | id Int @id @default(autoincrement()) |
@unique | 添加唯一约束 | email String @unique |
@default() | 设置默认值 | role Role @default(USER) |
@map() | 映射字段到不同的列名 | createdAt DateTime @map("created_at") |
@@map() | 映射模型到不同的表名 | @@map("blog_posts") |
@updatedAt | 记录变更时自动更新 | updatedAt DateTime @updatedAt |
@relation() | 定义与另一模型的关系 | author User @relation(fields: [authorId], references: [id]) |
model User {
id String @id @default(cuid())
email String @unique
name String? // optional field
bio String? @db.Text // native type mapping
tags String[] // array field (PostgreSQL)
role Role @default(USER)
score Float @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users") // table name in DB
@@index([email])
}关系
关系是任何关系型数据库的核心,Prisma 使它们变得声明式且类型安全。有三种基本关系类型。
一对一
一个模型中的每条记录对应另一个模型中的恰好一条记录。外键端必须在关系标量字段上包含 @unique。
model User {
id Int @id @default(autoincrement())
email String @unique
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String
avatar String?
user User @relation(fields: [userId], references: [id])
userId Int @unique // @unique makes it one-to-one
}// Query with one-to-one relation
const userWithProfile = await prisma.user.findUnique({
where: { id: 1 },
include: { profile: true },
});
// userWithProfile.profile?.bio一对多
最常见的关系类型。一条记录可以与另一个模型中的多条记录关联。"多"的一方持有外键。
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[] // one user has many posts
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int // foreign key
}
model Comment {
id Int @id @default(autoincrement())
text String
post Post @relation(fields: [postId], references: [id])
postId Int
author User @relation(fields: [authorId], references: [id])
authorId Int
}多对多
Prisma 支持隐式和显式多对多关系。隐式关系让 Prisma 管理连接表;显式关系让你完全控制。
隐式多对多(Prisma 管理连接表):
model Post {
id Int @id @default(autoincrement())
title String
categories Category[] // implicit many-to-many
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts Post[] // implicit many-to-many
}
// Prisma auto-creates a _CategoryToPost join table显式多对多(你定义连接表):
model Post {
id Int @id @default(autoincrement())
title String
tags PostTag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts PostTag[]
}
// Explicit join table — you control extra fields
model PostTag {
post Post @relation(fields: [postId], references: [id])
postId Int
tag Tag @relation(fields: [tagId], references: [id])
tagId Int
assignedAt DateTime @default(now())
assignedBy String?
@@id([postId, tagId]) // composite primary key
}自关系
自关系发生在模型引用自身时。它们在树结构、社交图和组织层级中很常见。
树结构(父子关系)
一个分类可以有多个子分类,每个子分类属于一个父分类:
model Category {
id Int @id @default(autoincrement())
name String
parent Category? @relation("CategoryTree", fields: [parentId], references: [id])
parentId Int?
children Category[] @relation("CategoryTree")
}
// Query: Get category with all children
const category = await prisma.category.findUnique({
where: { id: 1 },
include: {
children: {
include: { children: true }, // nested children
},
},
});关注者/关注中
多对多自关系,用户可以关注其他用户:
model User {
id Int @id @default(autoincrement())
name String
followers User[] @relation("UserFollows")
following User[] @relation("UserFollows")
}
// Follow a user
await prisma.user.update({
where: { id: 1 },
data: {
following: { connect: { id: 2 } },
},
});
// Get user with followers and following
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
followers: true,
following: true,
},
});索引
索引可以显著提升查询性能。Prisma 通过模型级属性提供多种索引类型。
| 属性 | 用途 | 示例 |
|---|---|---|
@@index | 创建标准索引 | @@index([email]) |
@@unique | 创建复合唯一约束 | @@unique([tenantId, email]) |
@@id | 创建复合主键 | @@id([postId, tagId]) |
@@fulltext | 创建全文搜索索引(MySQL) | @@fulltext([title, content]) |
model User {
id Int @id @default(autoincrement())
email String @unique
tenantId String
name String
@@unique([tenantId, email]) // composite unique
@@index([tenantId]) // index for tenant queries
@@index([name]) // index for name searches
}
model Post {
id Int @id @default(autoincrement())
title String
content String
authorId Int
published Boolean @default(false)
createdAt DateTime @default(now())
@@index([authorId]) // index for author lookups
@@index([published, createdAt]) // composite for published feed
@@fulltext([title, content]) // full-text search (MySQL)
}始终为出现在 WHERE 子句、JOIN 条件和 ORDER BY 中的列添加索引。监控慢查询并相应添加索引。
迁移
Prisma Migrate 从 schema 变更生成 SQL 迁移文件。这为你提供了每次数据库变更的版本化历史。
迁移工作流
- 编辑
schema.prisma文件 - 运行
npx prisma migrate dev --name 描述性名称 - Prisma 在
prisma/migrations/中生成 SQL 迁移文件 - Prisma 将迁移应用到开发数据库
- Prisma 重新生成 Prisma Client
关键命令
# Create and apply a migration (development)
npx prisma migrate dev --name add_user_profile
# Apply pending migrations (production)
npx prisma migrate deploy
# Reset database and re-apply all migrations
npx prisma migrate reset
# Push schema without creating migration files (prototyping)
npx prisma db push
# Pull existing database schema into Prisma
npx prisma db pull
# Generate Prisma Client
npx prisma generate
# Open Prisma Studio (GUI)
npx prisma studio
# Seed the database
npx prisma db seedprisma db push 非常适合原型开发——它同步 schema 而不创建迁移文件。在需要迁移历史的生产工作流中使用 prisma migrate dev。
// Example migration file: prisma/migrations/20240101_add_user_profile/migration.sql
-- CreateTable
CREATE TABLE "Profile" (
"id" SERIAL NOT NULL,
"bio" TEXT NOT NULL,
"avatar" TEXT,
"userId" INTEGER NOT NULL,
CONSTRAINT "Profile_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId");
-- AddForeignKey
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;Prisma Client 查询
Prisma Client 为所有数据库操作提供直观、类型安全的 API。每个查询都在编译时验证,因此字段名拼写错误或类型不正确会在代码运行前被捕获。
读取数据
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Find one by unique field
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
});
// Find one or throw
const user = await prisma.user.findUniqueOrThrow({
where: { id: 1 },
});
// Find first matching
const admin = await prisma.user.findFirst({
where: { role: 'ADMIN' },
});
// Find many with pagination
const users = await prisma.user.findMany({
where: { role: 'USER' },
orderBy: { createdAt: 'desc' },
skip: 0,
take: 20,
select: { // select specific fields
id: true,
email: true,
name: true,
},
});
// Count records
const userCount = await prisma.user.count({
where: { role: 'USER' },
});
// Aggregate
const stats = await prisma.order.aggregate({
_avg: { total: true },
_sum: { total: true },
_count: true,
});创建数据
// Create a single record
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
role: 'USER',
},
});
// Create with nested relation
const userWithPosts = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
posts: {
create: [
{ title: 'First Post', content: 'Hello World' },
{ title: 'Second Post', content: 'Prisma is great' },
],
},
},
include: { posts: true },
});
// Create many records
const result = await prisma.user.createMany({
data: [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
{ email: 'user3@example.com', name: 'User 3' },
],
skipDuplicates: true, // skip records with unique conflicts
});更新数据
// Update a single record
const user = await prisma.user.update({
where: { email: 'alice@example.com' },
data: { name: 'Alice Smith' },
});
// Update many
const result = await prisma.user.updateMany({
where: { role: 'USER' },
data: { role: 'MEMBER' },
});
// Upsert (create or update)
const user = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: { name: 'Alice Updated' },
create: {
email: 'alice@example.com',
name: 'Alice New',
},
});
// Delete
await prisma.user.delete({
where: { id: 1 },
});
// Delete many
await prisma.post.deleteMany({
where: { published: false },
});过滤与关系
Prisma 提供强大的过滤 API,支持逻辑运算符、关系查询和聚合:
// Complex filtering
const posts = await prisma.post.findMany({
where: {
AND: [
{ published: true },
{
OR: [
{ title: { contains: 'prisma', mode: 'insensitive' } },
{ content: { contains: 'typescript', mode: 'insensitive' } },
],
},
],
author: {
role: 'ADMIN', // filter by relation
},
createdAt: {
gte: new Date('2024-01-01'), // greater than or equal
},
},
include: {
author: {
select: { name: true, email: true },
},
_count: {
select: { comments: true }, // count related comments
},
},
orderBy: [
{ createdAt: 'desc' },
],
take: 10,
});
// Transaction
const [newUser, updatedPost] = await prisma.$transaction([
prisma.user.create({ data: { email: 'new@example.com', name: 'New' } }),
prisma.post.update({ where: { id: 1 }, data: { published: true } }),
]);高级模式
中间件
Prisma 中间件让你在查询执行前后拦截查询。这非常适合日志记录、软删除和访问控制:
// Logging middleware
prisma.$use(async (params, next) => {
const before = Date.now();
const result = await next(params);
const after = Date.now();
console.log(
`${params.model}.${params.action} took ${after - before}ms`
);
return result;
});软删除
不要永久删除记录,而是用时间戳标记为已删除。使用中间件自动过滤它们:
// Schema
model Post {
id Int @id @default(autoincrement())
title String
deletedAt DateTime? // null = not deleted
// ...
}
// Soft delete middleware
prisma.$use(async (params, next) => {
// Intercept delete -> update with deletedAt
if (params.model === 'Post' && params.action === 'delete') {
params.action = 'update';
params.args['data'] = { deletedAt: new Date() };
}
// Intercept deleteMany -> updateMany
if (params.model === 'Post' && params.action === 'deleteMany') {
params.action = 'updateMany';
if (params.args.data) {
params.args.data['deletedAt'] = new Date();
} else {
params.args['data'] = { deletedAt: new Date() };
}
}
// Filter out soft-deleted records on find
if (params.model === 'Post' && params.action === 'findMany') {
if (!params.args.where) params.args.where = {};
params.args.where['deletedAt'] = null;
}
return next(params);
});多租户
在每个模型中添加 tenantId 字段,并使用 Prisma 中间件自动将查询范围限定到当前租户:
// Schema
model User {
id Int @id @default(autoincrement())
email String
tenantId String
@@unique([tenantId, email])
@@index([tenantId])
}
// Multi-tenancy middleware
function createTenantMiddleware(tenantId: string) {
return prisma.$use(async (params, next) => {
// Automatically inject tenantId on create
if (params.action === 'create') {
params.args.data.tenantId = tenantId;
}
// Automatically scope queries to tenant
if (['findMany', 'findFirst', 'updateMany', 'deleteMany'].includes(params.action)) {
if (!params.args.where) params.args.where = {};
params.args.where.tenantId = tenantId;
}
return next(params);
});
}原始 SQL
当你需要完全控制或 Prisma Client 无法表达的复杂查询时,使用原始 SQL:
// Tagged template for safe parameterized queries
const email = 'alice@example.com';
const user = await prisma.$queryRaw`
SELECT * FROM "User" WHERE email = ${email}
`;
// Unparameterized raw query (use with caution)
const result = await prisma.$queryRawUnsafe(
'SELECT * FROM "User" WHERE id = $1',
1
);
// Execute raw SQL (INSERT, UPDATE, DELETE)
const affected = await prisma.$executeRaw`
UPDATE "User" SET name = ${'Alice Smith'}
WHERE email = ${email}
`;
// Raw SQL with type safety using Prisma.sql
import { Prisma } from '@prisma/client';
const orderBy = Prisma.sql`ORDER BY "createdAt" DESC`;
const users = await prisma.$queryRaw`
SELECT id, email, name FROM "User"
${orderBy}
LIMIT 10
`;Prisma vs TypeORM vs Drizzle
选择合适的 ORM 取决于你的项目需求。以下是三个最流行的 TypeScript ORM 的比较:
| 特性 | Prisma | TypeORM | Drizzle |
|---|---|---|---|
| 方法 | Schema-first | Code-first / Entity | Code-first / SQL-like |
| 类型安全 | 完全自动生成 | 部分(装饰器) | 完全(推断) |
| 迁移 | Prisma Migrate | TypeORM CLI | Drizzle Kit |
| 查询风格 | 对象 API | QueryBuilder / Repository | 类 SQL 构建器 |
| 原始 SQL | $queryRaw / $executeRaw | query() | sql`` |
| 学习曲线 | 低 | 中等 | 中等 |
| 包大小 | 较大(引擎二进制) | 中等 | 小 |
| Edge 兼容 | 通过 Accelerate/Driver Adapters | 不支持 | 原生支持 |
| 数据库支持 | PG, MySQL, SQLite, MSSQL, MongoDB | PG, MySQL, SQLite, MSSQL, Oracle | PG, MySQL, SQLite |
| 最适合 | 快速开发、类型安全 | 企业级、NestJS | 性能、Edge、SQL 控制 |
常见问题
Prisma 支持哪些数据库?
Prisma 支持 PostgreSQL、MySQL、MariaDB、SQLite、SQL Server、MongoDB 和 CockroachDB。每个数据库在 datasource 块中有自己的 Prisma provider。
可以在现有数据库上使用 Prisma 吗?
可以。运行 npx prisma db pull 来内省现有数据库并从中生成 Prisma schema。这称为"内省",适用于所有支持的数据库。
Prisma 如何在生产环境中处理迁移?
在生产环境中使用 npx prisma migrate deploy。它按顺序应用所有待处理的迁移,而不生成新的迁移。永远不要在生产环境中使用 prisma migrate dev 或 prisma db push。
Prisma 比原始 SQL 慢吗?
对于大多数查询,Prisma 增加的开销很小。生成的 SQL 经过高度优化。对于复杂的分析查询,你可以使用 prisma.$queryRaw 编写原始 SQL,同时仍然获得类型安全的结果。
如何使用 Prisma 进行数据库种子填充?
创建 prisma/seed.ts 文件,在 package.json 中添加 seed 脚本,然后运行 npx prisma db seed。Prisma 将在迁移后执行种子文件。你可以使用 createMany 进行批量插入。