乐闻世界logo
搜索文章和话题

TypeORM 如何进行性能优化?包括查询优化、缓存策略、批量操作等最佳实践

2月18日 22:12

性能优化是 TypeORM 开发中的重要环节,合理的优化策略可以显著提升应用响应速度和数据库效率。本文将详细介绍 TypeORM 的各种性能优化技巧和最佳实践。

查询优化

1. 避免 N+1 查询问题

N+1 查询是常见的性能问题,指执行 1 次主查询后,又对每个结果执行 N 次关联查询。

typescript
// ❌ 不好的做法:N+1 查询 const users = await userRepository.find(); for (const user of users) { const posts = await postRepository.find({ where: { authorId: user.id } }); user.posts = posts; } // ✅ 好的做法:使用关联查询 const users = await userRepository.find({ relations: ['posts'] }); // ✅ 更好的做法:使用 QueryBuilder const users = await userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .getMany();

2. 选择性加载字段

只查询需要的字段,减少数据传输量。

typescript
// ❌ 查询所有字段 const users = await userRepository.find(); // ✅ 只查询需要的字段 const users = await userRepository.find({ select: ['id', 'name', 'email'] }); // 使用 QueryBuilder 选择字段 const users = await userRepository .createQueryBuilder('user') .select(['user.id', 'user.name', 'user.email']) .getMany();

3. 使用索引优化查询

为常用查询条件添加数据库索引。

typescript
@Entity() @Index(['email']) // 单列索引 @Index(['status', 'createdAt']) // 复合索引 export class User { @PrimaryGeneratedColumn() id: number; @Column() @Index() // 装饰器方式添加索引 email: string; @Column() status: string; @Column() createdAt: Date; } // 或者使用迁移创建索引 public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.createIndex( 'user', new TableIndex({ name: 'IDX_USER_EMAIL', columnNames: ['email'], isUnique: true, }) ); }

4. 使用 LIMIT 和 OFFSET 实现分页

typescript
// 基本分页 const page = 1; const pageSize = 10; const users = await userRepository.find({ skip: (page - 1) * pageSize, take: pageSize, order: { createdAt: 'DESC' } }); // 使用 QueryBuilder 分页 const users = await userRepository .createQueryBuilder('user') .skip((page - 1) * pageSize) .take(pageSize) .orderBy('user.createdAt', 'DESC') .getMany(); // 获取总数和分页数据 const [users, total] = await userRepository.findAndCount({ skip: (page - 1) * pageSize, take: pageSize, });

5. 使用游标分页

对于大数据集,游标分页比 OFFSET 分页更高效。

typescript
// 基于游标的分页 async function getUsersWithCursor(cursor: number, limit: number = 10) { const query = userRepository .createQueryBuilder('user') .orderBy('user.id', 'ASC') .limit(limit + 1); // 多取一条判断是否有下一页 if (cursor) { query.where('user.id > :cursor', { cursor }); } const users = await query.getMany(); const hasNextPage = users.length > limit; if (hasNextPage) { users.pop(); // 移除多取的一条 } return { data: users, nextCursor: hasNextPage ? users[users.length - 1].id : null, hasNextPage }; }

缓存优化

1. 启用查询缓存

typescript
// 在 DataSource 配置中启用缓存 const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], cache: { type: 'redis', options: { host: 'localhost', port: 6379, }, duration: 30000, // 缓存 30 秒 }, }); // 在查询中使用缓存 const users = await userRepository.find({ cache: { id: 'users_list', milliseconds: 60000, // 缓存 60 秒 } }); // 使用 QueryBuilder 缓存 const users = await userRepository .createQueryBuilder('user') .cache(60000) // 缓存 60 秒 .getMany();

2. 实体缓存

typescript
@Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() @Cache() // 启用实体缓存 email: string; } // 查询时自动使用缓存 const user = await userRepository.findOne({ where: { id: 1 }, cache: true });

3. 自定义缓存策略

