这段时间里,我处理过不少PostgreSQL相关的生产故障。从连接数被打满到磁盘写爆,从慢查询拖垮库到复制中断无人察觉,问题五花八门,但排查思路其实有章可循。这篇内容就把我在生产环境里实际踩过的坑、验证过的方法,按故障类型梳理一遍,每个场景都配上可直接落地的排查命令和解决步骤。不管你是刚接手PG的运维新人,还是已经和这个数据库打过几年交道的熟手,这份排查手册应该都能帮你省掉一些试错时间。
1. 先理清思路:生产故障排查的正确姿势
1.1 排查前的心态与方法论
先说一个我自己的体会:生产环境出了问题,大家第一反应往往是"赶紧重启""先把库拉起来再说",这可以理解,但很多时候会踩到更大的坑。我曾经遇到一个连接数被打满的故障,现场同事为了快速恢复,直接重启了数据库实例。服务确实起来了,但几秒后连接数又被占满——因为根本没有解决根本问题,应用端的连接池配置没有改,而且由于重启,本来可以通过pg_stat_activity看到的一堆活跃会话线索全没了,排查难度反而更大了。
所以,我在处理生产故障时给自己定了几条规矩:
- 先观察、后动手。除非是硬件故障、数据损坏这类必须立即处置的情况,否则先花几分钟采集现场信息。
- 优先查看数据库的实时状态视图,这是最接近"案发现场"的资料。
- 任何kill会话、重启实例、修改配置的操作,都要先评估影响面,并在执行前记录当前状态。
- 排查过程要有序,不要东一榔头西一棒子,避免遗漏关键线索。
1.2 必备的监控与诊断工具
工欲善其事,必先利其器。生产环境的PG故障排查,靠眼睛瞪终端是不可靠的,必须有一些基础工具。我常用的组合是:
- 系统层:
top、iostat、vmstat、dstat,用来确认CPU、内存、磁盘IO是否存在瓶颈。 - 数据库层:
pg_stat_activity、pg_stat_database、pg_locks、pg_stat_replication,这些是排查问题的核心视图。 - 日志:PG的运行日志(
postgresql-%Y-%m-%d_%H%M%S.log)必须开启并合理配置,log_min_duration_statement建议设置到一个合理的阈值,比如500ms甚至更低,这会极大帮助定位慢查询。 - 扩展插件:
pg_stat_statements是必备神器,它可以持久化统计SQL语句的执行情况,让你能快速找到TOP SQL。
如果是大型实例,建议额外部署Prometheus+Grafana这类的监控方案,把关键指标曲线留档。这样出问题时,可以快速回看故障发生前后的指标变化,而不是凭感觉猜。
2. 连接类故障:最常被抢跑的第一道坎
2.1 连接数打满,新连接被拒
生产环境最常见的故障之一,就是应用突然报"FATAL: sorry, too many clients already"。这个问题说起来简单,就是连接数达到了max_connections的上限,但背后的成因可能有多种。
我在一次实际故障中,就遇到过这样的情况:应用的连接池配置是50个连接,但后台的定时任务框架错误地创建了大量短连接,没有走连接池,导致几分钟内连接数直接冲到了上限。当时我用下面这条SQL查看了当前的连接分布:
SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count(*) DESC;可以看到大量idle状态的连接堆积。idle连接占了连接数的大头,不一定是坏事,但如果这些连接来自非连接池的客户端,那么就会白白占着资源。我需要进一步确认连接来源和运行中的SQL,定位到具体是哪个应用:
SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity GROUP BY usename, application_name, client_addr ORDER BY count(*) DESC;基于结果,快速与对应的应用负责人确认。如果是短连接风暴,需要应用侧马上修复;如果是连接池配置不合理,则需要调整池大小。临时缓解手段是调大max_connections,但不要盲目调大——PG每个连接都会消耗内存(通常几MB),调太大可能导致内存不足,甚至OOM。
注意:
max_connections是PG的核心参数之一,修改需要重启实例才能生效,而且它和shared_buffers、work_mem等参数有联动关系。生产环境调整要谨慎,建议先在测试环境验证。
2.2 连接超时与认证失败
另一类连接故障,是应用侧报"connection timed out",或者"password authentication failed for user"。这类问题有时候会被忽略,因为它看起来不像数据库停机那么严重,但影响面同样不小。
连接超时,常见原因有:
- 网络抖动或防火墙拦截:如果应用和数据库之间跨机房,或者经过复杂的网络链路,偶尔的网络延迟波动会触发连接超时。这种问题排查需要在应用侧看延迟曲线,也要在数据库侧看
pg_stat_activity里是否有大量waiting状态的连接。 - 认证速度慢:尤其当PG配置了基于LDAP或外部认证时,如果认证服务响应慢,就会导致每次新建连接都卡顿。这种情况下,连接池预热也会失败。
认证失败则要区分几种情况:
- 密码错误或用户名错误,检查
pg_hba.conf的认证配置,确认使用的是md5、scram-sha-256还是trust方式。 - 客户端使用的IP不在
pg_hba.conf允许范围内,会报"no pg_hba.conf entry for host"。
对于认证失败,我最想提醒的一点是:不要在生产环境随手把pg_hba.conf改成trust。这是极其危险的操作,等于把数据库裸奔在网络上。正确做法是仔细检查认证规则,确保应用账号的加密方式与pg_hba.conf里配置的方法一致。
3. 性能类故障:慢查询与资源争抢
3.1 慢查询定位三板斧
性能故障在所有故障类型中占比最高。慢查询如果处理不及时,会拖垮整个数据库实例,进而影响所有业务。定位慢查询,我有一套固定打法。
第一板斧:看当前正在执行的SQL。
SELECT pid, state, wait_event_type, wait_event, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 seconds' ORDER BY duration DESC;这条SQL能列出所有执行时间超过5秒的活跃查询。看到结果后,第一时间判断是单条SQL性能差,还是被锁阻塞。wait_event如果显示Lock相关事件,说明是在等待锁。
第二板斧:启用pg_stat_statements,找历史高频SQL。
如果你的实例没有开启这个扩展,建议立刻开启。它会在实例级别记录SQL的调用次数、总耗时、平均耗时、IO消耗等信息。下面是查询TOP SQL的常用语句:
SELECT calls, total_time, mean_time, shared_blks_hit, shared_blks_read, substring(query, 1, 100) AS query_preview FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20;通过这个结果,你能找到总耗时最长的那些SQL,然后针对性地做优化。比如常见的问题是:查询没有走索引,或者索引失效。
第三板斧:用EXPLAIN ANALYZE分析具体SQL的执行计划。
这条SQL我实际工作中用的频率最高,但也最容易用错。很多人直接在慢SQL前加个EXPLAIN就跑,但EXPLAIN只是显示计划,并不会真正执行,EXPLAIN ANALYZE才会真实执行SQL并返回实际运行数据。
在分析执行计划时,常见的问题模式有:
- 全表扫描(Seq Scan):说明没有可用的索引,或者优化器认为全表扫描更便宜。这时候要检查是否缺索引,或者查询条件是否真的能用上索引。
- 行数估算严重不准:如果
EXPLAIN里的rows估算和实际返回行数差异巨大,多半是统计信息陈旧,需要执行ANALYZE。
提示:在生产环境执行
EXPLAIN ANALYZE时,SQL会真实执行,对高负载库来说可能加重负载。建议先在测试库上验证,或者使用BEGIN; EXPLAIN ANALYZE ...; ROLLBACK;的方式,在事务里执行,这样不会留下实际变更。
3.2 CPU飙升与锁等待:资源争抢的两种表现
CPU飙升是让我比较头疼的一类问题,因为它往往同时伴随着业务响应变慢、甚至实例不可用。排查的时候,我会先到系统层看进程状态:
top -Hp <pg_pid>确认是不是PostgreSQL进程本身在消耗CPU。如果确认是PG的问题,再到数据库层查看当前的活跃会话和SQL。这时候往往是某个SQL在疯狂扫描大表,或者某些SQL出现大规模hash join,消耗了大量CPU。
锁等待则更隐蔽一些。我曾经遇到过一个故障:业务方反馈某张表的所有更新操作都阻塞了,但当时数据库的CPU、内存、磁盘都很正常,连接数也不高。我通过下面的查询发现了真相:
SELECT a.pid, a.state, a.wait_event_type, a.wait_event, a.query AS blocked_query, b.pid AS blocking_pid, b.query AS blocking_query FROM pg_stat_activity a JOIN pg_stat_activity b ON a.pid != b.pid WHERE a.wait_event_type = 'Lock' AND a.state = 'active';可以看到有一条事务开启了但长时间未提交,持有锁,另一个更新操作在等待它。这种“长事务持有锁”的问题,在生产里非常常见,最常见的原因是:应用代码里开了事务但忘记提交,或者后台任务在一个事务里处理了大量数据,耗时过长。
遇到锁等待,处理的时候要很谨慎。pg_terminate_backend(pid)可以结束指定的会话,但必须先确认这个会话是不是可以安全中断。比如,如果一个长事务已经执行了30分钟,让它回滚可能要回滚很久,数据库压力反而更大。这种情况下,我会先评估这条事务的操作内容,与业务方确认后再决定是否强杀。
4. 存储与日志类故障:磁盘满和WAL失控
4.1 磁盘空间吃紧:不只是数据文件的问题
磁盘满的故障,是我在客户现场遇到最多的几种故障之一。通常表现为数据库无法写入新数据,甚至直接挂掉。很多人第一反应是看df -h,确认磁盘是不是满了,但如果只是"满"这个层面,还不够,还需要分清是哪种路径满了。
PG涉及磁盘空间的主要路径有:
- 数据目录(通常是
/var/lib/postgresql/下):放着数据文件、WAL日志。 - WAL目录(通常是
pg_wal/子目录,旧版本叫pg_xlog)。 - 临时文件目录(
base/pgsql_tmp/):排序、Hash Join等操作会生成临时文件。 - 日志目录:PG运行日志如果长期不打日志轮转策略,也会把磁盘写爆。
我遇到过的一个真实场景是:某台机器的其他文件路径空间充足,但pg_wal目录异常膨胀,把磁盘占满了。正常情况下,WAL文件会在检查点后自动清理,但如果归档失败或流复制下游节点长期断连,WAL文件就会不断积压。
排查时,先看当前WAL目录大小:
du -sh /var/lib/postgresql/15/main/pg_wal/如果发现WAL目录异常庞大,再检查归档状态和复制状态:
SELECT * FROM pg_stat_archiver; SELECT * FROM pg_stat_replication;归档状态里如果failed_count和last_failed_time在持续变化,说明了归档失败。复制状态中如果某个备库的state不是streaming,或者sent_lsn与write_lsn差距越来越大,就要注意了。
注意:如果磁盘快满了,千万不要直接删除
pg_wal文件来释放空间,这个操作可能直接导致数据库无法恢复。正确做法是:优先恢复归档或复制链路,让WAL自然清理;若实在紧急,可以在archive_command或restore_command配置上做临时调整,但要清楚后果。
4.2 临时文件与日志轮转:两个容易被忽视的坑
临时文件问题,往往和超大排序或Hash Join直接相关。work_mem配置过小时,PG会把排序操作放到磁盘临时文件执行,大幅降低性能,同时产生大量临时文件。曾经有个客户反馈某条聚合查询特别慢,我查了pg_stat_database里的temp_files和temp_bytes字段,发现数值高得离谱:
SELECT datname, temp_files, temp_bytes FROM pg_stat_database WHERE datname = 'your_db';这说明数据库在频繁使用磁盘临时文件进行排序。对于这种情况,我会针对特定SQL调整work_mem参数,或者给对应的查询加上合适的索引,减少排序规模。但要注意work_mem不是调越大越好,它在每个排序操作中都会分配,并发高时会吃掉大量内存,反而引起更严重的问题。
日志轮转问题,则是我在各式环境里都遇到过的小但致命的坑。有些安装默认log_rotation_age很大,或者logging_collector未开启,日志文件就一直写在一个文件里,时间久了能到十几GB。排查时发现磁盘空间被日志占满,数据库已经开始报警。配置里推荐设置:
logging_collector = on log_rotation_age = 1d log_rotation_size = 100MB log_truncate_on_rotation = on这样日志文件每天轮转,且单个文件超过100MB也会轮转,避免单个文件无限增长。这是生产环境的基本配置,但很多测试环境转生产时没有同步过去,容易踩坑。
5. 故障排查速查表与个人经验总结
5.1 快速定位问题速查表
结合我在多个生产环境的排查经历,整理了一个快速参考表,遇到问题时可以对照着看,能帮助你少走不少弯路。
| 故障现象 | 第一步排查命令/方法 | 常见原因 | 紧急处理 |
|---|---|---|---|
连接被拒绝too many clients | select count(*) from pg_stat_activity;查看总连接数 | 连接数打满、连接池配置不当、短连接风暴 | 临时调大max_connections,联系应用排查连接来源 |
| 连接超时 | 应用侧抓包/看延迟,数据库侧看pg_stat_activity | 网络问题、认证服务慢 | 网络链路排查,检查认证配置 |
| 运行中的SQL卡住不动 | select * from pg_stat_activity where state = 'active'; | 锁等待、慢查询、大事务 | 查pg_locks确认阻塞源,结合业务评估是否终止会话 |
| CPU飙升到100% | top确认PG进程,再查pg_stat_activity活跃SQL | 烂SQL全表扫描、大hash join、索引失效 | 定位SQL,用EXPLAIN分析,加索引或优化 |
| 磁盘满 | df -h,再查pg_wal、数据目录、日志目录 | WAL积压、归档失败、日志膨胀、临时文件过多 | 优先恢复归档链路,不能直接删WAL |
| 复制中断 | select * from pg_stat_replication; | 网络断连、备库故障、WAL发送进程崩溃 | 检查网络,重启备库的恢复进程 |
| 内存持续增长 | top看SS/RES,查看shared_buffers、work_mem配置 | 并行查询过多、连接数过多、work_mem过大 | 降低并发连接数,调整work_mem,优化大查询 |
这张表不是万能的,但它覆盖了我在生产环境里遇到故障后比较高频的排查动作,算是基础版的急救包。
5.2 一些实在的避坑心得
在踩过不少坑之后,我总结了几点对生产环境PostgreSQL运维很有价值的心得,拿出来分享给各位。
第一,任何生产变更都要有"回滚方案"。曾经有一次服务端调整shared_buffers配置,结果因为服务器内存估算错误,实例直接起不来,最后花了很长时间救场。后来我做参数变更前的强制动作就是:先备份postgresql.conf,再确认内存等资源余量,最后准备好如果参数不生效怎么回滚。
第二,别忽略checkpoint的行为。PG的检查点直接影响故障恢复速度和WAL清理。如果checkpoint_timeout设置过短,会增加IO压力;设置过长,则故障恢复时要重放大量WAL,恢复时间暴涨。生产环境推荐用checkpoint_completion_target = 0.9这样的合理值,并监控pg_stat_bgwriter的checkpoints_timed和checkpoints_req比例——如果checkpoints_req过高,说明系统一直跟不上WAL生成速度,需要调参数或换更快的磁盘。
第三,把"压测"作为上生产前的必答题。我见过很多故障,本质上都是"没做过压力测试"的代价。PG的参数默认值很保守,适合开发环境,但生产环境往往需要针对业务模型调整。而调整的依据,不是靠猜,而是靠压测数据。比如连接数、shared_buffers、work_mem、maintenance_work_mem等,都应该通过压测找到合适范围。
最后,养成写故障复盘的习惯。每次生产故障处理完,我会把时间线、现象、排查过程、根因、解决方案和后续改进措施记录下来。积累得多了,你再看问题,很多时候一眼就能锁定根因。
PostgreSQL的故障排查说到底是"耐心+方法"的结合。没有万能钥匙,但有了正确的排查思路和工具,大多数问题都能在短时间内定位。希望这篇内容对你有实质帮助。下次你的PG再闹脾气,沉着应对,先采集现场,再动手处理。