
PostgreSQL连接意外关闭:六种客户端场景实测分析
来源标题:Server closed the connection unexpectedly in Postgres
Now-Next团队在官方Postgres Docker镜像上实测发现,「server closed the connection unexpectedly」并非PostgreSQL发出的信息,而是客户端库libpq检测到连接已断开,kill -9、OOM、关机、超时、连接池崩溃和网络重置都会产生同一句提示。真正原因通常记录在服务器日志中;若日志无相关内容,则需检查PostgreSQL外部因素或确认进程是否被杀。测试于2026年10月30日至31日夜间在13.23、15.19、17.11和18.6版本上完成。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
PostgreSQL 服务器意外关闭了连接,我们在六个客户端上做了实测:每种原因在服务器日志里分别是什么样子、原因信息去了哪里,以及丢失的 COMMIT。
Chris van Eijk,Now-Next · 2026 年 11 月 2 日 · 阅读时长 14 分钟 Now-Next 构建 SaaS 产品,也接手已有的产品。我们构建了什么 →
server closed the connection unexpectedly 的意思是客户端库 libpq 发现连接已经没了。PostgreSQL 并不会发送这段文字,它没有 SQLSTATE,也不会说明原因:在 kill -9、OOM kill、shutdown、超时、连接池进程死亡和网络重置之后,我们得到的都是同一句话。原因通常在服务器日志里。如果那个时刻日志里什么都没有,就到 PostgreSQL 之外去找,或者看看 PostgreSQL 本身是不是被杀了。
这些原因和日志行都是实测得到的;修复建议是我们的意见,除非下面某一节说明我们测试过。在建立连接阶段,同一句话有不同的原因,见 Connection refused: is the server running?:有东西接受了连接,在我们的例子里是 Docker 的端口映射,而它背后并没有服务器。
我们在官方 postgres Docker 镜像 13.23、15.19、17.11 和 18.6 上测量,时间是 2026 年 10 月 30 日到 31 日夜间,并在当晚稍晚用下面的脚本复现了大部分用例。客户端是 psql 17.11、psycopg2 2.9.13、psycopg 3.3.6、pgJDBC 42.7.13、node-postgres 8.23.1 和 pgx 5.11.0,外加 SQLAlchemy 2.1.6 和 PgBouncer 1.26.0。文中没有标注版本的结果来自 17.11。
这句话来自 libpq 中的五处,没有一处来自服务器。在其中一处,也就是一次返回 0 字节的读取,源码写着 “the connection has been closed”,然后 libpq 打印 “This probably means the server terminated abnormally”。这个猜测可能是错的,下面的 reset 用例就说明了这一点。在这条消息里,“server” 只是指 socket 的另一端。
先看你客户端在这句话上面打印了什么。FATAL 表示服务器或连接池说明了原因。WARNING 表示会话是因为别的事情被结束的:另一个 backend 崩溃了、执行了 immediate shutdown,或者对同步备库的等待被取消了。上面什么都没有并不能说明什么,因为我们测试的 psycopg2 和其他库都没有显示这些消息。然后读那个时间点前后的服务器日志。搜 was terminated by signal,不要搜 server process:PostgreSQL 18 写的是 client backend (PID …),而 13、15 和 17 写的是 server process (PID …)。
如果你是旁观者,客户端会显示一条警告,psql 会打印它,而我们测试的库不会。对一个 backend 执行 kill -9 会让服务器结束每个会话:
WARNING: terminating connection because of crash of another server process DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. HINT: In a moment you should be able to reconnect to the database and repeat your command. server closed the connection unexpectedly
随后日志会显示恢复过程。在空数据库里,从 kill 到 database system is ready to accept connections 大约用了 150 ms;这并不能说明一个有真实 WAL 需要重放的服务器会怎样。有 WAL 需要重放时,你会看到 redo starts 和 redo done,而不是 redo is not required。
LOG: server process (PID 240) was terminated by signal 9: Killed DETAIL: Failed process was running: set application_name=victim; select pg_sleep(300) LOG: terminating any other active server processes LOG: all server processes terminated; reinitializing LOG: database system was interrupted; last known up at 2026-10-30 22:54:49 UTC LOG: database system was not properly shut down; automatic recovery in progress LOG: redo is not required LOG: database system is ready to accept connections
那条警告的 SQLSTATE 是 57P02,但你捕获不到它。它以 notice 的形式到达客户端,随后连接就结束了。在 psycopg 3 中,notice handler 看到了 57P02,而异常本身没有 SQLSTATE。在 15、17 和 18 上,这条警告只发给客户端;13 还会按会话记录它。从 15 开始,immediate shutdown 有自己的措辞(terminating connection due to immediate shutdown command),而 13 会报告一个从未发生过的其他进程崩溃。
因为同一个事件在不同客户端上看起来不一样。用当前的 psycopg2,服务器的原因到不了你的代码里:
pg_terminate_backend() 那一列在 13、15、17 和 18 上相同。psycopg2 对空闲会话和正在执行查询的会话都只给出它那一句话。所以用 psycopg2,57P01、超时和崩溃看起来一模一样,只有服务器日志能把它们区分开。
这取决于 psycopg2 的 wheel,以及它里面的 libpq。网上的旧回答显示服务器原因出现在文本里,那曾经是真的。我们在 17.11 上用四个 wheel 终止了同样的会话:
因此,捕获 psycopg2.errors.AdminShutdown 的代码在一次版本升级后就悄无声息地不再触发了。空闲会话和执行中的会话在每一行上表现相同。如果你的日志里只有上表最后一列那样的消息,比如 connection already closed,那是后续错误;去找它前面那一条。
因为客户端在空闲时并不读取 socket。设置 idle_session_timeout = 2s 后,服务器在 2 秒后关闭了会话,而 psql、psycopg2、psycopg 3、pgJDBC 和 pgx 直到下一次查询才察觉。所以一个等待十分钟的应用会在一次无害的 SELECT 上看到失败,而超时早就过去了。idle_in_transaction_session_timeout 和 transaction_timeout 表现相同。idle_session_timeout 在 13 上不存在(unrecognized configuration parameter);它在 15、17 和 18 上可用。
node-postgres 是例外:它一直在读取,所以在没有查询运行时也会收到 'error' 事件。没有监听器的话,这个事件会结束进程:
node:events:502 throw er; // Unhandled 'error' event error: terminating connection due to administrator command
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。