typescript
class CacheService { private cache = new Map<string, { data: any, expires: number }>(); async get<T>(key: string): Promise<T | null> { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expires) { this.cache.delete(key); return null; } return item.data; } async set<T>(key: string, data: T, ttl: number = 60000): Promise<void> { this.cache.set(key, { data, expires: Date.now() + ttl }); } async invalidate(key: string): Promise<void> { this.cache.delete(key); } } // 使用自定义缓存 const cacheService = new CacheService(); async function getUserWithCache(userId: number) { const cacheKey = `user:${userId}`; let user = await cacheService.get<User>(cacheKey); if (!user) { user = await userRepository.findOne({ where: { id: userId } }); await cacheService.set(cacheKey, user, 60000); // 缓存 60 秒 } return user; }

批量操作优化

1. 批量插入

typescript
// ❌ 不好的做法:逐个插入 for (const userData of usersData) { const user = userRepository.create(userData); await userRepository.save(user); } // ✅ 好的做法:批量插入 const users = userRepository.create(usersData); await userRepository.save(users); // ✅ 更好的做法:使用 QueryBuilder await userRepository .createQueryBuilder() .insert() .into(User) .values(usersData) .execute(); // ✅ 最佳做法:使用原生 SQL 批量插入 const values = usersData.map(u => `('${u.name}', '${u.email}')`).join(','); await userRepository.query( `INSERT INTO user (name, email) VALUES ${values}` );

2. 批量更新

typescript
// ❌ 不好的做法:逐个更新 for (const user of users) { user.status = 'active'; await userRepository.save(user); } // ✅ 好的做法:使用 QueryBuilder 批量更新 await userRepository .createQueryBuilder() .update(User) .set({ status: 'active' }) .where('id IN :ids', { ids: users.map(u => u.id) }) .execute(); // ✅ 更好的做法:使用原生 SQL const ids = users.map(u => u.id).join(','); await userRepository.query( `UPDATE user SET status = 'active' WHERE id IN (${ids})` );

3. 批量删除

typescript
// ❌ 不好的做法:逐个删除 for (const user of users) { await userRepository.delete(user.id); } // ✅ 好的做法:使用 QueryBuilder 批量删除 await userRepository .createQueryBuilder() .delete() .from(User) .where('id IN :ids', { ids: users.map(u => u.id) }) .execute(); // ✅ 更好的做法:使用原生 SQL const ids = users.map(u => u.id).join(','); await userRepository.query( `DELETE FROM user WHERE id IN (${ids})` );

连接池优化

1. 配置连接池

typescript
const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], extra: { connectionLimit: 10, // 最大连接数 acquireTimeout: 60000, // 获取连接超时时间 timeout: 60000, // 查询超时时间 waitForConnections: true, // 等待可用连接 queueLimit: 0, // 队列限制,0 表示无限制 }, });

2. 监控连接池状态

typescript
class ConnectionPoolMonitor { constructor(private dataSource: DataSource) {} getPoolStats() { const pool = this.dataSource.driver.master; return { totalConnections: pool.pool._allConnections.length, freeConnections: pool.pool._freeConnections.length, queuedRequests: pool.pool._connectionQueue.length, }; } logPoolStats() { const stats = this.getPoolStats(); console.log('Connection Pool Stats:', stats); } } // 定期监控连接池 const monitor = new ConnectionPoolMonitor(dataSource); setInterval(() => monitor.logPoolStats(), 60000);

懒加载优化

1. 使用懒加载

typescript
@Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => Post, post => post.author) posts: Promise<Post[]>; // 使用 Promise 实现懒加载 } // 查询时不会加载关联数据 const user = await userRepository.findOne({ where: { id: 1 } }); // 需要时才加载 const posts = await user.posts;

2. 控制懒加载深度

typescript
// 设置最大懒加载深度 const dataSource = new DataSource({ type: 'mysql', // ... 其他配置 maxQueryExecutionTime: 1000, // 最大查询执行时间 });

查询分析和监控

1. 启用查询日志

typescript
const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], logging: true, // 启用日志 maxQueryExecutionTime: 1000, // 记录超过 1 秒的查询 });

