NestJS+Prisma双客户端部署时出现‘Server has closed the connection’问题
解决Prisma+NestJS搭配pgBouncer主从PostgreSQL的连接关闭问题
核心问题定位
上线后出现的「Server has closed the connection」错误,本质是pgBouncer连接池配置与Prisma客户端连接管理不匹配,加上连接字符串的语法错误共同导致的。以下是具体排查和修复方案:
1. 修正连接字符串的语法错误
查看你的DATABASE_READ_URL配置,发现密码后多了一个多余的@符号:
DATABASE_READ_URL="postgresql://user:******:@[replicaip]:5432/stage-v2-panel?schema=public&pgbouncer=true"
改为:
DATABASE_READ_URL="postgresql://user:******@[replicaip]:5432/stage-v2-panel?schema=public&pgbouncer=true"
这个语法错误可能导致连接建立不稳定,是触发问题的潜在因素。
2. 调整pgBouncer的连接池配置
pgBouncer的默认参数会主动回收长期闲置的连接,而Prisma客户端如果持有这些被回收的连接,就会触发报错。修改pgBouncer的pgbouncer.ini配置:
- 设置
server_lifetime = 86400(1天):延长pgBouncer与PostgreSQL服务器的连接存活时间,避免频繁关闭连接 - 启用
pool_mode = transaction:事务模式下,pgBouncer会在每个事务结束后回收连接,更适配Prisma的ORM操作模式 - 确保
default_pool_size和max_client_conn足够大:根据业务并发量调整,避免因连接池耗尽导致的连接关闭
3. 优化Prisma客户端的连接管理
当前的PrismaService主动调用$connect()会长期持有连接,容易被pgBouncer回收,同时缺少错误重试和重连机制。修改你的PrismaService:
@Injectable() export class PrismaService implements OnModuleDestroy { readClient: PrismaClient; writeClient: PrismaClient; constructor() { this.readClient = this.createPrismaClient(process.env.DATABASE_READ_URL); this.writeClient = this.createPrismaClient(process.env.DATABASE_WRITE_URL); this.setupConnectionRecovery(); } private createPrismaClient(url: string): PrismaClient { return new PrismaClient({ datasources: { db: { url } }, retry: { maxRetries: 3, retryDelay: 100, retryableErrors: ['Server has closed the connection'] } }); } private setupConnectionRecovery(): void { // 处理读客户端连接错误 this.readClient.$on('error', async (e) => { if (e.message.includes('Server has closed the connection')) { await this.readClient.$disconnect(); this.readClient = this.createPrismaClient(process.env.DATABASE_READ_URL); } }); // 处理写客户端连接错误 this.writeClient.$on('error', async (e) => { if (e.message.includes('Server has closed the connection')) { await this.writeClient.$disconnect(); this.writeClient = this.createPrismaClient(process.env.DATABASE_WRITE_URL); } }); } async onModuleDestroy() { await this.readClient.$disconnect(); await this.writeClient.$disconnect(); } async enableShutdownHooks(app: INestApplication) { this.readClient.$on('beforeExit', async () => { await app.close(); }); this.writeClient.$on('beforeExit', async () => { await app.close(); }); } }
关键优化点:
- 移除
onModuleInit中的$connect()调用,让Prisma按需建立连接,避免长期持有闲置连接 - 添加自动重试机制,对连接关闭错误自动重试3次
- 监听连接错误事件,当检测到连接关闭时,自动断开并重新创建客户端实例
- 实现
OnModuleDestroy接口,在模块销毁时主动断开连接,避免资源泄漏
4. 主从架构额外检查
- 确认从库的复制延迟在可接受范围内,避免因数据不一致引发的间接错误
- 确保主从库的pgBouncer配置完全一致,避免因配置差异导致的连接行为不一致
内容的提问来源于stack exchange,提问作者rostamiani
相关产品推荐
相关产品推荐

