在 TypeScript 全栈开发中,直接拼接 SQL 容易漏字段、拼错表名,而传统 ORM 又常常用一套自定义 DSL 把 SQL 藏了起来。Drizzle ORM 选择了一条中间路线:它用 TypeScript 原生语法描述表和查询,把类型安全直接建立在 SQL 之上——你写的每一句查询都能在编译期推断出返回类型,运行时几乎没有额外开销。本文带你从安装、Schema 定义、类型安全查询到迁移上线,完整实战一遍。
一、为什么需要 Drizzle ORM
很多团队在 Node.js / Bun 项目里先是用 pg 裸写 SQL,到了后期发现类型对不上、重构没有安全感;于是又转向 Prisma 这类“代码生成 + 自定义查询语言”的方案,却牺牲了一部分对 SQL 的掌控力与包体积。
Drizzle 的核心卖点有三个:第一,Schema 就是普通的 TypeScript 对象,不生成隐藏代码;第二,查询结果类型由表结构自动推断,select 少了字段编译器立刻报错;第三,它几乎就是 SQL 的直译,你不需要重新学习一套抽象。对已经熟悉 PostgreSQL 的团队来说,迁移成本极低——如果你想复习 PG 与 SQL Server 的底层差异,可以先看这篇 PostgreSQL vs SQL Server 对比。
当然,Drizzle 并不是万能银弹。如果你的查询极其动态、需要在运行时拼接任意条件,或者团队里几乎没有人写 TypeScript,那么裸 SQL 加一层薄封装反而更直观。Drizzle 最适合的场景,是“Schema 稳定、查询可预测、又希望重构时编译器帮你兜住类型”的中后台业务——它把类型保护做足,却不替你隐藏 SQL 本身。
二、快速开始:安装与初始化
以 PostgreSQL 为例,先安装运行期依赖与开发期 CLI:
npm install drizzle-orm pg
npm install -D drizzle-kit
# 若使用 Bun 运行时
bun add drizzle-orm pg
bun add -d drizzle-kit
接着在根目录新建 drizzle.config.ts,告诉 CLI 去哪里找 Schema、生成到哪个目录、连哪套数据库:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
三、用 TypeScript 定义数据表(Schema)
Drizzle 的表定义就是纯 TypeScript。下面建两张关联表——users 与 posts,并声明外键与默认值:
import {
pgTable,
serial,
text,
integer,
timestamp,
} 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().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
views: integer('views').default(0),
});
字段类型与数据库列一一对应,serial 是自增主键,.references() 直接建立外键约束。当线上出现慢查询时,记得回看 PostgreSQL 慢查询优化实战 来定位索引问题。
Drizzle 还支持枚举 pgEnum、JSONB jsonb、数组等丰富类型,绝大多数 PostgreSQL 特有列都能在 TypeScript 里找到对应声明,几乎不需要回退到原生 DDL。当你需要给某列加 GIN 索引来加速 JSON 查询时,也可以直接写 index() 声明,迁移文件会一并生成,保证代码与数据库始终一致。
四、类型安全的增删改查
先用 pg 的连接池初始化 drizzle 实例,把 Schema 一并注入,这样后续查询才能拿到完整类型:
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 });
查询时条件用 eq 等辅助函数表达,返回行类型由 users 自动推导,result[0].email 不会因拼错字段而漏掉编译检查:
import { eq } from 'drizzle-orm';
// 查
const result = await db
.select()
.from(users)
.where(eq(users.email, 'ada@example.com'));
// 增
await db.insert(users).values({ name: 'Ada', email: 'ada@example.com' });
// 改
await db
.update(users)
.set({ name: 'Ada Lovelace' })
.where(eq(users.id, 1));
// 删
await db.delete(users).where(eq(users.id, 1));
五、关系查询与嵌套加载
在 Schema 同文件里补充 relations,即可用 db.query 的“关系 API”做嵌套查询,避免手写 JOIN:
import { relations } from 'drizzle-orm';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// 一键嵌套加载作者的全部文章
const data = await db.query.users.findMany({
with: { posts: true },
});
六、数据库迁移:generate 与 migrate
Drizzle 的迁移理念是“先生成 SQL 文件,再可控地执行”。本地开发可以用 push 直接同步结构,但生产环境务必走 generate + migrate,把 SQL 纳入版本管理:
# 开发期:根据 Schema 直接同步结构
npx drizzle-kit push
# 生产期:先生成带时间戳的 SQL 迁移文件
npx drizzle-kit generate
# 在 CI / 容器启动脚本里执行迁移
npx drizzle-kit migrate
如果你的项目用 Docker Compose 编排数据库与应用,可参考 Docker Compose 多容器实战;把 drizzle-kit migrate 写进启动脚本即可实现部署即迁移。
七、Drizzle 与传统方案对比
三种操作数据库的方式各有所长,选型时看团队对 SQL 掌控力与类型安全的要求:
| 维度 | Drizzle ORM | Prisma | 原生 SQL |
| 类型安全 | 编译期推断,零运行时 | 生成 Client,有运行时 | 需手写类型映射 |
| 包体积 | 极小(无代码生成) | 较大(引擎+生成) | 无 |
| SQL 掌控力 | 高,贴近原生 | 中,偏 DSL | 最高 |
| 迁移工具 | drizzle-kit | prisma migrate | 手动维护 |
八、生产落地清单与常见误区
把 Drizzle 真正用进生产,有几条经验值得提前记下来:
| 检查项 | 建议 |
| 连接池 | 用 pg 的 Pool 或服务端连接池,避免每请求新建连接 |
| 迁移策略 | 生产用 generate + migrate,避免直接 push |
| 环境变量 | DATABASE_URL 走密钥管理,别写进仓库 |
| DTO 推导 | 用 InferSelect / InferInsert 推导出入参类型 |
| CI 校验 | 在流水线里跑 drizzle-kit check 防止结构漂移 |
常见误区:一是把 push 当迁移用到生产,导致结构不可回滚;二是忽略连接池上限,高并发下打满 PG 连接;三是忘了在 CI 里做 drift 检查,Schema 与数据库悄悄分叉。想给 CI 接上自动化校验,可以复用 GitHub Actions 流水线实战 的思路。另外,给连接池设置合理的 idleTimeout 与 max,并配合 PostgreSQL 的连接数监控,能避免半夜被“remaining connection slots are reserved”告警叫醒——这在流量突增的促销活动里尤其关键。
九、延伸:向量与 AI 场景也能用
Drizzle 不止服务于普通业务表。当你在 PostgreSQL 里启用 pgvector 扩展、做 RAG 知识库时,同样可以用 drizzle 定义向量列并写入 embedding,与 pgvector 向量检索实战 形成完整链路;如果你更关心低代码搭建 LLM 应用,这篇 Dify + Ollama 私有化部署 也值得一读。前端工程化部分则可对照 前端工程化与 Monorepo 实战 组织代码结构。
十、Drizzle Kit 进阶:结构校验与可视化
随着表越来越多,仅靠 push 同步结构会逐渐积累隐患。drizzle-kit 还提供两个高频实用的子命令:check 会读取数据库当前结构并与本地 Schema 对比,提前发现“结构漂移”;studio 则启动一个本地网页,让你像用数据库客户端一样浏览和筛选数据,排查问题比直接连 psql 更顺手。
# 检查线上结构与本地 Schema 是否一致(CI 里也建议跑)
npx drizzle-kit check
# 启动可视化数据浏览(默认 http://localhost:4983)
npx drizzle-kit studio
总结一下:Drizzle ORM 用“TypeScript 原生 + SQL 直译 + 编译期类型安全”的组合,既保留了你对 SQL 的掌控,又补上了 ORM 最香的类型保护。从 npm install 到 drizzle-kit migrate,一小时内就能在真实 PostgreSQL 上跑通一套可维护的数据访问层。它和 TypeScript 生态(如前端工程化、Monorepo)天然契合,也便于在边缘运行时与 Serverless 中复用同一套查询逻辑,是中后台项目里值得长期信赖的数据访问层选型。



