PostgreSQL 查询性能分析

应用在数据库中存取数据时,需要监控哪些查询表现良好,哪些表现不佳。随着数据不断添加和修改,应用也随需求变化而演进,因此这项工作应该持续、频繁地进行。

pg_stat_statements 是跟踪查询统计信息的 PostgreSQL 扩展,对于发现数据库结构或查询的优化机会非常有帮助。

启用扩展

使用前,需要先启用扩展。原文交互教程中的 PostgreSQL 窗口已经完成配置,无需重复执行。

  1. 在 Postgres 服务器的 postgresql.conf 中设置 shared_preload_libraries = 'pg_stat_statements'。
  2. 连接数据库,运行:
 CREATE EXTENSION pg_stat_statements;

扩展显示

pg_stat_statements 包含大量信息。如果使用 PostgreSQL 标准命令行客户端 psql,先开启结果的扩展显示:

\x on

查看全部信息

最简单的查询可以查看启用了扩展的各数据库的大量统计信息:

 SELECT * FROM pg_stat_statements;

按数据库查看

查找性能不佳的查询时,通常希望关注针对某一个数据库执行的查询。因此,可以连接 pg_database,以便筛选。

下面的查询只选取少量列,显示数据库名称,以及所有数据库中执行时间最高的前五条查询:

 SELECT
  d.datname, s.total_exec_time, s.calls, s.rows,
  s.query
FROM pg_stat_statements s JOIN pg_database d ON (s.dbid = d.oid)
ORDER BY total_exec_time DESC
LIMIT 5;

找出最耗时的查询

如果想找出占用数据库大量时间的查询,该怎么办?

total_exec_time 表示该语句累计执行时间,单位为毫秒,精度很高。下面将它四舍五入到两位小数。

查询返回累计执行时间、调用次数和行数,并计算每条查询的平均时间,以及原文称为“CPU 百分比”的累计执行时间占比:

 SELECT
  d.datname, round(s.total_exec_time::numeric, 2) AS total_exec_time, s.calls, s.rows,
  round(s.total_exec_time::numeric / calls, 2) AS avg_time,
  round((100 * s.total_exec_time / sum(s.total_exec_time::numeric) OVER ())::numeric, 2) AS percentage_cpu,
  substring(s.query, 1, 50) AS short_query
FROM pg_stat_statements s JOIN pg_database d ON (s.dbid = d.oid)
ORDER BY percentage_cpu DESC
LIMIT 5;

得到表现最差的查询列表后,可以分别通过 EXPLAIN ANALYZE 深入分析。它会显示 Postgres 实际使用的执行计划,帮助判断可以在哪些地方改进。

平均查询执行时间

所有数据库中全部查询的平均执行时间是多少?

 SELECT (sum(total_exec_time) / sum(calls))::numeric(6,3) AS avg_execution_time
FROM pg_stat_statements;

对 shared_buffers 写入最多的查询

下面找出对 shared_buffers 写入最多的前五条查询。shared_buffers 是 PostgreSQL 的共享内存区域。这些查询使大量共享内存块变脏,因此可能存在改进空间。

 SELECT query, shared_blks_dirtied
FROM pg_stat_statements
WHERE shared_blks_dirtied > 0
ORDER BY 2 desc
LIMIT 5;

可能需要索引的表

pg_stat_statements 可以告诉我们哪些查询执行次数多、耗时长。再结合 pg_stat_user_tables 和 pg_stat_user_indexes 等统计视图,就可以获得更多性能优化线索。

查询表现不佳,常常是因为缺少帮助数据库定位目标行的索引。下面的查询可以指出可能缺少索引的表:

 SELECT relname, seq_scan - idx_scan AS too_much_seq,
  CASE WHEN seq_scan - idx_scan > 0 THEN 'Missing Index?' ELSE 'OK' END,
  pg_relation_size(relid) AS rel_size, seq_scan, idx_scan
FROM pg_stat_user_tables
WHERE schemaname <> 'information_schema' AND schemaname NOT LIKE 'pg%'
ORDER BY too_much_seq DESC;
    relname   | too_much_seq |      case      | rel_size | seq_scan | idx_scan
-------------+--------------+----------------+----------+----------+----------
 dependents  |           41 | Missing Index? |   139264 |       41 |        0
 countries   |           -3 | OK             |     8192 |        1 |        4
 locations   |           -3 | OK             |     8192 |        1 |        4
 regions     |          -24 | OK             |     8192 |        1 |       25
 departments |         -927 | OK             |     8192 |       81 |     1008
 employees   |        -3469 | OK             |   106496 |      161 |     3630

dependents 表的顺序扫描次数比索引扫描多 41 次。根据最近执行的查询,它可能需要一个索引。

但必须理解,索引也有成本,会减慢写入,因为新增或修改数据时,数据库还需要更新索引。因此,不应当直接为表的全部列都添加索引。

结语

pg_stat_statements 是获取查询及其性能影响信息的强大工具。结合 EXPLAIN 和另一个视图 pg_stat_activity,可以获得帮助更好管理应用的重要信息;后者将在另一篇教程中介绍。

完整说明见 PostgreSQL 的 pg_stat_statements 文档。


原文:Query performance analytics。作者/维护方:Crunchy Data 教程团队。本文为中文翻译,代码及命令保留原文。

原站版权声明:© 2018–2026 Crunchy Data Solutions, Inc.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容