大家好~ 今天给大家带来一篇超实用的PostgreSQL技术干货投稿,来自我的朋友分享!(某头部制造车企首席DBA,PG ACE,10年通信/电力/证券/保险等行业一/二线数据库维护服务经验。 数据库技术爱好者,热衷于技术的学习研究和交流,网名“李先生”)
各位道友们,本篇文章全程贴合PG 实战场景问题处理,干货可直接收藏,快快点赞关注、扫码进群吧,再说一句,今天的配图是上次夜游西湖的大漂亮风景,希望大家会喜欢,话不多说,我们快快回顾一下今天的案例分享:
凌晨三点,监控告警突然响起——两个逻辑复制槽状态异常。这不是第一次了,但这次的情况似乎更加棘手。面对即将触发的连锁反应,一场与WAL日志和事务ID的较量悄然开始。

第一章:初探战场——发现失效的复制槽
凌晨的告警显示,生产环境的逻辑复制出现异常。作为DBA,我第一时间连接到主库,开始排查。
1.1 第一道命令:查看复制槽全景
SELECT slot_name, plugin, slot_type, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size FROM pg_replication_slots;执行结果:
slot_name | plugin | slot_type | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn | wal_status | safe_wal_size -----------------------------------------------+----------+-----------+--------+------------+------+--------------+--------------+---------------------+------------+--------------- postgres_lxs_0_0 | | physical | t | 76250 | | | 2C7/4F7C8290 | | reserved | 21483453808 ods_oms_pmp_default_lxs_ri | pgoutput | logical | f | | | 238237296 | | 29F/353EE920 | lost | ods_oms_pmp_default_lxs_ri_prod | pgoutput | logical | f | | | 240507166 | | 2A3/F8192F30 | lost | ods_oms_pmp_default_lxs_ri_prod_1 | pgoutput | logical | f | | | 266139505 | 2C6/DC8D3E90 | 2C6/DCCF9528 | reserved | 19554073968(4 rows)🔍 初步分析:
发现关键问题: 两个逻辑复制槽的 wal_status 显示为 lost——这是致命的信号。WAL(Write-Ahead Log)丢失意味着复制槽无法继续工作。

第二章:深挖根源——WAL配置与延迟分析
2.1 检查WAL配置参数
SELECT name, setting, unit FROM pg_settings WHERE name IN ('wal_keep_size', 'max_slot_wal_keep_size', 'max_wal_size');执行结果:
name | setting | unit ------------------------+---------+------ max_slot_wal_keep_size | 20480 | MB max_wal_size | 8192 | MB wal_keep_size | 1024 | MB(3 rows)关键配置解读:
- max_slot_wal_keep_size = 20480 MB
(20GB):单个复制槽最多保留的WAL量 - max_wal_size = 8192 MB
(8GB):两次检查点间WAL最大估算值 - wal_keep_size = 1024 MB
(1GB):为复制保留的最小WAL量

2.2 计算复制槽延迟
SELECT'ods_oms_pmp_default_datacenter_secd_ri'AS slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), '29F/353EE920') AS lag_bytes, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '29F/353EE920')::bigint) AS lag_pretty;执行结果:
slot_name | lag_bytes | lag_pretty ----------------------------------------+--------------+------------ ods_oms_pmp_default_datacenter_secd_ri | 172244281184 | 160 GB(1 row)💡 瞬间明白:
根本原因浮出水面: 复制槽延迟达到160GB,远超20GB的限制。PostgreSQL不得不丢弃旧WAL,导致复制槽状态变为lost。

2.3 检查订阅关联情况
SELECT subname, subslotname, subenabled FROM pg_subscription WHERE subslotname IN ('ods_oms_pmp_default_datacenter_secd_ri', 'ods_oms_pmp_default_datacenter_secd_ri_prod');执行结果:
subname | subslotname | subenabled ---------+-------------+------------(0 rows)重要发现: 两个失效的复制槽没有关联的订阅,它们是孤儿状态。

第三章:意外发现——第三个复制槽的隐患
在清理两个失效复制槽之前,我注意到第三个复制槽的异常。
3.1 深入分析第三个复制槽
SELECT slot_name, slot_type, active, active_pid, catalog_xmin, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lag_bytes, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)::bigint) as lag_pretty, wal_status, safe_wal_sizeFROM pg_replication_slots WHERE slot_name ='ods_oms_pmp_default_datacenter_secd_ri_prod_1';执行结果:
slot_name | slot_type | active | active_pid | catalog_xmin | lag_bytes | lag_pretty | wal_status | safe_wal_size -----------------------------------------------+-----------+--------+------------+--------------+------------+------------+------------+--------------- ods_oms_pmp_default_datacenter_secd_ri_prod_1 | logical | f | | 266139505 | 2005052416 | 1912 MB | reserved | 19472957144(1 row)问题矩阵:

3.2 检查事务ID年龄——惊人发现
SELECT datname, age(datfrozenxid) as frozen_xid_age,CASEWHEN age(datfrozenxid) >200000000THEN'CRITICAL: 接近事务ID回卷'WHEN age(datfrozenxid) >100000000THEN'WARNING: 事务ID年龄过高'ELSE'OK'ENDas statusFROM pg_database WHERE datname = current_database();执行结果:
datname | frozen_xid_age | status ----------+----------------+-------------------------- postgres | 266402368 | CRITICAL: 接近事务ID回卷(1 row)🚨 红色警报:
PostgreSQL使用32位事务ID,最大值约20亿。当前数据库的冻结事务ID年龄已达到2.66亿,状态显示"接近事务ID回卷"。如果达到20亿,数据库将进入只读模式防止数据损坏!

