首页 / 文章

Drizzle与Prisma:如何选择一款没有过度宣传的TypeScript ORM

从模式设计、查询风格、嵌套写入、数据迁移、事务处理及性能等方面对比 Drizzle 与 Prisma,并给出关于哪种工具更适合不同团队的建议。

发布者:REACTAPP.TOP阅读约 7 分钟1588 词

来源: Drizzle vs Prisma: Choosing the Right TypeScript ORM in 2026 (Deep Dive)

在为 TypeScript 项目选择 ORM 工具时,最常被提及的便是 Drizzle ORM 和 Prisma。它们解决的是同一个问题,即将数据库访问转换为带类型的代码,但在应保持与 SQL 的接近程度这一理念上却截然不同。两者都没有错。如果您的团队已经熟练掌握其中某一种,那就继续使用它,把时间投入到产品开发上。只有在启动新项目或需要向同事解释现有架构时,这种对比才显得尤为重要。

这两种工具

Drizzle诞生于2022年,迅速在那些希望在不增加额外负担的情况下获得控制力的开发者中流行起来。它以TypeScript为首选:表格、关联关系和查询均以TypeScript形式定义。可以说它是在SQL之上的一层轻量级类型化层。类型可自动推断,原始SQL仍保持类型安全,且其设计非常适合对性能要求较高的代码。它支持PostgreSQL、MySQL、SQLite和SQL Server等数据库。

Prisma早在2018年就已问世,其声誉建立在出色的开发者体验、高效的工作效率以及完善的工具链之上。数据模型存储在用Prisma Schema Language(PSL)编写的.prisma文件中,而prisma generate命令则能将其转换为带有高级API的完全类型化的客户端。关系处理由系统自动完成,此外该生态系统更为庞大且成熟,还包括用于可视化浏览和编辑数据的Prisma Studio。它支持的数据库包括PostgreSQL、MySQL、SQLite以及MongoDB等。

定义模式

在这一点上,各方案的理念差异最为明显,而所做出的选择会直接影响日常开发工作及类型安全性。

Drizzle:代码优先

Drizzle的模式就是普通的TypeScript。表、列以及关系都使用与应用程序相同的语言来定义,因此无需生成步骤,修改内容会立即反映在编辑器中。

import { pgTable, serial, varchar, text, integer, timestamp } from "drizzle-orm/pg-core";

// Authors table
export const authors = pgTable("authors", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 100 }).notNull(),
  bio: text("bio"),
});

// Books table referencing Authors
export const books = pgTable("books", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 150 }).notNull(),
  summary: text("summary"),
  authorId: integer("author_id")
    .notNull()
    .references(() => authors.id),
  publishedAt: timestamp("published_at").defaultNow(),
});

由于类型是自动推断的,查询结果具有确定的结构,无需额外的模式文件。这减少了运行时的意外情况。

Prisma:先定义模式

Prisma将模型保存在单独的.prisma文件中:

model Author {
  id    Int     @id @default(autoincrement())
  name  String
  bio   String?
  books Book[]
}

model Book {
  id          Int      @id @default(autoincrement())
  title       String
  summary     String?
  publishedAt DateTime @default(now())
  authorId    Int
  author      Author   @relation(fields: [authorId], references: [id])
}

该架构与应用程序代码完全分离。每次进行更改后,prisma generate会重新生成客户端代码,以确保类型一致。对于SQL背景较弱的人来说,这种抽象层更加友好,因为只需使用生成的高级客户端,而无需处理SQL风格的逻辑。不过其代价是在代码与数据库之间多了一个层次。

编写查询

两种方式都能生成经过类型检查的查询,但编写时的感觉有所不同。

Drizzle

它的查询构建器设计得小巧且易于理解,读起来类似SQL。如果你熟悉连接操作和WHERE子句,使用起来会感觉很自然,而且你编写的代码与数据库中实际执行的代码高度对应。以下示例展示了如何将书籍与作者关联,并对结果进行过滤和排序:

const results = await db
  .select({
    bookId: books.id,
    title: books.title,
    publishedAt: books.publishedAt,
    authorName: authors.name,
  })
  .from(books)
  .leftJoin(authors, eq(books.authorId, authors.id))
  .where(eq(authors.id, authorId))
  .orderBy(books.publishedAt);

你可以实现精细的控制,并获得高度可预测的 SQL 语句。与 Prisma 相比,其在创建关联行时的表现存在明显差距。Drizzle 不支持嵌套写入,因此每次关系型数据的插入都需要手动处理,通常会在事务中完成,以确保各部分数据的一致性:

const result = await db.transaction(async (tx) => {
  const [newAuthor] = await tx.insert(authors).values({
    name: "Alice",
    bio: "Fantasy author",
  }).returning({ id: authors.id });

  const [newBook] = await tx.insert(books).values({
    title: "The Dream Forest",
    authorId: newAuthor.id,
  }).returning();

  return { newAuthor, newBook };
});

完全透明是其优势,而过多的样板代码则是其缺点。

Prisma

Prisma采取了相反的路径。你通过自动生成的客户端调用方法,而数据库则通过对象风格的方法来操作。同样的查询操作变为:

