Drizzle ORM + Next.js + PostgreSQL 实战:从 Schema 定义到生产部署避坑

主题: drizzle-orm-nextjs-postgres-setup更新于: 2026/6/22作者:AgentFactory 技术团队

如果你正在寻找一个比 Prisma 更轻量、更接近 SQL 的 ORM 来搭配 Next.js,Drizzle 是一个值得认真考虑的选择。本文直接面向有 PostgreSQL 基础、希望在 Next.js 项目中获得类型安全且高性能数据库操作的开发者,重点解决 Schema 定义、迁移策略、生产环境连接管理以及常见报错。

它解决什么问题 / 适用场景

Drizzle ORM 的核心价值在于:用 TypeScript 写 Schema,自动推断类型,生成原生 SQL 迁移文件,且包体积极小。它特别适合以下场景:

  • 对 Prisma 的抽象层感到不满:你希望直接控制 SQL,而不是被 Prisma 的自定义 API 限制。Drizzle 的查询语法几乎就是 SQL 的 TypeScript 映射。
  • 无服务器环境(如 Vercel Edge Functions):Drizzle 约 50KB 的包体积远小于 Prisma 的 800KB+,冷启动时间更短。
  • 需要复杂查询或原生 SQL 支持:Drizzle 原生支持 sql 模板标签,可以无缝嵌入原生 SQL,无需 $queryRaw 这种别扭的 API。
  • 与 Next.js Server Components / Server Actions 搭配:直接在服务端组件或 Server Action 中导入 db 实例即可查询,无需额外 API 层。

安装与快速上手

1. 安装依赖

BASH
npm install drizzle-orm postgres && npm install -D drizzle-kit
  • drizzle-orm:核心 ORM 库。
  • postgres:底层 PostgreSQL 驱动(也可选 @neondatabase/serverless 等)。
  • drizzle-kit:开发依赖,用于生成迁移、推送 Schema、启动 Studio。

2. 配置数据库连接

创建 src/db/index.ts

TYPESCRIPT
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const connectionString = process.env.DATABASE_URL!;

// 生产环境连接池配置
const client = postgres(connectionString, {
  max: 10,               // 最大连接数
  idle_timeout: 30,      // 空闲超时(秒)
  connect_timeout: 10,   // 连接超时(秒)
});

export const db = drizzle(client);

3. 定义 Schema

创建 src/db/schema.ts

TYPESCRIPT
import { pgTable, serial, text, 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(),
});

4. 生成并运行迁移

BASH
# 生成迁移文件(推荐生产环境使用)
npx drizzle-kit generate

# 运行迁移
npx drizzle-kit migrate

# 快速原型开发(直接推送 Schema,不生成迁移文件)
npx drizzle-kit push

5. 在 Server Component 中使用

TYPESCRIPT
// app/users/page.tsx
import { db } from '@/db';
import { users } from '@/db/schema';

export default async function UsersPage() {
  const allUsers = await db.select().from(users);
  return <pre>{JSON.stringify(allUsers, null, 2)}</pre>;
}

核心配置 / 参数说明

命令用途适用阶段注意事项
drizzle-kit push直接推送 Schema 到数据库开发/原型不生成迁移文件,不可版本控制,禁止用于生产
drizzle-kit generate生成 SQL 迁移文件到 ./drizzle 文件夹开发/CI每次 Schema 变更后运行,生成的文件应提交到 Git
drizzle-kit migrate执行已生成的迁移文件生产/CI确保迁移文件已生成且数据库连接正确
drizzle-kit studio启动浏览器端数据库管理界面开发默认端口 4983,可通过 --port--host 修改

生产环境迁移策略:永远使用 generate + migrate 组合。push 会直接覆盖数据库,无法回滚。

与同类方案对比(Drizzle vs Prisma)

对比维度Drizzle ORMPrisma
Schema 定义方式TypeScript 文件(.ts自定义 .prisma 文件
查询语法接近 SQL,如 db.select().from(users).where(eq(users.id, 1))自定义 API,如 prisma.user.findUnique({ where: { id: 1 } })
类型生成自动推断,无需手动运行命令需手动运行 prisma generate
包体积~50KB~800KB + 二进制引擎
原生 SQL 支持原生支持 sql 模板标签通过 $queryRaw 支持,但类型推断较弱
迁移文件生成可版本控制的 SQL 文件自动生成,但难以手动编辑
学习曲线适合懂 SQL 的开发者适合不熟悉 SQL 的开发者

Drizzle 的独特优势:TypeScript Schema 可复用变量和函数,例如:

TYPESCRIPT
const withTimestamps = {
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
};

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  ...withTimestamps,
});

生产环境实践与注意事项

1. 连接池管理:解决 HMR 导致的连接泄漏

在 Next.js 开发模式下,HMR 会导致模块重新加载,每次重新加载都会创建新的数据库连接,最终耗尽连接池。必须使用 globalThis 单例模式

