Skip to content

🗄 Node.js 技术选型(四):数据库选型与 ORM — MySQL · PostgreSQL · MongoDB · Prisma

系列导读:数据库是后端的心脏,选错了轻则性能瓶颈,重则数据丢失。 本篇从关系型 vs 文档型对比出发,深入 Prisma / TypeORM / Mongoose 三大 ORM, 帮你为项目选对数据存储方案。


📊 1. 数据库类型总览

类型代表数据模型适用场景
关系型(RDBMS)MySQL / PostgreSQL表 + 行 + 列 + 关系强一致性、复杂查询、事务
文档型(NoSQL)MongoDBJSON 文档 + 集合灵活 Schema、快速迭代
键值型RedisKey-Value缓存、会话、排行榜
时序型InfluxDB / TimescaleDB时间戳 + 指标监控、IoT、日志
图数据库Neo4j节点 + 边社交关系、知识图谱

🆚 2. MySQL vs PostgreSQL vs MongoDB

2.1 核心对比

维度MySQLPostgreSQLMongoDB
类型关系型关系型(对象关系型)文档型
协议GPL v2PostgreSQL LicenseSSPL
JSON 支持🟡 JSON 类型(基础)🟢 JSONB(高级索引)🟢 原生 JSON
事务✅ ACID✅ ACID✅ 多文档事务(4.0+)
复杂查询🟢 优秀🟢 极其强大(CTE/窗口函数)🟡 聚合管道
全文搜索🟡 基础🟢 内置支持🟢 Atlas Search
扩展性🟡 主从复制🟡 逻辑复制 / Citus 分布式🟢 原生分片
性能(读)🟢 极快(简单查询)🟢 快🟢 极快
性能(写)🟢 快🟡 中等🟢 极快
生态成熟度🟢 最广泛🟢 增长最快🟢 NoSQL 第一

2.2 选型决策树

你的数据结构是什么样的?

├── 结构固定、关系复杂(用户-订单-商品)
│   ├── 需要高级 SQL(CTE、窗口函数、JSONB) → ✅ PostgreSQL
│   ├── 团队熟悉 MySQL + 追求极简部署 → ✅ MySQL
│   └── 需要 GIS 地理信息查询 → ✅ PostgreSQL(PostGIS)

├── 结构灵活、快速迭代(内容管理、配置中心)
│   └── ✅ MongoDB

├── 混合场景(部分结构固定 + 部分灵活)
│   └── ✅ PostgreSQL(JSONB 兼顾结构化和半结构化)

└── 超高并发写入 + 水平扩展
    └── ✅ MongoDB(原生分片)

🔧 3. ORM 选型:Prisma vs TypeORM vs Mongoose

3.1 一表对比

维度PrismaTypeORMMongoose
支持数据库PG / MySQL / SQLite / MongoDBPG / MySQL / SQLite / OracleMongoDB 专用
Schema 定义.prisma DSL 文件TypeScript 装饰器/EntitySchema + Model
类型安全🟢 编译时类型推导(极强)🟡 运行时(依赖装饰器)🟡 需手动泛型
Migration🟢 自动生成🟡 需手动管理❌ Schema-less
API 风格函数式链式调用Repository/ActiveRecord链式查询构建器
学习曲线🟢 低🟡 中等🟢 低
NestJS 集成✅ @nestjs/prisma✅ @nestjs/typeorm✅ @nestjs/mongoose
Star ⭐42k+35k+27k+

3.2 Prisma — 类型安全王者

prisma
// schema.prisma — 声明式数据模型
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  name      String?
  role      Role      @default(USER)
  posts     Post[]    // 一对多关系
  profile   Profile?  // 一对一关系
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  @@map("users")  // 映射表名
}

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[]     // 多对多关系
  createdAt DateTime  @default(now())

  @@index([authorId])
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String?
  avatar String?
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[]
}

enum Role {
  USER
  ADMIN
  EDITOR
}
typescript
// Prisma 查询 — 全程类型安全
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// 创建用户(带关联)
const user = await prisma.user.create({
  data: {
    email: 'test@example.com',
    name: 'Alice',
    profile: {
      create: { bio: '前端工程师' }    // 嵌套创建
    },
    posts: {
      create: [
        { title: 'Hello Prisma', content: '...' }
      ]
    }
  },
  include: {
    profile: true,
    posts: true
  }
})

