PostgreSQL 故障排查:如何找出数据库中最耗时的 SQL 语句

文章目录

    • 一、核心目标与排查原则
      • [1. 明确"耗时"的定义](#1. 明确“耗时”的定义)
      • [2. 排查原则](#2. 排查原则)
    • [二、方法一:启用并分析 `pg_stat_statements`(首选方案)](#二、方法一:启用并分析 pg_stat_statements(首选方案))
      • [1. 安装与配置](#1. 安装与配置)
      • [2. 核心视图字段说明](#2. 核心视图字段说明)
      • [3. 实用查询模板](#3. 实用查询模板)
        • [(1)找出平均执行时间最长的 Top 20 SQL](#(1)找出平均执行时间最长的 Top 20 SQL)
        • [(2)找出总耗时最高的 Top 20 SQL](#(2)找出总耗时最高的 Top 20 SQL)
        • [(3)找出高物理 I/O 的 SQL(I/O 瓶颈)](#(3)找出高物理 I/O 的 SQL(I/O 瓶颈))
        • [(4)找出使用临时文件的 SQL(内存不足)](#(4)找出使用临时文件的 SQL(内存不足))
      • [4. 注意事项](#4. 注意事项)
    • [三、方法二:分析 PostgreSQL 日志(适用于未启用扩展的场景)](#三、方法二:分析 PostgreSQL 日志(适用于未启用扩展的场景))
      • [1. 配置日志记录慢查询](#1. 配置日志记录慢查询)
      • [2. 日志示例](#2. 日志示例)
      • [3. 日志分析技巧](#3. 日志分析技巧)
    • 四、方法三:实时监控当前活跃慢查询
      • [1. 查询 `pg_stat_activity`](#1. 查询 pg_stat_activity)
      • [2. 结合 `pg_blocking_pids()` 查锁阻塞](#2. 结合 pg_blocking_pids() 查锁阻塞)
      • [3. 终止问题会话(谨慎!)](#3. 终止问题会话(谨慎!))
    • [五、方法四:使用 `auto_explain` 自动记录执行计划](#五、方法四:使用 auto_explain 自动记录执行计划)
      • [1. 配置 `postgresql.conf`](#1. 配置 postgresql.conf)
      • [2. 日志示例](#2. 日志示例)
    • 六、高级技巧:结合系统视图深度分析
      • [1. 关联用户与数据库名](#1. 关联用户与数据库名)
      • [2. 识别未使用索引的表](#2. 识别未使用索引的表)
      • [3. 检查表膨胀与死元组](#3. 检查表膨胀与死元组)
    • 七、自动化与监控集成
      • [1. Prometheus + Grafana 监控](#1. Prometheus + Grafana 监控)
      • [2. 自定义告警脚本](#2. 自定义告警脚本)
      • [3. APM 工具联动](#3. APM 工具联动)
    • 八、排查流程总结(SOP)
    • 九、常见误区与注意事项

在 PostgreSQL 生产环境中,性能下降、CPU 飙升、连接堆积等问题往往源于少数几条"毒瘤"SQL。这些语句可能因缺失索引、数据倾斜、统计信息过期或设计缺陷,导致执行时间从毫秒级恶化至分钟甚至小时级。若不能快速、准确地识别并定位这些最耗时的 SQL,故障恢复将无从谈起。

本文将系统性地阐述 "找出最耗时 SQL" 的完整方法论 ,涵盖 日志分析、扩展工具、实时监控、执行计划解读及自动化手段,提供一套可立即落地的排查 SOP(标准操作流程),适用于 DBA、运维工程师及后端开发者。


一、核心目标与排查原则

1. 明确"耗时"的定义

  • 单次执行耗时长(如一条查询跑 5 分钟);
  • 累计总耗时高(如某简单查询每秒执行 1000 次,总耗时占 90% CPU);
  • 资源消耗大(高 I/O、高内存、高锁等待)。

因此,"最耗时"需从 平均耗时、总耗时、资源开销 三个维度综合判断。

2. 排查原则

  • 优先使用内置机制(避免外部依赖);
  • 区分历史累计与当前活跃
  • 结合上下文(参数、数据量、执行频率);
  • 生产环境操作需安全(避免加重负载)。

二、方法一:启用并分析 pg_stat_statements(首选方案)

pg_stat_statements 是 PostgreSQL 官方提供的 SQL 统计扩展,能按 SQL 模板(归一化) 聚合执行指标,是定位历史慢 SQL 的黄金标准。

1. 安装与配置

(1)修改 postgresql.conf
conf 复制代码
# 必须放在 shared_preload_libraries 中(需重启)
shared_preload_libraries = 'pg_stat_statements'

# 可选配置(动态生效)
pg_stat_statements.max = 10000        # 最多跟踪 1 万个不同 SQL
pg_stat_statements.track = all        # 跟踪所有语句(top, all, none)
pg_stat_statements.save = on          # 重启后保留统计

修改 shared_preload_libraries 后必须重启 PostgreSQL。

(2)创建扩展
sql 复制代码
CREATE EXTENSION pg_stat_statements;

2. 核心视图字段说明

查询 pg_stat_statements 视图,关键字段如下:

字段 类型 说明
userid oid 执行用户 OID
dbid oid 数据库 OID
queryid bigint SQL 模板唯一 ID(相同结构不同参数视为同一 ID)
query text 归一化后的 SQL(参数替换为 $1, $2
calls bigint 执行次数
total_exec_time double precision 总执行时间(毫秒)
mean_exec_time double precision 平均执行时间(毫秒)
rows bigint 返回总行数
shared_blks_hit bigint shared buffer 命中次数
shared_blks_read bigint 从磁盘读取的 shared buffer 块数
shared_blks_dirtied bigint 被修改的块数
shared_blks_written bigint 写回磁盘的块数
temp_blks_read/written bigint 临时文件 I/O(排序/哈希溢出)

注意:total_time 在 PostgreSQL 13+ 已更名为 total_exec_time

3. 实用查询模板

(1)找出平均执行时间最长的 Top 20 SQL
sql 复制代码
SELECT 
  query,
  calls,
  mean_exec_time,
  total_exec_time,
  rows / NULLIF(calls, 0) AS avg_rows
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

适用场景:识别"单次特别慢"的查询(如报表、批处理)。

(2)找出总耗时最高的 Top 20 SQL
sql 复制代码
SELECT 
  query,
  calls,
  total_exec_time,
  total_exec_time / NULLIF(calls, 0) AS avg_time,
  rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

适用场景:识别"高频低效"查询(如循环内未优化的 SELECT)。

(3)找出高物理 I/O 的 SQL(I/O 瓶颈)
sql 复制代码
SELECT 
  query,
  shared_blks_read,
  shared_blks_hit,
  ROUND(100.0 * shared_blks_read / NULLIF(shared_blks_read + shared_blks_hit, 0), 2) AS miss_pct
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 20;

miss_pct 高,说明缓存不足或全表扫描严重。

(4)找出使用临时文件的 SQL(内存不足)
sql 复制代码
SELECT 
  query,
  temp_blks_read,
  temp_blks_written,
  total_exec_time
FROM pg_stat_statements
WHERE temp_blks_read > 0 OR temp_blks_written > 0
ORDER BY temp_blks_written DESC;

临时文件通常由 ORDER BYGROUP BYHash Joinwork_mem 不足引起。

4. 注意事项

  • 归一化限制query 字段中的常量被替换为 $1,无法直接看到具体参数。需结合应用日志还原。
  • 内存开销 :跟踪大量 SQL 会占用共享内存,合理设置 pg_stat_statements.max
  • 重置统计 :执行 SELECT pg_stat_statements_reset(); 可清空当前统计(用于对比优化前后)。

三、方法二:分析 PostgreSQL 日志(适用于未启用扩展的场景)

若未提前配置 pg_stat_statements,可通过日志回溯慢 SQL。

1. 配置日志记录慢查询

postgresql.conf 中设置:

conf 复制代码
log_min_duration_statement = 1000   # 记录执行时间 ≥1000ms 的语句
log_statement = 'none'              # 避免记录所有语句(性能开销大)
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '  # 添加上下文

重载配置:

bash 复制代码
pg_ctl reload -D $PGDATA

2. 日志示例

复制代码
2026-02-08 10:30:45.123 UTC [12345]: [5-1] user=app_user,db=prod_db,app=web,client=10.0.0.5 LOG:  duration: 4523.678 ms  statement: SELECT * FROM orders WHERE user_id = $1 AND status = $2

3. 日志分析技巧

  • 使用 grep/awk 提取

    bash 复制代码
    # 提取耗时 >5s 的语句
    grep "duration:" postgresql.log | awk '$7 > 5000 {print $0}'
  • 使用工具解析

    • pgbadger:生成可视化 HTML 报告;
    • goaccess 或自定义脚本聚合。

缺点:无法按 SQL 模板聚合,需手动去重;且仅能分析配置后的日志。


四、方法三:实时监控当前活跃慢查询

上述方法针对历史数据。若数据库正在卡顿,需立即查看当前执行的慢 SQL。

1. 查询 pg_stat_activity

sql 复制代码
SELECT 
  pid,
  now() - query_start AS duration,
  usename,
  datname,
  client_addr,
  application_name,
  state,
  query
FROM pg_stat_activity
WHERE state = 'active'
  AND now() - query_start > INTERVAL '5 seconds'
ORDER BY duration DESC;

关键字段:

  • query_start:查询开始时间;
  • stateactive 表示正在执行;
  • wait_event:若非空,表示在等待(如 LockIO)。

2. 结合 pg_blocking_pids() 查锁阻塞

若查询长时间不动,可能是被锁阻塞:

sql 复制代码
-- 查看阻塞者
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;

3. 终止问题会话(谨慎!)

sql 复制代码
-- 取消查询(发送 SIGINT)
SELECT pg_cancel_backend(pid);

-- 强制终止会话(发送 SIGTERM)
SELECT pg_terminate_backend(pid);

仅在确认无业务影响时使用。


五、方法四:使用 auto_explain 自动记录执行计划

若需不仅知道"哪条 SQL 慢",还要知道"为什么慢",可启用 auto_explain 自动记录慢查询的执行计划。

1. 配置 postgresql.conf

conf 复制代码
shared_preload_libraries = 'pg_stat_statements, auto_explain'

# auto_explain 设置
auto_explain.log_min_duration = 1000   # ≥1s 的查询记录计划
auto_explain.log_analyze = true        # 记录实际执行时间(非估算)
auto_explain.log_buffers = true        # 记录 I/O
auto_explain.log_format = json         # JSON 格式便于解析

重启后生效。

2. 日志示例

复制代码
LOG:  duration: 4523.678 ms  plan:
{
  "Plan": {
    "Node Type": "Seq Scan",
    "Relation Name": "orders",
    "Alias": "orders",
    "Startup Cost": 0.00,
    "Total Cost": 123456.78,
    "Plan Rows": 1000000,
    "Actual Rows": 987654,
    "Actual Total Time": 4520.123
  }
}

优势:直接关联慢 SQL 与执行计划,无需手动 EXPLAIN


六、高级技巧:结合系统视图深度分析

1. 关联用户与数据库名

pg_stat_statements 中的 useriddbid 是 OID,需关联 pg_userpg_database 获取名称:

sql 复制代码
SELECT 
  d.datname,
  u.usename,
  s.query,
  s.mean_exec_time
FROM pg_stat_statements s
JOIN pg_database d ON s.dbid = d.oid
JOIN pg_user u ON s.userid = u.usesysid
ORDER BY s.mean_exec_time DESC
LIMIT 10;

2. 识别未使用索引的表

高耗时查询常伴随缺失索引:

sql 复制代码
SELECT 
  schemaname,
  tablename,
  idx_scan AS index_scans
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY schemaname, tablename;

3. 检查表膨胀与死元组

n_dead_tup 可能导致查询变慢:

sql 复制代码
SELECT 
  schemaname,
  tablename,
  n_live_tup,
  n_dead_tup,
  ROUND(n_dead_tup::float / (n_live_tup + 1), 2) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY dead_ratio DESC;

七、自动化与监控集成

1. Prometheus + Grafana 监控

  • 使用 postgres_exporter 采集 pg_stat_statements 指标;
  • 在 Grafana 中创建面板,实时展示 Top 慢 SQL。

2. 自定义告警脚本

定期运行 SQL 检查,若发现异常则告警:

python 复制代码
# 伪代码
if max_mean_time > 5000:
    send_alert("发现平均耗时 >5s 的 SQL: " + query)

3. APM 工具联动

  • New Relic、Datadog 等 APM 工具可自动捕获慢 SQL;
  • 结合应用上下文(如 URL、用户 ID)定位根因。

八、排查流程总结(SOP)

  1. 初步判断

    • 数据库是否正在卡顿?→ 查 pg_stat_activity
    • 是否有历史慢 SQL?→ 查 pg_stat_statements
  2. 若已配置 pg_stat_statements

    • mean_exec_timetotal_exec_time 排序;
    • 分析高 I/O、高临时文件的语句。
  3. 若未配置

    • 检查日志中 duration: 记录;
    • 启用 auto_explain 为后续排查做准备。
  4. 实时问题

    • pg_stat_activity 找活跃长查询;
    • 检查 wait_event 判断是否锁阻塞。
  5. 深度分析

    • 对 Top 慢 SQL 执行 EXPLAIN (ANALYZE, BUFFERS)
    • 检查索引、统计信息、参数配置。
  6. 长期预防

    • 开启 pg_stat_statementsauto_explain
    • 设置慢查询告警;
    • 建立 SQL 上线审核机制。

九、常见误区与注意事项

  • 误区 1 :"只看总耗时,忽略平均耗时"

    → 高频简单查询可能掩盖真正危险的单次慢查询。

  • 误区 2 :"直接 kill 长查询而不分析"

    → 可能反复发生,治标不治本。

  • 误区 3 :"认为 pg_stat_statements 会显著影响性能"

    → 实测开销通常 < 3%,远低于其带来的诊断价值。

  • 注意 :生产环境执行 EXPLAIN ANALYZE 时,避免对 DML(UPDATE/DELETE)使用,以免误操作。


结语:找出最耗时的 SQL 是 PostgreSQL 性能故障排查的第一步,也是最关键的一步。通过 pg_stat_statements 为核心,辅以日志、实时监控和执行计划分析,可构建一套高效、可靠的诊断体系。

记住:没有监控的数据库,就像没有仪表盘的飞机 。提前配置好 pg_stat_statementsauto_explain,才能在故障发生时从容应对,将 MTTR(平均恢复时间)降至最低。

相关推荐
qq_124987075310 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
枷锁—sha10 小时前
【SRC】SQL注入WAF 绕过应对策略(二)
网络·数据库·python·sql·安全·网络安全
Coder_Boy_10 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Gain_chance10 小时前
35-学习笔记尚硅谷数仓搭建-DWS层最近n日汇总表及历史至今汇总表建表语句
数据库·数据仓库·hive·笔记·学习
此生只爱蛋10 小时前
【Redis】主从复制
数据库·redis
马猴烧酒.11 小时前
【面试八股|JAVA多线程】JAVA多线程常考面试题详解
java·服务器·数据库
光蛋11 小时前
Docker Compose 助力阿里云 Linux 3 PostgreSQL 高可用部署
postgresql
天天爱吃肉821811 小时前
跟着创意天才周杰伦学新能源汽车研发测试!3年从工程师到领域专家的成长秘籍!
数据库·python·算法·分类·汽车
大巨头11 小时前
sql2008 数据库分页语句
数据库