Node.js 开发手册NODE.JS

CHAPTER 06应用层 · DATA

数据层与数据库

SQLite/PostgreSQL/Mongo、ORM(Prisma/Sequelize)、连接池、事务与迁移。把数据可靠地存下来。

开始阅读难度中级先修第五章约2.5小时
06.1

存储选型

强一致、关系复杂、需要事务 → 关系型(PostgreSQL/MySQL);schema 灵活、写入量大、文档结构 → MongoDB。原型期 SQLite 零配置。

场景推荐
事务/强一致/报表PostgreSQL
快速原型/单机SQLite
灵活 schema/日志/画像MongoDB
06.2

原生驱动

直接用 pg(PostgreSQL)等驱动可完全控制 SQL,适合复杂查询与极致性能。

import pg from 'pg';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
console.log(rows);
参数化查询

永远用 $1, $2 占位符,不要字符串拼接 SQL,杜绝注入。

06.3

Prisma ORM

Prisma 用 schema 声明模型,自动生成类型安全的客户端与迁移,开发体验极佳。

// schema.prisma
model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  posts Post[]
}
model Post {
  id     Int  @id @default(autoincrement())
  title  String
  author User @relation(fields: [authorId], references: [id])
  authorId Int
}
import { PrismaClient } from '@prisma/client';
const db = new PrismaClient();
const user = await db.user.findUnique({
  where: { email: 'a@b.com' },
  include: { posts: true }
});
06.4

连接池

每个请求都新建数据库连接代价高。连接池复用连接;注意上限,避免超过数据库 max_connections。

// 全局单例,不要每个请求 new 一个
const pool = new pg.Pool({ max: 10 });
// Serverless 环境用完要结束,否则进程不退出
process.on('SIGTERM', () => pool.end());
池耗尽可能性

请求里忘记释放连接(未 await 或异常路径未关)会耗尽池,导致后续请求全部挂起。用 ORM 或 try/finally 保证释放。

06.5

事务

多个写操作要么全成要么全败,用事务包裹。Prisma 提供 $transaction,原生驱动用 BEGIN/COMMIT。

const result = await db.$transaction(async (tx) => {
  const order = await tx.order.create({ data: { total } });
  await tx.stock.update({
    where: { id: sku },
    data: { qty: { decrement: 1 } }
  });
  return order;          // 任一抛错自动回滚
});
06.6

迁移与种子

迁移把 schema 变更纳入版本管理;种子初始化基础数据。两者都应可重放、可回滚。

npx prisma migrate dev --name init   # 生成并执行迁移
npx prisma db seed                   # 灌入种子数据
06.7

MongoDB 文档库

Mongoose 提供 schema 建模与校验;原生 mongodb 驱动更轻。适合嵌套结构、灵活字段。

import mongoose from 'mongoose';
await mongoose.connect(process.env.MONGO_URL);
const User = mongoose.model('User', new mongoose.Schema({
  email: { type: String, required: true, unique: true }
}));
await User.create({ email: 'a@b.com' });
06.8

常见陷阱与 FAQ

连接池多大合适?

没有银弹。通常每实例 10–20,乘以实例数不超过数据库 max_connections。先小再观测调大。

ORM 还是原生 SQL?

业务 CRUD 用 ORM 提效、保证类型安全;复杂报表/批量用原生 SQL 控制执行计划。两者可共存。

迁移能在生产直接跑吗?

大表加字段/索引要锁表,应在低峰期用 CONCURRENTLY 等在线手段,并在 CI 演练。

06.9

实战练习与资源

延伸资源