PostgreSQL 的 exit_on_error:看似快速失败,实际会直接销毁会话

2026-07-17 42 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

PostgreSQL 的 exit_on_error 看起来像一个干脆利落的故障处理开关:只要会话中出现错误,数据库就立即终止该会话。但它并不只是让当前 SQL “失败得更快”,而是把普通 ERROR 提升为连接终止事件。随连接一起消失的,还可能包括未提交事务、临时表、会话级配置、预备语句和应用锁。

它改变的不是错误,而是错误后的会话命运

默认情况下,一条 SQL 发生错误并不等于数据库连接必须断开。在事务外,客户端通常还能继续发送下一条语句;在显式事务中,事务会进入失败状态,客户端可以执行 ROLLBACK,然后继续使用同一连接。

启用 exit_on_error 后,错误仍然是原来的错误,但服务器不会把会话留给客户端恢复。连接被终止,尚未提交的事务由 PostgreSQL 回滚。

这与 psqlON_ERROR_STOP 不是一回事:

  • ON_ERROR_STOP 是客户端行为,用于控制脚本遇到错误后是否停止执行。
  • exit_on_error 是服务器会话参数,错误发生后直接终止后端会话。
  • 前者可以停止脚本但保留数据库会话;后者会让连接、事务和会话状态一起消失。

因此,把 exit_on_error 理解成数据库版的异常抛出并不准确。它更接近“任何错误都触发断开连接”。

破坏性来自会话中尚未落盘的状态

已提交的数据不会因为连接终止而丢失,PostgreSQL 的事务保证仍然有效。真正危险的是应用可能误以为“只有失败的语句受影响”,却忽略了会话中其他未持久化状态:

  • 当前事务内已经成功执行、但尚未提交的写入会全部回滚。
  • 临时表和其中的数据随会话销毁。
  • SET 写入的会话级参数、搜索路径和角色状态消失。
  • 预备语句、游标以及会话级 advisory lock 被释放。
  • 连接池补充的新连接不会自动继承旧连接的临时状态。

更隐蔽的问题出现在自动重连之后。驱动或连接池可能迅速提供一个新连接,让应用看起来已经恢复;但新的后端进程拥有全新的会话。若业务代码把“连接恢复”误认为“事务继续”,就可能跳过必要的回滚、重试或状态重建。

用容器观察一次完整回滚

下面的实验可以这样实践。它启动一个临时 PostgreSQL 容器,在事务中插入一行,然后故意制造除零错误。运行前需要安装 Docker,并确保本机没有同名容器。

docker run --name pg-exit-on-error \
  -e POSTGRES_PASSWORD=postgres \
  -d postgres:17

until docker exec pg-exit-on-error pg_isready -U postgres >/dev/null 2>&1; do
  sleep 1
done

docker exec -i pg-exit-on-error psql -U postgres <<'SQL'
CREATE TABLE exit_demo (
  id integer PRIMARY KEY,
  note text NOT NULL
);

BEGIN;
INSERT INTO exit_demo VALUES (1, 'not committed');
SET exit_on_error = on;
SELECT 1 / 0;
COMMIT;
SQL

执行 SELECT 1 / 0 后,该会话会被终止,因此后面的 COMMIT 不会成功。重新建立连接检查表内容:

docker exec pg-exit-on-error \
  psql -U postgres -c 'SELECT * FROM exit_demo;'

预期结果是零行,因为插入发生在未提交事务中。表本身仍然存在,是因为 CREATE TABLE 在事务开始前已经提交。

实验结束后可以删除容器:

docker rm -f pg-exit-on-error

如果只需要让部署脚本在首个错误处停止,更合适的做法通常是让客户端负责:

psql -X -v ON_ERROR_STOP=1 -f migration.sql

这样脚本会返回非零退出码,但不会把所有普通数据库错误都重新定义成会话终止事件。迁移文件仍应使用显式事务,确保多条变更要么全部提交,要么全部回滚。

哪些场景才适合启用

exit_on_error 可以用于明确要求“一旦状态偏离预期,就不再复用该会话”的受控环境,例如某些测试工具或生命周期很短的专用会话。它不应被当成通用的事务管理、迁移失败处理或连接池健康检查机制。

上线前至少确认以下事项:

  1. 客户端能区分 SQL 错误、连接断开和提交结果未知三种情况。
  2. 自动重连后会重新设置必要的角色、search_path 和会话参数。
  3. 重试操作具有幂等性,不会因为重复执行产生第二次扣款或重复任务。
  4. 连接池会丢弃已断开的连接,而不是把它交给下一个请求。
  5. 监控能够看到异常断连数量及对应的服务器日志。
  6. 团队确实需要销毁会话,而不只是希望脚本遇错停止。

exit_on_error 的名字容易让人只注意“停止”,忽略“退出”的对象其实是整个数据库会话。它提供的不是免费的快速失败,而是一种扩大错误影响范围的策略。只有当应用已经把断线恢复、事务重试和会话初始化设计清楚时,这个开关才可能符合预期。


相关推荐