跳至主内容区

TypeORM 中的性能与优化

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

1. 性能优化简介

  • 在使用 TypeORM 等 ORM 框架的应用中,性能优化至关重要,可确保系统流畅运行、降低延迟并高效利用资源。

  • ORM 使用中的常见挑战包括不必要的数据检索、N+1 查询问题,以及未能充分利用索引或缓存等优化工具。

  • 优化的主要目标包括:

    • 减少发送至数据库的 SQL 查询数量
    • 优化复杂查询的执行速度
    • 利用缓存和索引加速数据检索
    • 通过恰当的加载策略(惰性加载 vs 积极加载)确保高效数据获取

2. 高效使用 Query Builder

2.1. 避免 N+1 查询问题

  • 当系统为检索的每行数据执行过多子查询时,就会产生 N+1 查询问题。

  • 为避免此问题,可使用 leftJoinAndSelectinnerJoinAndSelect 在单次查询中合并数据表,而非执行多次查询。

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.getMany()
  • 此处,leftJoinAndSelect 能通过单次查询获取用户所有帖子,避免多次小规模查询。

2.2. 仅需原始数据时使用 getRawMany()

  • 当不需要完整对象时,可使用 getRawMany() 获取原始数据,避免 TypeORM 处理过多信息。
const rawPosts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.select("post.title, post.createdAt")
.getRawMany()

2.3. 使用 select 限制字段

  • 为优化内存使用并减少冗余数据,应通过 select 仅选择必要字段。
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.name", "user.email"])
.getMany()

3. 使用索引

  • 索引通过减少数据库扫描数据量来加速查询性能。TypeORM 支持使用 @Index 装饰器在表字段上创建索引。

3.1. 创建索引

  • 可直接在实体中使用 @Index 装饰器创建索引。
import { Entity, Column, Index } from "typeorm"

@Entity()
@Index(["firstName", "lastName"]) // Composite index
export class User {
@Column()
firstName: string

@Column()
lastName: string
}

3.2. 唯一索引

  • 可创建唯一索引确保字段值无重复。
@Index(["email"], { unique: true })

4. 惰性加载与积极加载

TypeORM 提供两种主要的数据关系加载方式:惰性加载(Lazy Loading)和积极加载(Eager Loading),每种方式对应用性能的影响各不相同。

4.1. 惰性加载

  • 惰性加载仅在需要时加载关联数据,当并非总是需要完整关联数据时可降低数据库负载。
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { lazy: true })
posts: Promise<Post[]>
}
  • 当需要获取数据时,只需调用:
const user = await userRepository.findOne(userId)
const posts = await user.posts
  • 优势:

    • 资源高效:仅在真正需要时加载必要数据,降低查询开销和内存占用
    • 适用于选择性数据场景:在不需要完整关联数据时尤为理想
  • 劣势:

    • 查询复杂度增加:每次访问关联数据都会触发额外数据库查询,管理不当可能增加延迟
    • 难以追踪:使用不当易引发 n+1 查询问题

4.2. 积极加载

  • 积极加载会在执行主查询时自动获取所有关联数据。这种方式虽便捷,但当存在过多复杂关联时可能引发性能问题。
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { eager: true })
posts: Post[]
}
  • 此场景中,用户数据一旦获取就会立即加载帖子数据。

  • 优势:

    • 自动加载关联数据,无需额外查询即可便捷访问关系
    • 避免 n+1 查询问题:所有数据通过单次查询获取,不会产生冗余多次查询
  • 劣势:

    • 一次性获取所有关联数据可能导致大型查询,即使部分数据并不需要
    • 不适合仅需部分关联数据的场景,易造成资源浪费
  • 欲了解惰性与积极关系的配置及使用详情,请访问 TypeORM 官方文档:积极与惰性关系

5. 高级优化技巧

5.1. 使用查询提示(Query Hints)

  • 查询提示(Query Hints)是随 SQL 查询发送的指令,可帮助数据库选择更高效的执行策略。

  • 不同关系数据库系统支持不同类型的提示,例如建议使用索引或选择合适的 JOIN 类型。

await dataSource.query(`
SELECT /*+ MAX_EXECUTION_TIME(1000) */ *
FROM user
WHERE email = 'example@example.com'
`)
  • 上例中,MAX_EXECUTION_TIME(1000) 指令会使 MySQL 在查询超过 1 秒时自动终止。

5.2. 分页机制

  • 分页是检索大量数据时提升性能的关键技术。通过将数据拆分为小批量页面,可降低数据库负载并优化内存使用,避免一次性获取全量数据。

  • 在 TypeORM 中,可通过 limitoffset 实现分页。

const users = await userRepository
.createQueryBuilder("user")
.limit(10) // Number of records to fetch per page
.offset(20) // Skip the first 20 records
.getMany()
  • 分页机制能有效避免一次性获取大量数据,从而降低延迟并优化内存使用。实现分页时,建议采用分页游标(pagination cursors)以更高效地处理动态数据。

5.3. 缓存策略

  • 缓存技术通过临时存储查询结果或数据,使得后续请求无需每次都访问数据库即可复用。

  • TypeORM 提供内置缓存支持,开发者可自定义缓存的具体实现方式。

const users = await userRepository
.createQueryBuilder("user")
.cache(true) // Enable caching
.getMany()
  • 此外,还可配置缓存持续时间,或使用 Redis 等外部缓存工具来提升效率。
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
cache: {
type: "redis",
options: {
host: "localhost",
port: 6379
}
}
});