Supabase 连接池耗尽:3 个原因 + 修复路径

error: remaining connection slots are reserved——Serverless 直连 Postgres 必踩。

你的 Vercel / Cloudflare / Netlify 函数突然全线 500,日志里满是:

error: remaining connection slots are reserved for non-replication
       superuser and rds_superuser connections

或者:

sorry, too many clients already

这是 Supabase(其实是 Postgres)经典的连接池耗尽。你的 Free 档默认 60 连接、Pro 档 200 连接,听起来很多,但 Serverless 平台一开 100 个并发实例,每个开一个 connection 就直接爆。问题不在 connection 数太少,而在 Serverless + 直连 Postgres 本身是反模式

理解关键:Postgres 的连接是重资源(每个 ~10MB 内存),不是 HTTP 连接那种轻便资源。Serverless 大规模并发必须经过 connection pooler(Supabase 内置 Supavisor / PgBouncer)。

常见原因

按命中率从高到低:

1. Serverless 直连 :5432(不走 pooler)

最高频。代码用 Supabase Dashboard 给的”Direct connection” string,每个 Lambda / Workers 实例独立开 connection。突发流量 → 100 个实例 → 100 个 connection → 爆。

如何判断:connection string 是 postgres://...supabase.com:5432/postgres 还是 :6543?5432 = 直连,6543 = pooler。

2. 没释放连接 / 长事务

const client = await pool.connect();
const result = await client.query('SELECT ...');
// ❌ 忘了 client.release()

每个 request 漏 1 个连接,几小时就耗尽。

如何判断:日志里 connection count 单调递增不下降。

3. pgbouncer mode 选了 session

Supavisor 默认 transaction mode,但有些教程让你切到 session mode(兼容性更好但占连接)。session mode 失去 pooling 优势。

如何判断:connection string 里 ?pgbouncer=true&pool_mode=session

4. 用了 PgBouncer 不支持的 statement

prepared statements、LISTEN、temporary tables 等在 transaction mode pooler 下不工作;ORM(如 Prisma)默认会用 prepared statements 必须特殊配置。

如何判断:报 prepared statement "X" does not exist 或类似。

5. 后台 cron 跑慢查询占连接

定时统计查询跑 30 秒、同时正常 traffic 进来也要 connection → 池子被 cron 占了。

如何判断:错误集中在 cron 触发时段。

6. 旁路服务(pgAdmin、Metabase)开了大量连接

你 / 同事用 GUI 工具直连 prod DB,每个 GUI 占 5-10 个连接。

如何判断:DB 上 SELECT * FROM pg_stat_activity 看 application_name。

最短修复路径

Step 1:紧急止血——kill 老连接

-- 在 Supabase Dashboard SQL Editor 跑
SELECT pid, usename, application_name, state, query_start
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

-- kill 老于 5 分钟的 idle in transaction
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND query_start < now() - interval '5 minutes';

立刻让 connection 空出来恢复服务,再慢慢修。

Step 2:换 pooler 连接串

Supabase Dashboard → Project Settings → Database → Connection string:

直连(避免在 Serverless 用):
postgres://postgres:[password]@db.xyz.supabase.co:5432/postgres

Supavisor pooler(Serverless 必用):
postgres://postgres.xyz:[password]@aws-0-region.pooler.supabase.com:6543/postgres

把代码 / env var 改成 pooler 字符串。重启服务。

Step 3:确认 pool_mode

postgres://...@...:6543/postgres?pgbouncer=true
                                  ^^^^^^^^^^^^
                              不指定 = transaction(推荐)

如果你显式加了 &pool_mode=session,去掉。session mode 失去 pooling 意义。

Step 4:Prisma / ORM 特殊处理

// .env
DATABASE_URL="postgres://...@:6543/postgres?pgbouncer=true&connection_limit=1"
DIRECT_URL="postgres://...@:5432/postgres"  // migration 用

// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")  // prisma migrate 走 direct
}

Prisma + pgbouncer 必加 ?pgbouncer=true&connection_limit=1

Step 5:query 必 release

// ❌ 漏 release
const client = await pool.connect();
await client.query(...);
// 缺 finally

// ✅ 正确
const client = await pool.connect();
try {
  await client.query(...);
} finally {
  client.release();
}

// 或更简单:用 pool.query 一次性,自动 release
await pool.query('SELECT * FROM users WHERE id = $1', [id]);

Step 6:监控 + 警报

-- 当前 active connections
SELECT count(*), state FROM pg_stat_activity GROUP BY state;

-- 长 idle in transaction(漏 release 信号)
SELECT pid, query, query_start, state
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND query_start < now() - interval '1 minute';

设置 alert:connection count > 80% cap 立刻通知。

Step 7:升级 plan / connection limit

Pro plan 默认 200 connections,Team 400,Enterprise 可调。短期不够先升 plan,长期还是要优化 pooling。

预防建议

  • Serverless 项目永远走 :6543 pooler,不要碰 :5432
  • 长跑后端服务(Fly.io、Railway、自建 EC2)才用 :5432 直连
  • 用 ORM 时配 connection_limit=1(serverless 每实例只要 1 个连接)
  • 每个 query 必 await + try/finally release
  • 监控 connection count,超 80% cap alert
  • 定时任务(cron、batch)用独立 connection / 独立 service,不抢主 pool
  • 禁止从笔记本直连 prod DB——用 Supabase Studio 或加 read-replica
  • 大流量项目从 day 1 就估算 connection 需求,免费档 60 连接很容易爆

相关阅读

标签: #独立开发 #排查 #排查