DevToolBox免费
博客

Drizzle ORM 指南 2026:类型安全 SQL、Schema、迁移、关系与 Serverless

18 分钟阅读作者 DevToolBox
TL;DR

Drizzle ORM 是一个轻量级、类型安全的 TypeScript ORM,使用类 SQL 语法编写查询,提供完整自动补全和编译时类型检查。支持 PostgreSQL、MySQL 和 SQLite,零运行时开销。用 TypeScript 定义 schema,用 drizzle-kit 运行迁移,用 Drizzle Studio 浏览数据,轻松部署到无服务器环境。

什么是 Drizzle ORM?

Drizzle ORM 是一个采用根本不同方法的 TypeScript ORM。它不像传统 ORM 那样用方法链隐藏 SQL,而是拥抱 SQL 语法并将其直接映射到 TypeScript。每个查询看起来像 SQL,但具有完整的类型推断、自动补全和编译时验证。

为什么选择 Drizzle?

  • 类 SQL 语法:查询读起来像 SQL,而非方法链
  • 类型安全:从 schema 到查询结果的完整 TypeScript 推断
  • 轻量级:零依赖,最小包体积(gzip 后约 7.4KB)
  • 无服务器就绪:无连接池问题,适用于边缘运行时
  • 多数据库:PostgreSQL、MySQL、SQLite 使用相同 API
  • Drizzle Kit:CLI 工具提供 schema 迁移、推送和内省
  • Drizzle Studio:基于浏览器的数据库 GUI,用于查看和编辑数据

Drizzle vs Prisma vs TypeORM

每个 ORM 有不同的权衡。Drizzle 优先考虑 SQL 熟悉度和最小开销。Prisma 优先考虑开发者体验。TypeORM 遵循传统 ORM 模式。

FeatureDrizzlePrismaTypeORM
Schema languageTypeScriptPrisma Schema (.prisma)TypeScript decorators
Query styleSQL-likeMethod chainingRepository / QueryBuilder
Type safetyFull (inferred from schema)Full (generated client)Partial (decorators)
Bundle size~7.4KB gzipped~2MB (Prisma Client)~1.5MB
DependenciesZeroPrisma Engine (Rust)Many (reflect-metadata, etc.)
DatabasesPostgreSQL, MySQL, SQLitePostgreSQL, MySQL, SQLite, MongoDB, SQL ServerPostgreSQL, MySQL, SQLite, many more
ServerlessExcellent (minimal overhead)Good (engine binary needed)Poor (heavy startup)
Migrationsdrizzle-kit generate/pushprisma migrateTypeORM CLI
GUI ToolDrizzle StudioPrisma StudioNone built-in
Raw SQLFirst-class (sql`` tag)prisma.$queryRawquery()

Schema 定义

Drizzle schema 用纯 TypeScript 文件编写。每个表使用直接映射到 SQL 列类型的辅助函数定义。schema 同时作为数据库定义和 TypeScript 类型。

PostgreSQL Schema

// src/db/schema.ts
import {
  pgTable, serial, varchar, text, integer,
  timestamp, boolean, pgEnum, uniqueIndex
} from "drizzle-orm/pg-core";

// Define an enum
export const roleEnum = pgEnum("role", ["admin", "user", "guest"]);

// Users table
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  role: roleEnum("role").default("user").notNull(),
  bio: text("bio"),
  isActive: boolean("is_active").default(true).notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
}, (table) => ({
  emailIdx: uniqueIndex("email_idx").on(table.email),
}));

// Posts table
export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 500 }).notNull(),
  content: text("content").notNull(),
  published: boolean("published").default(false).notNull(),
  authorId: integer("author_id").notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// Comments table
export const comments = pgTable("comments", {
  id: serial("id").primaryKey(),
  body: text("body").notNull(),
  postId: integer("post_id").notNull()
    .references(() => posts.id, { onDelete: "cascade" }),
  authorId: integer("author_id").notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

MySQL Schema

// src/db/schema.ts (MySQL)
import {
  mysqlTable, serial, varchar, text,
  int, timestamp, boolean, mysqlEnum
} from "drizzle-orm/mysql-core";

export const users = mysqlTable("users", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  role: mysqlEnum("role", ["admin", "user", "guest"])
    .default("user").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

export const posts = mysqlTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 500 }).notNull(),
  content: text("content").notNull(),
  authorId: int("author_id").notNull()
    .references(() => users.id),
});

