You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS+TypeORM本地正常生产环境报错:循环依赖问题求助

NestJS + Postgres生产环境循环依赖错误排查

问题描述

服务基于NestJS和Postgres构建,本地运行正常,但生产服务器连接生产Postgres时始终抛出以下错误:

[Nest] 20  - 09/05/2023, 8:43:09 PM   ERROR [ExceptionsHandler] Cyclic dependency: "d"
TypeORMError: Cyclic dependency: "d"
at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:147:23)
at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:164:21)
at SubjectTopoligicalSorter.toposort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:143:17)
at SubjectTopoligicalSorter.sort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:53:45)
at SubjectExecutor.execute (/app/node_modules/typeorm/persistence/SubjectExecutor.js:91:108)
at EntityPersistExecutor.execute (/app/node_modules/typeorm/persistence/EntityPersistExecutor.js:140:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)

环境差异:

  • 本地开发服务器连接远程生产Postgres:运行正常
  • 生产服务器连接生产Postgres:触发上述错误

技术栈信息

Node: 16.19.1
Package Manager: npm 8.19.3
OS: darwin arm64

生产依赖

"dependencies": {
        "@angular/animations": "^13.0.0",
        "@angular/cdk": "^13.0.0",
        "@angular/common": "^13.0.0",
        "@angular/compiler": "^13.0.0",
        "@angular/core": "^13.0.0",
        "@angular/forms": "^13.0.0",
        "@angular/platform-browser": "^13.0.0",
        "@angular/platform-browser-dynamic": "^13.0.0",
        "@angular/router": "^13.0.0",
        "@nestjs/common": "^8.0.0",
        "@nestjs/config": "^3.0.0",
        "@nestjs/core": "^8.0.0",
        "@nestjs/jwt": "^10.0.3",
        "@nestjs/mapped-types": "^2.0.2",
        "@nestjs/platform-express": "^8.0.0",
        "@nestjs/serve-static": "^4.0.0",
        "@nestjs/typeorm": "^10.0.0",
        "@nrwl/angular": "13.3.1",
        "bootstrap": "^5.2.3",
        "class-transformer": "^0.5.1",
        "class-validator": "^0.14.0",
        "cloudinary": "^1.37.3",
        "express": "^4.18.2",
        "http-status-codes": "^2.2.0",
        "multer": "^1.4.5-lts.1",
        "pg": "^8.11.1",
        "primeflex": "^3.3.0",
        "primeicons": "^4.1.0",
        "primeng": "^13.4.1",
        "reflect-metadata": "^0.1.13",
        "rxjs": "~7.4.0",
        "sqlite3": "^5.1.6",
        "tslib": "^2.0.0",
        "typeorm": "^0.3.17",
        "uuid": "^9.0.0",
        "zone.js": "~0.11.4"
    }

开发依赖

"devDependencies": {
        "@angular-devkit/build-angular": "~13.0.0",
        "@angular-eslint/eslint-plugin": "~13.0.1",
        "@angular-eslint/eslint-plugin-template": "~13.0.1",
        "@angular-eslint/template-parser": "~13.0.1",
        "@angular/cli": "^13.3.4",
        "@angular/compiler-cli": "^13.0.0",
        "@angular/language-service": "^13.0.0",
        "@nestjs/schematics": "^8.0.0",
        "@nestjs/testing": "^8.0.0",
        "@nrwl/cli": "13.3.1",
        "@nrwl/cypress": "13.3.1",
        "@nrwl/eslint-plugin-nx": "13.3.1",
        "@nrwl/jest": "13.3.1",
        "@nrwl/linter": "13.3.1",
        "@nrwl/nest": "13.3.1",
        "@nrwl/node": "13.3.1",
        "@nrwl/tao": "13.3.1",
        "@nrwl/workspace": "13.3.1",
        "@types/express": "^4.17.17",
        "@types/jest": "27.0.2",
        "@types/multer": "^1.4.7",
        "@types/node": "^15.14.0",
        "@typescript-eslint/eslint-plugin": "~5.3.0",
        "@typescript-eslint/parser": "~5.3.0",
        "cypress": "^8.3.0",
        "eslint": "8.2.0",
        "eslint-config-prettier": "8.1.0",
        "eslint-plugin-cypress": "^2.10.3",
        "jest": "27.2.3",
        "jest-preset-angular": "11.0.0",
        "prettier": "^2.3.1",
        "ts-jest": "27.0.5",
        "typescript": "~4.4.3"
    }

