PostgreSQL 上的内存问题通常是配置问题。 控制内存的参数协同工作。 该 work_mem 参数适用于每个排序或哈希操作、每个查询以及每个连接。 因此,一个看起来不大的数值,可能会被消耗数百次。
内存指南可帮助你找到哪个乘数是问题。
本指南介绍的症状
- 内存利用率始终很高,或者持续攀升且没有回落。
- 内存耗尽错误,或被 OOM 终止机制终止的后端进程。
- 参数更改后,内存使用量急剧上升。
- 与连接数而非查询量相关的性能下降。
开始之前
启用 PostgreSQL 服务器日志、会话数据和查询存储运行时。 将 pg_qs.query_capture_mode 设置为 TOP 或 ALL。 启用 metrics.collector_database_activity。 请参阅 “使用故障排除指南”。
PostgreSQL 的内存实际上都去了哪里
了解这四类用户后,这些选项卡的含义就不言自明了,因为每个选项卡都对应其中一类:
| 消费者 | 是什么驱动它 | 选项卡 |
|---|---|---|
| 共享内存 |
shared_buffers,在服务器启动时为整个服务器一次性分配 |
内存参数 |
| 每次操作的内存 |
work_mem,按排序或哈希分配,因此单个复杂查询可以多次使用它 |
查询 |
| 每个连接的内存 | 每个连接都会对应一个进程,在执行任何操作之前都会产生各自的开销 | 用户连接 |
| 维护内存 |
maintenance_work_mem,由清空、索引生成和还原使用 |
内存参数 |
那些乘数型因素、work_mem 以及每个连接的开销,几乎是所有意外情况的根源。
本指南的组织方式
| 选项卡 | 它所回答的问题 |
|---|---|
| Memory | 内存是否真正提升,何时? |
| 工作量 | 工作量是否增加? |
| 会话 | 特定会话是否保存内存? |
| 查询 | 哪些查询涉及最多的数据? |
| 用户连接 | 连接数才是真正的驱动因素吗? |
| 内存参数 | 此 SKU 的参数大小是否合适? |
演练指南:调优变更后内存持续增长
请注意该横幅。 本指南报告 检测到内存参数更改,这意味着一个或多个内存参数高于其默认值。 此信息会立即更改调查的重点:工作负荷前的可疑配置。
确认时间安排。 内存选项卡显示,内存利用率是逐步上升并保持在该水平,而不是突然飙升后再回落。 持续存在的阶跃式变化通常表明是配置变更,而不是流量事件。
排除工作负荷。工作负荷 显示跨步骤保持不变的元组活动。 服务器未执行更多操作;每个工作单元现在花费更多的内存。
检查乘数。用户连接 显示大约 400 个活动连接。 内存参数 显示
work_mem已提升到 64 MB。 把这两个数字结合起来,结论就是:400 个连接,每个连接都可能运行多种排序或哈希操作,而且每种操作各占用 64 MB,这比表面上的标称值所显示的资源投入大得多。双方采取行动。 你将
work_mem下调回接近安全基线的水平,并针对少数确实需要更高值的报表查询按角色分别进行设置。 此外,你引入 PgBouncer 来减少连接数,从而同时降低乘数和每个连接的开销。
通用原则是:对于内存,解读一个参数值时,一定要结合它可同时并发应用的次数一起看。
选项卡参考
Memory
确认症状和对应的窗口。 区分两个形状:
- 持续存在的阶跃变化表明是配置问题。
- 恢复的峰值表明存在特定查询或工作负载事件。
工作量
读取元组活动与写入元组活动。 随着内存一同增加的,是实实在在的额外工作量。 当内存攀升时,平面意味着每个工作单元变得更加昂贵,或者某些内容不会释放内存。
Sessions
来自采样数据 pg_stat_activity 的长时间运行会话:
-
连接持续时间(
collection_time - backend_start):使用连接池时,预期会较高。 -
查询持续时间 (
collection_time - query_start):应保持在正常范围内。
长时间运行单个查询的会话,可能会在排序或哈希操作中占用大量内存,并且直到查询结束才会释放。
当会话处于空闲状态且连接时长超过阈值时,本指南会显示检测到长时间空闲会话横幅提示。 空闲会话仍会占用各自连接专属的内存,因此,大量这类会话即使不执行任何实际工作,也会抬高基础内存占用。 确定并结束过时的会话,查看使会话保持打开状态的应用程序逻辑,并设置 idle_session_timeout (PostgreSQL 14 及更高版本)。
立即缓解。 请先取消当前正在运行的语句。 这是两个选项中干扰较小的一个,因为会话及其事务都得以保留:
SELECT pg_cancel_backend(<pid>);
如果会话处于事务空闲状态,或者取消操作也无法释放资源,请终止整个后端进程:
SELECT pg_terminate_backend(<pid>);
Caution
终止后端会回滚其未完成的事务。 在结束会话之前,请先确认该会话当前正在执行什么操作。 PID 可能属于长时间运行的迁移任务,或属于会从头开始重新启动的批处理作业。
设置防护措施,防止再次发生。 设置会话和事务可以运行多长时间的限制:
| 参数 | 它所限定的范围 | 备注 |
|---|---|---|
statement_timeout |
单个语句 | 最广泛的安全网。 如果某些作业确实会运行较长时间,应按角色或按会话进行设置,而不是在整个服务器范围内统一设置。 |
idle_in_transaction_session_timeout |
处于空闲状态且存在未提交事务的会话 | 默认值为 0 (从不)。 此设置通常可防止清理被阻止。
300000 (5 分钟)是一个常见的起点。 |
idle_session_timeout |
无事务的空闲会话 | PostgreSQL 14 及更高版本。 回收已废弃客户端占用的连接槽位。 |
transaction_timeout |
事务总持续时间 | PostgreSQL 17 及更高版本。 |
Queries
按 数据用量对查询进行排名,即窗口内 shared_blks_hit 和 shared_blks_dirtied 的总和。
注释
在一次执行过程中,一个页面可以被重复固定,并且每次固定都会计入次数。 因此,查询报告的数据使用量确实可能超过 shared_buffers,甚至超过服务器总内存。 它衡量的是对缓冲池执行的工作量,而不是驻留字节数。
选择一个查询 ID 以查看每个存储桶的详细信息:平均行数和总行数、平均/最小/最大数据使用量以及执行时长。 数据读取量高但返回行数少的查询,其读取的数据远多于实际返回的数据,通常是由于缺少索引或谓词条件过于宽泛。
- 运行 索引优化 ,根据实际工作负荷获取索引建议。 此步骤是最有效的长期修复。
- 对该查询运行
EXPLAIN (ANALYZE, BUFFERS),查看时间和 I/O 实际消耗在何处。 在大型表上查找顺序扫描、针对大行计数的嵌套循环,以及对溢出到磁盘的排序。 - 减小表臃肿。 作为一次性步骤,运行
VACUUM (ANALYZE, VERBOSE) <table_name>;。 如果臃肿问题反复出现,根源在上游。 请参阅 监控 autovacuum。 - 添加计划所需的索引,并删除不缩小结果的联接。
- 对持续热的大型表进行分区。
- 重新审视表设计:为子表中的外键创建索引,删除未使用的索引(它们会给每次写入带来开销),并在批量加载期间禁用触发器。
具体就内存而言,请有针对性地调优 shared_buffers、work_mem、maintenance_work_mem、max_locks_per_transaction 和 max_parallel_workers_per_gather。 每项设置都会增加内存消耗,而并行工作进程会使每个查询的内存消耗成倍增加。 如果工作负荷确实占用大量内存,则从 “常规用途 ”迁移到 “内存优化 ”时,每个 vCore 的内存比在同一层中纵向扩展更多。
注释
在只读副本上运行 ,并在 VACUUM 上创建索引;更改会复制过去。 可以按每个会话设置 work_mem,也可以在副本服务器级别进行设置,且独立于主服务器。
用户连接
按状态和持续时间进行连接: 短 (低于 1 秒)、 正常 (1 秒到 20 分钟)、 长 (超过 20 分钟),派生自日志断开连接消息。
连接计数是内存乘数,因此此选项卡通常比它看起来更重要。 使用 statement_timeout、idle_in_transaction_session_timeout 和 idle_session_timeout 限制空闲会话(PostgreSQL 14 及更高版本)。
每个 PostgreSQL 连接都是一个单独的操作系统进程,具有自己的内存。 即使在执行任何一条查询之前,数以百计的多数处于空闲状态的连接也会实实在在地占用 CPU 和内存资源,因此使用连接池通常比纵向扩展更划算。
Azure Database for PostgreSQL灵活服务器中的 PgBouncer 内置于Azure Database for PostgreSQL灵活服务器中。 使用 pgbouncer.enabled 启用它,并启用 metrics.pgbouncer_diagnostics 以获取其指标。 两者都是动态的,无需重启。 PgBouncer 不受 可突发 层级支持。
调整池的大小:
| 设置 | Guidance |
|---|---|
pool_mode |
对于由大量短事务组成的高吞吐量工作负载,请使用 transaction,因为它能实现高效得多的连接复用。 如果需要使用依赖连接的功能,例如预处理语句、建议锁或/LISTENNOTIFY,请使用 session。 |
default_pool_size |
适用于每个用户/数据库对,而不是每个服务器。 把你预计的每个池都加总起来,并将总量控制在 max_connections 以下,留出足够余量。 |
server_idle_timeout |
保留未使用的服务器连接的时间。 将其调低,以便更早释放空闲后端。 |
query_timeout |
将单个查询绑定到连接池管理器。 |
两种值得识别的故障模式:
- 资源池耗尽。 活动服务器连接数保持在
default_pool_size,而等待中的客户端数量则在上升。 提高default_pool_size、优化占用连接的慢查询,或将模式从session切换为transaction。 - PgBouncer 饱和度。 PgBouncer 是单线程的,因此无论服务器有多大,它都使用一个核心。 其典型特征是:6432 端口上的延迟比直接连接到 5432 更高,同时等待的客户端不断增加,而活跃的服务器连接数则保持在较低水平
default_pool_size。 在单独的 VM 上跨多个 PgBouncer 实例横向扩展,或评估多线程池程序,例如 PgCat。
内存参数
当前值、默认值和已修改的内容。
如何阅读:
- 保留在其 PostgreSQL 默认值处的参数不显示任何风险指示器。 仅对修改后的值进行求值。
- 建议的值派生自 SKU 的内存和当前
max_connections值,并且从不建议低于 PostgreSQL 默认值。 - 如果无法识别 SKU,则基于比率的建议显示为 “未知 ”,并且仅应用绝对规则。
- 值为服务器级别。 通过
SET或ALTER ROLE ... SET按会话或按角色设置的覆盖项不会显示,因此此处看起来正常的值并不能保证就是实际生效的配置。
优秀的标准是什么样的
| 信号 | 正常 | 调查 |
|---|---|---|
| 内存利用率 | 稳定且有余量 | 持续上升而未恢复,或在峰值时没有余量 |
| 一段时间内的内存形状 | 会恢复的尖峰 | 持续的阶跃变化 |
work_mem ×并发连接 |
轻松处于总内存范围内 | 接近或超过该值 |
| 连接数 | 稳定,共用 | 数百个直接连接 |
| 数据使用量与返回行数对比 | 成比例的 | 数据使用量高,行数少 |
| Parameters | 处于建议值或接近建议值 | 多个已修改的值被标记 |
本指南无法告诉你的内容
- 每个后端的实际驻留内存。 数据使用量衡量的是缓冲池的工作情况,而不是已占用的字节数。
- 给定会话具有哪些有效设置。 仅显示服务器级值。
- 特定排序或哈希溢出。 使用
EXPLAIN (ANALYZE, BUFFERS),请参阅 排查高临时文件利用率 问题,以便进行溢出分析。 - OOM 是否是 PostgreSQL 的错误。 扩展和后台进程也会消耗内存。
- 子采样事件。 会话数据是采样得到的;因此,短暂的分配峰值可能会被遗漏。