在生产环境中,PostgreSQL 数据库突发 CPU 占用 100%、连接堆积、响应超时 等故障,往往意味着系统正遭受“毒瘤 SQL”的冲击。这类问题若不及时干预,轻则服务降级,重则数据库雪崩、业务中断。此时,DBA 需要一套 快速定位、精准熔断、临时恢复、根因分析 的标准化应急流程。

本文将围绕 “高 CPU + 慢查询 + 资源耗尽” 这一典型故障场景,系统性地阐述 PostgreSQL 的 紧急排查 SOP(标准操作流程)SQL 熔断机制,涵盖实时诊断、会话终止、资源限制、预防策略四大维度,助你在 5 分钟内控制局面,30 分钟内恢复服务。


一、典型故障现象与影响

1. 常见表现

  • tophtop 显示 postgres 进程 CPU 使用率持续 ≥90%;
  • 应用大量报错:Query execution time exceededConnection timeout
  • 新连接被拒绝:FATAL: sorry, too many clients already
  • pg_stat_activity 中大量 activeidle in transaction 会话;
  • 系统负载(load average)飙升,I/O wait 可能同步升高。

2. 根本原因分类

类型 特征 常见场景
失控的复杂查询 单条 SQL 消耗大量 CPU(如全表 JOIN、无索引排序) 报表、BI 工具、未审核的 ad-hoc 查询
高频低效查询 简单 SQL 被高频调用(如循环内查 DB) 应用逻辑缺陷、缓存穿透
长事务阻塞 事务长时间未提交,持有锁或膨胀 MVCC 快照 手动 BEGIN 后忘记 COMMIT、应用异常未回滚
死锁/锁等待风暴 多事务相互阻塞,CPU 消耗在锁管理上 更新顺序不一致、热点行竞争
统计信息过期 优化器选择错误执行计划(如该走索引却 Seq Scan) 大批量数据导入后未 ANALYZE

⚠️ 注意:CPU 100% 不一定是计算密集型查询——也可能是大量轻量查询并发执行导致上下文切换开销剧增。


二、紧急排查 SOP:5 分钟定位罪魁祸首

步骤 1:确认数据库进程状态(10 秒)

# 查看 CPU 占用最高的 postgres 进程
top -c -p $(pgrep -d',' -f "postgres")

# 或使用 htop(更直观)
htop

记录 PID(如 12345),用于后续关联。


步骤 2:登录数据库,查看活跃会话(30 秒)

-- 查看所有非空闲会话,按 CPU 时间/运行时间排序
SELECT 
  pid,
  usename,
  application_name,
  client_addr,
  backend_start,
  query_start,
  now() - query_start AS query_duration,
  state,
  wait_event_type,
  wait_event,
  query
FROM pg_stat_activity
WHERE state <> 'idle'
  AND pid <> pg_backend_pid()  -- 排除当前会话
ORDER BY query_duration DESC;

重点关注:

  • query_duration:运行时间最长的查询;
  • state = 'active':正在执行;
  • state = 'idle in transaction':危险!长事务;
  • wait_event:若为 Lock,说明被阻塞。

💡 若无法登录(连接池满),可使用 本地 Unix Socket + 超级用户 强制连接:

psql -U postgres -h /var/run/postgresql -d your_db

步骤 3:关联操作系统 PID 与数据库会话(关键!)

PostgreSQL 的每个后端进程 PID 与 pg_stat_activity.pid 一致。

-- 假设 top 中看到 PID=12345 占用高 CPU
SELECT pid, query, query_start, state 
FROM pg_stat_activity 
WHERE pid = 12345;

若返回结果,则该 SQL 即为嫌疑对象。

若无结果,可能是后台进程(如 autovacuum、checkpointer),需另查。


步骤 4:分析嫌疑 SQL 的执行计划(1–2 分钟)

对嫌疑 SQL 执行 轻量级 EXPLAIN(避免加重负载):

-- 仅估算,不实际执行(安全!)
EXPLAIN (VERBOSE, BUFFERS) <嫌疑SQL>;

关注:

  • 是否出现 Seq Scan on large table
  • 是否有 Nested Loop with huge rows
  • 是否缺少 Index Scan

⚠️ 切勿直接使用 EXPLAIN ANALYZE!它会真实执行 DML,可能雪上加霜。


步骤 5:检查锁阻塞链(若存在等待)

-- 查看谁在阻塞谁
SELECT 
  blocked.pid AS blocked_pid,
  blocked.query AS blocked_query,
  blocking.pid AS blocking_pid,
  blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking 
  ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE blocked.wait_event IS NOT NULL;

