Skip to content

Node.js 数据库操作

Node.js 中数据库连接管理、ORM 选型与性能优化

目录


连接管理

原生 mysql2 连接池

typescript
import mysql from 'mysql2/promise';

// 创建连接池
const pool = mysql.createPool({
  host: 'localhost',
  port: 3306,
  user: 'root',
  password: 'password',
  database: 'mydb',
  
  // 连接池配置
  connectionLimit: 10,       // 最大连接数
  queueLimit: 0,             // 等待队列无限制
  waitForConnections: true,  // 无可用连接时等待
  
  // 连接配置
  connectTimeout: 10000,     // 连接超时 10s
  enableKeepAlive: true,     // 保持连接
  keepAliveInitialDelay: 10000
});

// 使用连接池查询
async function query<T>(sql: string, params?: any[]): Promise<T[]> {
  const [rows] = await pool.execute(sql, params);
  return rows as T[];
}

// 获取单个连接(用于事务)
async function getConnection() {
  return pool.getConnection();
}

// 优雅关闭
process.on('SIGTERM', async () => {
  await pool.end();
  process.exit(0);
});

预处理语句防 SQL 注入

typescript
// ✅ 正确:使用参数化查询
const [users] = await pool.execute(
  'SELECT * FROM users WHERE id = ? AND status = ?',
  [userId, 'active']
);

// ❌ 危险:字符串拼接
const sql = `SELECT * FROM users WHERE id = ${userId}`; // SQL 注入风险

ORM 选型

方案对比

ORM类型特点适用场景
PrismaSchema-first类型安全、迁移工具完善新项目首选
TypeORMCode-first装饰器语法、成熟传统 ORM 用户
DrizzleSchema-first轻量、SQL-like API性能敏感场景
Knex.jsQuery Builder灵活、无 ORM 开销复杂查询

Prisma 实践

prisma
// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}
typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// 查询
const users = await prisma.user.findMany({
  where: { email: { contains: '@example.com' } },
  include: { posts: true },
  orderBy: { createdAt: 'desc' },
  take: 10
});

// 创建
const user = await prisma.user.create({
  data: {
    email: 'user@example.com',
    name: 'John',
    posts: {
      create: [
        { title: 'First Post' }
      ]
    }
  }
});

// 更新
await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Jane' }
});

// 删除
await prisma.user.delete({ where: { id: 1 } });

TypeORM 实践

typescript
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, ManyToOne } from 'typeorm';

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

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

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

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

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

// Repository 模式
const userRepository = dataSource.getRepository(User);

const users = await userRepository.find({
  where: { email: Like('%@example.com') },
  relations: ['posts'],
  order: { id: 'DESC' },
  take: 10
});

事务处理

原生事务

typescript
async function transferMoney(fromId: number, toId: number, amount: number) {
  const connection = await pool.getConnection();
  
  try {
    await connection.beginTransaction();
    
    // 扣款
    await connection.execute(
      'UPDATE accounts SET balance = balance - ? WHERE id = ?',
      [amount, fromId]
    );
    
    // 入账
    await connection.execute(
      'UPDATE accounts SET balance = balance + ? WHERE id = ?',
      [amount, toId]
    );
    
    await connection.commit();
  } catch (error) {
    await connection.rollback();
    throw error;
  } finally {
    connection.release();
  }
}

Prisma 事务

typescript
// 交互式事务
await prisma.$transaction(async (tx) => {
  const from = await tx.account.update({
    where: { id: fromId },
    data: { balance: { decrement: amount } }
  });
  
  if (from.balance < 0) {
    throw new Error('Insufficient balance');
  }
  
  await tx.account.update({
    where: { id: toId },
    data: { balance: { increment: amount } }
  });
});

// 批量事务
await prisma.$transaction([
  prisma.user.create({ data: { email: 'a@test.com' } }),
  prisma.user.create({ data: { email: 'b@test.com' } })
]);

事务隔离级别