TYPESCRIPT
// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const connectionString = process.env.DATABASE_URL!;

const globalForDb = globalThis as unknown as {
  client: ReturnType<typeof postgres> | undefined;
};

const client = globalForDb.client ?? postgres(connectionString, {
  max: 10,
  idle_timeout: 30,
  connect_timeout: 10,
});

if (process.env.NODE_ENV !== 'production') {
  globalForDb.client = client;
}

export const db = drizzle(client);

2. 安全与权限

  • 数据库用户权限:连接字符串中的用户应仅拥有 SELECT, INSERT, UPDATE, DELETE 等必要权限,禁止使用超级用户
  • 网络安全:确保 PostgreSQL 端口(默认 5432)不对外暴露。在云环境使用 VPC 或安全组限制访问来源。
  • 环境变量DATABASE_URL 不应硬编码,使用 .env.local 或云服务商的环境变量服务。

3. 并发与锁

Drizzle 不提供内置的乐观锁或悲观锁。如果需要处理并发写入冲突,需自行实现:

  • 乐观锁:在表中添加 version 字段,更新时检查版本号。
  • 悲观锁:使用 PostgreSQL 的 SELECT ... FOR UPDATE
TYPESCRIPT
// 悲观锁示例
const [user] = await db
  .select()
  .from(users)
  .where(eq(users.id, 1))
  .for('update'); // 注意:Drizzle 的 for update 语法需使用 sql 模板标签

常见报错与排查

报错 1:Cannot find module 'drizzle-orm/postgres-js'

原因:未安装 drizzle-ormpostgres 包。

解决

BASH
npm install drizzle-orm postgres

如果使用 pnpm,可能需要配置 public-hoist-patterns 或使用 --shamefully-hoist 标志。

报错 2:Connection refused. Is the database running? (ECONNREFUSED)

原因:PostgreSQL 服务未启动或连接信息错误。

解决

  1. 检查 PostgreSQL 服务状态:docker ps(如果使用 Docker)或 systemctl status postgresql
  2. 验证 DATABASE_URL 格式:postgresql://user:password@host:port/database
  3. 本地开发默认使用 localhost:5432,确认端口未被占用。

报错 3:relation "users" does not exist

原因:表尚未创建,或 Schema 中的表名与数据库中的实际表名不一致。

解决

BASH
# 生成迁移文件
npx drizzle-kit generate

# 运行迁移
npx drizzle-kit migrate

# 如果使用 push 模式(仅开发环境)
npx drizzle-kit push

报错 4:开发模式下出现多个数据库连接(HMR 问题)

原因:Next.js HMR 导致模块重新加载,每次加载都创建新连接。

解决:使用上文提到的 globalThis 单例模式管理连接。

常见问题 FAQ

Q: Drizzle ORM 是否支持数据库迁移的回滚操作?

A: Drizzle Kit 本身不提供内置的 rollback 命令。但生成的迁移文件是 SQL 文件,你可以手动编写回滚 SQL 并执行。推荐的做法是:在生成迁移文件后,立即编写对应的 down.sql 文件,并在部署脚本中实现回滚逻辑。或者使用第三方工具如 dbmate 来管理迁移版本。

Q: 如何在 Next.js 的 API Routes 或 Server Actions 中使用 Drizzle?

A: 在 API Routes 中,直接导入 db 实例并使用即可,与 Server Components 类似。在 Server Actions 中,由于它们也在服务端运行,同样可以直接使用 db。注意:在 Server Actions 中执行写操作时,建议使用 try-catch 处理错误,并返回适当的响应。

TYPESCRIPT
// app/actions.ts
'use server';
import { db } from '@/db';
import { posts } from '@/db/schema';

export async function createPost(formData: FormData) {
  try {
    await db.insert(posts).values({
      title: formData.get('title') as string,
      content: formData.get('content') as string,
    });
    return { success: true };
  } catch (error) {
    return { success: false, error: String(error) };
  }
}

Q: Drizzle 是否支持连接池配置?如何优化生产环境性能?

A: Drizzle 本身不管理连接池,连接池由底层驱动(如 postgres.js)管理。postgres.js 默认使用连接池,可通过参数配置:

TYPESCRIPT
const client = postgres(connectionString, {
  max: 10,           // 最大连接数
  idle_timeout: 30,  // 空闲超时(秒)
  connect_timeout: 10, // 连接超时(秒)
});

生产环境建议:

  1. 设置合理的 max 连接数(通常 10-20)。
  2. 启用空闲超时,避免连接长时间占用。
  3. 使用 PgBouncer 等外部连接池工具。
  4. 在 Vercel 等无服务器环境,考虑使用 Neon 或 Supabase 的 serverless 驱动,它们自动管理连接。

相关深度解决方案

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 PostgreSQL WAL 归档与 PITR 深度实战:从零搭建生产级灾难恢复体系

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 PostgreSQL → Kafka → MySQL CDC 实时同步:Debezium 深度配置、生产调优与故障排查白皮书