从零搭建 SQL 数据库到 ORM 选型:Prisma 与 Drizzle 深度对比
在 TypeScript 全栈开发中,数据库层是绕不开的一环。很多团队在选型时会陷入一个误区------纠结"哪个 ORM 更好"。但真正需要回答的问题是:你的团队愿意接受哪种类型的构建期成本? Prisma 把 schema 压缩成自定义 DSL 和一个代码生成步骤;Drizzle 把一切保留在 TypeScript 模块中,通过结构推断类型。
这篇文章分为两大部分:第一部分从零搭建一个 SQL 数据库 ,覆盖 Docker 环境、基础 SQL、数据表设计;第二部分深入对比 Prisma 和 Drizzle 这两个主流 ORM,从架构、类型系统、迁移工作流、性能到边缘运行时支持,帮你做出有据可依的选型决策。
第一部分:SQL 数据库搭建与数据表管理
1.1 用 Docker 搭建本地 PostgreSQL
本地直接安装数据库容易污染系统环境,推荐用 Docker Compose 管理。新建 docker-compose.yml:
yaml
version: '3.8'
services:
postgres:
image: postgres:16
container_name: dev-postgres
restart: always
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev123
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
启动并连接:
bash
docker compose up -d
psql -h localhost -U dev -d myapp
pgdata 卷保证了数据持久化,删除容器不会丢失数据。需要重置时,直接删除卷即可。
1.2 基础 SQL:从建表到查询
在引入 ORM 之前,先用原生 SQL 建立对数据库的直观理解。以下是一个典型的博客系统表结构:
sql
-- 用户表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
-- 文章表
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
author_id INTEGER REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW()
);
-- 为常用查询字段建立索引
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_users_email ON users(email);
插入和查询:
sql
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice');
SELECT p.title, u.name AS author
FROM posts p
JOIN users u ON u.id = p.author_id
WHERE p.published = true
ORDER BY p.created_at DESC;
1.3 数据表管理的最佳实践
- 所有结构变更都通过迁移文件 ,不要手动执行
ALTER TABLE。 - 迁移文件提交到版本控制,方便回滚和追溯变更历史。
- 不要修改已执行的迁移,新增迁移来修正。
- 为外键和频繁查询字段建索引。
- 生产环境迁移前先备份。
第二部分:Prisma 与 Drizzle 深度对比
2.1 核心架构差异
两者的根本分歧在于类型从哪里来。
| 维度 | Prisma | Drizzle |
|---|---|---|
| 查询引擎 | Rust 二进制(Prisma 7 起改为纯 TS) | 纯 TypeScript,进程内运行 |
| Schema 格式 | .prisma DSL 文件 |
TypeScript 模块 |
| 类型生成 | 构建期 prisma generate 预计算 |
编译期 TypeScript 结构推断 |
| 运行时依赖 | @prisma/client + 引擎 |
零运行时依赖 |
| SQL 访问 | 抽象 API + $queryRaw |
直接 SQL-like API + sql 模板 |
Prisma 在 prisma generate 阶段预计算类型并写入 .d.ts 文件,编辑器直接加载这些类型;Drizzle 则让 TypeScript 编译器在每次写查询时从 schema 实时推断类型。
这个差异带来一个关键后果:Prisma 的类型检查性能稳定且可预测,因为大部分计算提前完成了;Drizzle 的类型检查开销随查询复杂度和项目规模增长而增加。在基准测试中,Prisma 的 schema 类型生成只需要 428 次类型实例化,而 Drizzle 需要 5,017 次,差距约 91%。
2.2 Schema 定义方式
Prisma 使用专用的 Schema 语言:
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
DSL 写法简洁,但脱离了 TypeScript 的 IDE 感知------重构工具、lint 规则无法直接作用于它。
Drizzle 用 TypeScript 定义 schema:
ts
import { pgTable, serial, varchar, text, integer, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial().primaryKey(),
email: varchar({ length: 255 }).unique().notNull(),
name: varchar({ length: 100 }),
createdAt: timestamp().defaultNow(),
});
export const posts = pgTable('posts', {
id: serial().primaryKey(),
title: varchar({ length: 255 }).notNull(),
authorId: integer('author_id').references(() => users.id),
});
因为 schema 就是 TypeScript,重构、lint、自动补全天然可用。代价是模型变复杂后,schema 文件会变得冗长。
2.3 查询 API 哲学
Prisma 的查询映射到应用层概念,把 SQL 抽象掉了:
ts
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
include: { posts: { where: { published: true } } },
});
Drizzle 的查询贴近 SQL,像链式构建器一样组合:
ts
const result = await db
.select()
.from(users)
.leftJoin(posts, eq(posts.authorId, users.id))
.where(eq(users.email, 'alice@example.com'));
前者上手更快,后者对 SQL 熟悉的人更自然。Vercel 的对比文档指出,这个差异直接影响代码审查的方式、新人上手速度,以及出问题时调试的难度。
2.4 迁移工作流
Prisma Migrate 提供了一套完整的声明式迁移流程:
bash
npx prisma migrate dev --name add_user_role # 开发环境
npx prisma migrate deploy # 生产环境
Prisma Migrate 内置了数据丢失检测、advisory locking 和确认流程 ,开发迁移时需要影子数据库(shadow database)。生产环境务必使用 migrate deploy,绝不能用 migrate dev。
Drizzle Kit 的工作流更接近传统 SQL 迁移工具:
bash
npx drizzle-kit generate # 从 schema 差异生成 SQL 文件
npx drizzle-kit migrate # 按顺序应用未执行的迁移
Drizzle Kit 生成的是纯 SQL 文件 ,方便在 PR 中审查。它对数据丢失检测和回滚的自动化程度较低,更多依赖团队自身的流程管理。生产环境同样应该用 generate + migrate,而不是 push。
2.5 性能对比
Prisma 7 将 Rust 查询引擎替换为纯 TypeScript 客户端后,性能差距大幅缩小------包体积缩小约 90%,查询速度提升最高 3 倍。Drizzle 的运行时仍然显著更小。
根据 2026 年的基准数据:
| 指标 | Drizzle | Prisma |
|---|---|---|
| 包体积 | ~35KB | ~230KB |
| 冷启动 | ~10ms | ~250ms |
| 简单 SELECT | 基准 | 慢 2-3 倍 |
| 复杂 JOIN | 基准 | 慢 2-5 倍 |
| 内存占用 | ~10MB | ~50MB |
不过 Prisma 8(基于 TypeScript 的新基础)已经达到原生 pg 驱动约 87% 的峰值吞吐量,比 Prisma 7 高出 52%。在高负载场景下,Prisma 的性能表现正在快速收敛。
在读写特征上,有研究显示 Prisma 在轻到中等负载的读操作中延迟更低,而 Drizzle 在 create、update、delete 操作上表现更好。
2.6 边缘运行时与 Serverless
这是 Drizzle 的传统优势领域。Drizzle 设计上就是 serverless-ready 的,包体积小巧,零依赖,支持 Node.js、Bun、Deno、Cloudflare Workers 以及各类 Edge Runtime。
Prisma 在边缘环境方面也在追赶。Prisma 7 移除 Rust 引擎后,包体积大幅缩小,配合 Driver Adapters 可以在边缘环境运行,但配置复杂度仍然高于 Drizzle。Prisma Accelerate 则提供了连接池和缓存方案来缓解 serverless 场景下的连接耗尽问题。
2.7 工具链与生态
Prisma 的工具链更完整:
- Prisma Studio:可视化数据浏览器
- Prisma Accelerate:连接池与缓存
- Prisma Pulse:实时数据库事件
- 内置连接池管理
- Client Extensions 支持中间件
Drizzle 的工具链更精简:
- Drizzle Studio:数据库浏览器
- 连接池需要自带(pg Pool、postgres.js 等)
- 没有内置的中间件系统,需要通过 SQL 钩子实现
- 生态相对年轻,示例和社区资源不如 Prisma 丰富
在 npm 下载量上,Prisma 仍然领先:截至 2026 年 7 月,Prisma 月下载量 5530 万,Drizzle 4810 万。
2.8 选型决策指南
| 你的情况 | 推荐 |
|---|---|
| 团队 SQL 经验丰富,想要精细控制 | Drizzle |
| 需要边缘运行时或极小包体积 | Drizzle |
| 追求最佳开发体验和类型安全 | Prisma |
| 想要完整的迁移、Studio、监控工具链 | Prisma |
| 项目模型多、类型检查速度敏感 | Prisma |
| 无服务器架构,冷启动敏感 | Drizzle |
| 需要中间件、扩展、实时事件 | Prisma |
一句话总结:
Prisma 是"抽象优先,用构建期代码生成换类型安全和开发效率";Drizzle 是"SQL 优先,用 TypeScript 推断换轻量、可控和边缘友好"。
两者不是在竞争"谁的质量更好",而是在竞争"团队愿意接受哪种构建期成本"------一个代码生成步骤,还是更重的类型检查。
结语
数据库层没有银弹。先通过原生 SQL 建立对数据模型和查询的直觉,再根据团队的技术偏好和部署约束选择 ORM,才是成熟的做法。如果你追求开箱即用的完整工具链和最佳 DX,Prisma 是稳妥的选择;如果你想要 SQL 级别的控制力和极致的运行时轻量,Drizzle 值得认真考虑。