热点问题本质上是一种资源竞争失衡:少数SQL、少数表、少数数据页或少数连接占用了绝大部分数据库资源,导致整体吞吐下降、响应时间抖动。PostgreSQL提供了相当完善的统计视图和日志工具,只要方法得当,定位热点并不困难。困难的地方在于治理——很多热点是业务设计缺陷的体现,单纯加索引或调参数只能缓解,不能根治。本文从定位和治理两个层面展开,给出一套可直接落地的操作路径。

一、热点定位:先找到吃资源的元凶
定位热点的第一步是启用扩展pg_stat_statements,它是PostgreSQL中最核心的SQL级统计工具,能够记录每条SQL的调用次数、总耗时、缓存命中率、临时块读写量等关键指标。启用方法是在postgresql.conf中把shared_preload_libraries设置为pg_stat_statements,重启后执行CREATE EXTENSION即可。
-- 查看总耗时最高的前10条SQL
SELECT queryid,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS avg_ms,
rows,
100 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_ratio
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
看这份结果时有几个要点需要注意。第一,排序建议用总耗时而不是平均耗时,平均耗时长但调用少的SQL往往不是全局瓶颈;第二,hit_ratio低的SQL说明大量读走了磁盘,通常是缓存未命中或扫描量过大;第三,temp_blks_written偏大意味着SQL在排序或哈希时落盘,往往是缺少索引或work_mem不足。
除了SQL维度,还要看会话维度。pg_stat_activity可以实时观察当前连接在做什么,尤其是state为active且wait_event_type不为空的会话,它们正在等待锁、IO或CPU,等待事件本身就是热点的直接信号。
-- 查看当前正在等待的会话及等待类型
SELECT pid, usename, state, wait_event_type, wait_event,
now() - query_start AS running_time, left(query, 60) AS query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY running_time DESC;
如果wait_event_type是Lock,说明存在锁竞争;如果是IO,说明磁盘是瓶颈;如果大量会话显示Lock且等待的relation相同,基本可以判断是热点表上的长事务阻塞。配合log_min_duration_statement记录慢SQL日志,把慢查询采样时间与业务高峰对齐,就能还原出热点的发生规律。
二、执行计划分析:确认热点的技术成因
锁定嫌疑SQL后,不要急着改,先用EXPLAIN ANALYZE看真实执行计划。热点SQL的常见问题无非几类:全表扫描或全索引扫描、错误的连接方式(该用哈希连接却走了嵌套循环)、行数估算严重偏差导致计划走偏。重点观察执行计划中的rows估算值与actual rows实际值的差距,如果相差一个数量级以上,说明统计信息过期,先执行ANALYZE往往就能改善。
-- 查看真实执行计划, BUFFERS可观察缓存命中情况 EXPLAIN (ANALYZE, BUFFERS) SELECT o.order_no, c.name FROM orders o JOIN customer c ON c.id = o.customer_id WHERE o.created_at >= now() - interval '1 day';
BUFFERS选项输出的shared hit与read数量非常有价值。如果一个高频SQL每次都产生大量shared read,说明它的数据不在shared_buffers中,可能是扫描范围太大把缓存冲掉了,也可能是缓存被其他大查询污染。前者要靠索引缩小扫描范围,后者要考虑把报表类大查询隔离到只读副本或限制其并发。
索引层面要特别警惕两类隐性热点:一是冗余索引导致的写放大,二是索引失效。写多读少的表上,每多一个索引就多一份维护开销,可以用pg_stat_user_indexes中idx_scan为0的索引来排查无用索引。索引失效则常见于对索引列使用函数或表达式、隐式类型转换、以及复合索引不满足最左前缀等场景。
三、治理策略:从SQL优化到架构调整
定位清楚之后,治理要分层进行。第一层是SQL与索引优化,包括补齐缺失索引、改写低效SQL、用覆盖索引避免回表。第二层是数据库参数与资源控制,比如合理设置work_mem减少落盘、控制max_connections避免连接风暴,并强烈建议在应用侧使用PgBouncer等连接池,PostgreSQL每个连接是一个进程,连接数本身就是稀缺资源。
第三层是数据层面的冷热分离。热点往往集中在最近的数据上,对于持续增长的业务表,按时间分区是最有效的手段。查询近期数据时只扫描热分区,历史归档数据可以放到低频存储甚至单独实例,热点表的体积得到控制,VACUUM和索引维护的开销也随之下降。
第四层是架构层面的读写分离与削峰。把报表、导出类只读查询分流到流复制副本,主库只承载交易写入;对突发流量引入应用层限流或消息队列异步化,避免瞬时并发直接压垮数据库。此外还要注意两个容易被忽视的热点:一是序列热点,高并发下大量会话争抢同一个序列的缓冲区,可以将序列CACHE值调大;二是频繁更新的行锁冲突,典型的如秒杀扣库存场景,可以用Redis预扣、库存分桶等方式在应用层化解行级争用。
最后,热点治理不是一次性动作。建议把pg_stat_statements的快照定期落表,观察total_exec_time的分布变化趋势,只有让数据说话,才能在业务增长之前提前发现新的热点,而不是等报警响了再被动救火。
PostgreSQL热点分析pg_stat_statements慢SQL优化修改时间:2026-09-15 06:08:28