You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用NestJS TypeORM触发N+1查询问题?一对多关联场景未出现N+1问题的原因排查

关于TypeORM N+1查询问题的解惑与复现方法

嘿,我来帮你理清这个问题~你现在没遇到N+1是因为当前的查询写法本来就不会触发它,下面我分两部分给你解释:

为什么你的代码不会触发N+1查询

你在findfindOne方法里都传入了{ relations: ['employee'] }{ relations: ['company'] },TypeORM在这种情况下会自动生成LEFT JOIN的SQL语句,一次性把主表和关联表的数据拉取完成。比如查询所有公司时,SQL大致是这样的:

SELECT * FROM COMPANY LEFT JOIN EMPLOYEE ON COMPANY.companyId = EMPLOYEE.companyId

这是单条查询,自然不会出现“1次查主表+N次查关联表”的N+1问题。

另外你提到尝试了懒加载和急加载,但可能没用到触发N+1的场景:

  • 急加载(Eager):如果给关联设置eager: true,TypeORM会自动在每次查询主实体时通过JOIN加载关联数据,同样是单条查询,不会N+1。
  • 懒加载(Lazy):你当前的实体关联没有开启lazy: true,就算开启了,只要用了relations选项,TypeORM还是会优先用JOIN加载,不会触发懒加载的额外查询。

如何在NestJS TypeORM中触发N+1查询

下面给你几种具体的场景,照着改代码就能复现N+1:

1. 不使用relations选项,手动遍历访问关联属性

修改CompanyServicegetAllCompany方法,去掉relations配置:

getAllCompany() {
  return this.companyRepository.find(); // 只查询公司表,不加载员工关联
}

然后在调用这个方法后,遍历每个公司并访问employee属性:

// 比如在Controller里
const companies = await this.companyService.getAllCompany();
for (const company of companies) {
  // 每循环一次就会触发一条SELECT * FROM EMPLOYEE WHERE companyId = ?
  const employees = await company.employee; 
}

这时候就会出现:1次查询所有公司 + N次查询每个公司的员工,也就是典型的N+1问题。

2. 开启懒加载模式,且不提前加载关联

先修改Company实体的关联定义,开启懒加载:

@Entity('COMPANY')
export class Company extends TimeStamped {
  // ... 其他属性

  @OneToMany(() => Employee, (employee) => employee.company, { 
    onDelete: 'CASCADE',
    lazy: true // 开启懒加载
  })
  employee: Promise<Employee[]>; // 类型要改成Promise
}

然后查询时不用relations

getAllCompany() {
  return this.companyRepository.find();
}

之后遍历访问employee属性,就会触发N次额外的员工查询。

3. 使用QueryBuilder但不做JOIN,后续访问关联

用QueryBuilder查询公司,但不关联员工表:

async getAllCompany() {
  const companies = await this.companyRepository
    .createQueryBuilder('company')
    .getMany();
  
  // 遍历访问关联属性,触发N次查询
  for (const company of companies) {
    await company.employee;
  }
  return companies;
}

4. 从子实体查询,不关联父实体后遍历访问

修改EmployeeServicegetAllEmployee方法,去掉relations

getAllEmployee() {
  return this.employeeRepository.find(); // 只查询员工表,不加载公司关联
}

调用后遍历每个员工访问company属性:

const employees = await this.employeeService.getAllEmployee();
for (const emp of employees) {
  // 每个员工触发一次查询公司的SQL,N+1问题出现
  const company = await emp.company;
}

注意:如果之前给关联设置了eager: true,要先把它改成false,或者在QueryBuilder里用setEagerLoad(false)禁用自动急加载,否则TypeORM还是会自动JOIN查询,不会触发N+1。

内容的提问来源于stack exchange,提问作者momo

火山引擎 最新活动