一、是不是连接池耗尽

错误信息形如:

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 URLTCP, persistent限 100单实例 Node 应用
Prisma + Pooler URLTCP, pooled1000Serverless(带 pgbouncer=true)
Drizzle + Pooler URLTCP, pooled1000Serverless
Neon Serverless DriverHTTP, stateless无限Edge Functions 推荐
pg + Pooler URLTCP, pooled1000Node 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 监控不中断。

七、相关报错