九月 25, 2025
摘要:在本教程中,您将学习如何在 PostgreSQL 中处理 “database is not accepting commands …” 的错误。
目录
如果您遇到 “数据库不接受命令” 这条错误信息,说明您已濒临事务 ID 回卷的危险境地。大多数 PostgreSQL 用户都理解事务 ID 回卷背后的原理,但是,很多 PostgreSQL 用户,对如何修复该问题也存在某些误解。
如何会出现 “数据库不接受命令” 的情况?
一旦出现这种错误,您的应用程序将陷入停机状态,需要您手动修复问题。在这种状态下,您仍可执行查询操作,但无法再进行任何数据修改。很少有人会遇到这种极端情况,因为 PostgreSQL 在采取这种最后的强制措施之前,设置了多道防御机制:
- 若某个表中包含存活时间超过
autovacuum_freeze_max_age
(默认值为 2 亿)个事务的行,PostgreSQL 将启动一个防回卷自动清理进程。 - 若某个表中包含存活时间超过
vacuum_failsafe_age
(默认值为 16 亿)个事务的行,PostgreSQL 将启动一个紧急防回卷自动清理进程,该进程会跳过索引清理步骤,以最快速度运行。 - 在离发生事务 ID 回卷前还剩 4000 万个事务时,日志中会出现警告信息。
只有当所有这些安全机制都无法阻止问题恶化时,PostgreSQL 才会停止数据修改操作。
以下几种情况会导致 PostgreSQL 无法自行修复该问题:
- 某个数据库事务被永久保持打开状态。
- 存在未提交或未回滚的预备事务。
- 存在遗留的复制槽,且备用服务器已启用
hot_standby_feedback
(热备反馈)功能。 - 存在数据损坏,导致
VACUUM
操作失败。
应对 “数据库不接受命令” 的正确措施是什么?
文档中对该问题的修复方法有如下描述:
在此状态下,已处于执行中的事务可继续进行,但仅能启动只读事务。修改数据库记录或截断关系(TRUNCATE)的操作将会失败。VACUUM
命令仍可正常执行。请注意,与早期版本中有时建议的做法不同,要恢复正常运行,无需也不建议停止 Postmaster(PostgreSQL 主进程)或进入单用户模式。相反,请遵循以下步骤:
- 解决旧的预备事务:可通过查询
pg_prepared_xacts
系统视图,找出其中age(transactionid)
值较大的行。此类事务应进行提交或回滚处理。 - 终止长时间运行的打开事务:可通过查询 pg_stat_activity 系统视图,找出其中
age(backend_xid)
或age(backend_xmin)
值较大的行。此类事务应进行提交或回滚,也可使用pg_terminate_backend
命令终止对应的会话。 - 删除旧的复制槽:使用 pg_stat_replication 系统视图,找出其中
age(xmin)
或age(catalog_xmin)
值较大的复制槽。很多情况下,这类复制槽是向那些已不存在或长期停机的服务器进行复制而创建的。若删除的复制槽对应的服务器仍存在,且可能会尝试连接该复制槽,则该备用服务器可能需要重新构建。 - 在目标数据库中执行
VACUUM
操作:最简单的方式是执行整个数据库范围的VACUUM
;若要缩短执行时间,也可对pg_class.relminxid
最早的表手动执行VACUUM
命令。在此场景下,请勿使用VACUUM FULL
,因为该命令需要一个事务 ID(XID),因此会执行失败;即便在超级用户模式下,它也会消耗一个事务 ID,从而增加事务 ID 回卷的风险。同样请勿使用VACUUM FREEZE
,因为它会执行超出恢复正常运行所需最小工作量的操作。
恢复正常运行后,请确保目标数据库的自动清理(autovacuum)配置正确,以避免未来再次出现此类问题。
容易误导用户的不当措施
上文引用的文档特意指出了一些常见的错误认知:
很多人认为需要关闭 PostgreSQL 并以单用户模式启动,才能执行VACUUM
操作。但事实并非如此。相反,以单用户模式启动服务器会使恢复过程复杂化,并增加停机时间。此外,单用户模式会放开防止您使用更多事务 ID 的安全机制,而消耗事务 ID 会使您更接近因事务 ID 回卷导致的数据损坏风险。
很多人将VACUUM (FULL)
视为更高级的VACUUM
,因此在这种紧急情况下很容易想使用它。但VACUUM (FULL)
会执行比普通的VACUUM
多得多的工作,会导致停机时间大幅延长。此外,如文档所述,它会迫使您使用单用户模式。
使用VACUUM (FREEZE)
确实能修复问题,但它会冻结表中的所有行。这属于不必要的额外工作,会导致停机时间延长。
PostgreSQL 项目中的提交日志
在 PostgreSQL 中,在下面的提交记录之前,存在这样一条错误消息:
ERROR: database is not accepting commands to avoid wraparound data loss in database "..."
HINT: Stop the postmaster and vacuum that database in single-user mode.
You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
改写关于事务 XID 耗尽预警的提示信息。
首先,我们不应建议切换到单用户模式,因为这是非常糟糕的建议。尤其是在较新版本中,当接近事务 XID 耗尽时,VACUUM 操作会进入紧急模式,此时直接在多用户模式下执行 VACUUM 完全可行。这种方式的破坏性更小,还能避免放开防止实际事务 ID 回卷的保护机制,因此应优先推荐这种做法。
其次,对于“接近限制时将发生什么”或“实际达到限制时正在发生什么”,描述需更精确。数据库并不会关闭,也不会拒绝所有命令,它只会拒绝会分配“即将耗尽的事务 XID”的命令。
不进行回溯补丁更新。现有提示中“建议进入单用户模式”的内容确实存在严重问题,即便我们通常会避免在历史版本分支中修改面向用户的提示信息,但从合理性角度,删除或修改该内容本无可厚非。不过,更稳妥的做法是保持保守,将此修复仅限定在主分支中。除了修改通常可能带来的翻译失效风险外,用户也可能已习惯现有提示信息,甚至可能有监控脚本正依赖该提示信息进行检测。
讨论:http://postgr.es/m/CA+TgmoZBg95FiR9wVQPAXpGPRkacSt2okVge+PKPPFppN7sfnQ@mail.gmail.com
PostgreSQL 17 版本中的此次提交,将该错误信息修改为了当前的形式:
ERROR: database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "..."
HINT: Execute a database-wide VACUUM in that database.
You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
因此,许多 PostgreSQL 用户至今仍认为,要从 “事务 ID 回卷预警” 状态中恢复,必须使用单用户模式,这种做法其实已经过时了!