更新时间:2026-08-06 GMT+08:00
分享

RDS for PostgreSQL内存过载定位及处理建议

本章节的所有SQL操作都使用root用户执行。

场景介绍

生产数据库的内存要有一定的冗余,正常使用应保持在70%以下,若内存使用率长期高于阈值,建议考虑扩大内存规格。当内存使用率超过阈值并产生告警时,可能会产生以下问题。关于内存使用率的介绍详见内存使用率说明

  • 系统会频繁地进行内存交换,数据会在内存与磁盘之前大量交换,会消耗大量CPU,使数据库性能下降,影响数据的读取和写入速度等。
  • 严重时可能会发生OOM,若发生OOM,数据库服务进程会发生重启,数据库会断开现有连接,且新连接无法接入。此时HA会重新拉起数据库,期间数据库会回放从上一次checkpoint到发OOM时之间产生的wal日志内容,以保证事务一致性。

内存使用率说明

PostgreSQL数据库中内存使用率主要是由共享内存和本地内存组成。

  • 共享内存:主要用于数据的缓冲区、wal日志的缓冲区等,以便提高读写性能。除此之外也包含一些全局信息,如进程、锁信息等。

    shared_buffers参数的值决定初始申请共享内存的大小,RDS for PostgreSQL设置该参数开始值为系统物理内存的25%,该参数合理设置区间为25%~40%,如超过物理内存的 40%,就会发现缓冲的效果并不明显,这是因为PostgreSQL是运行在文件系统之上的,若文件系统也有缓存,将导致双缓存过多,造成负面影响。

  • 本地内存:后台服务会申请一些本地内存,以便暂存一些不需要全局存储的数据,一般分为以下几类:
    • temp_buffers:设置每个会话用于访问临时表的本地缓冲区的最大块数量。
    • work_mem:内部排序聚合操作和hash表在使用临时磁盘文件时使用的内存缓冲区。注意:每个排序操作都会消耗一个work_mem内存,并不是一个SQL消耗一个。
    • maintenance_work_mem:维护操作使用的最大内存数。

可能原因

内存过载的原因排查思路如图1

图1 排查思路
  • 连接/活跃连接数
    • 通过管理控制台中的监控平台中内存使用率,连接数使用率,数据库连接数和活跃连接数监控项进行查看,查看目标时间段相关监控指标,观察指标是否同时增降。
    • 使用root用户执行以下SQL,查看当前数据库最大连接数。
      show max_connections;
    • 使用root用户执行以下SQL,查看当前数据库活跃连接数。
      select count(1) from pg_stat_activity where state <> 'idle';
    • 使用root用户执行以下SQL,查看当前数据库空闲连接数。
      select count(1) from pg_stat_activity where state = 'idle';
  • 慢SQL
    • 通过管理控制台中的监控平台中内存使用率,已执行1s的SQL数,已执行3s的SQL数和已执行5s的SQL数监控项进行查看,查看目标时间段相关监控指标,观察指标是否同时增降。
    • 执行以下SQL,查看当前正在运行时长前三慢SQL(RDS for PostgreSQL 10及以上版本),检查返回的query字段中sql是否使用join、order等语法。

      select (extract(epoch from now() - query_start)) query_time, datname, usename, client_addr, wait_event, state, query from pg_stat_activity where state not like 'idle%' and query_start is not null and backend_type = 'client backend' and pid <> pg_backend_pid() order by 1 desc limit 3;

    • 通过查询pg_stat_statements视图,获取统计信息并查询最耗共享内存的SQL,详见4
  • TPS

    通过管理控制台中的监控平台中内存使用率,TPS监控项进行查看,查看目标时间段相关监控指标,观察指标是否同时增降。

  • 长连接
    • 执行以下SQL,查看正在运行时长前三的长连接(RDS for PostgreSQL 10及以上版本),其中返回的“conn_time”字段为连接存活时长,“query”字段为该进程执行的SQL。
      select (extract(epoch from now()-backend_start)) conn_time, datname, pid, usename, client_addr, wait_event_type, wait_event, state, query from pg_stat_activity where backend_type = 'client backend' order by conn_time desc nulls last limit 3;
    • 长连接会在数据库中会保持一个持久的连接状态,这个连接状态会维持一些缓存信息,如查询结果、事务信息、锁信息等等。如果长连接数量和连接时间较大,缓存的信息也会相应增多,从而占用更多的内存。需要进一步排查时,可通过上一条SQL返回的“query”字段查询pg_stat_statements视图,查看该SQL的共享内存消耗情况。
      select userid::regrole, dbid, shared_blks_hit, shared_blks_dirtied from pg_stat_statements where query =  'query';

