现象
出现 server closed the connection unexpectedly 或 could not receive data from server: Connection reset by peer;连接空闲一段时间后的第一条 SQL 失败,下一次取连接又正常;服务端日志可能出现 FATAL: terminating connection due to administrator command。
诊断
先看服务端日志有没有对应 backend 的记录:
grep -E 'terminating connection|server process .* was terminated|connection reset' \
/var/log/postgresql/postgresql-16-main.log | tail -50
有记录说明是服务端主动关闭(重启、故障切换、pg_terminate_backend、OOM);同一秒没有记录则更像 NAT、防火墙或负载均衡把空闲 TCP 流丢弃。用 SELECT pg_postmaster_start_time(); 判断集群是否刚重启,再用 now() - state_change 观察空闲会话年龄:大量空闲连接恰好卡在 300 秒、350 秒这类整数附近,基本可以确认是链路上的空闲超时。dmesg -T | grep -i 'killed process' 可排除 OOM killer 杀掉后端。
根因
TCP 连接在 PostgreSQL 完成认证之后被关闭,而客户端连接池仍认为它可用。下一次复用这个已收到 FIN/RST 的 socket 时,语句执行失败,表现为连接重置,而不是连接建立失败。
修复
在 libpq 或驱动中开启 TCP keepalive,并把连接池最大存活时间设在链路上最小空闲超时之下。node-postgres 可这样配置:
const pool = new Pool({
max: 10,
keepAlive: true,
idleTimeoutMillis: 30_000,
maxLifetimeSeconds: 240, // 小于负载均衡空闲超时
});
只在语句尚未发出、SQLSTATE 为 08006 时重连并重试一次;可能已经提交的事务不能重试。如果使用 PgBouncer,检查 server_idle_timeout 与 server_lifetime 是否小于应用池的空闲时间。
验证
让连接池空闲超过负载均衡超时后再发起查询,客户端应重新建连且不报错;连续多轮后对比客户端错误时间戳与服务端日志,确认没有新的 reset。pg_stat_activity 中空闲会话年龄应始终低于连接池存活上限。