Drizzle ORM 实战:类型安全的 SQL 数据库操作

在 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 ORMPrisma原生 SQL
类型安全编译期推断,零运行时生成 Client,有运行时需手写类型映射
包体积极小(无代码生成)较大(引擎+生成)无
SQL 掌控力高,贴近原生中,偏 DSL最高
迁移工具drizzle-kitprisma 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 中复用同一套查询逻辑,是中后台项目里值得长期信赖的数据访问层选型。

上一篇 Full GC 排查实战:MAT 定位 Java 内存泄漏
下一篇 PostgREST 实战:把 PostgreSQL 一键变成安全 REST API
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]