从零搭建 SQL 数据库到 ORM 选型:Prisma 与 Drizzle 深度对比

文章来源声明: 原文作者:为你学会写情书; 来源站点:掘金; 原文链接:https://juejin.cn/post/7688305723760361512; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

文章价值在于把 ORM 选型转化为“构建期成本”取舍,并给出场景化决策表。适合 TypeScript 全栈团队在数据库选型与架构评审时参考。

从零搭建 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

<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 核心架构差异

两者的根本分歧在于类型从哪里来

维度PrismaDrizzle
查询引擎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 年的基准数据:

指标DrizzlePrisma
包体积~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 值得认真考虑。