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

TypeORM 如何进行性能优化?包括查询优化、索引使用和批量操作

2月18日 22:18

性能优化是数据库操作中的关键环节,TypeORM 提供了多种优化策略和技巧。

查询优化

1. 选择性加载字段

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(); // 排除特定字段 const users = await userRepository.find({ select: { id: true, name: true, email: true, password: false, // 排除密码字段 }, });

2. 使用索引

typescript
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; @Entity() @Index(['email']) // 单列索引 @Index(['name', 'email']) // 复合索引 @Index(['createdAt']) // 普通索引 @Index(['status'], { unique: true }) // 唯一索引 @Index(['name'], { fulltext: true }) // 全文索引 export class User { @PrimaryGeneratedColumn() id: number; @Column() @Index() // 列级索引 name: string; @Column() @Index({ unique: true }) email: string; @Column() status: string; @Column({ type: 'timestamp' }) createdAt: Date; @Column({ type: 'text' }) bio: string; } // 在查询中使用索引 const users = await userRepository.find({ where: { email: 'john@example.com', // 使用索引 }, }); // 使用复合索引 const users = await userRepository.find({ where: { name: 'John', email: 'john@example.com', }, });

3. 分页优化

typescript
// 使用 skip 和 take 分页 const users = await userRepository.find({ skip: 0, take: 10, }); // 使用 QueryBuilder 分页 const users = await userRepository .createQueryBuilder('user') .skip(0) .take(10) .getMany(); // 使用游标分页(性能更好) const users = await userRepository .createQueryBuilder('user') .where('user.id > :cursor', { cursor: lastId }) .orderBy('user.id', 'ASC') .take(10) .getMany(); // 使用 LIMIT 和 OFFSET const users = await userRepository .createQueryBuilder('user') .limit(10) .offset(0) .getMany();

关联优化

1. 懒加载 vs 急切加载

typescript
// ❌ 不好的做法:N+1 查询问题 const users = await userRepository.find(); for (const user of users) { const posts = await user.posts; // 每次都查询数据库 } // ✅ 好的做法:使用急切加载 const users = await userRepository.find({ relations: ['posts'], }); // 使用 QueryBuilder 急切加载 const users = await userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .getMany(); // 选择性加载关联 const users = await userRepository.find({ relations: { posts: true, profile: false, // 不加载 profile }, }); // 使用 join 加载关联 const users = await userRepository .createQueryBuilder('user') .leftJoin('user.posts', 'post') .addSelect(['post.id', 'post.title']) .getMany();

2. 关联查询优化

typescript
// 使用 innerJoin 代替 leftJoin(如果确定有关联数据) const users = await userRepository .createQueryBuilder('user') .innerJoin('user.posts', 'post') .where('post.published = :published', { published: true }) .getMany(); // 使用 join 条件过滤 const users = await userRepository .createQueryBuilder('user') .leftJoin('user.posts', 'post', 'post.published = :published', { published: true }) .getMany(); // 使用子查询优化 const users = await userRepository .createQueryBuilder('user') .where((qb) => { const subQuery = qb .subQuery() .select('post.userId') .from(Post, 'post') .where('post.published = :published', { published: true }) .getQuery(); return 'user.id IN ' + subQuery; }) .setParameter('published', true) .getMany();

批量操作优化

1. 批量插入

typescript
// ❌ 不好的做法:循环插入 for (const userData of usersData) { const user = userRepository.create(userData); await userRepository.save(user); } // ✅ 好的做法:批量插入 const users = usersData.map(userData => userRepository.create(userData)); await userRepository.save(users); // 使用 QueryBuilder 批量插入 await userRepository .createQueryBuilder() .insert() .into(User) .values(usersData) .execute(); // 使用 bulk insert await userRepository.insert(usersData);

2. 批量更新

typescript
// ❌ 不好的做法:循环更新 for (const user of users) { await userRepository.update(user.id, { status: 'active' }); } // ✅ 好的做法:批量更新 await userRepository.update( { status: 'inactive' }, { status: 'active' } ); // 使用 QueryBuilder 批量更新 await userRepository .createQueryBuilder() .update(User) .set({ status: 'active' }) .where('status = :status', { status: 'inactive' }) .execute(); // 使用 CASE WHEN 批量更新 await userRepository .createQueryBuilder() .update(User) .set({ status: () => 'CASE WHEN age > 18 THEN "active" ELSE "inactive" END', }) .execute();

3. 批量删除

typescript
// ❌ 不好的做法:循环删除 for (const user of users) { await userRepository.delete(user.id); } // ✅ 好的做法:批量删除 await userRepository.delete({ status: 'deleted' }); // 使用 QueryBuilder 批量删除 await userRepository .createQueryBuilder() .delete() .from(User) .where('status = :status', { status: 'deleted' }) .execute(); // 使用软删除批量删除 await userRepository.softDelete({ status: 'deleted' });

事务优化

1. 事务范围控制

typescript
// ❌ 不好的做法:大事务 await dataSource.transaction(async (manager) => { // 执行大量操作 for (let i = 0; i < 1000; i++) { await manager.save(User, { name: `User ${i}` }); } }); // ✅ 好的做法:分批处理 const batchSize = 100; for (let i = 0; i < 1000; i += batchSize) { await dataSource.transaction(async (manager) => { const users = []; for (let j = i; j < i + batchSize && j < 1000; j++) { users.push({ name: `User ${j}` }); } await manager.save(User, users); }); }

2. 使用 QueryRunner 优化