const results = await prisma.book.findMany({
  where: { authorId },
  select: {
    id: true,
    title: true,
    publishedAt: true,
    author: {
      select: { name: true },
    },
  },
  orderBy: { publishedAt: "asc" },
});

在这里无需使用 SQL 进行思考,这也解释了 Prisma 受到广泛欢迎的原因。它在处理关联数据时同样表现出色,因为支持嵌套写入操作。只需一次调用即可同时创建一本书及其作者信息:

const newBook = await prisma.book.create({
  data: {
    title: "The Dream Forest",
    summary: "A surreal adventure.",
    author: {
      create: {
        name: "Alice",
        bio: "Fantasy author",
      },
    },
  },
});

无需手动连接任何外键。但您会失去可视化能力,因为实际执行的SQL对您来说是隐藏的,因此相比Drizzle,您的控制权更弱。

迁移

在任何实际项目中,随着时间推移修改架构都是必要的,这两种工具都能很好地处理这一问题。

Drizzle

由于架构采用TypeScript编写,它就是数据的真实来源,而CLI(drizzle-kit)会通过比较定义中的差异来生成迁移文件。在基于迁移的工作流中,您首先需要生成:

npx drizzle-kit generate

该功能会将当前状态与 TypeScript 架构进行对比,并生成包含 SQL 修改内容的迁移文件,您可以在使用前手动编辑该文件。应用这些修改则需要另一个独立的命令:

npx drizzle-kit migrate

对于快速原型设计,还有一个快捷方法:

npx drizzle-kit push

它直接将 TypeScript 架构写入数据库,而不生成迁移文件。这在初期非常快捷方便,但由于没有迁移历史记录,因此不适合用于生产环境。

Prisma

使用 Prisma 时,.prisma 文件是唯一真实的来源,该工具会为每一项更改生成相应的 SQL 语句。将已提交的迁移应用到数据库时需要使用:

npx prisma migrate deploy

在开发过程中,prisma migrate dev 是用于创建包含 SQL 脚本的新迁移文件夹、应用这些脚本并更新历史记录表的命令。这样就形成了一种整洁且基本自动化的工作流程。

这里也有一个用于原型设计的快捷方式:

npx prisma db push

它像 Drizzle 的推送方式一样直接更新数据库,无需迁移文件,因此仅适用于非生产环境。

事务处理

两者都支持事务处理,只是在实现方式上存在些许差异。

Drizzle 使用带有回调函数的 db.transaction。其中的每个查询要么全部成功,要么全部失败,而且由于仍基于 SQL,你可以精确控制该代码块内的操作流程:

await db.transaction(async (tx) => {
  const newAuthor = await tx.insert(authors).values({
    name: "Alice Walker",
    bio: "Pulitzer Prize-winning author",
  }).returning();

  await tx.insert(books).values({
    title: "Journey to the Mountains",
    summary: "A story about adventure and discovery.",
    authorId: newAuthor[0].id,
  });
});

Prisma 提供了 prisma.$transaction 方法,它接受查询数组或异步回调函数。提交和回滚操作会自动完成:

await prisma.$transaction(async (tx) => {
  const newAuthor = await tx.author.create({
    data: {
      name: "Alice Walker",
      bio: "Pulitzer Prize-winning author",
    },
  });

  await tx.book.create({
    data: {
      title: "Journey to the Mountains",
      summary: "A story about adventure and discovery.",
      authorId: newAuthor.id,
    },
  });
});

性能

在性能方面,底层的设计理念最为重要。

Drizzle体积轻巧,其查询几乎可以直接映射到数据库操作,因此抽象开销极小。这对于复杂查询、批量操作以及高吞吐量代码来说非常有用。由于可以对连接、事务和查询结构进行完全控制,因此优化性能更为容易,但这要求使用者熟悉SQL和数据库设计。

Prisma生成的客户端通过高级API隐藏了SQL代码。这会带来一定的性能损失,但在大多数普通应用中这种差异可以忽略不计。关系定义、嵌套查询以及类型化结果都是免费提供的,这有助于加快开发速度。不过这种便利性有时会导致生成的SQL不如精心手写的查询那样高效。

如何选择

这两者都是具备类型安全、数据迁移、关联关系处理及事务支持的现代、支持 TypeScript 的 ORM,因此选择哪一种取决于具体需求优先级。

如果希望贴近 SQL 语法、掌控查询与数据迁移过程,并且在高负载或复杂工作场景下需要可预测的性能,那么请选择 Drizzle。TypeScript 是唯一的真实数据来源,没有任何信息被隐藏。

如果团队希望加快开发速度、专注于功能实现,并借助自动化工具处理数据迁移、查询及关联关系,那么请选择 Prisma。其以模式优先的设计理念以及自动生成的客户端代码,能够提供简洁、高级的 API,非常适合初学者使用。

简而言之:

  • 模式:Drizzle 使用 TypeScript 代码,而 Prisma 则使用 .prisma 文件并结合生成代码。
  • 查询方式:Drizzle 采用类似 SQL 的显式写法,Prisma 则采用对象风格的抽象写法。
  • 嵌套写入操作:Drizzle 需要手动实现,Prisma 则内置支持。
  • 性能方面:更为精简且易于控制,但为提升便利性会带来稍高的开销。