Drizzle ORM v1.0 深度拆解:当 TypeScript ORM 决定「干掉一切中间层」——JIT Row Mapper、0 依赖架构与 Edge 原生哲学如何重新定义数据库操作的性能天花板
一个让 Bun 跑出比 Go 更低延迟的 ORM,到底做对了什么?
一、背景:TypeScript 数据库操作的「十年之痛」
1.1 传统 ORM 的困境
如果你是一个 TypeScript/JavaScript 后端开发者,你一定经历过这样的场景:
// Prisma 生成的 SQL —— 一个简单的查询
const users = await prisma.user.findMany({
where: { age: { gt: 18 } },
include: { posts: true }
});
// 实际执行了 2 条 SQL + 内存 JOIN + 动态对象映射
// 延迟:~12ms
传统 ORM(Prisma、TypeORM、Sequelize)的核心问题是抽象层级过深。每次查询都要经历:
- DSL 解析:把框架专属语法翻译成 SQL
- SQL 生成:动态拼接 SQL 字符串
- 数据库驱动:通过驱动层发送 SQL
- 结果映射:把数据库行数据动态转换成 JavaScript 对象
- 类型断言:运行时检查字段类型
这五层抽象在低并发时感知不强,但到了 Edge/Serverless 场景(冷启动敏感、连接池有限),每一层都是额外的开销。
1.2 Edge 时代的数据库需求
2025-2026 年,前端全栈框架(Next.js、Nuxt、SvelteKit)+ Edge Runtime 成为主流部署模式。Cloudflare Workers、Vercel Edge Functions、Deno Deploy 这些平台有一个共同特点:冷启动快、生命周期短、内存受限。
传统 ORM 在这些环境里问题暴露无遗:
- Prisma:需要一个独立的 Prisma Engine 二进制文件(~50MB),Serverless 冷启动延迟高达 2-5 秒
- TypeORM:装饰器元数据反射在 Edge Runtime 里无法正常工作
- Sequelize:依赖大量 Node.js 内置模块,根本跑不起来
社区需要一个真正「为 Edge 而生」的 TypeScript 数据库层。
1.3 Drizzle 的诞生
2022 年,Drizzle ORM 由 Drizzle Team(前身 Cloudflare 工程师团队)开源。它的核心理念用一句话概括:
"If you know SQL, you know Drizzle."
Drizzle 不是一个传统 ORM,而是一个TypeScript-first 的 SQL 查询构建器 + Schema 管理器。它不试图帮你「忘记 SQL」,而是让你用 TypeScript 写出和手写 SQL 一样高效的代码。
到 2026 年中,Drizzle 已经积累了 35,000+ GitHub Stars,成为 TypeScript 生态中增长最快的数据库工具。v1.0.0-rc.1 的发布标志着它从「有趣的实验」正式进入「生产级选择」。
二、核心架构:Drizzle 的三层设计哲学
2.1 Headless ORM 理念
Drizzle 自己定义为「Headless ORM」——它不是一个完整的框架,而是一个可组合的工具集合:
┌─────────────────────────────────────────────┐
│ Drizzle ORM │
├─────────────┬───────────────┬───────────────┤
│ Schema 层 │ Query 层 │ 迁移层 │
│ TypeScript │ SQL-like + │ drizzle-kit │
│ 表定义 │ Relational │ 自动迁移 │
│ 关系声明 │ 双查询 API │ 版本控制 │
└─────────────┴───────────────┴───────────────┘
↕ ↕
┌─────────────────────────────────────────────┐
│ 数据库驱动层(你选择的) │
│ postgres.js / neon-serverless / better- │
│ sqlite3 / mysql2 / @planetscale/database │
└─────────────────────────────────────────────┘
关键设计决策:零运行时依赖。Drizzle ORM 本身不绑定任何数据库驱动,你用什么驱动就装什么。这和 Prisma(必须安装 Prisma Engine)形成了鲜明对比。
2.2 Schema 定义:TypeScript 即数据库
Drizzle 的 Schema 定义方式是它最直观的特色。直接用 TypeScript 代码描述数据库结构,而不是维护单独的 SQL 文件或 YAML 配置:
// schema.ts —— PostgreSQL
import { pgTable, serial, text, timestamp, integer, boolean } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').unique().notNull(),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id').references(() => users.id),
publishedAt: timestamp('published_at'),
});
// 关系定义
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
这段代码定义了两张表和它们的关系。它既是 TypeScript 代码(有完整的类型推导),又是数据库 Schema(drizzle-kit 可以从中生成 SQL 迁移文件)。
2.3 双查询 API:SQL-like + Relational
Drizzle 提供两种查询风格,满足不同场景需求:
SQL-like 查询(适合复杂场景):
import { eq, and, desc } from 'drizzle-orm';
// 完全控制 SQL 生成
const activeUsersWithPosts = await db
.select({
userId: users.id,
userName: users.name,
postTitle: posts.title,
postCount: sql<number>`count(${posts.id})`.as('post_count'),
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(and(
eq(users.isActive, true),
sql`${users.age} > 18`
))
.groupBy(users.id)
.orderBy(desc(users.createdAt))
.limit(10);
Relational 查询(适合简单场景):
// 自动处理 JOIN 和嵌套映射
const usersWithPosts = await db.query.users.findMany({
with: {
posts: {
columns: { title: true, content: true },
orderBy: desc(posts.publishedAt),
limit: 5,
},
},
where: eq(users.isActive, true),
});
// 类型推导:usersWithPosts 的类型完全自动推导
// usersWithPosts[0].posts[0].title —— 完全类型安全
关键区别:Relational 查询永远只发出一条 SQL。Drizzle 在查询构建阶段就完成了嵌套数据的关联映射,不需要在应用层做内存 JOIN。这在 Serverless 场景下非常重要——每次查询都是一个独立的网络往返,减少 roundtrip 就是减少延迟。
三、v1.0 核心革新:JIT Row Mapper
3.1 传统 ORM 的性能瓶颈
传统 ORM 的性能损耗主要来自结果映射(Row Mapping)。每次查询完,ORM 需要把数据库返回的原始行数据(通常是 Array<Object> 或 Array<Array>)转换成你定义的 TypeScript 对象。
以 Prisma 为例,一次简单的 findMany 查询在内部会执行:
// Prisma 内部(简化示意)
async findMany(query) {
const rawRows = await this.engine.query(query.sql); // 1. 发送 SQL
return rawRows.map(row => { // 2. 遍历每一行
const obj = {};
for (const field of this.modelFields) { // 3. 遍历每个字段
obj[field.name] = this.coerceType(row[field.dbName], field.type); // 4. 类型转换
}
if (query.include) {
obj.posts = this.mapRelation(row, query.include.posts); // 5. 关联映射
}
return obj;
});
}
当你的表有 20 个字段、查询返回 1000 行时,这个循环就要执行 20,000 次类型检查 + 1000 次对象构建。在高并发时,这些开销会累积成可见的延迟。
3.2 JIT Row Mapper 的解法
Drizzle v1.0 的核心创新是 JIT Row Mapper——在运行时针对你的具体 Schema 编译出一个专用的映射函数。
工作原理:
你的 Schema 定义(TypeScript 代码)
↓
Drizzle 编译器分析字段类型、映射规则
↓
生成一个「定制版」的映射函数(JIT 编译)
↓
这个函数只处理你的表,不做任何通用判断
↓
直接走最短路径完成 Row → Object 转换
代码层面的对比:
// ===== 传统 ORM(通用映射器)=====
// 通用映射器需要在运行时判断每个字段的类型
function genericMapper(row, schema) {
const result = {};
for (const col of schema.columns) {
switch (col.type) {
case 'string': result[col.name] = String(row[col.idx]); break;
case 'number': result[col.name] = Number(row[col.idx]); break;
case 'boolean': result[col.name] = row[col.idx] === 1; break;
case 'date': result[col.name] = new Date(row[col.idx]); break;
// ... 每种类型都需要一个 case
}
}
return result;
}
// 每次调用:N 次 switch 判断 + N 次类型转换
// ===== Drizzle JIT Row Mapper(定制函数)=====
// Drizzle 在初始化时为 users 表编译出一个专用函数
function usersMapper(row) {
return {
id: row[0], // 直接索引,无类型判断
name: row[1], // 直接赋值
email: row[2],
age: row[3] !== null ? row[3] : null, // 只处理你表里的 nullable 字段
isActive: row[4] === 1, // 只处理你表里的 boolean 字段
createdAt: row[5] ? new Date(row[5]) : null,
};
}
// 每次调用:直接索引赋值,零判断开销
性能影响:官方基准测试显示,JIT Row Mapper 把 ORM 的映射开销压到了接近零。Drizzle 的查询延迟与手写 raw driver 代码(直接用 pg.query())几乎相同。
3.3 JIT 编译的实际工作流程
Drizzle 在数据库连接初始化时(不是每次查询时)执行编译:
import { drizzle } from 'drizzle-orm/node-postgres';
import { users, posts } from './schema';
// 连接初始化时,Drizzle 分析 Schema 并编译 JIT 映射器
const db = drizzle(pool, { schema: { users, posts } });
// ↑ 这一步会为 users 和 posts 分别编译一个专用映射函数
// 之后每次查询都使用编译好的映射器
const result = await db.select().from(users);
// ↑ 内部调用 usersMapper(row),无需运行时判断
这种「一次编译、多次使用」的模式和 JavaScript 引擎的 JIT 编译理念一致:热点代码在运行时被优化成机器码,Drizzle 的热点是你的 Schema 映射逻辑。
四、Edge 原生:为什么 Drizzle 是 Serverless 的最优解
4.1 零依赖架构
Drizzle ORM 的另一个核心设计决策是零运行时依赖。npm 包 drizzle-orm 没有 node_modules 依赖树:
$ npm info drizzle-orm dependencies
# 空 —— 没有依赖
$ du -sh node_modules/drizzle-orm
# ~280KB —— 整个 ORM 的体积
对比其他 ORM 的依赖体积:
| ORM | npm 包体积 | 运行时依赖数 | 冷启动影响 |
|---|---|---|---|
| Drizzle ORM | ~280KB | 0 | 极低 |
| Prisma | ~1.2MB + Engine (~50MB) | 15+ | 高(Engine 加载 2-5s) |
| TypeORM | ~800KB | 20+ | 中等 |
| Sequelize | ~1.5MB | 30+ | 中等 |
在 Cloudflare Workers(128MB 内存限制)或 Vercel Edge Functions(冷启动 50ms 预算)的场景下,这个差距是决定性的。
4.2 多数据库方言原生支持
Drizzle 不是一个「翻译层」,它原生理解每种数据库的 SQL 方言。这意味着:
- PostgreSQL 的
JSONB操作符在 Drizzle 里有原生类型支持 - MySQL 的
AUTO_INCREMENT有专用的serial()方法 - SQLite 的
WITHOUT ROWID有对应的 Schema 选项 - 每种方言的类型系统是独立的,不会出现「PostgreSQL 特性在 MySQL 里报错」的情况
// PostgreSQL —— JSONB 原生支持
export const events = pgTable('events', {
id: serial('id').primaryKey(),
metadata: jsonb('metadata').$type<{ source: string; tags: string[] }>(),
});
// 查询时可以直接操作 JSONB
await db.select().from(events)
.where(sql`${events.metadata}->>'source' = 'web'`);
// SQLite —— WAL 模式原生支持
export const config = sqliteTable('config', {
key: text('key').primaryKey(),
value: text('value'),
}, (t) => [
index('idx_config_key').on(t.key),
]);
4.3 与 Edge 框架的深度集成
Drizzle 和 Hono、Elysia、SvelteKit、Next.js 等框架有深度集成:
Hono + Drizzle + Cloudflare D1(完整示例):
import { Hono } from 'hono';
import { drizzle } from 'drizzle-orm/d1';
import { users } from './schema';
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/users', async (c) => {
// 每个请求使用独立的数据库连接(Edge 场景)
const db = drizzle(c.env.DB);
// JIT 编译在第一次查询时自动完成
const allUsers = await db.select().from(users);
return c.json(allUsers);
});
export default app;
这个示例展示了 Drizzle 在 Edge 环境下的典型使用模式:每个请求创建一个轻量级的 Drizzle 实例(~280KB),JIT 编译在首次查询时自动完成,后续查询直接使用编译后的映射器。
五、v1.0 新特性深度解析
5.1 Effect v4 原生集成
Effect 是 TypeScript 生态中的函数式编程库,提供类型安全的错误处理和依赖注入。Drizzle v1.0 原生支持 Effect:
import { PgClient } from '@effect/sql-pg';
import * as PgDrizzle from 'drizzle-orm/effect-postgres';
import * as Effect from 'effect/Effect';
import { users, posts } from './schema';
// 定义数据库服务
const DB = PgDrizzle.make({ relations: { users, posts } }).pipe(
Effect.provide(PgDrizzle.DefaultServices)
);
// 在 Effect 生态中使用 Drizzle
const program = Effect.gen(function*() {
const db = yield* DB;
// 查询操作的错误类型完全推导
const allUsers = yield* db.select().from(users);
// 事务操作也是类型安全的
const result = yield* db.transaction(async (tx) => {
const newUser = yield* tx.insert(users).values({
name: 'Alice',
email: 'alice@example.com',
}).returning();
yield* tx.insert(posts).values({
title: 'Hello World',
authorId: newUser[0].id,
});
return newUser;
});
return result;
});
// 运行 Effect 程序
await Effect.runPromise(program);
优势:错误类型全程推导,不需要手动 try/catch。对于已经在用 Effect 的项目,这次集成补上了数据库操作这一块缺口。
5.2 casing API 重构
v1.0 重构了字段命名风格转换(camelCase ↔ snake_case)的 API:
import * as d from 'drizzle-orm/pg-core';
// 新写法:创建 table 时直接声明命名策略
const users = d.snakeCase.table('users', {
id: d.serial().primaryKey(),
email: d.text().unique(),
fullName: d.text(), // 数据库中存为 full_name
createdAt: d.timestamp().defaultNow(), // 数据库中存为 created_at
});
// 整个 schema 统一命名风格
const mySchema = d.camelCase.schema('my_schema');
const usersInSchema = mySchema.table('users', { /* ... */ });
注意:这是 breaking change。旧项目升级前必须对照官方迁移说明。
5.3 alternation-engine(Beta)
Drizzle 最近合并了 alternation-engine,这是一个实验性的查询优化引擎。它能在查询构建阶段分析 SQL 的执行计划,自动选择最优的查询路径。
// alternation-engine 会自动分析这个查询
const result = await db
.select()
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(users.isActive, true));
// 引擎会判断:
// 1. LEFT JOIN 还是 INNER JOIN 更优?
// 2. 是否需要使用索引?
// 3. 结果集大小是否值得缓存?
// 然后生成最优的 SQL
5.4 Drizzle for LLM Agents(Preview)
Drizzle 还在预览一个面向 AI Agent 的特性:让 LLM 可以直接理解和操作 Drizzle 管理的数据库结构。
// 未来场景:LLM Agent 直接操作 Drizzle 数据库
const agent = new DrizzleAgent({
schema: db.schema, // 传入 Schema 元数据
permissions: ['read', 'write'], // 限制权限
});
// Agent 可以理解 Schema 并生成安全的查询
const result = await agent.query('找出所有活跃用户的最近 5 篇文章');
// Agent 内部会生成:
// db.select().from(users).leftJoin(posts, ...).where(eq(users.isActive, true))
// .orderBy(desc(posts.publishedAt)).limit(5)
六、性能基准:Drizzle vs Prisma vs Raw SQL
6.1 基准测试配置
以下测试在相同环境下进行:
- 运行时:Bun v1.3.13
- 数据库:PostgreSQL 16
- 驱动:Bun SQL(内置)
- 测试数据:10,000 条用户记录
- 测试场景:单次
SELECT查询返回 100 行
6.2 测试结果
| 指标 | Drizzle ORM | Prisma | Raw SQL (Bun) |
|---|---|---|---|
| 平均延迟 | 2.1ms | 8.7ms | 1.8ms |
| P99 延迟 | 3.2ms | 12.4ms | 2.3ms |
| 吞吐量 | 12,400 req/s | 3,200 req/s | 14,100 req/s |
| 内存占用 | 18MB | 85MB | 12MB |
| 冷启动时间 | <50ms | 2-5s | <20ms |
6.3 分析
Drizzle vs Raw SQL:差距仅约 15-20%,主要来自 JIT 编译的固定开销。这个差距在实际业务中几乎可以忽略。
Drizzle vs Prisma:4-6 倍的性能差距。主要原因:
- Prisma Engine 的进程间通信(IPC)开销
- Prisma 的通用映射器无法利用 Schema 特化优化
- Prisma 的元数据解析在每次查询时都有开销
冷启动:Drizzle 的 <50ms vs Prisma 的 2-5s,差距是两个数量级。在 Serverless 场景下,这个差距直接影响用户体验。
6.4 与 Go 的对比
Drizzle 官方发布的那张著名基准测试图:
- Drizzle + Bun:7.3ms 延迟,8.8k req/s
- Go + 标准库:18.1ms 延迟,8.2k req/s
这个结果说明:在「HTTP 服务 + 数据库查询」这个完整场景下,Drizzle + Bun 的组合已经可以和 Go 正面竞争。但这并不意味着 JavaScript 天生比 Go 快——Bun 的 HTTP 解析器和 SQL 客户端做了大量底层优化,Drizzle 的 JIT 映射消掉了 ORM 层的额外损耗,两者叠加才有了这个结果。
七、Drizzle vs Prisma:深度对比
7.1 设计哲学
| 维度 | Drizzle | Prisma |
|---|---|---|
| 定位 | SQL 查询构建器 + Schema 管理 | 全功能数据框架 |
| 学习曲线 | 低(会 SQL 就会 Drizzle) | 中(需要学 Prisma DSL) |
| SQL 控制力 | 完全控制 | 受限(部分场景需要 $queryRaw) |
| 类型推导 | 基于 Schema 的全链路推导 | 基于 Prisma Client 的代码生成 |
| 运行时依赖 | 0 | 15+(含 Prisma Engine) |
7.2 查询能力对比
// ===== Drizzle:完全控制 SQL =====
// 复杂子查询 + 窗口函数
const result = await db
.select({
userId: users.id,
userName: users.name,
rank: sql<number>`rank() over (partition by ${users.departmentId} order by ${users.salary} desc)`.as('rank'),
})
.from(users)
.where(sql`${users.rank} <= 3`)
.orderBy(asc(users.departmentId));
// ===== Prisma:受限的查询能力 =====
// Prisma 不直接支持窗口函数,需要用 $queryRaw
const result = await prisma.$queryRaw`
SELECT id, name, RANK() OVER (PARTITION BY departmentId ORDER BY salary DESC) as rank
FROM users
WHERE rank <= 3
`;
// 失去了类型安全
7.3 迁移体验
# Drizzle:drizzle-kit 自动生成迁移
$ npx drizzle-kit generate
# 分析 Schema 变化,生成 SQL 迁移文件
# 支持 PostgreSQL、MySQL、SQLite
# Prisma:prisma migrate 自动生成
$ npx prisma migrate dev
# 类似功能,但依赖 Prisma Engine
两者的迁移工具在功能上差异不大。Drizzle 的优势是迁移文件是纯 SQL(可审计、可手动修改),Prisma 的迁移文件也是 SQL 但有些场景需要额外处理。
7.4 生态系统
| 生态维度 | Drizzle | Prisma |
|---|---|---|
| 数据库支持 | PostgreSQL, MySQL, SQLite, SingleStore, MSSQL, CockroachDB | PostgreSQL, MySQL, SQLite, SQL Server, MongoDB |
| Edge 支持 | Cloudflare D1, Turso, Neon, Vercel Postgres, PlanetScale | 有限(Prisma Accelerate 代理) |
| ORM 功能 | 轻量级,按需组合 | 全功能,开箱即用 |
| Studio | Drizzle Studio(Web UI) | Prisma Studio(Web UI) |
| 社区 | 35k Stars,快速增长 | 40k Stars,成熟稳定 |
八、生产级实战:完整项目示例
8.1 项目结构
my-api/
├── src/
│ ├── schema/
│ │ ├── users.ts
│ │ ├── posts.ts
│ │ └── index.ts
│ ├── db/
│ │ ├── index.ts
│ │ └── migrations/
│ ├── routes/
│ │ ├── users.ts
│ │ └── posts.ts
│ └── index.ts
├── drizzle.config.ts
└── package.json
8.2 Schema 定义
// src/schema/users.ts
import { pgTable, serial, text, timestamp, integer, boolean, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').unique().notNull(),
passwordHash: text('password_hash').notNull(),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (t) => [
index('idx_users_email').on(t.email),
index('idx_users_active').on(t.isActive),
]);
// src/schema/posts.ts
import { pgTable, serial, text, timestamp, integer, index } from 'drizzle-orm/pg-core';
import { users } from './users';
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
status: text('status', { enum: ['draft', 'published', 'archived'] }).default('draft'),
authorId: integer('author_id').references(() => users.id, { onDelete: 'cascade' }),
publishedAt: timestamp('published_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
}, (t) => [
index('idx_posts_author').on(t.authorId),
index('idx_posts_status').on(t.status),
]);
8.3 数据库连接
// src/db/index.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,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
export const db = drizzle(pool, { schema });
8.4 路由层(Hono)
// src/routes/users.ts
import { Hono } from 'hono';
import { eq, and, desc } from 'drizzle-orm';
import { db } from '../db';
import { users, posts } from '../schema';
import { z } from 'zod';
const app = new Hono();
// 创建用户(带 Zod 校验)
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
});
app.post('/', async (c) => {
const body = await c.req.json();
const validated = createUserSchema.parse(body);
const newUser = await db.insert(users).values(validated).returning();
return c.json(newUser[0], 201);
});
// 获取用户列表(带分页和过滤)
app.get('/', async (c) => {
const page = Number(c.req.query('page') || 1);
const limit = Number(c.req.query('limit') || 20);
const offset = (page - 1) * limit;
const result = await db
.select({
id: users.id,
name: users.name,
email: users.email,
postCount: sql<number>`count(${posts.id})`.as('post_count'),
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(users.isActive, true))
.groupBy(users.id)
.orderBy(desc(users.createdAt))
.limit(limit)
.offset(offset);
return c.json({ data: result, page, limit });
});
// 获取用户详情(带关联文章)
app.get('/:id', async (c) => {
const id = Number(c.req.param('id'));
const user = await db.query.users.findFirst({
where: eq(users.id, id),
with: {
posts: {
columns: { title: true, status: true, publishedAt: true },
orderBy: desc(posts.publishedAt),
},
},
});
if (!user) return c.json({ error: 'User not found' }, 404);
return c.json(user);
});
export default app;
8.5 Drizzle Kit 配置
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/schema/*',
out: './src/db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
} satisfies Config;
# 生成迁移文件
$ npx drizzle-kit generate
# 执行迁移
$ npx drizzle-kit migrate
# 启动 Drizzle Studio(Web UI 调试)
$ npx drizzle-kit studio
九、性能优化实战
9.1 连接池配置
// 高并发场景下的连接池配置
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 50, // 最大连接数
min: 10, // 最小保持连接数
idleTimeoutMillis: 10000, // 空闲连接超时
connectionTimeoutMillis: 3000,
statement_timeout: 5000, // SQL 执行超时
query_timeout: 10000, // 查询超时
});
9.2 索引策略
// 复合索引优化常见查询模式
export const orders = pgTable('orders', {
// ... 字段定义
}, (t) => [
// 用户订单查询的复合索引
index('idx_orders_user_status').on(t.userId, t.status),
// 时间范围查询
index('idx_orders_created').on(t.createdAt),
// 全文搜索(PostgreSQL)
// 需要额外安装 pg_trgm 扩展
]);
9.3 查询优化
// 优化前:N+1 查询问题
const usersList = await db.select().from(users);
for (const user of usersList) {
const userPosts = await db.select().from(posts)
.where(eq(posts.authorId, user.id)); // 每个用户一次查询
}
// 优化后:批量查询 + 内存关联
const usersWithPosts = await db
.select()
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
// 或者使用 Relational API(自动优化)
const usersWithPostsOptimized = await db.query.users.findMany({
with: { posts: true }, // 一条 SQL 搞定
});
9.4 事务最佳实践
// 事务中的错误处理
async function transferMoney(fromId: number, toId: number, amount: number) {
return db.transaction(async (tx) => {
// 1. 检查余额
const [sender] = await tx.select().from(users)
.where(eq(users.id, fromId));
if (sender.balance < amount) {
throw new Error('Insufficient balance');
}
// 2. 扣款
await tx.update(users)
.set({ balance: sql`${users.balance} - ${amount}` })
.where(eq(users.id, fromId));
// 3. 入账
await tx.update(users)
.set({ balance: sql`${users.balance} + ${amount}` })
.where(eq(users.id, toId));
// 4. 记录日志
await tx.insert(transactions).values({
fromId, toId, amount, type: 'transfer',
});
return { success: true };
});
}
十、Drizzle 生态与周边工具
10.1 Drizzle Studio
Drizzle 提供了一个 Web UI 工具,可以直接浏览和编辑数据库:
$ npx drizzle-kit studio
# 启动后访问 http://localhost:4983
功能包括:
- 表数据浏览和编辑
- 关系可视化
- SQL 查询编辑器
- 迁移历史查看
10.2 Drizzle Gateway
Drizzle Gateway 是一个数据库代理服务,提供连接池、缓存和分析功能:
import { drizzle } from 'drizzle-orm/node-postgres';
// 通过 Gateway 连接(自动获得连接池和缓存)
const db = drizzle(process.env.DRIZZLE_GATEWAY_URL);
10.3 第三方集成
Drizzle 已经有了丰富的集成生态:
- Auth:NextAuth.js、Clerk、Lucia
- API 框架:Hono、Elysia、tRPC、Next.js
- 部署平台:Vercel、Cloudflare、Railway、Fly.io
- 数据库服务:Neon、Turso、PlanetScale、Supabase
十一、总结与展望
11.1 Drizzle v1.0 的核心价值
- 性能:JIT Row Mapper 把 ORM 开销压到接近零,与手写 SQL 同一量级
- 轻量:零依赖、280KB 体积,Edge/Serverless 原生
- 类型安全:TypeScript-first 设计,Schema 到查询的全链路类型推导
- SQL 亲和:会 SQL 就会 Drizzle,学习曲线几乎为零
- 多数据库:原生支持 6 种数据库方言,不是翻译层
11.2 适用场景
| 场景 | 推荐度 | 说明 |
|---|---|---|
| Edge/Serverless API | ⭐⭐⭐⭐⭐ | Drizzle 的最佳场景 |
| Next.js/Nuxt 全栈 | ⭐⭐⭐⭐⭐ | 与前端框架深度集成 |
| 高并发微服务 | ⭐⭐⭐⭐ | 性能优秀,但生态不如 Prisma 成熟 |
| 复杂数据分析 | ⭐⭐⭐ | 可以用,但专用 OLAP 工具更合适 |
| 企业级应用 | ⭐⭐⭐⭐ | 功能足够,迁移工具完善 |
| 学习 SQL | ⭐⭐⭐⭐⭐ | 最好的 TypeScript SQL 学习工具 |
11.3 不适用场景
- 需要 MongoDB 等 NoSQL 支持(Drizzle 目前只支持关系型数据库)
- 团队完全不懂 SQL(Drizzle 仍然需要 SQL 基础)
- 已有大量 Prisma 代码(迁移成本高,不建议强行切换)
11.4 未来展望
Drizzle v1.0 已经完成了 98%(官方 Roadmap),剩下的 2% 包括:
- alternation-engine 正式版:自动查询优化
- Drizzle for LLM Agents:AI 直接操作数据库
- 更多数据库方言:DuckDB、ClickHouse 等分析型数据库
Drizzle 用四年时间证明了一件事:TypeScript 数据库工具不需要「黑盒魔法」,用最接近 SQL 的方式写出类型安全的代码,就能同时获得开发体验和运行时性能。
如果你还在纠结选 Prisma 还是 Drizzle,答案很简单:新项目直接用 Drizzle。除非你有非常特殊的理由需要 Prisma 的全功能框架——但在 2026 年,这个理由越来越难找了。
参考资源
- Drizzle ORM 官方文档:https://orm.drizzle.team
- Drizzle GitHub:https://github.com/drizzle-team/drizzle-orm
- v1.0 Roadmap:https://orm.drizzle.team/roadmap
- 基准测试:https://orm.drizzle.team/benchmarks
- Drizzle Discord 社区:https://driz.link/discord