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

Nest.js+TypeORM多租户架构MySQL连接数过多问题排查

Nest.js + TypeORM 多租户架构中 MySQL 连接数过多(Too many connections)问题排查与解决

问题描述

我基于 Nest.js 搭建了多租户微服务架构,采用 TypeORM 最新的 DataSource API 实现多租户数据库连接。升级 TypeORM 到最新版本后,出现了 MySQL 的「Too many connections」错误。

我了解到新版本新增了 poolSize 选项用于控制活跃连接数,已经配置该选项但问题仍未解决。理论上 TypeORM 应该在数据库操作完成后关闭或复用连接,但目前 MySQL 中存在大量处于 sleep 状态的未关闭连接,新请求还会创建新连接(可通过 show processlist; 命令查看)。

以下是我为每个请求创建租户连接的代码实现:

export const databaseSource = new DataSource({
      type: process.env.DB_CONNECTION,
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT, 10),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_DATABASE,
      entities: ["src/models/entities/**/*.ts"],
      synchronize: true,
      poolSize: 10,
    });

const connectionFactory = {
  provide: CONNECTION,
  scope: Scope.REQUEST,
  useFactory: async (request: RequestContext) => {
    const tenantHost = request.data["tenantId"] || request.data;
    if (tenantHost) {
      const tenantConnection: DataSource = await getTenantConnection(
        tenantHost
      );
      return tenantConnection;
    }

    return null;
  },
  inject: [REQUEST],
};

@Global()
@Module({
  providers: [connectionFactory],
  exports: [CONNECTION],
})
export class TenancyModule {}

export async function getTenantConnection(
  tenantHost: string
): Promise<DataSource> {
  const tenantId = getTenantId(tenantHost);
  const connectionName = `${tenantId}`;

  const DBConfig: DataSourceOptions = {
      type: process.env.DB_CONNECTION,
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT, 10),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: connectionName,
      entities: ["src/models/entities/**/*.ts"],
      synchronize: true,
      poolSize: 10,
  };

  const dataSource = new DataSource(DBConfig);

  if (!dataSource.isInitialized) {
     await dataSource.initialize();
  }

  return dataSource;
}

初始化数据源后,我将其注入 Service 并通过 getRepository 执行数据库操作。我尝试过提高 MySQL 的 max_connections 限制、配置 TypeORM 的 poolSize,但均无效。

请问我在创建租户连接时是否存在错误?有没有方法在数据库操作完成后手动关闭连接?


问题根源与解决方案

核心问题分析

你的代码存在一个关键错误:每次请求都会创建新的 DataSource 实例,而非复用已初始化的租户连接池。

在 getTenantConnection 函数中,每次调用都会执行 new DataSource(DBConfig),即使同一个租户的连接已经完成初始化。这会导致每个请求都创建独立的连接池,而非复用已有连接池,最终快速耗尽 MySQL 的连接数上限。

修复步骤

1. 缓存已初始化的租户 DataSource 实例

维护一个全局缓存对象,存储已初始化的租户连接,避免重复创建连接池:

// 全局缓存已初始化的租户数据源,避免重复创建
const tenantDataSources = new Map<string, DataSource>();

export async function getTenantConnection(
  tenantHost: string
): Promise<DataSource> {
  const tenantId = getTenantId(tenantHost);
  const connectionName = `${tenantId}`;

  // 优先从缓存获取已存在的数据源
  if (tenantDataSources.has(connectionName)) {
    return tenantDataSources.get(connectionName)!;
  }

  const DBConfig: DataSourceOptions = {
      type: process.env.DB_CONNECTION as any,
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT, 10),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: connectionName,
      entities: ["src/models/entities/**/*.ts"],
      synchronize: true,
      poolSize: 10,
  };

  const dataSource = new DataSource(DBConfig);
  await dataSource.initialize();
  
  // 将初始化后的数据源存入缓存
  tenantDataSources.set(connectionName, dataSource);

  return dataSource;
}

2. 请求结束后手动管理连接(可选)

如果需要针对长期不活跃的租户释放连接,可以利用 Nest.js 的请求生命周期钩子,在请求完成后销毁连接池:

import { Injectable, Scope, OnModuleDestroy, Inject } from '@nestjs/common';
import { CONNECTION } from './tenancy.module';
import { DataSource } from 'typeorm';

@Injectable({ scope: Scope.REQUEST })
export class TenantConnectionCleanup implements OnModuleDestroy {
  constructor(@Inject(CONNECTION) private readonly dataSource: DataSource) {}

  async onModuleDestroy() {
    // 销毁整个连接池,释放所有连接
    await this.dataSource.destroy();
  }
}

在 TenancyModule 中注册该 Provider:

@Global()
@Module({
  providers: [
    connectionFactory,
    TenantConnectionCleanup
  ],
  exports: [CONNECTION],
})
export class TenancyModule {}

注意:destroy() 会关闭整个连接池,仅适合长期无请求的租户;如果租户请求频繁,建议依赖连接池的自动复用机制,不要每次请求都销毁连接池。

3. 优化连接池配置

除了 poolSize,补充配置连接池的超时参数,自动回收空闲连接:

const DBConfig: DataSourceOptions = {
  // ... 其他基础配置
  poolSize: 10,
  waitForConnections: true, // 连接池满时等待可用连接
  connectionTimeout: 30000, // 连接超时时间(毫秒)
  idleTimeout: 60000, // 空闲连接超时时间(毫秒),超时自动关闭
};

额外建议

  • 生产环境禁止使用 synchronize: true,该选项会自动同步实体结构,可能导致数据丢失
  • 定期通过 show status like 'Threads_connected'; 监控 MySQL 连接数变化
  • 若租户数量极大,可考虑「共享数据库+隔离 Schema」的多租户模式,减少连接池数量

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 07:10:32