第 180 章:数据库高级特性
学习目标
- 掌握数据库连接池配置
- 学会读写分离
- 理解数据库事务传播
- 学会 SQL 调优与日志
一、连接池配置
typescript
TypeOrmModule.forRoot({
type: 'mysql',
// ... 其他配置
// 连接池
extra: {
connectionLimit: 10, // 最大连接数
waitForConnections: true, // 等待可用连接
queueLimit: 0, // 排队上限(0 不限)
idleTimeout: 60000, // 空闲超时(60s)
},
});二、日志配置
2.1 简单日志
typescript
TypeOrmModule.forRoot({
// ...
logging: 'all', // 所有日志
// logging: ['query', 'error'], // 只记录查询和错误
// logging: false, // 关闭
});2.2 自定义 Logger
typescript
import { Logger } from '@nestjs/common';
TypeOrmModule.forRoot({
// ...
logging: true,
logger: 'advanced', // advanced / simple / formatted / debug
});
// 或自定义
TypeOrmModule.forRoot({
// ...
logger: new TypeOrmLogger(),
});
class TypeOrmLogger implements Logger {
logQuery(query: string, parameters?: any[]) {
Logger.log(`SQL: ${query}`, 'TypeORM');
}
logQueryError(error: string, query: string, parameters?: any[]) {
Logger.error(`SQL: ${query}`, error, 'TypeORM');
}
// ...
}2.3 慢查询监控
typescript
class SlowQueryLogger implements Logger {
logQuery(query: string, parameters?: any[]) {
const start = Date.now();
// 执行后记录耗时
setImmediate(() => {
const elapsed = Date.now() - start;
if (elapsed > 1000) {
Logger.warn(`Slow query (${elapsed}ms): ${query}`, 'TypeORM');
}
});
}
}三、读写分离
3.1 配置多数据源
typescript
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
replication: {
master: {
host: process.env.DB_MASTER_HOST,
port: 3306,
username: 'root',
password: 'root',
database: 'myapp',
},
slaves: [
{
host: process.env.DB_SLAVE1_HOST,
port: 3306,
username: 'readonly',
password: 'readonly',
database: 'myapp',
},
{
host: process.env.DB_SLAVE2_HOST,
port: 3306,
username: 'readonly',
password: 'readonly',
database: 'myapp',
},
],
},
entities: [__dirname + '/**/*.entity{.ts,.js}'],
}),
});3.2 自动路由
TypeORM 会自动:
- 读操作 → slaves(随机选一个)
- 写操作 → master
typescript
// 写
await repo.save(user); // → master
// 读
await repo.find(); // → slave(随机)
await repo.findOne({ id: 1 }); // → slave四、Query Hint(MySQL)
typescript
const users = await userRepo
.createQueryBuilder('user')
.hint('SQL_CALC_FOUND_ROWS') // MySQL 专属
.getMany();五、批量操作
5.1 批量插入
typescript
const users = Array.from({ length: 1000 }, (_, i) => ({
name: `User${i}`,
email: `user${i}@x.com`,
}));
// 方式一:save
await repo.save(users); // 1000 次插入
// 方式二:insert
await repo.insert(users); // 1 次批量插入(快)
// 方式三:queryBuilder
await repo
.createQueryBuilder()
.insert()
.into(User)
.values(users)
.execute();5.2 批量更新
typescript
// 单 SQL
await repo.update({ role: 'user' }, { status: 'normal' });5.3 批量删除
typescript
await repo.delete({ status: 'banned' });六、悲观锁与乐观锁
6.1 悲观锁
typescript
const user = await repo
.createQueryBuilder('user')
.setLock('pessimistic_write') // SELECT ... FOR UPDATE
.where('user.id = :id', { id: 1 })
.getOne();6.2 乐观锁
typescript
// entity
import { VersionColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@VersionColumn()
version: number;
}
// 更新时自动检查版本
await repo.save(user);
// UPDATE users SET ..., version = 2 WHERE id = 1 AND version = 1
// 如果 version 已变,影响行数为 0,抛错七、嵌套事务(Savepoints)
typescript
await dataSource.transaction(async manager => {
await manager.save(User, { name: 'Tom' });
// 嵌套(失败只回滚内部)
try {
await manager.transaction(async innerManager => {
await innerManager.save(Post, { title: 'Bad' });
throw new Error('Rollback inner only');
});
} catch (e) {
// Tom 还在
}
});八、性能优化技巧
8.1 只查需要的字段
typescript
// ❌ 查全部
const users = await repo.find();
// ✅ 只查需要的字段
const users = await repo.find({
select: ['id', 'name', 'email'],
});
// 或用 QueryBuilder
const users = await repo
.createQueryBuilder('user')
.select(['user.id', 'user.name'])
.getMany();8.2 加索引
typescript
@Entity()
@Index(['email'])
@Index(['createdAt'])
export class User {
// ...
}8.3 避免 N+1
typescript
// ❌ N+1
const posts = await postRepo.find();
for (const p of posts) {
p.author = await userRepo.findOne(p.authorId); // N 次查询!
}
// ✅ 一次查
const posts = await postRepo.find({ relations: ['author'] });8.4 分页而不是全量
typescript
// ❌ 查所有
const all = await repo.find(); // 100w 行 = 内存爆炸
// ✅ 分页
const [page1] = await repo.findAndCount({
skip: 0,
take: 20,
});8.5 用 stream 处理大数据
typescript
const stream = await repo
.createQueryBuilder('user')
.stream();
stream.on('data', (user) => {
// 一行一行处理
});
stream.on('end', () => {
// 完成
});九、数据库健康检查
typescript
@Get('health')
async health() {
try {
await this.dataSource.query('SELECT 1');
return { status: 'ok', db: 'connected' };
} catch (e) {
return { status: 'fail', db: 'disconnected' };
}
}十、TypeORM 调试技巧
typescript
// 开启 SQL 日志
TypeOrmModule.forRoot({
// ...
logging: 'all',
});
// 在 Service 中打印实际 SQL
const qb = repo.createQueryBuilder('user');
console.log(qb.getSql()); // 打印 SQL
console.log(qb.getParameters());十一、本章小结
| 概念 | 用途 |
|---|---|
| 连接池 | 复用连接,提性能 |
| 读写分离 | 主写从读 |
| 悲观锁 | SELECT FOR UPDATE |
| 乐观锁 | VersionColumn |
| 慢查询 | 日志监控 |
| Stream | 大数据流式处理 |
动手练习
- 配置连接池,设置 connectionLimit = 10
- 实现主从读写分离
- 给 User 实体加 @VersionColumn 乐观锁
- 用 stream 处理 10w 行数据
推荐阅读
下一章:第 181 章:中间件 →