为什么需要连接池
Node.js 本身是异步非阻塞的,但数据库操作本质上是同步的 I/O 操作。当多个请求同时需要数据库服务时,单个连接会成为瓶颈。
连接池的核心思想:预创建多个数据库连接实例,请求来临时从池中取用,用完归还。
Node.js 应用
├── 请求1 → 从连接池取连接 → 查询 → 归还连接
├── 请求2 → 从连接池取连接 → 查询 → 归还连接
└── 请求3 → 等待空闲连接 → ...
连接池(10个连接)
├── Connection 1 → MySQL
├── Connection 2 → MySQL
├── ...
└── Connection 10 → MySQL
text
主流 ORM 和数据库本身都内置了连接池支持,只需配置一个属性即可启用。
连接池不是越大越好。每个连接都会占用内存,且数据库服务器有最大连接数限制。合理大小取决于 CPU 核数和负载模式。
TypeORM 连接池
TypeORM 通过 extra 选项配置连接池(底层使用 mysql2 / pg 驱动的连接池):
// app.module.ts
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get('DB_HOST'),
// ... 其他配置
extra: {
connectionLimit: 10, // 连接池大小
},
}),
});
typescript
或通过 poolSize 属性(适用于部分驱动):
{
type: 'mysql',
// ...
poolSize: 10,
}
typescript
Prisma 连接池
Prisma 的查询引擎自动管理连接池,在首次查询或调用 $connect() 时创建。
推荐连接池大小
连接数 = CPU 物理核数 × 2 + 1
text
例如 4 核服务器:4 × 2 + 1 = 9 个连接。
注意是物理核数(physical CPU cores),不是线程数。
配置方式
通过连接字符串的 connection_limit 参数:
# .env
DATABASE_URL="mysql://root:example@localhost:3306/testdb?connection_limit=10"
bash
或在 Prisma Client 构造时:
const prisma = new PrismaClient({
datasources: {
db: {
url: `mysql://root:example@localhost:3306/testdb?connection_limit=10`,
},
},
});
typescript
Prisma 还支持连接池的额外参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
connection_limit | 最大连接数 | 按公式计算 |
pool_timeout | 等待空闲连接的超时时间(秒) | 10 |
Mongoose 连接池
Mongoose 通过连接选项配置连接池:
// 方式一:连接选项
MongooseModule.forRoot('mongodb://localhost/nest', {
maxPoolSize: 10, // 最大连接数
minPoolSize: 2, // 最小连接数
});
typescript
或通过连接字符串参数:
# .env
MONGODB_URI="mongodb://root:example@localhost:27017/nest?maxPoolSize=10"
bash
Mongoose 还提供了多租户连接方式——在同一个 MongoDB 实例上切换不同数据库:
// 同一连接,不同数据库
const connection = mongoose.createConnection('mongodb://localhost:27017/main');
const tenantDb = connection.useDb('tenant_1');
typescript
这是非关系型数据库的特性:创建数据库成本低,切换方便。但这种方式只适合同一 MongoDB 实例的多租户场景。如需连接不同位置的数据库,需要创建多个 mongoose.createConnection() 实例并缓存。
连接池对比
| 维度 | TypeORM | Prisma | Mongoose |
|---|---|---|---|
| 配置方式 | extra.connectionLimit 或 poolSize | URL 参数 connection_limit | maxPoolSize 选项 |
| 推荐大小 | 按需设置 | CPU 核数 × 2 + 1 | 按需设置 |
| 创建时机 | 应用启动 | 首次查询或 $connect() | 应用启动 |
| 最小连接数 | 可配置 | 不支持 | minPoolSize |
| 超时配置 | 驱动层面 | pool_timeout 参数 | 连接选项 |
完整配置示例
// .env
DB_POOL_SIZE=10
DB_POOL_TIMEOUT=10
typescript
// TypeORM
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: config.get('DB_TYPE', 'mysql') as 'mysql',
host: config.get('DB_HOST', '127.0.0.1'),
port: config.get<number>('DB_PORT', 3306),
username: config.get('DB_USERNAME', 'root'),
password: config.get('DB_PASSWORD', ''),
database: config.get('DB_DATABASE', 'testdb'),
extra: {
connectionLimit: config.get<number>('DB_POOL_SIZE', 10),
},
synchronize: false,
autoLoadEntities: true,
}),
});
typescript
// Prisma(多租户动态连接)
function createPrismaClient(config: DbConfig) {
return new PrismaClient({
datasources: {
db: {
url: `${config.type}://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}?connection_limit=${config.poolSize || 10}`,
},
},
});
}
typescript
// Mongoose
MongooseModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
uri: config.get('MONGODB_URI'),
connectionFactory: (connection) => {
// 可在此配置连接池等选项
return connection;
},
}),
});
typescript
多租户连接管理
在多租户场景中,连接池管理需要额外注意:
// 租户连接缓存
const tenantConnections = new Map<string, PrismaClient>();
function getTenantClient(tenantId: string): PrismaClient {
if (!tenantConnections.has(tenantId)) {
const config = getTenantDbConfig(tenantId);
const client = createPrismaClient(config);
tenantConnections.set(tenantId, client);
}
return tenantConnections.get(tenantId)!;
}
typescript
每个租户创建独立的连接池,用完归还时并不销毁连接,而是保持在池中等待复用。这样既保证了隔离性,又避免了频繁创建连接的开销。
↑