SQLite Schema

// src/db/schema.ts (SQLite)
import {
  sqliteTable, integer, text
} from "drizzle-orm/sqlite-core";

export const users = sqliteTable("users", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  role: text("role", { enum: ["admin", "user", "guest"] })
    .default("user").notNull(),
  createdAt: text("created_at")
    .default("CURRENT_TIMESTAMP").notNull(),
});

export const posts = sqliteTable("posts", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  title: text("title").notNull(),
  content: text("content").notNull(),
  authorId: integer("author_id").notNull()
    .references(() => users.id),
});

项目设置和配置

设置 Drizzle 需要安装 ORM 包、数据库驱动和 drizzle-kit。配置比其他 ORM 简单得多。

# Install Drizzle ORM + PostgreSQL driver
npm install drizzle-orm postgres
npm install -D drizzle-kit

# Or with MySQL
npm install drizzle-orm mysql2
npm install -D drizzle-kit

# Or with SQLite
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
// src/db/index.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";

const connection = postgres(process.env.DATABASE_URL!);
export const db = drizzle(connection, { schema });

// For MySQL:
// import { drizzle } from "drizzle-orm/mysql2";
// import mysql from "mysql2/promise";
// const pool = mysql.createPool(process.env.DATABASE_URL!);
// export const db = drizzle(pool, { schema });

// For SQLite:
// import { drizzle } from "drizzle-orm/better-sqlite3";
// import Database from "better-sqlite3";
// const sqlite = new Database("local.db");
// export const db = drizzle(sqlite, { schema });

类型安全查询

Drizzle 提供两种查询 API:类 SQL 核心 API 和关系查询 API。核心 API 直接映射 SQL 语法,关系 API 提供更声明式的嵌套数据获取方式。

SELECT 查询

import { eq, gt, like, and, or, desc, asc, count, sql } from "drizzle-orm";
import { db } from "./db";
import { users, posts } from "./db/schema";

// Select all users
const allUsers = await db.select().from(users);
// Type: { id: number; name: string; email: string; role: string; ... }[]

// Select specific columns
const userNames = await db.select({
  id: users.id,
  name: users.name,
}).from(users);
// Type: { id: number; name: string }[]

// WHERE clause with conditions
const activeAdmins = await db.select()
  .from(users)
  .where(
    and(
      eq(users.role, "admin"),
      eq(users.isActive, true)
    )
  );

// Complex filtering
const searchResults = await db.select()
  .from(users)
  .where(
    or(
      like(users.name, "%alice%"),
      like(users.email, "%alice%")
    )
  )
  .orderBy(desc(users.createdAt))
  .limit(10)
  .offset(0);

// Aggregation
const userCount = await db.select({
  role: users.role,
  total: count(),
}).from(users).groupBy(users.role);
// Type: { role: string; total: number }[]

INSERT 操作

// Insert a single row
const newUser = await db.insert(users).values({
  name: "Alice",
  email: "alice@example.com",
  role: "admin",
}).returning();
// Returns: { id: 1, name: "Alice", ... }[]

// Insert multiple rows
await db.insert(users).values([
  { name: "Bob", email: "bob@example.com" },
  { name: "Charlie", email: "charlie@example.com" },
]);

// Upsert (insert or update on conflict)
await db.insert(users)
  .values({ name: "Alice", email: "alice@example.com" })
  .onConflictDoUpdate({
    target: users.email,
    set: { name: "Alice Updated" },
  });

UPDATE 操作

// Update rows matching a condition
await db.update(users)
  .set({
    role: "admin",
    updatedAt: new Date(),
  })
  .where(eq(users.email, "alice@example.com"));

// Update with returning
const updated = await db.update(users)
  .set({ isActive: false })
  .where(eq(users.id, 1))
  .returning({ id: users.id, name: users.name });

DELETE 操作

// Delete rows matching a condition
await db.delete(users)
  .where(eq(users.id, 1));

// Delete with returning
const deleted = await db.delete(posts)
  .where(
    and(
      eq(posts.published, false),
      gt(posts.createdAt, new Date("2024-01-01"))
    )
  )
  .returning();