第四章:关键连接——catalog_xmin的阻塞效应
4.1 确认catalog_xmin的影响
SELECT'catalog_xmin'as source,266139505::text as xmin_value, txid_current() -266139505as age_from_currentUNIONALLSELECT'current_xid'as source, txid_current()::text as xmin_value,0as age_from_current;执行结果:
source | xmin_value | age_from_current --------------+------------+------------------ catalog_xmin | 266139505 | 268551 current_xid | 266408056 | 0(2 rows)连接点发现:
复制槽的 catalog_xmin = 266,139,505 当前事务ID:266,408,056 两者差距:268,551个事务
这意味着VACUUM无法清理这268,551个事务中产生的死元组。复制槽的catalog_xmin像一道闸门,挡住了事务清理的通道。

4.2 检查活跃事务持有者
SELECT pid, usename, application_name, backend_type, backend_xmin::text, backend_xid::text, query_start, state,LEFT(query, 100) as short_queryFROM pg_stat_activity WHERE backend_xmin ISNOT NULLORDERBY backend_xmin::text::bigintLIMIT 5;执行结果:
pid | usename | application_name | backend_type | backend_xmin | backend_xid | query_start | state | short_query ---------+----------+------------------+----------------+--------------+-------------+-------------------------------+--------+------------------------- 1555972 | postgres | psql | client backend | 266408056 | | 2026-01-22 14:00:22.896688+08 | active | SELECT + | | | | | | | | pid, + | | | | | | | | usename, + | | | | | | | | application_name, + | | | | | | | | backend_type, + | | | | | | | | backend_xmin::text,+ | | | | | | | | ba(1 row)确认: 复制槽的catalog_xmin确实是当前系统中最老的事务点!
第五章:决策与行动——清理孤儿复制槽
5.1 最终状态确认
SELECT slot_name, active, wal_status, catalog_xmin::text, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as lag_prettyFROM pg_replication_slots WHERE slot_name ='ods_oms_pmp_default_datacenter_secd_ri_prod_1';执行结果:
slot_name | active | wal_status | catalog_xmin | lag_pretty -----------------------------------------------+--------+------------+--------------+------------ ods_oms_pmp_default_datacenter_secd_ri_prod_1 | f | reserved | 266139505 | 2134 MB(1 row)数据变化趋势:
延迟从1912MB → 2134MB(15分钟内增长222MB) 确认无人消费,持续积累

5.2 执行清理操作
基于以上分析,决定清理所有孤儿复制槽:
第一步:删除两个失效的复制槽
-- 删除第一个失效槽SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri');-- 删除第二个失效槽 SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri_prod');第二步:删除第三个有隐患的复制槽
SELECT pg_drop_replication_slot('ods_oms_pmp_default_datacenter_secd_ri_prod_1');第三步:验证清理结果
SELECT slot_name, active, wal_status FROM pg_replication_slots;预期结果:
slot_name | active | wal_status -----------------------+--------+------------ postgres_109fe3a1_0_0 | t | reserved(1 row)第六章:经验总结与预防措施
6.1 问题根源分析
6.2 监控指标建议
建立以下监控告警:
- 复制槽延迟 > max_slot_wal_keep_size的80%
- wal_status = lost 立即告警
- 事务ID年龄 > 1亿 告警
- 复制槽活跃状态 > 1小时非活跃告警

6.3 预防措施
-- 定期检查孤儿复制槽SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lagFROM pg_replication_slots WHERE active =falseAND slot_type ='logical'AND pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) >1024*1024*1024; -- 延迟>1GB-- 监控事务ID年龄SELECT datname, age(datfrozenxid) as frozen_xid_ageFROM pg_database WHERE age(datfrozenxid) >100000000;
后记:凌晨四点的思考
处理完这个紧急问题,窗外已现微光。这次事件提醒我们几个重要原则:
- 复制槽不是"设置即忘记":它们需要持续监控和维护
- 延迟是沉默的杀手:缓慢积累的延迟最终会导致灾难性故障
- 事务ID回卷是真实威胁:特别是在高负载的OLTP系统中
- 自动化清理很重要:建立自动清理孤儿复制槽的机制

最深刻的教训是:在分布式系统中,一个组件的故障往往会引发连锁反应。复制槽失效不仅仅是复制问题,它可能演变成整个数据库的可用性问题。
关键数字回顾:
🔴 160 GB:失效复制槽的延迟 🔴 2.66亿:数据库事务ID年龄 🔴 268,551:被阻塞的事务数量 🟢 20 GB:复制槽WAL保留上限 🟢 1.9 → 2.1 GB:第三个复制槽延迟增长
这次排查像一次侦探工作,从表面的复制故障,追踪到WAL配置,最终发现隐藏在深处的事务ID危机。每个数字背后,都是系统状态的密码。
如果你在运维PostgreSQL时,也遇到类似问题,欢迎在评论区留言交流,一起探讨解决方案,少踩坑、提效率
同时也欢迎大家扫码入技术分享群,和我们一起学习进步,快快扫描如上二维码吧!





