一、是不是连接池耗尽
错误信息形如:
FATAL: remaining connection slots are reserved for non-replication superuser connections
或:
sorry, too many clients already
或:
Error: P2024: Timed out fetching a new connection from the connection pool
最后一条是 Prisma 报错,意思也是后端拒绝建新连接。
二、最短处理路径(5 步)
Step 1:切换到 Pooler URL
打开 Neon Dashboard → Connection Details → 选择 「Pooled connection」:
# ❌ Direct connection(最多 100 连接)
postgresql://user:[email protected]/db
# ✅ Pooler connection(最多 1000 连接,Free 套餐)
postgresql://user:[email protected]/db
差别就是 -pooler 后缀。
Step 2:调整 Prisma 配置
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// 环境变量
DATABASE_URL="postgresql://user:[email protected]/db?pgbouncer=true&connection_limit=1"
pgbouncer=true 让 Prisma 跳过 prepared statements(PgBouncer transaction 模式不兼容)。
connection_limit=1 让每个 Prisma Client 实例只开 1 个连接。
Step 3:检查 Serverless 环境的全局复用
// ❌ 每次请求都新建(Edge / Lambda 冷启动会重复)
export async function POST(req: Request) {
const prisma = new PrismaClient()
// ...
}
// ✅ 复用全局实例
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export async function POST(req: Request) {
// 用全局 prisma
await prisma.user.findMany()
}
Step 4:用 Neon Serverless Driver
如果跑 Vercel Edge / Cloudflare Workers,更优解是 Neon Serverless Driver(HTTP-based,无传统 connection):
import { neon } from '@neondatabase/serverless'
const sql = neon(process.env.DATABASE_URL!)
const users = await sql`SELECT * FROM users WHERE id = ${userId}`
HTTP 模式不占 connection slot,无限并发。
Step 5:升套餐
如果业务真的需要 > 1000 连接:
- Launch $19/月:1000 直连 + 10000 pooler
- Scale $69/月:10000 直连 + 100000 pooler
三、为什么会耗尽(原理)
Neon 是 PG + 自研 storage 分离架构。每个 PG instance 启动时硬限制 max_connections(Free 套餐是 100)。
你的应用 Neon Direct(100 slots) Neon Storage
[Edge function] ─────→ [PG instance: max_conn=100] ──→ [Storage]
↓
slot 满了 → reject
通过 Pooler(PgBouncer):
你的应用 Neon Pooler Neon Direct
[Edge functions] ─→ [PgBouncer pool] ─────→ [PG instance: max_conn=100]
[Edge functions] ─→ [PgBouncer pool] ─────→ [PG instance: max_conn=100]
[Edge functions] ─→ [PgBouncer pool] ─────→ [PG instance: max_conn=100]
1000 client conns 100 actual conns
PgBouncer 复用底层 100 个真实 connection 给 1000 个 client。
四、Prisma vs Drizzle vs Neon Serverless Driver 对比
| 方案 | 连接模式 | 并发 | 推荐场景 |
|---|---|---|---|
| Prisma + Direct URL | TCP, persistent | 限 100 | 单实例 Node 应用 |
| Prisma + Pooler URL | TCP, pooled | 1000 | Serverless(带 pgbouncer=true) |
| Drizzle + Pooler URL | TCP, pooled | 1000 | Serverless |
| Neon Serverless Driver | HTTP, stateless | 无限 | Edge Functions 推荐 |
| pg + Pooler URL | TCP, pooled | 1000 | Node only |
Edge Functions 强烈推荐 Neon Serverless Driver。
五、进阶排查
5.1 找泄漏的 connection
SELECT pid, usename, application_name, state, query_start, query
FROM pg_stat_activity
WHERE state = 'idle' AND query_start < now() - interval '5 minutes';
idle 状态且长时间未释放的连接通常是 connection leak。
5.2 kill idle 连接
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle' AND query_start < now() - interval '1 hour';
紧急情况临时清理。
5.3 检查应用层是否手动关闭
// ❌ 没关
const client = await pool.connect()
const res = await client.query('SELECT ...')
// 忘记 release
// ✅ 正确
const client = await pool.connect()
try {
const res = await client.query('SELECT ...')
} finally {
client.release() // 必须
}
仍未恢复的备选方案
6.1 临时降级到本地 Postgres
如果 Neon 仍不够:
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=pass postgres:16
切到本地 + 加大 max_connections=500。
6.2 用 Supabase + Connection Pooler
Supabase 默认带 Supavisor pooler(端口 6543),Free 套餐 60 并发,Pro 套餐 200 并发。
6.3 跨境环境检查
调试 Neon Dashboard + 跑 Prisma migrate + 监控 production 连接数都需要稳定访问 Neon / Supabase / 各自 Dashboard。
如果你需要稳定登录 Neon Dashboard + 配 GitHub Actions CI + 联调 Stripe / Mercury,配一条海外服务跑 GitHub Actions / Cloudflare 的稳定线路能保证 DB 监控不中断。