在 TypeScript 全栈开发中,数据库层是绕不开的一环。很多团队在选型时会陷入一个误区——纠结“哪个 ORM 更好”。但真正需要回答的问题是:你的团队愿意接受哪种类型的构建期成本? Prisma 把 schema 压缩成自定义 DSL 和一个代码生成步骤;Drizzle 把一切保留在 TypeScript 模块中,通过结构推断类型。
这篇文章分为两大部分:第一部分从零搭建一个 SQL 数据库,覆盖 Docker 环境、基础 SQL、数据表设计;第二部分深入对比 Prisma 和 Drizzle 这两个主流 ORM,从架构、类型系统、迁移工作流、性能到边缘运行时支持,帮你做出有据可依的选型决策。
第一部分:SQL 数据库搭建与数据表管理
1.1 用 Docker 搭建本地 PostgreSQL
本地直接安装数据库容易污染系统环境,推荐用 Docker Compose 管理。新建 docker-compose.yml:
<span>version:</span> <span>'3.8'</span>
<span>services:</span>
<span>postgres:</span>
<span>image:</span> <span>postgres:16</span>
<span>container_name:</span> <span>dev-postgres</span>
<span>restart:</span> <span>always</span>
<span>environment:</span>
<span>POSTGRES_USER:</span> <span>dev</span>
<span>POSTGRES_PASSWORD:</span> <span>dev123</span>
<span>POSTGRES_DB:</span> <span>myapp</span>
<span>ports:</span>
<span>-</span> <span>"5432:5432"</span>
<span>volumes:</span>
<span>-</span> <span>pgdata:/var/lib/postgresql/data</span>
<span>volumes:</span>
<span>pgdata:</span>
启动并连接:
docker compose up -d
psql -h localhost -U dev -d myapp
pgdata 卷保证了数据持久化,删除容器不会丢失数据。需要重置时,直接删除卷即可。
1.2 基础 SQL:从建表到查询
在引入 ORM 之前,先用原生 SQL 建立对数据库的直观理解。以下是一个典型的博客系统表结构:
<span>-- 用户表</span>
<span>CREATE</span> <span>TABLE</span> users (
id SERIAL <span>PRIMARY</span> KEY,
email <span>VARCHAR</span>(<span>255</span>) <span>UNIQUE</span> <span>NOT</span> <span>NULL</span>,
name <span>VARCHAR</span>(<span>100</span>),
created_at <span>TIMESTAMP</span> <span>DEFAULT</span> NOW()
);
<span>-- 文章表</span>
<span>CREATE</span> <span>TABLE</span> posts (
id SERIAL <span>PRIMARY</span> KEY,
title <span>VARCHAR</span>(<span>255</span>) <span>NOT</span> <span>NULL</span>,
content TEXT,
published <span>BOOLEAN</span> <span>DEFAULT</span> <span>FALSE</span>,
author_id <span>INTEGER</span> <span>REFERENCES</span> users(id),
created_at <span>TIMESTAMP</span> <span>DEFAULT</span> NOW()
);
<span>-- 为常用查询字段建立索引</span>
<span>CREATE</span> INDEX idx_posts_author <span>ON</span> posts(author_id);
<span>CREATE</span> INDEX idx_users_email <span>ON</span> users(email);
插入和查询:
<span>INSERT</span> <span>INTO</span> users (email, name) <span>VALUES</span> (<span>'alice@example.com'</span>, <span>'Alice'</span>);
<span>SELECT</span> p.title, u.name <span>AS</span> author
<span>FROM</span> posts p
<span>JOIN</span> users u <span>ON</span> u.id <span>=</span> p.author_id
<span>WHERE</span> p.published <span>=</span> <span>true</span>
<span>ORDER</span> <span>BY</span> p.created_at <span>DESC</span>;
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 语言:
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:
<span>import</span> { pgTable, serial, varchar, text, integer, timestamp } <span>from</span> <span>'drizzle-orm/pg-core'</span>;
<span>export</span> <span>const</span> users = <span>pgTable</span>(<span>'users'</span>, {
<span>id</span>: <span>serial</span>().<span>primaryKey</span>(),
<span>email</span>: <span>varchar</span>({ <span>length</span>: <span>255</span> }).<span>unique</span>().<span>notNull</span>(),
<span>name</span>: <span>varchar</span>({ <span>length</span>: <span>100</span> }),
<span>createdAt</span>: <span>timestamp</span>().<span>defaultNow</span>(),
});
<span>export</span> <span>const</span> posts = <span>pgTable</span>(<span>'posts'</span>, {
<span>id</span>: <span>serial</span>().<span>primaryKey</span>(),
<span>title</span>: <span>varchar</span>({ <span>length</span>: <span>255</span> }).<span>notNull</span>(),
<span>authorId</span>: <span>integer</span>(<span>'author_id'</span>).<span>references</span>(<span>() =></span> users.<span>id</span>),
});
因为 schema 就是 TypeScript,重构、lint、自动补全天然可用。代价是模型变复杂后,schema 文件会变得冗长。
2.3 查询 API 哲学
Prisma 的查询映射到应用层概念,把 SQL 抽象掉了:
<span>const</span> user = <span>await</span> prisma.<span>user</span>.<span>findUnique</span>({
<span>where</span>: { <span>email</span>: <span>'alice@example.com'</span> },
<span>include</span>: { <span>posts</span>: { <span>where</span>: { <span>published</span>: <span>true</span> } } },
});
Drizzle 的查询贴近 SQL,像链式构建器一样组合:
<span>const</span> result = <span>await</span> db
.<span>select</span>()
.<span>from</span>(users)
.<span>leftJoin</span>(posts, <span>eq</span>(posts.<span>authorId</span>, users.<span>id</span>))
.<span>where</span>(<span>eq</span>(users.<span>email</span>, <span>'alice@example.com'</span>));
前者上手更快,后者对 SQL 熟悉的人更自然。Vercel 的对比文档指出,这个差异直接影响代码审查的方式、新人上手速度,以及出问题时调试的难度。
2.4 迁移工作流
Prisma Migrate 提供了一套完整的声明式迁移流程:
npx prisma migrate dev --name add_user_role <span># 开发环境</span>
npx prisma migrate deploy <span># 生产环境</span>
Prisma Migrate 内置了数据丢失检测、advisory locking 和确认流程,开发迁移时需要影子数据库(shadow database)。生产环境务必使用 migrate deploy,绝不能用 migrate dev。
Drizzle Kit 的工作流更接近传统 SQL 迁移工具:
npx drizzle-kit generate <span># 从 schema 差异生成 SQL 文件</span>
npx drizzle-kit migrate <span># 按顺序应用未执行的迁移</span>
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 值得认真考虑。
文章价值在于把 ORM 选型转化为“构建期成本”取舍,并给出场景化决策表。适合 TypeScript 全栈团队在数据库选型与架构评审时参考。