// Delete all rows (use with caution)
// await db.delete(users);

关系和连接

Drizzle 同时支持显式 SQL 连接和关系查询 API。关系与 schema 分开定义,保持表定义整洁。

定义关系

// src/db/relations.ts
import { relations } from "drizzle-orm";
import { users, posts, comments } from "./schema";

// User has many posts and comments
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
  comments: many(comments),
}));

// Post belongs to user, has many comments
export const postsRelations = relations(posts, ({ one, many }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
  comments: many(comments),
}));

// Comment belongs to post and user
export const commentsRelations = relations(comments, ({ one }) => ({
  post: one(posts, {
    fields: [comments.postId],
    references: [posts.id],
  }),
  author: one(users, {
    fields: [comments.authorId],
    references: [users.id],
  }),
}));

SQL 连接

// Inner join: users with their posts
const usersWithPosts = await db.select({
  userId: users.id,
  userName: users.name,
  postTitle: posts.title,
  postCreated: posts.createdAt,
}).from(users)
  .innerJoin(posts, eq(users.id, posts.authorId))
  .where(eq(posts.published, true));

// Left join: all users, including those without posts
const allUsersWithPosts = await db.select({
  userId: users.id,
  userName: users.name,
  postTitle: posts.title,
}).from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

// Multi-table join
const postWithComments = await db.select({
  postTitle: posts.title,
  commentBody: comments.body,
  authorName: users.name,
}).from(posts)
  .innerJoin(comments, eq(posts.id, comments.postId))
  .innerJoin(users, eq(comments.authorId, users.id))
  .where(eq(posts.id, 1));

关系查询

// Fetch user with their posts and comments (nested)
const userWithData = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: [desc(posts.createdAt)],
      limit: 5,
      with: {
        comments: {
          with: {
            author: true,
          },
        },
      },
    },
  },
});
// Result type is fully inferred:
// {
//   id: number; name: string; email: string;
//   posts: {
//     id: number; title: string;
//     comments: { body: string; author: { name: string } }[]
//   }[]
// }

// Find many with filtering
const recentPosts = await db.query.posts.findMany({
  where: eq(posts.published, true),
  orderBy: [desc(posts.createdAt)],
  limit: 20,
  with: {
    author: {
      columns: { id: true, name: true },
    },
  },
});

使用 drizzle-kit 迁移

Drizzle Kit 通过对比 TypeScript schema 和当前数据库状态生成 SQL 迁移文件。支持 generate、push 和 introspect 工作流。

生成迁移

# Generate a migration from schema changes
npx drizzle-kit generate

# Output: drizzle/0001_cool_migration.sql
# CREATE TABLE "users" (
#   "id" serial PRIMARY KEY NOT NULL,
#   "name" varchar(255) NOT NULL,
#   "email" varchar(255) NOT NULL,
#   ...
# );

# Apply migrations programmatically
# In your application entry point:
# import { migrate } from "drizzle-orm/postgres-js/migrator";
# await migrate(db, { migrationsFolder: "./drizzle" });

Push(开发环境)

# Push schema directly to database (development only)
# Skips migration files, applies changes immediately
npx drizzle-kit push

# Check what changes would be applied
npx drizzle-kit check

内省现有数据库

# Generate Drizzle schema from existing database
npx drizzle-kit introspect

# Output: drizzle/schema.ts
# This creates TypeScript schema files that match
# your existing database structure.
# Useful for migrating from another ORM or raw SQL.

Drizzle Studio

Drizzle Studio 是 drizzle-kit 内置的浏览器数据库 GUI。运行一个命令即可获得可视化界面,用于浏览表、编辑行、运行查询和检查 schema。

# Launch Drizzle Studio
npx drizzle-kit studio

# Opens a browser GUI at https://local.drizzle.studio
# Features:
# - Browse all tables and their data
# - Edit rows inline
# - Run custom SQL queries
# - View table schemas and relations
# - Filter and sort data
# - Export query results
#
# Uses the same drizzle.config.ts for DB connection.
# No additional setup required.

框架集成

Drizzle 适用于任何 TypeScript 运行时。以下是最流行框架的集成模式。

Next.js 集成

// src/db/index.ts (Next.js with Neon serverless)
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