// 复杂查询
const users = await prisma.user.findMany({
  where: {
    AND: [
      { role: 'ADMIN' },
      { posts: { some: { published: true } } }  // 关联过滤
    ]
  },
  select: {
    id: true,
    name: true,
    _count: { select: { posts: true } }  // 关联计数
  },
  orderBy: { createdAt: 'desc' },
  skip: 0,
  take: 20,
})

// 事务
const [order, payment] = await prisma.$transaction([
  prisma.order.create({ data: { ... } }),
  prisma.payment.create({ data: { ... } }),
])

3.3 TypeORM — 装饰器驱动

typescript
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, CreateDateColumn } from 'typeorm'
import { Post } from './post.entity'

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ unique: true })
  email: string

  @Column({ nullable: true })
  name: string

  @Column({ type: 'enum', enum: ['USER', 'ADMIN'], default: 'USER' })
  role: string

  @OneToMany(() => Post, (post) => post.author)
  posts: Post[]

  @CreateDateColumn()
  createdAt: Date
}

// 查询
const users = await userRepository.find({
  where: { role: 'ADMIN' },
  relations: ['posts'],
  order: { createdAt: 'DESC' },
  skip: 0,
  take: 20,
})

// QueryBuilder(复杂查询)
const result = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.role = :role', { role: 'ADMIN' })
  .andWhere('post.published = :published', { published: true })
  .orderBy('user.createdAt', 'DESC')
  .skip(0)
  .take(20)
  .getManyAndCount()

3.4 Mongoose — MongoDB 专属

typescript
// user.schema.ts(NestJS 风格)
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { HydratedDocument } from 'mongoose'

export type UserDocument = HydratedDocument<User>

@Schema({ timestamps: true, collection: 'users' })
export class User {
  @Prop({ required: true, unique: true })
  email: string

  @Prop()
  name: string

  @Prop({ enum: ['USER', 'ADMIN'], default: 'USER' })
  role: string

  @Prop({ type: [{ type: Object }] })
  addresses: Record<string, any>[]  // 嵌套灵活结构
}

export const UserSchema = SchemaFactory.createForClass(User)

// 查询
const users = await this.userModel
  .find({ role: 'ADMIN' })
  .sort({ createdAt: -1 })
  .skip(0)
  .limit(20)
  .populate('posts')    // 填充关联文档
  .exec()

// 聚合管道
const stats = await this.userModel.aggregate([
  { $match: { role: 'ADMIN' } },
  { $lookup: { from: 'posts', localField: '_id', foreignField: 'authorId', as: 'posts' } },
  { $addFields: { postCount: { $size: '$posts' } } },
  { $sort: { postCount: -1 } },
])

🔄 4. Migration(数据迁移)对比

维度PrismaTypeORMMongoose
迁移生成prisma migrate dev 自动生成 SQLtypeorm migration:generate无(Schema-less)
迁移文件.sql 文件TypeScript 文件不适用
回滚✅ 支持✅ 支持不适用
生产环境prisma migrate deploytypeorm migration:run无需迁移
种子数据prisma db seed手动脚本手动脚本
bash
# Prisma 迁移工作流
npx prisma migrate dev --name add_user_table   # 开发环境
npx prisma migrate deploy                       # 生产环境
npx prisma db seed                               # 填充种子数据
npx prisma studio                                # 可视化数据管理

📋 5. ORM 选型决策

你用什么数据库?

├── MongoDB
│   └── ✅ Mongoose(MongoDB 专用最佳选择)

├── PostgreSQL / MySQL
│   ├── 追求极致类型安全 + 现代化 API → ✅ Prisma
│   ├── 需要复杂原始 SQL + 灵活性 → ✅ TypeORM
│   └── NestJS 项目 + 团队无偏好 → ✅ Prisma(推荐)

└── 多数据库混合
    └── ✅ Prisma(2024 已支持 MongoDB + SQL 混用)

⚠️ 6. 常见踩坑

错误后果建议
表设计没有加索引数据量大时查询卡死WHERE/JOIN 字段必须建索引
MongoDB 存关系复杂数据关联查询性能差关系密集用关系型数据库
ORM 查询 N+1 问题一个列表 N 次数据库查询用 include/populate 预加载
Prisma 不用 select 过滤返回所有字段,浪费带宽用 select 只取需要的字段
不用事务做关联操作数据不一致关联写入必须用事务

✅ 本篇重点 Checklist


数据库选得好,半夜不加班。 下一篇我们聊 API 设计范式 — RESTful vs GraphQL vs tRPC


📝 作者:NIHoa | 系列:Node.js技术选型与架构系列 | 更新日期:2025-02-04