级别脏读不可重复读幻读说明
READ UNCOMMITTED最低隔离
READ COMMITTED多数 DB 默认
REPEATABLE READMySQL 默认
SERIALIZABLE完全隔离
typescript
// Prisma 设置隔离级别
await prisma.$transaction(async (tx) => {
  // ...
}, {
  isolationLevel: 'Serializable'
});

性能优化

N+1 问题

typescript
// ❌ N+1 问题
const posts = await prisma.post.findMany();
for (const post of posts) {
  const author = await prisma.user.findUnique({
    where: { id: post.authorId }
  });
}

// ✅ 使用 include 一次加载
const posts = await prisma.post.findMany({
  include: { author: true }
});

// ✅ 使用 select 只取需要的字段
const posts = await prisma.post.findMany({
  select: {
    id: true,
    title: true,
    author: { select: { name: true } }
  }
});

索引优化

sql
-- 高频查询字段加索引
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_post_author ON posts(author_id);

-- 复合索引(左匹配原则)
CREATE INDEX idx_order_user_status ON orders(user_id, status, created_at);

慢查询分析

sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;

-- 查看执行计划
EXPLAIN SELECT * FROM users WHERE email LIKE '%@test.com';

批量操作

typescript
// ❌ 逐条插入
for (const user of users) {
  await prisma.user.create({ data: user });
}

// ✅ 批量插入
await prisma.user.createMany({
  data: users,
  skipDuplicates: true
});

// ✅ 原生批量插入
const values = users.map(u => `('${u.email}', '${u.name}')`).join(',');
await pool.execute(`INSERT INTO users (email, name) VALUES ${values}`);

连接池监控

typescript
import mysql from 'mysql2/promise';

const pool = mysql.createPool({ /* config */ });

// 监控连接池状态
setInterval(() => {
  const status = pool.pool;
  console.log({
    all: status.all.length,        // 所有连接
    idle: status.idle.length,      // 空闲连接
    queue: status.queue.length     // 等待队列
  });
}, 10000);

// 健康检查
async function healthCheck(): Promise<boolean> {
  try {
    await pool.execute('SELECT 1');
    return true;
  } catch {
    return false;
  }
}

高频面试题

Q1: 连接池大小如何设置?

经验公式connections = (core_count * 2) + effective_spindle_count

  • CPU 密集型:连接数 ≈ CPU 核心数
  • I/O 密集型:可适当增加到 2-4 倍核心数
  • 通用推荐:10-20 个连接,根据监控调整

Q2: 如何解决 N+1 查询问题?

  1. Eager Loading:使用 include / relations 预加载关联数据
  2. DataLoader:批量合并相同类型的查询
  3. 手动 JOIN:复杂场景使用原生 SQL
typescript
// DataLoader 示例
const userLoader = new DataLoader(async (ids: number[]) => {
  const users = await prisma.user.findMany({
    where: { id: { in: ids } }
  });
  return ids.map(id => users.find(u => u.id === id));
});

Q3: 事务中如何处理死锁?

  1. 统一获取锁顺序:按固定顺序访问表/行
  2. 减少事务时间:快进快出
  3. 重试机制:捕获死锁异常后重试
typescript
async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
  for (let i = 0; i < retries; i++) {
    try {
      return await fn();
    } catch (error: any) {
      if (error.code === 'ER_LOCK_DEADLOCK' && i < retries - 1) {
        await new Promise(r => setTimeout(r, 100 * (i + 1)));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

Q4: Prisma vs TypeORM 如何选择?

维度PrismaTypeORM
类型安全⭐⭐⭐⭐⭐⭐⭐⭐
学习曲线
原生 SQL支持支持
迁移工具完善一般
性能
生态新兴成熟

最佳实践

  • [ ] 使用连接池,避免频繁建立连接
  • [ ] 参数化查询防止 SQL 注入
  • [ ] 使用 include / relations 避免 N+1
  • [ ] 为高频查询字段添加索引
  • [ ] 事务中做好错误回滚
  • [ ] 监控连接池和慢查询
  • [ ] 批量操作代替循环单条操作

前端面试知识库