2. 使用查询分析器

typescript
class QueryAnalyzer { private queries: Map<string, { count: number, totalTime: number }> = new Map(); logQuery(query: string, duration: number) { const key = query; if (!this.queries.has(key)) { this.queries.set(key, { count: 0, totalTime: 0 }); } const stats = this.queries.get(key); stats.count++; stats.totalTime += duration; } getSlowQueries(threshold: number = 1000) { const slowQueries: Array<{ query: string, avgTime: number, count: number }> = []; for (const [query, stats] of this.queries.entries()) { const avgTime = stats.totalTime / stats.count; if (avgTime > threshold) { slowQueries.push({ query, avgTime, count: stats.count }); } } return slowQueries.sort((a, b) => b.avgTime - a.avgTime); } }

3. 使用 EXPLAIN 分析查询

typescript
async function analyzeQuery(query: string) { const result = await dataSource.query(`EXPLAIN ${query}`); console.log('Query Analysis:', result); // 检查是否使用了索引 const usingIndex = result.some(row => row.type === 'ref' || row.type === 'eq_ref'); if (!usingIndex) { console.warn('Query not using index:', query); } } // 使用示例 analyzeQuery('SELECT * FROM user WHERE email = "test@example.com"');

数据库优化

1. 优化表结构

typescript
@Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column({ type: 'varchar', length: 100 }) // 指定合适的长度 name: string; @Column({ type: 'varchar', length: 255, unique: true }) email: string; @Column({ type: 'tinyint', default: 1 }) // 使用 tinyint 代替 boolean isActive: boolean; @Column({ type: 'int', unsigned: true }) // 使用 unsigned 节省空间 age: number; @CreateDateColumn({ type: 'timestamp' }) createdAt: Date; }

2. 使用合适的数据类型

typescript
// ✅ 好的做法:使用合适的数据类型 @Column({ type: 'tinyint' }) status: number; // 0-255 @Column({ type: 'smallint' }) score: number; // -32768 到 32767 @Column({ type: 'int' }) count: number; // -2147483648 到 2147483647 @Column({ type: 'bigint' }) largeNumber: number; // 大整数 @Column({ type: 'decimal', precision: 10, scale: 2 }) price: number; // 金额使用 decimal

3. 定期维护数据库

typescript
// 定期优化表 async function optimizeTables() { const tables = ['user', 'post', 'comment']; for (const table of tables) { await dataSource.query(`OPTIMIZE TABLE ${table}`); } } // 定期分析表 async function analyzeTables() { const tables = ['user', 'post', 'comment']; for (const table of tables) { await dataSource.query(`ANALYZE TABLE ${table}`); } }

性能测试和基准测试

1. 查询性能测试

typescript
import { performance } from 'perf_hooks'; async function benchmarkQuery( name: string, query: () => Promise<any>, iterations: number = 100 ) { const times: number[] = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); await query(); const end = performance.now(); times.push(end - start); } const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); console.log(`${name}:`); console.log(` Average: ${avgTime.toFixed(2)}ms`); console.log(` Min: ${minTime.toFixed(2)}ms`); console.log(` Max: ${maxTime.toFixed(2)}ms`); } // 使用示例 await benchmarkQuery('Find users with relations', async () => { await userRepository.find({ relations: ['posts'] }); });

最佳实践总结

  1. 避免 N+1 查询: 使用关联查询或 QueryBuilder
  2. 选择性加载字段: 只查询需要的字段
  3. 使用索引: 为常用查询条件添加索引
  4. 实现分页: 使用 LIMIT 和 OFFSET 或游标分页
  5. 启用缓存: 对不常变化的数据使用缓存
  6. 批量操作: 使用批量插入、更新、删除
  7. 优化连接池: 合理配置连接池参数
  8. 监控性能: 启用日志,分析慢查询
  9. 优化表结构: 使用合适的数据类型和长度
  10. 定期维护: 优化和分析数据库表

通过以上优化策略,可以显著提升 TypeORM 应用的性能,提供更好的用户体验。

标签:TypeORM