如果你受够了传统 ORM 的黑箱魔法和性能损耗,Drizzle ORM 可能是你一直在找的答案。


它是什么

Drizzle ORM 是一个 TypeScript 优先的数据库工具链,2023 年开源后迅速获得 25k+ GitHub Star。和 Prisma、TypeORM 这些老牌选手不同,Drizzle 的设计哲学只有一句话:你写的 TypeScript 代码,1:1 映射到 SQL

没有隐藏的查询生成,没有不可预测的 N+1,没有臃肿的代理层。定义 Schema → 写查询 → 拿到结果,整个过程透明可控。

目前支持的数据库:PostgreSQL、MySQL、SQLite、PlanetScale、Neon、Turso、Cloudflare D1、AWS Data API 等,几乎覆盖了现代服务端所有主流选型。


核心优势

1. 零黑箱——你能看到每一行 SQL

大多数 ORM 的查询构建器会在运行时动态拼接 SQL,你根本不知道最后发出的查询是什么。Drizzle 的做法完全不同:

const result = await db
  .select({
    name: users.name,
    orderCount: count(orders.id),
  })
  .from(users)
  .leftJoin(orders, eq(users.id, orders.userId))
  .groupBy(users.name);

// 在开发模式下,可以直接拿到生成的 SQL:
// SELECT "users"."name", count("orders"."id") FROM "users"
// LEFT JOIN "orders" ON "users"."id" = "orders"."userId"
// GROUP BY "users"."name"

不需要 console.log(query.toSQL()),不需要猜测。Drizzle 的查询 API 直接对标 SQL 语义,你写什么就得到什么。这对调试和性能排查来说价值巨大。

2. 运行时开销几乎为零

Drizzle 不维护对象图、不做脏检查、不搞懒加载。它只是一个类型安全的查询构建器加一个轻量的数据库驱动封装。

实际压测数据(来源:Drizzle 官方 benchmark):

工具 简单查询 (ops/s) 联表查询 (ops/s)
Drizzle 52,000 18,500
Prisma 18,200 6,800
TypeORM 9,400 3,200

差距在三到五倍之间。原因很简单:Prisma 的查询引擎是 Rust 写的二进制文件,通过 HTTP/gRPC 与 Node.js 通信,中间多了一次序列化;TypeORM 有 Active Record 和 Data Mapper 双重模式的包袱。Drizzle 直接用 pg / mysql2 / better-sqlite3 这些底层驱动,没有中间层。

3. 类型安全贯穿全链路

从 Schema 定义到查询结果,TypeScript 类型始终保持完整:

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

export const users = pgTable("users", {
  id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow(),
});

type User = typeof users.$inferSelect;    // 自动推导出 SELECT 结果的类型
type NewUser = typeof users.$inferInsert; // 自动推导出 INSERT 输入的类型

迁移文件也是 .sql 明文,你在 drizzle/ 目录下能看到每一次迁移的完整 SQL。这意味着你可以在生产环境直接拿迁移文件审计,而不需要依赖某个 GUI 工具或黑箱命令。

4. 极致的 Bundle 体积

Drizzle 的模块化设计允许按需引入,最终打包体积做到极致:

  • drizzle-orm 核心仅 ~11KB gzipped
  • 加上 drizzle-orm/pg-core 和 drizzle-orm/node-postgres,总计约 30KB

对比 Prisma Client 动辄 2MB+ 的体积,这在 Serverless / Edge Function 场景下是决定性的差异——冷启动时间直接受包体积影响。

5. 原生 SQL 的优雅降级

不是所有查询都适合用查询构建器。当你需要写复杂 CTE、窗口函数、递归查询时,Drizzle 允许你直接写原生 SQL 并且拿到完整的类型推断:

import { sql } from "drizzle-orm";

const result = await db.all<{ total: number; month: string }>(sql`
  SELECT 
    DATE_TRUNC('month', created_at) AS month,
    SUM(amount) AS total
  FROM orders
  WHERE created_at >= ${startDate}
  GROUP BY 1
  ORDER BY 1
`);

你不是在"逃离 ORM",而是在需要的时候直接把它当成一个类型安全的数据库客户端。


适用场景

场景一:Serverless / Edge 环境

在 Cloudflare Workers、Vercel Edge Functions、AWS Lambda 这类短生命周期环境中,包体积和冷启动时间是核心指标。Drizzle 的 ~30KB 体积配合原生驱动直连,比 Prisma 的冷启动快 3-5 倍。

而且 Drizzle 对 Cloudflare D1、Neon Serverless、PlanetScale HTTP API 都有第一方支持,不需要自己折腾连接池。

场景二:需要 SQL 审计和精细控制的团队

金融、医疗、合规要求严格的行业,数据库迁移必须逐条审计。Drizzle 的 SQL 文件迁移和 Drizzle Kit 生成的纯 SQL 文件可以直接走审批流程。相比之下,Prisma 的迁移目录里虽然也有 SQL,但它的 schema.prisma 才是真实数据源,你没法绕过那层抽象。

场景三:微服务与多语言项目

如果你的后端是 Go + TypeScript 混排,或者逐步从 Rails 迁移到 Node.js,Drizzle 的「用 DDL 定义真实数据源 + 用 Zod Schema 生成 Drizzle Schema 代码」这条管线能让你从已有的数据库逆推出 TypeScript 代码:

npx drizzle-kit pull

执行后自动读取你的数据库结构,生成对应的 Schema 文件。对于已经运行了几年的老数据库,这种零成本的接入方式是 TypeORM 的 CLI 做不到的。

场景四:高性能 API 服务