typescript
// 使用 QueryRunner 获得更好的性能控制 const queryRunner = dataSource.createQueryRunner(); try { await queryRunner.connect(); await queryRunner.startTransaction(); // 执行多个操作 await queryRunner.manager.save(User, user1); await queryRunner.manager.save(User, user2); await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); throw error; } finally { await queryRunner.release(); }

缓存优化

1. 查询缓存

typescript
// 启用查询缓存 const users = await userRepository.find({ cache: true, }); // 自定义缓存时间 const users = await userRepository.find({ cache: { id: 'users_list', milliseconds: 60000, // 60 秒 }, }); // 使用 QueryBuilder 缓存 const users = await userRepository .createQueryBuilder('user') .cache(true) .getMany(); // 清除缓存 await dataSource.queryResultCache?.remove(['users_list']);

2. 应用级缓存

typescript
// 使用内存缓存 const cache = new Map<string, any>(); async function getUserWithCache(userId: number) { const cacheKey = `user:${userId}`; // 尝试从缓存获取 if (cache.has(cacheKey)) { return cache.get(cacheKey); } // 从数据库获取 const user = await userRepository.findOne({ where: { id: userId }, }); // 存入缓存 cache.set(cacheKey, user); return user; }

数据库连接优化

1. 连接池配置

typescript
const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, // 连接池配置 extra: { connectionLimit: 20, // 连接池大小 acquireTimeout: 60000, // 获取连接超时时间 timeout: 60000, // 查询超时时间 waitForConnections: true, // 等待可用连接 queueLimit: 100, // 队列限制 }, });

2. 读写分离

typescript
// 主数据库(写操作) const writeDataSource = new DataSource({ type: 'mysql', name: 'write', host: 'write-db.example.com', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, }); // 从数据库(读操作) const readDataSource = new DataSource({ type: 'mysql', name: 'read', host: 'read-db.example.com', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, }); // 使用读写分离 class UserRepository { async findById(id: number) { // 从从数据库读取 const repository = readDataSource.getRepository(User); return await repository.findOne({ where: { id } }); } async save(user: User) { // 写入主数据库 const repository = writeDataSource.getRepository(User); return await repository.save(user); } }

监控和调优

1. 查询日志分析

typescript
// 启用查询日志 const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: ['query', 'error', 'slow'], // 记录查询、错误和慢查询 maxQueryExecutionTime: 1000, // 慢查询阈值(毫秒) }); // 自定义日志记录 const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logger: new QueryLogger(), }); class QueryLogger implements Logger { logQuery(query: string, parameters?: any[]) { console.log('Query:', query); console.log('Parameters:', parameters); } logQueryError(error: string, query: string, parameters?: any[]) { console.error('Query Error:', error); console.error('Query:', query); } logQuerySlow(time: number, query: string, parameters?: any[]) { console.warn('Slow Query:', query); console.warn('Execution Time:', time); } }

2. 性能监控

typescript
// 查询性能监控 class QueryPerformanceMonitor { private queries: Map<string, number[]> = new Map(); trackQuery(query: string, executionTime: number) { if (!this.queries.has(query)) { this.queries.set(query, []); } this.queries.get(query)!.push(executionTime); } getAverageExecutionTime(query: string): number { const times = this.queries.get(query); if (!times || times.length === 0) { return 0; } const sum = times.reduce((a, b) => a + b, 0); return sum / times.length; } getSlowQueries(threshold: number = 1000): string[] { const slowQueries: string[] = []; for (const [query, times] of this.queries.entries()) { const avgTime = this.getAverageExecutionTime(query); if (avgTime > threshold) { slowQueries.push(query); } } return slowQueries; } } // 使用性能监控 const monitor = new QueryPerformanceMonitor(); const startTime = Date.now(); const users = await userRepository.find(); const executionTime = Date.now() - startTime; monitor.trackQuery('SELECT * FROM user', executionTime);

最佳实践

1. 避免 N+1 查询

typescript
// ❌ 不好的做法:N+1 查询 const users = await userRepository.find(); for (const user of users) { const posts = await user.posts; // N+1 查询 } // ✅ 好的做法:使用急切加载 const users = await userRepository.find({ relations: ['posts'], }); // ✅ 好的做法:使用 join const users = await userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .getMany();

2. 使用适当的数据类型

typescript
// ✅ 好的做法:使用适当的数据类型 @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column({ type: 'varchar', length: 255 }) // 限制长度 name: string; @Column({ type: 'int' }) // 使用整数 age: number; @Column({ type: 'decimal', precision: 10, scale: 2 }) // 使用精确小数 balance: number; @Column({ type: 'boolean' }) // 使用布尔值 isActive: boolean; @Column({ type: 'enum', enum: ['active', 'inactive'] }) // 使用枚举 status: string; @Column({ type: 'json' }) // 使用 JSON metadata: any; }

3. 定期维护

typescript
// 定期清理数据 async function cleanupOldData() { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); await userRepository.delete({ createdAt: LessThan(thirtyDaysAgo), status: 'deleted', }); } // 定期重建索引 async function rebuildIndexes() { await dataSource.query('ANALYZE TABLE user'); await dataSource.query('OPTIMIZE TABLE user'); } // 定期更新统计信息 async function updateStatistics() { await dataSource.query('ANALYZE TABLE user'); }

TypeORM 的性能优化需要综合考虑查询、关联、批量操作、事务等多个方面,合理使用优化技巧可以显著提高应用性能。

标签:TypeORM