// app/users/page.tsx (Server Component)
import { db } from "@/db";
import { users } from "@/db/schema";
import { desc } from "drizzle-orm";

export default async function UsersPage() {
  const allUsers = await db.select()
    .from(users)
    .orderBy(desc(users.createdAt))
    .limit(50);

  return (
    <ul>
      {allUsers.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// app/api/users/route.ts (Route Handler)
import { NextResponse } from "next/server";
import { db } from "@/db";
import { users } from "@/db/schema";

export async function POST(request: Request) {
  const body = await request.json();
  const newUser = await db.insert(users)
    .values(body)
    .returning();
  return NextResponse.json(newUser[0]);
}

Hono 集成

// src/index.ts (Hono with Drizzle)
import { Hono } from "hono";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { users } from "./db/schema";
import { eq } from "drizzle-orm";

const sql = postgres(process.env.DATABASE_URL!);
const db = drizzle(sql);

const app = new Hono();

app.get("/users", async (c) => {
  const allUsers = await db.select().from(users);
  return c.json(allUsers);
});

app.get("/users/:id", async (c) => {
  const id = Number(c.req.param("id"));
  const user = await db.select().from(users)
    .where(eq(users.id, id));
  if (!user.length) return c.json({ error: "Not found" }, 404);
  return c.json(user[0]);
});

export default app;

无服务器和边缘

// Cloudflare Workers with D1 (SQLite)
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

export default {
  async fetch(request: Request, env: Env) {
    const db = drizzle(env.DB, { schema });
    const allUsers = await db.select().from(schema.users);
    return Response.json(allUsers);
  },
};

// Vercel Edge with Neon
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";

export const runtime = "edge";

export async function GET() {
  const sql = neon(process.env.DATABASE_URL!);
  const db = drizzle(sql);
  const result = await db.select().from(users);
  return Response.json(result);
}

// AWS Lambda with PlanetScale
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Client } from "@planetscale/database";

const client = new Client({ url: process.env.DATABASE_URL! });
const db = drizzle(client);

事务和预处理语句

Drizzle 支持数据库事务用于原子操作,预处理语句用于提高重复查询的性能。

使用事务

// Basic transaction
await db.transaction(async (tx) => {
  // All operations inside share the same transaction
  const [newUser] = await tx.insert(users)
    .values({ name: "Alice", email: "alice@example.com" })
    .returning();

  await tx.insert(posts).values({
    title: "First Post",
    content: "Hello world!",
    authorId: newUser.id,
  });

  // If any operation fails, all changes are rolled back
});

// Nested transaction with savepoints
await db.transaction(async (tx) => {
  await tx.insert(users)
    .values({ name: "Bob", email: "bob@example.com" });

  try {
    await tx.transaction(async (nestedTx) => {
      await nestedTx.insert(posts).values({
        title: "Draft",
        content: "...",
        authorId: 999, // invalid FK
      });
    });
  } catch (e) {
    // Nested transaction rolled back,
    // outer transaction continues
    console.log("Nested transaction failed:", e);
  }
  // Bob is still inserted
});

预处理语句

import { sql, placeholder } from "drizzle-orm";

// Prepared statement with placeholder
const getUserById = db.select()
  .from(users)
  .where(eq(users.id, placeholder("id")))
  .prepare("get_user_by_id");

// Execute with different values (reuses query plan)
const user1 = await getUserById.execute({ id: 1 });
const user2 = await getUserById.execute({ id: 2 });

// Prepared statement with multiple placeholders
const searchUsers = db.select()
  .from(users)
  .where(
    and(
      eq(users.role, placeholder("role")),
      eq(users.isActive, placeholder("active"))
    )
  )
  .limit(placeholder("limit"))
  .prepare("search_users");

const admins = await searchUsers.execute({
  role: "admin",
  active: true,
  limit: 10,
});

// Raw SQL when needed
const result = await db.execute(
  sql`SELECT * FROM users WHERE name ILIKE ${"%" + search + "%"}`
);

性能优化

Drizzle 由于薄抽象层已经是最快的 TypeScript ORM 之一。这些模式帮助你在生产中获得更好的性能。

  • 使用 select() 指定列而非查询所有列以减少数据传输
  • 为 WHERE、ORDER BY 和 JOIN 条件中使用的列创建数据库索引
  • 对频繁执行的查询使用预处理语句以跳过查询规划
  • 在事务中批量处理相关写入以减少往返
  • 在无服务器环境中使用连接池
  • 在开发中启用查询日志以尽早发现 N+1 查询
  • 使用关系查询 API 获取嵌套数据,而非多次顺序查询
  • 对所有返回列表的查询添加 .limit() 以防止无限结果集
// Performance patterns

// 1. Select only needed columns
const names = await db.select({
  id: users.id,
  name: users.name,
}).from(users); // faster than db.select().from(users)

// 2. Add indexes in schema
import { index } from "drizzle-orm/pg-core";

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 500 }).notNull(),
  authorId: integer("author_id").notNull(),
  published: boolean("published").default(false),
  createdAt: timestamp("created_at").defaultNow(),
}, (table) => ({
  authorIdx: index("author_idx").on(table.authorId),
  publishedIdx: index("published_idx")
    .on(table.published, table.createdAt),
}));