当你需要榨干 PostgreSQL/MySQL 的每一分性能,同时又不想放弃 TypeScript 的类型安全时,Drizzle 是最接近裸写的选择。它支持批量 INSERT、upsert、CTE、窗口函数、事务嵌套、部分索引、物化视图等高级特性,且全部有类型提示。


实操:从零搭建一个博客系统

下面以一个简单的博客 API 为例,展示 Drizzle 的完整工作流。

Step 1:初始化项目

mkdir drizzle-blog && cd drizzle-blog
npm init -y
npm install drizzle-orm postgres
npm install -D drizzle-kit @types/node typescript tsx

Step 2:定义 Schema

创建 src/schema.ts:

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

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow(),
});

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: text("title").notNull(),
  content: text("content").notNull(),
  published: boolean("published").default(false),
  authorId: integer("author_id")
    .notNull()
    .references(() => users.id),
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow(),
});

Step 3:配置 Drizzle Kit

创建 drizzle.config.ts:

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Step 4:生成并执行迁移

npx drizzle-kit generate   # 生成 SQL 迁移文件
npx drizzle-kit migrate    # 执行迁移

这一步会在 drizzle/ 目录生成类似 0000_bright_iron_man.sql 的文件,内容就是你 Schema 定义对应的 CREATE TABLE / ALTER TABLE 语句。

Step 5:建立数据库连接

创建 src/db.ts:

import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export const db = drizzle(pool, { schema });

Step 6:编写业务逻辑

import { db } from "./db";
import { users, posts } from "./schema";
import { eq, and, desc, count } from "drizzle-orm";

// ===== CRUD =====

// 创建用户
async function createUser(name: string, email: string) {
  const [user] = await db
    .insert(users)
    .values({ name, email })
    .returning();
  return user;
}

// 按 ID 查询用户
async function getUserById(id: number) {
  return db.query.users.findFirst({
    where: eq(users.id, id),
    with: {
      posts: true, // 自动联表查询该用户的所有文章
    },
  });
}

// 分页查询已发布文章
async function getPublishedPosts(page: number, pageSize: number) {
  return db.query.posts.findMany({
    where: eq(posts.published, true),
    orderBy: desc(posts.createdAt),
    limit: pageSize,
    offset: (page - 1) * pageSize,
    with: {
      author: {
        columns: { name: true }, // 只取作者名
      },
    },
  });
}

// 批量发布文章
async function publishPosts(ids: number[]) {
  return db
    .update(posts)
    .set({ published: true, updatedAt: new Date() })
    .where(and(eq(posts.published, false), /* ... in 条件用 inArray */));
}

// 统计每个作者的已发布文章数
async function getAuthorStats() {
  return db
    .select({
      authorName: users.name,
      postCount: count(posts.id),
    })
    .from(users)
    .leftJoin(posts, eq(users.id, posts.authorId))
    .groupBy(users.id, users.name)
    .having(({ postCount }) => sql`${postCount} > 0`);
}

Step 7:事务支持

import { db } from "./db";

async function transferPostOwnership(
  postId: number,
  fromUserId: number,
  toUserId: number
) {
  await db.transaction(async (tx) => {
    // 检查当前归属
    const post = await tx.query.posts.findFirst({
      where: and(
        eq(posts.id, postId),
        eq(posts.authorId, fromUserId)
      ),
    });

    if (!post) {
      throw new Error("文章不存在或不属于该用户");
    }

    // 转移归属
    await tx
      .update(posts)
      .set({ authorId: toUserId, updatedAt: new Date() })
      .where(eq(posts.id, postId));
  });
}

与其他 ORM 的对比小结

维度 Drizzle Prisma TypeORM
学习曲线 需要 SQL 基础 有自己的 DSL 需要理解 Active Record / Data Mapper
性能 接近原生驱动 中等(Rust 引擎桥接有开销) 较低
包体积 ~30KB ~2MB+ ~500KB
SQL 可见性 1:1 映射,完全可见 黑箱,需要 Prisma Studio 才能查看 部分可见
迁移可审计性 纯 SQL 文件 依赖 schema.prisma 依赖装饰器或配置文件
Edge 支持 官方支持 D1、Neon 等 有 Edge Client 但有限制 不友好
社区成熟度 增长快,生态较新 最成熟 老牌但维护放缓
原生 SQL 支持 一等公民,带类型推断 需要 $queryRaw 需要 query()
迁移可逆性 支持 drizzle-kit drop 支持 migrate dev --create-only 但需手动回滚 需手动管理

什么时候不该用 Drizzle

坦诚地说,Drizzle 不适合以下情况:

  1. 团队完全没有 SQL 基础。Drizzle 假设你理解 SQL 语义,select / where / join / groupBy 这些 API 直接映射 SQL 概念。如果团队更习惯 Prisma 那种声明式的 include 语法,Drizzle 会有上手成本。

  2. 需要 Prisma Studio 这样的 GUI 数据库浏览工具。Drizzle 有 drizzle-kit studio,但目前还比较简陋,功能远不如 Prisma Studio 完善。

  3. 项目深度依赖 Prisma 生态,比如 nestjs-prisma、prisma-nestjs-graphql 这类社区工具链。迁移成本需要评估。

  4. 极度保守的企业环境,要求 ORM 有至少 5 年以上生产实践和商业支持。Drizzle 第一个正式版本是 2023 年底发布的,在大厂核心系统的选型中可能还排不上优先级。


总结

Drizzle ORM 不是要取代 SQL,而是让 SQL 在 TypeScript 里拥有完整的类型安全。对于需要精细化控制查询、追求性能、工作在 Serverless 环境的团队来说,它是一个非常务实的选择。

SQL 不会消失,只会换一种方式被写出来。Drizzle 把这件事做得足够干净。 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