对业务的影响

  • OOM Kill:进程被系统强制杀死,数据库服务中断。
  • Swap使用:性能急剧下降,磁盘I/O成为瓶颈。
  • 查询失败:内存分配失败导致查询异常终止。
  • 连接中断:现有连接可能被意外断开。
  • 缓存命中率下降:内存不足导致缓冲池命中率降低,磁盘I/O增加。

处理建议

出现过载异常前的建议:

  • 监控内存使用:关注监控大盘内存使用量,设置预警阈值。
  • 控制并发:合理限制并发查询数量,避免内存瞬时峰值。
  • 优化查询:减少一次性加载大量数据的查询(如SELECT *)。
  • 适当升级实例规格:业务高峰期前确保内存裕度充足。
  • 清理历史数据:定期归档或清理冷数据。

过载异常中的建议:

  • 连接/活跃连接数

    若连接数或空闲连接数过多,可使用root用户执行如下SQL释放当前数据库的所有空闲连接,使用连接池或配置客户端连接超时参数及时释放空闲的连接。若活跃连接数过多,可减少业务并发量,或扩大内存规格。您也可以kill不需要的会话,降低实例内存消耗,详见管理实时会话

    kill会话操作可能会导致业务断连,建议业务有重连机制,请谨慎操作。

    select pg_terminate_backend(pid) from pg_stat_activity where state = 'idle';
  • 慢SQL被大量执行

    定位到导致内存消耗增加的SQL,对SQL进行优化,或扩大内存规格。

  • TPS事务数过高

    降低事务数,或扩大内存规格。

  • 长连接数量多/连接存活时长久

    避免长连接,长连接的缓存可能较大,导致内存不足,建议定期释放长连接。

过载异常后的复盘优化:

  • SQL层面优化
    • 避免大内存消耗查询
      • 禁止一次性加载全表到内存(如大表JOIN、无LIMIT的全表扫描)。
      • 对大表分页查询使用书签式扫描(Keyset Pagination)替代OFFSET。
      • 拆分复杂查询为多个简单查询。
    • 优化排序和聚合
      • 确保ORDER BY字段有索引,减少内存排序。
      • 使用窗口函数替代多次子查询。
  • 架构层面优化
    • 增加只读实例:分担当前实例的内存压力。
    • 数据归档:将冷数据迁移至归档库,减少活跃数据量。
    • 表分区:按时间分区表,便于清理历史分区释放内存。
  • 容量规划

常见问题

Q:使用pg_dump时导出数据时,为什么内存使用率会升高,如何避免出现该问题?

A:使用pg_dump导出数据时,有一个进程会访问目标库中所有的表、索引等对象来获取结构数据,如果访问的表、索引等对象过大,可能会导致 relcache(关系表缓存)、catcache(系统目录表缓存)过大,且无法释放,从而导致存使用率升高,严重时会发生OOM。

建议执行pg_dump任务时:

  1. 不要执行DDL操作。
  2. 监控好慢SQL的指标,出现锁冲突时直接kill掉对应冲突的进程。
  3. 低峰期执行pg_dump任务。
  4. 将shared_buffers、work_mem参数继续降低到当前的1/2或1/4甚至更多,任务结束后回退修改的参数。
  5. 扩大内存规格。

相关文档