3. 连接参数与资源消耗

PostgreSQL 采用“每连接一进程”的模型,客户端每建立一条连接,postmaster 就会 fork 出一个后端进程(backend)。这意味着连接数并不是一个抽象数字,而是直接对应操作系统层面的进程数、内存占用和文件描述符消耗。理解 max_connections、superuser_reserved_connections 以及它们背后的资源账本,是连接管理从“能连上”走向“连得稳”的关键一步。

一、max_connections 与 superuser_reserved_connections 的关系

max_connections 是数据库允许同时存在的客户端连接总数上限,默认值通常为 100。它并不等于“普通用户可用连接数”,因为其中一部分被预留给超级用户。PostgreSQL 规定:普通用户可用的连接数为 max_connections - superuser_reserved_connections,而超级用户始终可以额外使用被预留的槽位。默认 superuser_reserved_connections 为 3。

这个预留机制的意义在于:当连接池耗尽、普通用户无法连接时,DBA 仍能以超级用户身份登录,执行 pg_terminate_backend() 或调整参数来恢复服务。因此,即使业务侧连接数已经打满,也不应把预留槽位挪给普通用户。

需要特别注意的是,max_connections 是实例级参数,修改后必须重启 PostgreSQL 才能生效。它还会影响共享内存的分配:每个连接槽位都会在共享内存中占用一定结构(如 PGPROC、信号量等),因此把 max_connections 设得过大,会推高共享内存需求,甚至导致启动失败。

二、连接的内存开销:不止是 work_mem

很多人误以为“一个连接的内存 = work_mem”。实际上,后端进程的内存由多部分组成:

  • 进程基础开销:每个 backend 进程本身有栈、堆、代码段映射,通常几 MB 到十几 MB。
  • 会话级缓存:如 catalog cache、relcache、plan cache,随访问对象增多而增长。
  • 排序/哈希类操作:受 work_mem 控制,但它是“每个排序/哈希节点”的上限,一个复杂查询可能同时使用多个 work_mem。
  • 维护类操作:maintenance_work_mem 用于 VACUUM、CREATE INDEX 等,通常只有少数连接会触发。

因此,估算连接内存时不能简单用 max_connections × work_mem。更稳妥的做法是:先测出单个空闲连接的实际 RSS,再叠加典型查询的峰值增量。例如,假设单个空闲 backend 约 8 MB,典型 OLTP 查询峰值再增加 4 MB,那么 500 个连接的基础内存约为 500 × 12 MB ≈ 6 GB,这还不包括 shared_buffers 和操作系统缓存。

教学示例:若 shared_buffers = 4GB,max_connections = 500,单连接综合开销按 12 MB 估算,则仅连接相关内存约 6 GB,加上共享内存后,实例总内存需求已接近 10 GB 量级。实际值需以 pg_stat_activity 与操作系统监控为准。

三、文件描述符:最容易被忽略的硬上限

每个 PostgreSQL 连接至少占用一个客户端 socket 文件描述符;此外,后端进程还会打开数据文件、WAL 文件、临时文件等。因此,操作系统对 postmaster 进程的 nofile 限制必须显著大于 max_connections。经验上,nofile 至少应为 max_connections × 2 + 系统预留,高并发场景建议直接设为 65535 或更高。

在 Linux 上,可通过以下方式检查和调整:

# 查看当前 postmaster 进程的限制
cat /proc/$(head -1 /var/run/postgresql/*.pid 2>/dev/null || pgrep -o postgres)/limits | grep "open files"

# systemd 管理的实例,编辑 override
systemctl edit postgresql
# 加入:
[Service]
LimitNOFILE=65535

# 重新加载并重启
systemctl daemon-reload
systemctl restart postgresql

如果 nofile 不足,典型现象是:连接数尚未达到 max_connections,但新连接开始报 “could not open file” 或 “too many open files”,已有连接也可能在打开新文件时失败。这类故障往往被误判为“连接数满了”,实际根因在文件描述符。

四、内核参数调优:让连接真正可用

仅有 max_connections 还不够,内核层面的几个参数决定了高并发连接能否稳定建立和维持:

参数作用建议方向
net.core.somaxconn监听队列最大长度提高到 1024 或更高,避免瞬时连接被丢弃
net.ipv4.tcp_max_syn_backlogSYN 队列长度与 somaxconn 匹配,提升握手抗压能力
net.ipv4.ip_local_port_range客户端可用端口范围扩大范围,避免客户端端口耗尽
net.ipv4.tcp_tw_reuse复用 TIME_WAIT 连接客户端侧可设为 1,缓解短连接端口压力
vm.overcommit_memory内存分配策略数据库场景常设为 2,配合 overcommit_ratio 控制

这些参数修改后通常需要 sysctl -p 生效。它们不改变 PostgreSQL 内部的连接上限,但决定了“连接洪峰到来时,系统是否接得住”。

五、把参数、内存与 fd 放在一张图里看

下面的关系图把本节的核心对象串起来:max_connections 决定连接槽位总数,superuser_reserved_connections 从中划出保留区,每个连接同时消耗内存和文件描述符,而内核参数则决定连接建立阶段是否顺畅。

连接参数与资源消耗关系图 max_connections = 500 普通用户可用槽位 500 − 3 = 497 个普通连接槽位 superuser_reserved _connections = 3 仅超级用户可用 用于故障恢复登录 划出 每个连接同时消耗三类资源 内存 进程基础 + 会话缓存 + work_mem 文件描述符 socket + 数据文件 + WAL 共享内存结构 PGPROC / 信号量等 内核参数:决定连接能否稳定建立 somaxconn 监听队列长度 tcp_max_syn_backlog SYN 队列长度 ip_local_port_range 客户端端口范围 nofile 进程 fd 上限 受限于

六、配置检查清单

在调整连接相关参数时,建议按以下顺序核对,避免只改一个参数而留下短板:

  1. 确认 max_connections 与业务峰值连接数匹配,并预留 20% 余量。
  2. 确认 superuser_reserved_connections 未被调小到 0,保留至少 2~3 个恢复入口。
  3. 用 SELECT pg_size_pretty(sum(...)) 或操作系统监控估算连接内存,确保不超过物理内存的合理比例。
  4. 检查 postmaster 的 nofile,确保大于 max_connections × 2。
  5. 检查 somaxconn、tcp_max_syn_backlog 是否与连接洪峰匹配。
  6. 修改 max_connections 后重启实例,并用 SHOW max_connections; 与 pg_stat_activity 验证。
不要通过无限增大 max_connections 来解决连接不足。连接数越高,上下文切换、内存和锁竞争成本越大。更合理的路径是引入连接池(如 PgBouncer),把数据库侧连接控制在数百级别,由池化层承接成千上万的客户端连接。

总结来说,max_connections 是“槽位总数”,superuser_reserved_connections 是“应急通道”,内存和文件描述符是“每个槽位的真实成本”,内核参数则是“连接建立阶段的路况”。只有把这四者放在同一张资源账本里核算,连接管理才具备工程意义上的可控性。