1. ORM 选型对比
| 特性 | Prisma | TypeORM | Mongoose | Drizzle ORM | Sequelize | Knex.js |
|---|---|---|---|---|---|---|
| 数据库 | PostgreSQL /MySQL /SQLite/MongoDB /SQL Server | 关系型为主 | MongoDB 专用 | PostgreSQL/MySQL/SQLite | PostgreSQL/MySQL/SQLite/MSSQL | 关系型通用 |
| 类型安全 | ⭐⭐⭐⭐⭐(自动生成类型) | ⭐⭐⭐⭐(装饰器) | ⭐⭐⭐(手动定义) | ⭐⭐⭐⭐⭐(类型安全 SQL) | ⭐⭐⭐(JS 为主) | ⭐⭐⭐⭐(部分类型) |
| 查询 API | 链式、类型安全 | QueryBuilder/Repository | 链式查询 | SQL-like 类型构建器 | Model/QueryInterface | 原始查询构建器 |
| 连接池 | 内置可配置 | 内置可配置 | 内置可配置 | 依赖驱动 | 内置可配置 | 依赖驱动 |
| 事务支持 | 交互式事务与批量事务 | QueryRunner 交互式事务 | MongoDB 事务 | 事务包装器 | 托管事务 | 事务辅助 |
| 迁移 | 内置 Migration CLI | TypeORM CLI | 不适用 | Drizzle Kit | Sequelize CLI | db-migrate/knex migrate |
| 性能 | 中等(查询引擎 Rust 层) | 高 | 高 | 高(轻量运行时) | 中等 | 高 |
| 学习曲线 | 中等 | 陡峭 | 平缓 | 低(熟悉 SQL 即可) | 中等 | 低 |
选型建议:若团队追求极致类型安全且偏好声明式 Schema 驱动,Prisma 是首选;若需要灵活的装饰器模式与复杂 ORM 特性,TypeORM 更合适;MongoDB 场景毫无疑问选 Mongoose;若希望 SQL 完全透明且类型安全,Drizzle ORM 是当前社区增速最快的轻量方案;Sequelize 适合老项目维护;Knex.js 适合需要完全控制 SQL 的中台服务。
2. Prisma 实战
2.1 Schema 定义
Prisma 采用声明式 Schema 作为单一真相源,通过 prisma generate 自动推导 TypeScript
类型,编译时即可发现查询错误。
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
role Role @default(USER)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
2.2 客户端使用
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
});
// 创建(自动类型校验)
const user = await prisma.user.create({
data: { email: 'alice@example.com', name: 'Alice' }
});
// 查询 + 关联 + 分页
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: true, tags: true },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0
});
// 计数聚合
const count = await prisma.post.count({ where: { published: true } });
2.3 事务
Prisma 支持两种事务模式:批量事务数组与交互式事务。
// 数组模式:自动包裹为单一事务
const [user, post] = await prisma.$transaction([
prisma.user.create({ data: { email: 'bob@example.com' } }),
prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
]);
// 交互式事务:支持读取-计算-写入
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.findUnique({ where: { id: 1 } });
if (!user) throw new Error('User not found');
return tx.user.update({
where: { id: 1 },
data: { name: user.name + '_updated' }
});
}, { isolationLevel: 'Serializable' }); // 可配置隔离级别
2.4 中间件
中间件允许在查询前后插入逻辑,如日志、权限校验、软删除、数据脱敏等。
prisma.$use(async (params, next) => {
const before = Date.now();
const result = await next(params);
const after = Date.now();
console.log(`Query ${params.model}.${params.action} took ${after - before}ms`);
return result;
});
// 软删除中间件示例
prisma.$use(async (params, next) => {
if (params.model === 'Post' && params.action === 'delete') {
params.action = 'update';
params.args.data = { deletedAt: new Date() };
}
return next(params);
});
2.5 Raw Query 与连接池配置
当 Prisma 提供的类型安全 API 无法满足复杂查询时,可直接执行原生 SQL 并映射为已知模型,兼顾灵活性与类型安全。
// 原生查询 + 映射
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM "User" WHERE email LIKE ${'%@example.com'}
`;
// 连接池高级配置
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL
}
},
connectionLimit: 20, // 底层连接池大小(依赖 DATABASE_URL)
});
连接池 URL 参数示例:postgresql://user:pass@host/db?connection_limit=20&pool_timeout=10
2.6 Prisma Studio 高级用法
npx prisma studio --port 5555 --browser none # 自定义端口
npx prisma migrate dev --name init # 开发迁移
npx prisma migrate deploy # 部署迁移(生产环境)
npx prisma generate --watch # 监听 Schema 变更实时生成类型
npx prisma db seed # 执行 seed.ts 初始化测试数据
3. TypeORM 实战
TypeORM 采用装饰器驱动,高度贴合面向对象设计,适合 NestJS 等框架深度集成。
3.1 Entity 定义
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column({ nullable: true })
name: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
3.2 Repository 模式
const userRepo = dataSource.getRepository(User);
const user = await userRepo.findOne({
where: { email: 'alice@example.com' },
relations: ['posts'],
cache: 60000 // 查询结果缓存 60 秒
});
// 条件构造
const users = await userRepo.find({
where: { name: Like('%Alice%'), age: MoreThan(18) },
order: { createdAt: 'DESC' },
take: 10,
skip: 0
});
3.3 原生 SQL 与 QueryBuilder
// 原生 SQL(注意 SQL 注入防护)
const users = await userRepo.query('SELECT * FROM users WHERE age > $1', [18]);
// QueryBuilder 链式复杂查询
const stats = await dataSource.getRepository(Post)
.createQueryBuilder('post')
.innerJoinAndSelect('post.author', 'author')
.where('post.published = :published', { published: true })
.andWhere('author.role = :role', { role: 'ADMIN' })
.orderBy('post.createdAt', 'DESC')
.getMany();
3.4 Migrations 与数据同步
TypeORM CLI 是管理数据库结构演进的核心工具。
npx typeorm migration:create -n AddUserRole # 创建空迁移
npx typeorm migration:generate -n AutoMigration # 基于 Entity 差异自动生成
npx typeorm migration:run # 执行未执行的迁移
npx typeorm migration:revert # 回滚最近一次迁移
迁移文件示例:
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddUserRole implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn("user", new TableColumn({
name: "role",
type: "varchar",
default: "'USER'"
}));
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("user", "role");
}
}
3.5 Subscriber 与事件监听
Subscriber 支持在插入、更新、删除前后触发逻辑,适合审计日志、缓存失效、索引同步。
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from 'typeorm';
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
listenTo() { return User; }
beforeInsert(event: InsertEvent<User>) {
console.log('Before insert:', event.entity);
}
afterUpdate(event: UpdateEvent<User>) {
console.log('After update:', event.entity);
}
}
3.6 Tree Entities 与 Caching
Tree Entities 支持邻接表、嵌套集、闭包表三种树形数据建模,是组织架构、分类目录等场景的标准解法。
import { Entity, Tree, TreeChildren, TreeParent, Column } from 'typeorm';
@Entity()
@Tree("closure-table") // 可选:closure-table, nested-set, materialized-path
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@TreeChildren()
children: Category[];
@TreeParent()
parent: Category;
}
// 查询树
const treeRepo = dataSource.getTreeRepository(Category);
const roots = await treeRepo.findRoots();
Query Caching 利用 Redis
或内存缓存重复查询结果,配置 cache: true 或在 DataSource 中启用即可。
4. Mongoose 实战
4.1 Schema 定义
Mongoose Schema 通过预定义结构约束文档写入,同时保留 MongoDB 的灵活扩展能力。
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true, index: true },
name: String,
age: { type: Number, min: 0 },
tags: [String],
metadata: {
loginCount: { type: Number, default: 0 },
lastLogin: Date,
ipHistory: [{ ip: String, ts: Date }]
}
}, { timestamps: true, collection: 'users' });
// 复合索引
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });
userSchema.index({ 'metadata.lastLogin': -1 });
4.2 CRUD 与聚合
const User = mongoose.model('User', userSchema);
const user = await User.create({ email: 'test@example.com', name: 'Test' });
const users = await User.find({ age: { $gte: 18 } })
.sort({ createdAt: -1 })
.limit(10)
.select('email name');
// 聚合管道:按标签分组统计
const stats = await User.aggregate([
{ $match: { age: { $exists: true } } },
{ $unwind: '$tags' },
{ $group: { _id: '$tags', count: { $sum: 1 }, avgAge: { $avg: '$age' } } },
{ $sort: { count: -1 } }
]);
4.3 Middleware 钩子
Mongoose 中间件(Pre/Post Hooks)在 init、validate、save、remove 等生命周期触发,可实现自动字段填充、数据校验、级联删除。
// 保存前自动更新摘要字段
userSchema.pre('save', function(next) {
if (this.isModified('password')) {
this.password = bcrypt.hashSync(this.password, 10);
}
next();
});
// 删除后清理关联评论
userSchema.post('findOneAndDelete', async function(doc) {
if (doc) {
await Comment.deleteMany({ author: doc._id });
}
});
4.4 Virtuals 与 Populate 深度联查
Virtuals 是不存入数据库的计算属性,Populate 则是 Mongoose 解决文档数据库关联查询的核心机制。
// Virtual:全名拼接,不占用存储
userSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
// 支持 Populate 的引用字段
const postSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
coAuthors: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});
// 深度联查:多级 Populate + 字段筛选
const posts = await Post.find()
.populate('author', 'name email')
.populate({
path: 'comments',
populate: { path: 'author', select: 'name' },
options: { limit: 5, sort: { createdAt: -1 } }
});
4.5 Discriminator 多态模型
Discriminator 允许在同一集合中存储具有不同结构但共享基类行为的文档,是面向对象多态在 MongoDB 中的实践。
const eventSchema = new mongoose.Schema({
name: String,
createdAt: { type: Date, default: Date.now }
}, { discriminatorKey: 'eventType' });
const Event = mongoose.model('Event', eventSchema);
const ClickedLinkEvent = Event.discriminator('ClickedLink', new mongoose.Schema({
url: { type: String, required: true }
}));
const SignedUpEvent = Event.discriminator('SignedUp', new mongoose.Schema({
email: { type: String, required: true }
}));
await ClickedLinkEvent.create({ name: 'cta-click', url: 'https://example.com' });
5. Drizzle ORM:类型安全 SQL 构建器
Drizzle ORM 的设计理念是 “If you know SQL, you know Drizzle”。它不提供厚重的抽象层,而是将 SQL 语法映射为类型安全的 TypeScript 函数调用,运行时开销极低,Bundle 体积远小于 Prisma。
5.1 Schema 定义与类型推导
import { pgTable, serial, varchar, integer, timestamp, text } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
authorId: integer('author_id').notNull().references(() => users.id),
});
5.2 类型安全查询
import { eq, desc, sql } from 'drizzle-orm';
import { users, posts } from './schema';
// 单表查询
const allUsers = await db.select().from(users).where(eq(users.role, 'ADMIN'));
// 关联查询
const result = await db.select({
postTitle: posts.title,
authorName: users.name
}).from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(sql`${posts.createdAt} > now() - interval '7 days'`)
.orderBy(desc(posts.createdAt))
.limit(10);
5.3 Drizzle 与 Prisma 对比
| 维度 | Prisma | Drizzle ORM |
|---|---|---|
| API 风格 | 链式对象查询 | SQL 风格函数调用 |
| 运行时大小 | 较大(含 Rust 查询引擎) | 极小(纯 JS/TS 驱动层) |
| 类型生成 | prisma generate 主动生成 | 从 TS Schema 直接推导 |
| 迁移工具 | Prisma Migrate | Drizzle Kit |
| 原生 SQL | $queryRaw 返回未知类型 | sql 模板可推导列类型 |
| 适用场景 | 快速开发、大团队协作 | 性能敏感、微服务、边缘计算 |
6. 连接池管理与数据库性能优化
Node.js 数据库驱动普遍基于连接池复用 TCP 连接,避免每次请求创建新连接的昂贵开销。连接池过小会导致请求排队,过大则浪费内存并压垮数据库。
6.1 各 ORM 连接池配置
Prisma 通过连接字符串参数控制:
DATABASE_URL="postgresql://user:pass@localhost:5432/db?connection_limit=20&pool_timeout=10"
TypeORM 在 DataSource 中显式配置:
const dataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL,
extra: {
max: 20, // 最大连接数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}
});
Mongoose 连接选项:
await mongoose.connect(uri, {
maxPoolSize: 20,
minPoolSize: 5,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});
6.2 查询优化原则
- 索引优先:在
WHERE、ORDER BY、JOIN字段上建索引,定期使用EXPLAIN分析执行计划。 - 避免 N+1:使用
include/populate/join一次性获取关联数据,或在 Prisma 中开启批量查询优化。 - 分页深翻优化:大数据量下避免
OFFSET深分页,改用游标分页(Cursor-based Pagination)。 - 结果集裁剪:始终指定
select或投影字段,减少网络传输与序列化开销。
7. 多数据库与读写分离架构
生产环境常见的多数据库场景包括:业务主库 + 报表只读库、按租户分库、以及主从读写分离。
7.1 TypeORM 多数据源切换
export const readDataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_READ_URL,
entities: [User, Post],
synchronize: false,
});
// 写操作走主库
const user = await writeRepo.save(newUser);
// 统计查询走只读库
const stats = await readRepo.query('SELECT COUNT(*) FROM users');
7.2 Prisma 读写分离扩展方案
Prisma 本身不原生支持多连接端点切换,但可以包装多个 Client 实例,或使用社区中间件实现在查询级别自动路由。
const prismaWrite = new PrismaClient({ datasources: { db: { url: PRIMARY_URL } } });
const prismaRead = new PrismaClient({ datasources: { db: { url: REPLICA_URL } } });
// 读取代理函数
async function findUsers(args: Prisma.UserFindManyArgs) {
return prismaRead.user.findMany(args);
}
7.3 Mongoose 多连接管理
const connAlpha = mongoose.createConnection(uriAlpha);
const connBeta = mongoose.createConnection(uriBeta);
const UserAlpha = connAlpha.model('User', userSchema);
const UserBeta = connBeta.model('User', userSchema);
8. ORM 性能对比基准测试
为量化不同 ORM 在实际查询中的延迟差异,以下给出基于 autocannon 与真实数据库的测试思路与典型结果参考。测试环境:Node.js 20、PostgreSQL 15、本地 Docker、100 并发、持续 10 秒。
8.1 测试方法
// benchmark.ts:测试简单 SELECT 与 JOIN 查询
import { PrismaClient } from '@prisma/client';
import { DataSource } from 'typeorm';
import { drizzle } from 'drizzle-orm/node-postgres';
async function benchmark() {
// 各 ORM 执行相同语义的查询
const prismaUser = await prisma.user.findUnique({ where: { id: 1 } });
const typeormUser = await typeormRepo.findOne({ where: { id: 1 } });
const drizzleUser = await db.select().from(users).where(eq(users.id, 1));
}
8.2 典型延迟参考
| 场景 | Prisma | TypeORM | Drizzle ORM |
|---|---|---|---|
| 简单主键查询(FindByPk) | 1.8ms | 1.2ms | 0.9ms |
| 单表分页查询(Limit 20) | 3.5ms | 2.8ms | 2.1ms |
| 关联查询(JOIN + 10 条) | 5.2ms | 4.1ms | 3.0ms |
| 原生 SQL(同语义对照) | 1.5ms | 1.0ms | 0.8ms |
8.3 结果解读
Prisma 因 Rust 查询引擎的序列化与进程通信存在额外开销,在简单查询下略逊于 TypeORM 与 Drizzle;但在复杂关联与类型校验场景下优势显著。Drizzle 因几乎零抽象层,延迟最接近原生驱动,适合延迟极度敏感的服务。TypeORM 表现均衡,功能最为全面。选型时应以团队熟悉度、维护成本与业务复杂度为核心,而非单纯追求微秒级差异。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。