Drizzle ORM 从入门到精通:下一代 TypeScript ORM 的全面指南
引言
在 JavaScript/TypeScript 生态中,ORM 工具一直是开发者的得力助手。传统 ORM 如 Sequelize、TypeORM 提供了丰富的抽象,但往往伴随着学习曲线陡峭、类型推断不完善、运行时性能损耗等问题。近年来,Drizzle 以“无代码生成、类型安全、SQL 风格”的独特设计崭露头角,成为越来越多 TypeScript 项目的首选。
本文将从零开始,循序渐进地带你掌握 Drizzle 的核心概念、常用操作与进阶技巧,帮助你在实际项目中熟练使用这一现代化 ORM。
一、Drizzle 是什么?
Drizzle 是一个专为 TypeScript 设计的轻量级 ORM(对象关系映射)库。它的核心理念是:
- 无代码生成:直接使用 TypeScript 类型系统推导数据库 schema,不依赖额外的代码生成器。
- SQL 友好:API 设计贴合 SQL 语法,让熟悉 SQL 的开发者零成本上手。
- 可预测的性能:生成的 SQL 清晰可控,无隐藏魔法,便于优化和调试。
- 强类型安全:编译期即可发现 SQL 错误,避免运行时才暴露的类型问题。
Drizzle 支持 PostgreSQL、MySQL、SQLite 和 D1(Cloudflare Workers 数据库)等主流数据库,并且提供 ORM 和 Drizzle Kit(迁移工具)两部分,分别负责数据访问和数据库迁移。
二、快速开始
2.1 安装
以 PostgreSQL 为例,先安装依赖:
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg
如果你的项目使用 SQLite:
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
2.2 定义 Schema
Drizzle 使用 TypeScript 对象定义表结构。创建一个 schema.ts:
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
age: integer('age'),
createdAt: timestamp('created_at').defaultNow(),
});
export type User = typeof users.$inferSelect; // 查询返回类型
export type NewUser = typeof users.$inferInsert; // 插入参数类型
这里我们创建了 users 表,字段类型用 pg-core 模块中的对应函数定义。$inferSelect 和 $inferInsert 能自动推导出查询和插入的类型,非常便捷。
2.3 建立连接
创建 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 });
对于 SQLite:
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
const sqlite = new Database('sqlite.db');
export const db = drizzle(sqlite, { schema });
2.4 使用 Drizzle Kit 执行迁移
先配置 drizzle.config.ts:
import type { Config } from 'drizzle-kit';
export default {
schema: './src/schema.ts',
out: './drizzle',
dialect: 'postgresql', // 或 'sqlite' / 'mysql'
dbCredentials: {
url: process.env.DATABASE_URL!,
},
} satisfies Config;
然后执行命令:
npx drizzle-kit generate
npx drizzle-kit migrate
generate 会根据 schema 生成 SQL 迁移文件,migrate 会将迁移应用到数据库。也可以使用 npx drizzle-kit push 跳过文件直接同步表结构,适合开发环境。
三、基础查询:CRUD 操作
3.1 插入(Create)
const newUser = await db.insert(users).values({
name: '张三',
email: 'zhangsan@example.com',
age: 25,
});
// 返回受影响行数(默认)
// 返回插入的数据(PostgreSQL 支持)
const [user] = await db.insert(users).values({ name: '李四', email: 'lisi@example.com' }).returning();
批量插入时使用数组:
await db.insert(users).values([
{ name: 'A', email: 'a@test.com' },
{ name: 'B', email: 'b@test.com' },
]);
3.2 查询(Select)
const allUsers = await db.select().from(users);
// 全部用户
const someUsers = await db.select().from(users).where(eq(users.age, 25));
// 年龄等于 25 的用户
const youngUsers = await db.select({ name: users.name, email: users.email })
.from(users)
.where(lt(users.age, 30));
// 只选特定字段,年龄小于 30
// 排序与分页
const pageUsers = await db.select()
.from(users)
.orderBy(desc(users.createdAt))
.limit(10)
.offset(20);
3.3 更新(Update)
await db.update(users)
.set({ age: 26 })
.where(eq(users.name, '张三'));
// 使用表达式更新
import { sql } from 'drizzle-orm';
await db.update(users)
.set({ age: sql`${users.age} + 1` })
.where(eq(users.id, 1));
3.4 删除(Delete)
await db.delete(users).where(eq(users.id, 1));
// 清空表(谨慎!)
await db.delete(users);
四、条件查询与运算符
Drizzle 提供了丰富的 SQL 运算符,直接以函数形式暴露:
import { eq, ne, gt, lt, gte, lte, and, or, not, isNull, isNotNull, inArray, like, ilike, between } from 'drizzle-orm';
await db.select().from(users).where(
and(
eq(users.age, 30),
like(users.name, '%张%'),
)
);
await db.select().from(users).where(
or(
eq(users.age, 18),
eq(users.age, 60),
)
);
await db.select().from(users).where(inArray(users.id, [1,2,3]));
await db.select().from(users).where(between(users.age, 18, 30));
对于复杂条件,Drizzle 也支持 sql 模板标签,保证类型安全的同时提供全面灵活性:
await db.select().from(users).where(sql`${users.age} > 20 AND ${users.name} ILIKE '%张%'`);
五、表关联与连接(Joins)
5.1 定义关联关系
假设有两张表:用户和订单。
// schema.ts
export const orders = pgTable('orders', {
id: serial('id').primaryKey(),
userId: integer('user_id').references(() => users.id),
total: integer('total').notNull(),
status: text('status').default('pending'),
});
5.2 内连接(Inner Join)
const result = await db
.select()
.from(users)
.innerJoin(orders, eq(users.id, orders.userId));
// 返回扁平结构:{ users: {...}, orders: {...} }
5.3 左连接(Left Join)
const result = await db
.select()
.from(users)
.leftJoin(orders, eq(users.id, orders.userId));
// 没有订单的用户,orders 字段为 null
5.4 选择连接后的字段
const result = await db
.select({
userId: users.id,
userName: users.name,
orderTotal: orders.total,
})
.from(users)
.leftJoin(orders, eq(users.id, orders.userId));
5.5 多表连接
const result = await db
.select()
.from(users)
.innerJoin(orders, eq(users.id, orders.userId))
.innerJoin(payments, eq(orders.id, payments.orderId));
5.6 使用关系查询(Relational Query)
Drizzle 还提供了一种更高级的查询方式,类似 Prisma 的嵌套查询,需要先在 schema 中定义关系。在 schema.ts 中添加:
import { relations } from 'drizzle-orm';
export const usersRelations = relations(users, ({ many }) => ({
orders: many(orders),
}));
export const ordersRelations = relations(orders, ({ one }) => ({
user: one(users, { fields: [orders.userId], references: [users.id] }),
}));
然后可以这样查询:
const result = await db.query.users.findMany({
with: {
orders: true,
},
});
// 返回结构:{ id: 1, name: '张三', orders: [{ id: 10, total: 100 }, ...] }
甚至支持嵌套过滤:
const result = await db.query.users.findMany({
where: (users, { eq }) => eq(users.id, 1),
with: {
orders: {
where: (orders) => eq(orders.status, 'completed'),
},
},
});
关系查询需要将 db 实例的 schema 选项中传入新定义的 relations。确保 drizzle(pool, { schema }) 中 schema 包含所有表和 relations。
六、聚合与分组
Drizzle 支持几乎所有的 SQL 聚合函数:
import { count, sum, avg, min, max } from 'drizzle-orm';
const stats = await db.select({
total: count(users.id),
avgAge: avg(users.age),
}).from(users);
// 分组统计
const byAge = await db.select({
age: users.age,
count: count(users.id),
}).from(users)
.groupBy(users.age)
.having(gt(count(users.id), 1));
七、子查询与 CTE
7.1 子查询
在 where 条件中使用子查询:
const deletedUsers = db.select({ id: users.id }).from(users).where(eq(users.age, 0));
await db.delete(users).where(inArray(users.id, deletedUsers));
7.2 公共表达式(CTE)
Drizzle 提供了 with 关键字支持 CTE:
import { with: withFn } from 'drizzle-orm';
const cte = db.select({ id: users.id }).from(users).where(eq(users.age, 18));
const result = await withFn(cte, function(cte) {
return db.select().from(cte);
});
八、事务处理
事务是保证数据一致性的关键。Drizzle 提供了类似 JDBC 的事务 API:
await db.transaction(async (tx) => {
await tx.insert(users).values({ name: '王五', email: 'wangwu@test.com' });
await tx.update(orders).set({ status: 'paid' }).where(eq(orders.id, 1));
});
// 任一操作失败则全部回滚
事务中的操作必须使用事务对象 tx,而不是原始 db。
事务嵌套与隔离级别(PostgreSQL)
await db.transaction(async (tx) => {
// 嵌套事务(实际为保存点)
await tx.transaction(async (tx2) => {
// ...
});
}, {
isolationLevel: 'serializable',
});
隔离级别支持:'read_uncommitted'、'read_committed'、'repeatable_read'、'serializable'。
九、批量操作与性能优化
9.1 批量插入
使用 insert().values() 传入数组即可,但大批量数据时可使用 returning 配合生成器:
const input = Array.from({ length: 1000 }, (_, i) => ({
name: `User${i}`,
email: `user${i}@test.com`,
}));
await db.insert(users).values(input);
对于非常大的数据集,建议分批插入或使用 drizzle-orm 的 batch 方法(在某些方言中支持)。
9.2 使用 $dynamic 动态查询
如果需要根据前端参数动态构建查询,可使用 db.select().from(users).$dynamic():
async function search(name?: string, age?: number) {
const query = db.select().from(users).$dynamic();
if (name) {
query.where(eq(users.name, name));
}
if (age) {
query.where(eq(users.age, age));
}
return await query;
}
注意 .where() 可以多次调用,Drizzle 会以 AND 方式合并。
9.3 索引与查询计划
Drizzle 的 schema 定义支持索引和约束:
import { index, uniqueIndex } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull(),
age: integer('age'),
}, (table) => {
return {
emailIdx: uniqueIndex('email_idx').on(table.email),
ageIdx: index('age_idx').on(table.age),
};
});
合理创建索引能大幅提升查询性能。
十、Drizzle Kit:更强大的迁移管理
10.1 生成迁移 SQL
npx drizzle-kit generate --name=add_users_table
会在 drizzle 目录下生成带有时间戳的 SQL 文件,你可以查看和编辑这些文件。
10.2 自动应用迁移
npx drizzle-kit migrate
10.3 推送模式(不生成文件)
npx drizzle-kit push
此命令将 schema 直接同步到数据库,适用于快速原型开发,但生产环境建议使用迁移文件。
10.4 查看 schema 状态
npx drizzle-kit check
用于检查 schema 和迁移文件是否一致。
十一、高级技巧与最佳实践
11.1 类型推导技巧
避免显式定义 DTO,直接利用 typeof:
export type UserInsert = typeof users.$inferInsert;
export type UserSelect = typeof users.$inferSelect;
const data: UserInsert = { name: 'test', email: 'test@test.com' };
11.2 使用 sql 运算符执行原生 SQL
const result = await db.execute(sql`SELECT * FROM users WHERE age > 25`);
// 返回 raw result
对于复杂查询,可以直接使用 .execute() 运行原生 SQL,同时享受参数绑定:
await db.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
11.3 软删除模式
可以添加 deletedAt 字段并在查询时过滤:
export const users = pgTable('users', {
// ...
deletedAt: timestamp('deleted_at'),
});
const activeUsers = await db.select().from(users).where(isNull(users.deletedAt));
11.4 与框架集成
- Hono / Elysia / Fastify:直接在中间件中调用
db即可。 - Next.js (App Router):避免在客户端组件中使用数据库,使用服务端组件或 Route Handlers。
- Cloudflare Workers:使用
drizzle-orm/d1适配器。
示例(Hono):
import { Hono } from 'hono';
import { db } from './db';
const app = new Hono();
app.get('/users', async (c) => {
const users = await db.select().from(usersTable);
return c.json(users);
});
11.5 日志与调试
开启 SQL 日志以观察 Drizzle 生成的语句:
export const db = drizzle(pool, {
schema,
logger: { logQuery: (query, params) => console.log(query, params) },
});
或者使用简化版本:logger: true(在 node 环境中)。
11.6 避免 N+1 查询
关系查询的 .with 会预加载关联数据,避免循环查询。但对于复杂场景,建议使用显式 join 或预查询。
十二、Drizzle 与传统 ORM 的对比
| 特性 | Drizzle | Prisma | TypeORM |
|---|---|---|---|
| 代码生成 | 无 | 有 | 无 |
| 类型安全 | 极强 | 强 | 中等 |
| 学习曲线 | 低(懂 SQL 即可) | 中等 | 高 |
| 性能 | 高(几乎无开销) | 中(有引擎层开销) | 中 |
| 灵活度 | 高(可直接写 SQL) | 受限 | 较高 |
Drizzle 更适合注重性能和 SQL 掌控力的团队,而 Prisma 的自动迁移和管理界面在某些场景下更易用。
十三、常见坑与解决思路
- 忘记配置 schema 对象导致关系查询不可用 —— 确保
drizzle(pool, { schema })中传入了完整的 schema。 - 多个表同名字段产生类型混淆 —— 使用
.select({ alias: table.column })重命名。 - 事务中意外使用
db而不是tx—— 注意作用域,事务内必须用tx。 - SQLite 不支持
returning—— 如需返回数据,先查询后更新或使用关系查询。 - 日期类型时区问题 —— PostgreSQL 的
timestamp with time zone与 JavaScript 的 Date 相互转换时注意时区。
结语
Drizzle 以其精简的设计和强大的类型系统,正在成为 TypeScript 全栈开发者的新宠。通过本文的系统介绍,相信你已经掌握了从搭建环境、定义 schema、执行 CRUD 到事务、迁移和性能优化等全套技能。
Drizzle 的 API 设计非常贴近 SQL,这意味着你对数据库的理解越深,使用 Drizzle 就越得心应手。建议在实际项目中将官方文档作为参考手册,将这里的知识作为起点,逐步构建稳健高效的数据层。
Happy coding with Drizzle!
评论 0
暂无评论。