在 PostgreSQL 中,SQL 查询的解析、执行等全流程的触发,均以应用程序与数据库建立有效连接为前提。
这一连接建立过程看似是简单的交互握手,实则背后蕴含复杂的底层机制------涵盖进程管理、身份认证,以及保障高效通信的二进制协议等核心环节。
深入理解 PostgreSQL 的连接处理机制,不仅能明晰连接池的重要性、掌握连接问题的排查思路,还可厘清其架构与基于线程的数据库的本质差异。下文将完整梳理应用程序与 PostgreSQL 建立连接的全流程。
主进程:PostgreSQL 的守护进程
启动 PostgreSQL 时,率先启动的进程为主进程(postmaster)。该进程可视为数据库的接待进程,负责监听传入连接并协调连接的处理方式。
主进程并不直接处理查询请求。当客户端发起连接时,主进程会创建一个全新的后端进程,专门用于处理该客户端的相关操作。每个连接均拥有独立的进程,且分配有专用于查询处理的私有内存空间。
但后端进程并非完全独立运行,所有后端进程均可通过共享内存访问公共资源,其中最关键的资源为共享缓冲区高速缓存,PostgreSQL 会将数据库页面存储于该区域。借助共享内存机制,后端进程能够高效共享数据,无需在各进程中重复存储数据副本。
这种连接-进程架构可实现高度隔离。若某后端进程因查询异常崩溃,不会对其他连接产生影响。主进程仅负责连接管理,不涉及共享内存访问或复杂查询处理,因此始终保持轻量与稳定的运行状态。
该架构的弊端在于,PostgreSQL 创建连接的成本高于基于线程的数据库。创建操作系统进程所需的资源多于生成线程,因此连接池技术对高流量应用的稳定运行至关重要。
连接流程:从 TCP 连接到就绪状态
PostgreSQL 连接的建立包含多个明确步骤,具体流程如下。
步骤 1:建立 TCP 连接
应用程序向 PostgreSQL 发起标准 TCP 连接请求,默认端口为 5432。主进程接收该连接请求。此阶段尚未发生任何 PostgreSQL 专属通信,仅完成基础网络连接的搭建。
TCP 连接建立后,下一步将进行安全校验。
步骤 2:SSL 协商(可选)
若配置 SSL 协议,客户端与服务器会进行加密协商。该步骤需提前执行,因其会影响后续所有通信过程。SSL 连接建立后,所有数据传输均通过加密通道完成,可有效防止密码与查询数据被拦截。
此时客户端即可正式请求建立数据库会话。
步骤 3:发送启动数据包
客户端发送的启动数据包包含以下内容:
-
客户端使用的 PostgreSQL 协议版本(当前主流版本为 3.0)
-
目标数据库名称
-
用户名
-
可选连接参数(时区、字符编码等)
该数据包是客户端发起数据库会话请求的正式载体。
主进程接收会话请求后,将创建专用进程处理该连接。
步骤 4:创建后端进程
主进程对启动数据包进行验证,检查请求的数据库是否存在、系统是否能够承载新的连接。验证通过后,主进程将通过操作系统的 fork 机制创建新的后端进程。
完成进程创建后,主进程将该连接移交至新的后端进程,随后返回监听状态,等待接收新的连接请求。自此,客户端将直接与专属的后端进程进行通信。
但在连接投入使用前,还需完成一项关键步骤------客户端身份验证。
步骤 5:身份验证
此步骤为安全校验环节。后端进程接管连接后,将依据pg_hba.conf(PostgreSQL 基于主机的身份验证配置文件)中的规则执行身份验证。
配置文件中的规则会根据连接来源、访问的数据库以及登录用户这三个维度,确定对应的身份验证方式。
PostgreSQL 支持多种身份验证方式,包括信任认证(无需凭据)、基于密码的认证(如 SCRAM-SHA-256 算法)、SSL 证书认证,以及与 LDAP、Kerberos 等外部系统的集成认证。不同场景下可配置的认证方式差异显著,例如本地连接可采用信任认证,而远程连接则需强制使用证书认证。
身份验证通过后,将进入最终步骤。
步骤 6:进入查询就绪状态
身份验证成功后,后端进程将切换至就绪状态。此时连接完全建立,客户端与服务器可通过 PostgreSQL 的有线协议通信,实现查询语句与结果集的交互。
下文将进一步解析该有线协议的定义与工作机制。
有线协议:客户端与服务器的通信标准
PostgreSQL 有线协议定义了客户端与服务器之间交互查询语句和结果集的通信规范。该协议为二进制协议,具备高效性与可靠性的特性。
该协议的核心是一套简洁且高效的消息格式。
消息结构
所有消息均遵循统一的基础格式:
- 长度字段(4 字节):标识消息的总长度。
- 消息类型标识符(1 字节):通常为 ASCII 字符。
- 消息内容(长度可变)。
这种结构使通信双方能够高效解析消息,可处理从简短命令到大规模结果集的各类数据传输场景。
下面将阐述消息如何协同工作以完成查询执行。
两种查询执行协议
PostgreSQL 提供两种独立的查询执行协议,分别适用于不同的应用场景。
简单查询协议
简单查询协议采用直接方式:发送完整 SQL 语句并接收执行结果。
以用户信息查询为例,客户端发送一条包含完整 SQL 语句的 Query 消息:
Client → Server: Query ('Q') + "SELECT name, email FROM users WHERE id = 42"
PostgreSQL 接收该消息后,依次执行 SQL 解析、查询计划生成、语句执行操作,并按顺序返回以下消息:
Server → Client: RowDescription ('T') + column metadata
- Column 1: "name" (text type)
- Column 2: "email" (text type)
-
RowDescription消息用于描述查询结果的列信息,包括列名、数据类型及相关元数据,使客户端能够正确接收并解析后续数据。Server → Client: DataRow ('D') + "John Doe", "john@example.com"
-
DataRow消息承载实际查询结果数据。对于返回多行结果的查询,PostgreSQL 会为每一行分别发送一条DataRow消息。Server → Client: CommandComplete ('C') + "SELECT 1"
-
CommandComplete表示查询已成功完成,并附带执行结果标识。"SELECT 1" 表示本次查询返回了一行数据。Server → Client: ReadyForQuery ('Z') + transaction status
ReadyForQuery 表示服务器已准备好接收下一条命令,同时携带当前事务状态,用于指示连接处于空闲状态、事务块中或失败事务状态。
简单查询协议适用于一次性或临时执行的查询场景。其局限在于,每次执行都会重新进行 SQL 解析和查询规划,即使查询结构相同、仅参数值不同,仍无法复用已有执行计划。
针对需要频繁重复执行的查询,PostgreSQL 提供了更为高效的扩展查询协议。
扩展查询协议
该协议将查询准备阶段与执行阶段进行分离。
与简单查询协议不同,扩展查询协议支持创建预处理语句,无需在每次执行时发送完整的 SQL 语句。
下文仍以用户信息查询为例,梳理扩展查询协议的执行流程。该流程相对复杂,但在重复查询场景下的优势十分显著。
-
创建查询模板
客户端发送包含占位符的查询模板:Client → Server: Parse ('P') + statement name "get_user" +
"SELECT name, email FROM users WHERE id = $1" +
parameter types [INTEGER]
Parse 消息用于通知 PostgreSQL 创建名为get_user的预处理语句。SQL 语句中包含占位符$1,用于填充实际用户 ID 参数,同时指定该占位符的数据类型为 INTEGER。PostgreSQL 会对该 SQL 语句进行解析并生成可复用的查询计划。
Server → Client: ParseComplete ('1')
ParseComplete 消息用于确认预处理语句已创建完成,可投入使用。
-
绑定参数值至模板
客户端将具体参数值绑定至查询模板:Client → Server: Bind ('B') + portal name "user_portal" +
statement "get_user" +
parameter values [42]
Bind 消息用于创建门户,即填充实际参数值后的预处理语句实例。此步骤将数值 42 绑定至get_user语句的占位符$1,并创建名为user_portal的门户。
Server → Client: BindComplete ('2')
BindComplete 消息用于确认门户已创建完成,可执行查询操作。
-
执行门户查询
客户端发送门户执行请求:Client → Server: Execute ('E') + portal "user_portal"
Execute 消息用于触发门户的执行操作。PostgreSQL 返回结果的流程与简单查询协议一致:
Server → Client: RowDescription ('T') + column metadata
- Column 1: "name" (text type)
- Column 2: "email" (text type)
Server → Client: DataRow ('D') + "John Doe", "john@example.com"
Server → Client: CommandComplete ('C') + "SELECT 1"
-
同步会话状态
Client → Server: Sync ('S')
Server → Client: ReadyForQuery ('Z') + transaction status
在重复查询场景下,该协议的优势尤为突出。例如查询用户 ID 为 99 的数据时,可直接跳过解析步骤,基于已创建的get_user模板创建新门户并绑定参数 99,随后执行查询即可。PostgreSQL 可复用已解析的查询计划,大幅提升后续查询的执行效率。
此外,扩展查询协议还能增强对 SQL 注入攻击的防护能力。由于参数与 SQL 语句结构分离传输,采用类型化数值而非字符串拼接的方式填充参数,可从根源上避免 SQL 注入风险。
上文已阐述连接建立与通信的完整流程,下文将说明连接终止的相关机制。
连接终止机制
PostgreSQL 连接可通过以下几种方式终止。
- 正常断开
客户端发送Terminate消息,后端进程完成未执行的操作,释放占用的资源,随后正常退出。
- 空闲超时断开
PostgreSQL 支持自动关闭长时间处于空闲状态的连接。其中idle_in_transaction_session_timeout参数尤为关键,该参数可防止连接长时间持有数据库锁,避免影响其他操作的执行。
- 管理员强制终止
数据库管理员可通过pg_terminate_backend()等命令强制关闭连接。该功能在终止失控查询或强制断开异常应用程序连接时至关重要。
- 进程崩溃终止
若后端进程崩溃,将无法执行正常的清理操作。主进程会检测到该崩溃事件,并启动恢复流程,确保数据库的一致性。连接-进程架构可将问题隔离在单个后端进程内,避免单个进程崩溃对其他连接造成影响。
总结
PostgreSQL 的连接架构以主进程为核心,主进程会为每个客户端连接创建专属的后端进程。连接建立需依次完成六个步骤:建立 TCP 连接、SSL 协商、发送启动数据包、创建后端进程、身份验证、进入就绪状态。
连接建立后,客户端与服务器通过有线协议通信,支持两种查询执行方式:适用于临时查询的简单查询协议,以及适用于重复查询的扩展查询协议。扩展查询协议不仅支持查询计划复用,还能有效提升 SQL 注入防护能力。
本文已详细解析 PostgreSQL 连接机制的核心原理,下一篇文章将聚焦 SQL 查询的处理流程:解析器。届时将阐述 PostgreSQL 如何将 SQL 文本转换为能够表征查询语义与结构的结构化解析树。
作者:Jesús Espino
原文链接:
https://internals-for-interns.com/posts/postgres-connections-and-communication/
HOW 2026 议题招募中
2026 年 4 月 27-28 日,由 IvorySQL 社区联合 PGEU(欧洲 PG 社区)、PGAsia(亚洲 PG 社区)共同打造的 HOW 2026(IvorySQL & PostgreSQL 技术峰会) 将再度落地济南。届时,PostgreSQL 联合创始人 Bruce Momjian 等顶级大师将亲临现场。
自开启征集以来,HOW 2026 筹备组已感受到来自全球 PostgreSQL 爱好者的澎湃热情。为了确保大会议题的深度与广度,我们诚邀您在 2026 年 2 月 27 日截止日期前,提交您的技术见解。