若发现 双向阻塞,极可能是死锁前兆。


三、SQL 熔断:快速终止问题会话

定位到问题 SQL 后,立即采取熔断措施。

1. 终止单个会话

-- 温和取消(发送 SIGINT,允许回滚)
SELECT pg_cancel_backend(12345);

-- 强制终止(发送 SIGTERM,立即断开)
SELECT pg_terminate_backend(12345);

优先使用 pg_cancel_backend,避免事务残留。

2. 批量终止同类会话

若多个会话执行相同 SQL(如应用 bug 导致循环查询):

-- 终止所有执行某类查询的会话
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE query LIKE '%SELECT * FROM huge_table%';

3. 按用户/应用熔断

-- 熔断整个应用(谨慎!)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE application_name = 'risky_report_app';

四、资源限制:防止问题复发(临时防护)

在根因修复前,可通过资源限制防止再次爆发。

1. 设置语句超时(statement_timeout)

-- 会话级(推荐)
SET statement_timeout = '5s';

-- 全局(postgresql.conf)
statement_timeout = 5s

所有查询超过 5 秒自动取消。

2. 设置事务超时(idle_in_transaction_session_timeout)

防止长事务:

idle_in_transaction_session_timeout = 30s

事务中空闲超过 30 秒自动断开。

3. 限制单用户连接数

-- 限制某用户最多 10 个连接
ALTER USER risky_user CONNECTION LIMIT 10;

4. 使用 Resource Queue(PostgreSQL 15+)

PostgreSQL 15 引入 Resource Groups(需编译时启用),可限制 CPU/内存配额:

CREATE RESOURCE GROUP web_users 
  WITH (cpu_rate_limit=0.6, memory_limit='4GB');

ALTER ROLE app_user SET RESOURCE GROUP web_users;

注:此功能默认未启用,需从源码编译或使用 EnterpriseDB 等发行版。


五、高级熔断:基于 pgBouncer 的应用层拦截

若问题来自特定应用,可在连接池层熔断。

1. pgBouncer 黑名单(通过 HUP 重载)

编辑 pgbouncer.ini

; 临时禁止某应用连接
ignore_appname = risky_app

重载配置:

kill -HUP $(cat /var/run/pgbouncer/pgbouncer.pid)

2. 动态暂停数据库

-- 在 pgBouncer 管理接口执行
PAUSE mydb;
-- 所有新连接排队,现有连接继续
RESUME mydb; -- 恢复

六、根因分析与长期治理

1. 事后复盘 checklist

  • 问题 SQL 是否缺失索引?
  • 是否可拆分为分页查询?
  • 应用是否有缓存?是否缓存穿透?
  • 是否应走只读副本而非主库?
  • 是否需加入 SQL 审核流程?

2. 建立预防机制

(1)上线前 SQL 审核
  • 强制要求 EXPLAIN 计划评审;
  • 使用工具(如 sqlcheck、SonarQube)检测反模式。
(2)监控告警
  • Prometheus 监控 pg_stat_statements.mean_exec_time
  • 告警规则:rate(pg_stat_statements_total_time[5m]) > 阈值
(3)自动熔断(进阶)
  • 使用 ProxySQL自研中间件,实现:
    • 单 SQL QPS > 100 自动限流;
    • 平均延迟 > 1s 自动熔断。

七、附录:紧急命令速查表

场景 命令
查看高 CPU 进程 top -c -p $(pgrep -d',' -f "postgres")
登录超级用户 psql -U postgres -h /var/run/postgresql
查活跃会话 SELECT ... FROM pg_stat_activity WHERE state <> 'idle';
终止单个会话 SELECT pg_terminate_backend(12345);
批量终止 SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE query LIKE '%...%';
设置超时 SET statement_timeout = '5s';
查锁阻塞 SELECT ... FROM pg_stat_activity blocked JOIN ... pg_blocking_pids...
重置统计 SELECT pg_stat_statements_reset();

结语:面对 PostgreSQL CPU 100% 等紧急故障,速度决定损失。通过本文所述 SOP:

  1. 5 分钟内:定位高 CPU 进程 → 关联 SQL → 分析执行计划;
  2. 10 分钟内:熔断问题会话,恢复服务可用性;
  3. 30 分钟内:设置资源限制,防止二次爆发;
  4. 事后:根因治理,建立预防机制。

记住:最好的熔断,是让问题 SQL 根本无法上线。将性能意识融入开发流程,才是长久之计。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