// 3. Batch operations in transactions
await db.transaction(async (tx) => {
  await tx.insert(posts).values(batchOfPosts);
  await tx.update(users)
    .set({ updatedAt: new Date() })
    .where(eq(users.id, authorId));
});

// 4. Always use .limit()
const recent = await db.select().from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(20);  // never fetch unbounded result sets

// 5. Enable query logging
const db = drizzle(connection, {
  schema,
  logger: true,  // logs all queries to console
});

常见问题

Drizzle ORM 与 Prisma 相比如何?

Drizzle 使用 TypeScript 定义 schema 和类 SQL 查询语法,Prisma 使用自己的 schema 语言。Drizzle 零依赖、包体积更小,更适合无服务器环境。

Drizzle 支持 MongoDB 吗?

不支持。Drizzle 专为 SQL 数据库设计:PostgreSQL、MySQL 和 SQLite。MongoDB 可以考虑 Mongoose 或 Prisma。

可以对现有数据库使用 Drizzle 吗?

可以。使用 drizzle-kit introspect 从现有数据库生成 TypeScript schema 文件。

Drizzle 生产环境可用吗?

可以。Drizzle ORM 被许多公司在生产中使用,API 稳定,积极维护。

生产环境如何处理数据库迁移?

使用 drizzle-kit generate 创建迁移文件,然后在 CI/CD 中运行。将迁移文件存入版本控制。

Drizzle 适用于无服务器平台吗?

非常适合。Drizzle 包体积小、零依赖,适用于 Vercel Edge、Cloudflare Workers、AWS Lambda。

什么是 Drizzle Studio?

Drizzle Studio 是 drizzle-kit 内置的浏览器数据库 GUI。运行 npx drizzle-kit studio 启动。

如何处理 Drizzle 中的关系?

Drizzle 提供两种方式:显式 SQL JOIN 和关系查询 API(使用 relations() 函数和 with 选项)。

Key Takeaways
  • Drizzle ORM 提供类 SQL 的 TypeScript 查询,具有完整类型推断和零运行时开销
  • 支持 PostgreSQL、MySQL 和 SQLite,API 一致且有数据库特定功能
  • TypeScript 中的 schema 定义同时充当数据库 DDL 和应用类型
  • drizzle-kit 处理迁移(generate、push、introspect)并包含 Drizzle Studio
  • 最小包体积(约 7.4KB)使 Drizzle 非常适合无服务器和边缘部署
  • 关系支持 SQL 连接和声明式关系查询 API
  • 事务、预处理语句和选择性列查询优化生产性能
𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterTSJSON to TypeScriptSQLSQL Formatter

相关文章

Prisma Schema 与关联关系指南

掌握 Prisma Schema 设计:模型、关联关系(1:1、1:N、M:N)、枚举、索引和迁移。常见数据库模式的实用示例。

Prisma vs Drizzle vs TypeORM: ORM 对比 2026

对比 TypeScript ORM:Prisma、Drizzle 和 TypeORM。

Next.js 高级指南:App Router、服务端组件、数据获取、中间件与性能优化

完整的 Next.js 高级指南,涵盖 App Router 架构、React 服务端组件、流式 SSR、数据获取模式、中间件、路由处理器、并行和拦截路由、缓存策略、ISR、图片优化和部署最佳实践。