实体定义

PatientEntity

@Entity()
export class PatientEntity {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    name: string;

    @Column({ type: 'date' })
    dateOfBirth: Date;

    @Column({ unique: true })
    email: string;

    @Column({ nullable: true })
    phoneNumber: string;

    @Column({ nullable: true })
    photo: string | null;

    @OneToMany(
        () => MedicalPlanEntity,
        plan => plan.patient,
        { cascade: true }
    )
    plans: MedicalPlanEntity[];
}

MedicalPlanEntity

@Entity()
export class MedicalPlanEntity {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column({ nullable: true })
    name: string;

    @Column({ nullable: true })
    description: string;

    @ManyToOne(
        () => PatientEntity,
        patient => patient.plans,
        { nullable: true }
    )
    patient: PatientEntity;
}

ORM配置

import { registerAs } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export default registerAs(
    'orm.config',
    (): TypeOrmModuleOptions => ({
        ssl: true,

        extra: {
            ssl: { rejectUnauthorized: false }
        },
        type: 'postgres',
        autoLoadEntities: true,
        host: process.env.DB_HOST,
        port: Number(process.env.DB_PORT),
        username: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
    })
);

原因分析与解决方案

核心原因

  1. 生产数据存在异常关联:生产库中可能存在MedicalPlan的patient_id为null,或指向不存在/异常的Patient数据,导致TypeORM在处理关联时形成无效循环引用链。本地开发数据量小或无此类脏数据,因此未触发问题。
  2. TypeORM拓扑排序bug:当前使用的typeorm@0.3.17在特定环境(如Linux生产服务器)下,拓扑排序逻辑存在缺陷,误判正常关联为循环依赖。
  3. Cascade配置过度:PatientEntity的cascade: true会触发全量级联操作,生产环境处理批量数据时,递归加载关联实体容易触发循环检测。

解决方案

  • 清理生产脏数据:执行SQL排查并修复无效关联:

    -- 找出无关联或关联无效的MedicalPlan数据
    SELECT * FROM medical_plan WHERE patient_id IS NULL OR patient_id NOT IN (SELECT id FROM patient);
    -- 删除或修正这些数据
    DELETE FROM medical_plan WHERE patient_id IS NULL;
    
  • 调整Cascade策略:将全量级联改为指定操作,避免不必要的递归加载:

    @OneToMany(
        () => MedicalPlanEntity,
        plan => plan.patient,
        { cascade: ['insert', 'update'] } // 仅允许插入和更新级联
    )
    plans: MedicalPlanEntity[];
    
  • 升级TypeORM版本:将typeorm升级到0.3.x最新稳定版(如0.3.19+),修复已知的拓扑排序bug,同时确保@nestjs/typeorm版本兼容。

  • 手动指定实体列表:禁用autoLoadEntities,改为手动导入实体,避免自动加载时的实体顺序问题:

    // orm.config.ts
    import { PatientEntity, MedicalPlanEntity } from './entities'; // 替换为你的实体路径
    
    export default registerAs(
        'orm.config',
        (): TypeOrmModuleOptions => ({
            // ...其他配置
            entities: [PatientEntity, MedicalPlanEntity],
            // autoLoadEntities: true, // 注释掉该行
        })
    );
    

内容的提问来源于stack exchange,提问作者Lorraine Ram-El

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:35:12