Nest.js+TypeORM多租户架构MySQL连接数过多问题排查
问题描述
我基于 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

