6.2 连接与会话候选规则
应用拿到一个 PostgreSQL connection 时,业务代码通常把它当作“数据库”。实际上,它是一组会改变 SQL 含义和失败方式的上下文:host/service 把流量送到某个 instance,database 决定 catalog 边界,role 决定权限,GUC 决定名称解析、时间展示与超时,连接池还可能让同一个 server connection 被多个 client request 复用。
因此,连接规约不能只检查 TCP 和认证成功。它必须同时约束目标、身份、语义、预算与归因。
6.2.1 连接上下文、超时与 application_name
本书把连接上下文拆成两层:
连接前声明
service / host / port / dbname / user
connect_timeout / application_name / TLS policy
连接后验证
current_database()
session_user / current_user
pg_is_in_recovery()
current_setting(...)
schema/model version第一层表达意图,第二层证明意图落在了正确对象上。只做其中一层都不够:连接字符串写对了仍可能遇到 DNS、service 或 failover 配置错误;连接后只看 SELECT 1 又无法知道自己是谁、在哪个库、是否落到只读副本。
用 service name 固定目标身份
libpq service file 把一组连接参数绑定到一个稳定名称:
[pg36-admin]
host=<L1_HOST>
port=5436
dbname=pg36_shop
user=dbuser_dba
application_name=pg36-ch06
connect_timeout=5
options=-c statement_timeout=30s -c lock_timeout=5s客户端以 service=pg36-admin 或 PGSERVICE=pg36-admin 连接。用户级 service file 默认为 ~/.pg_service.conf,也可以用绝对路径 PGSERVICEFILE 指定;直接连接字符串中的同名参数又会覆盖 service file 值。因此 service 是集中声明,不是不可绕过的安全边界,gate 仍须检查运行事实。
自动化入口使用:
export PGSERVICEFILE=/absolute/private/path/pg_service.conf
export PGSERVICE=pg36-admin
psql -X -w "service=$PGSERVICE application_name=pg36-ch06-review"-X 不读取用户 psqlrc,避免本地宏、变量或 SET 改变脚本;-w 禁止在无人值守任务里退回交互密码提示。第 2 章的 service file 示例 没有 secret,密码应来自 mode 0600 的 passfile 或组织 secret provider。Unix 上权限过宽的 password file 会被 libpq 忽略;这是一项客户端保护,不等于凭据已经完成轮换、审计和最小授权。
四类超时不是同一个旋钮
| 预算 | 控制的阶段 | 典型失败 |
|---|---|---|
connect_timeout | 建立连接 | 网络、DNS、endpoint 不可达 |
statement_timeout | 单条 statement 执行 | 查询/写入超过请求预算 |
lock_timeout | 等待任意单次 lock acquisition | DDL/DML 被冲突锁长期阻塞 |
idle_in_transaction_session_timeout | transaction 中无客户端活动 | 遗忘事务长期持锁或 snapshot |
本章实验值为 5 秒连接、30 秒 statement、5 秒 lock、60 秒 idle-in-transaction。它们是 L1 教学 workload 的默认,不是生产通用答案。在线请求、报表、批处理、migration 应分别从 SLO、锁风险和恢复方式推导预算。
statement_timeout=0 和 lock_timeout=0 表示禁用超时,不表示“立刻超时”。lock_timeout 若等于或大于 statement_timeout,通常没有独立触发机会。超时也不是资源隔离:30 秒内仍可能消耗大量 CPU/I/O,后续章节还要加入并发、连接数、work memory 和 workload routing。
application_name 是归因键,不是认证身份
application_name 会出现在 pg_stat_activity,配置允许时也可进入日志。命名至少区分:
product / component / workload / environment实验脚本还加入 action,例如 pg36-ch06-all。生产 trace 应在应用侧把 request/trace ID 与 backend PID、backend_start、transaction/query start 和 dashboard 时间窗关联;不要把每个 request ID 都塞进一个高基数、长度受限的 application_name。
客户端可以自行声明这个值,所以它不能替代 session_user、证书身份或审计主体。它的作用是把等待、日志和指标归到正确 workload/owner;安全判断必须使用服务器验证的身份。
由此形成两条规则:
SAFE-CONN-001:写入前验证 database、effective role、primary、search_path和模型版本;DEFAULT-SESS-001:每类 workload 声明可归因 application name 与连接/statement/lock/idle transaction 预算。
6.2.2 时区、编码、search_path 与会话状态
SQL 文本相同,不保证会话语义相同。下面这些 session state 都可能改变结果:
client_encoding决定客户端字节怎样转换为数据库字符;TimeZone改变timestamptz的输入解释和输出展示;DateStyle影响含歧义的日期文本;search_path决定未限定 table、function、type 与 operator 名称解析;- transaction isolation/read-only/deferrable 改变并发观察;
- role 与 row security 设置改变可见对象和数据;
- timeout、planner GUC 与 locale/collation 影响失败或计划选择。
本章默认固定:
SET client_encoding = 'UTF8';
SET TimeZone = 'UTC';
SET search_path = pg_catalog, shop;
SET statement_timeout = '30s';
SET lock_timeout = '5s';
SET idle_in_transaction_session_timeout = '60s';UTC 是存储/接口默认,不妨碍 UI 按用户时区展示;UTF-8 是跨系统文本默认,不替代 collation 设计。若业务输入使用本地时区,接口必须同时携带 zone/offset 并测试 DST 重叠与跳跃,不能依赖 application server 的系统时区。
search_path 是信任边界
search_path 不只用于缩短表名。PostgreSQL 也按它解析 function、type 和 operator;把某个可被不受信用户 CREATE 的 schema 放入 path,就等于信任该用户可以影响未限定名称的解析。
对 application session,本书采用:
pg_catalog, shop同时从 public 撤销 PUBLIC CREATE。需要注意版本和升级历史:PostgreSQL 15 新建数据库的默认权限与从 PostgreSQL 14 或更早升级的数据库可能不同,不能靠“大版本默认应该安全”代替 catalog 检查:
SELECT has_schema_privilege('public', 'CREATE');对 SECURITY DEFINER function 要更严格:只保留可信 schema,把 pg_temp 放到最后或明确排除不可信解析路径,敏感对象使用 schema-qualified name,并在创建 function 的同一事务中 REVOKE ALL ... FROM PUBLIC 后按需 GRANT EXECUTE。这是 SAFE-DEFR-004 的安全边界,第 4 章已有 catalog 反证。
会话默认与每次请求声明
配置可以有多个层次:
postgresql.conf / ALTER SYSTEM
< ALTER DATABASE / ALTER ROLE
< ALTER ROLE ... IN DATABASE
< startup options / connection parameters
< session SET
< transaction SET LOCAL具体生效值应由 current_setting() 或 pg_settings 观察,不应只查看某一层配置文件。对稳定的 database/role 默认,可由 Pigsty pg_databases.parameters 或版本化 SQL 声明;对单次事务预算,优先在 transaction 开始后 SET LOCAL,让它随 commit/rollback 自动恢复。
在 Pgbouncer transaction pooling 下,client session 与 PostgreSQL backend 不是永久一一对应。应用不能假设上一请求的 SET、临时对象、prepared statement 或 session lock 会在下一事务仍然存在,也不能让状态泄漏给后续请求。需要 session affinity 的 workload 应选择 session pooling 或 direct service,并把理由写进 waiver;普通 OLTP 则应把事务所需状态显式放进 startup/role default 或每个 transaction。
因此 DEFAULT-CONT-002 的真正要求不是“所有地方硬编码同一串 SET”,而是:
- 定义 canonical session profile;
- 选择一个可重复应用的层次;
- 在取得连接后验证关键语义;
- 对 pool reuse 不做隐式假设;
- 在 evidence 中保存实际值。
6.2.3 用错误连接案例验证规则价值
只有正例的 gate 可能永远绿色,即使检查本身已经失效。本章提供两个故意失败的 fixture:
错误会话
wrong-session.sql 主动设置:
SET TimeZone = 'Asia/Shanghai';
SET search_path = public;
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;然后要求 baseline 以自定义 SQLSTATE P0601 拒绝。gate 不是笼统检查“命令失败”,而是同时断言:
psql exit = 3
stderr 中恰好一个 ERROR: P0601若脚本因为语法错误、认证失败或其他 SQLSTATE 退出,negative gate 仍失败;否则一个与规则无关的故障也会被误报为“安全控制成功”。
错误目标
session-profile.sql 默认期待 pg36_shop。negative action 将 expected database 改为一个确定不存在于合同中的名字:
psql ... \
--set=expected_db=definitely_not_pg36_shop \
--set=VERBOSITY=sqlstate \
--file=session-profile.sqlcontext.sql 在任何业务读取前拒绝,预期 exit 3、SQLSTATE P0001。这证明 target guard 确实参与路径,而不是写在文件里却从未被调用。
正确会话
quality-gate.sh live 运行相同 profile,要求输出:
status=ok
database=pg36_shop
effective_role=pg36_owner
application_name=pg36-ch06-<action>
client_encoding=UTF8
timezone=UTC
search_path=pg_catalog, shop
statement_timeout=30s
lock_timeout=5s
idle_in_transaction_session_timeout=1min注意输出格式可能把 60s 规范化为 1min;验收 SQL 用 ::interval 比较语义,不比较展示文本。类似地,host、PID、server version 和 timestamp 是本次证据,不应做 golden value。
仍然没有证明什么
这个 gate 证明检查时刻的 PostgreSQL session 与 ch04-v1 模型符合预期,但没有证明:
- TLS、证书和 HBA 满足生产安全策略;
- 所有应用连接都使用同一个 profile;
- HAProxy endpoint 在 failover 后仍按预期路由;
- passfile/secret provider 的生命周期与轮换正确;
- 30 秒预算适合真实 SLO;
- Pgbouncer reset 与 driver 行为没有其他差异。
这些边界必须明确写出,否则一个绿色实验会被错误扩大为生产认证。正确做法是把相邻控制接入同一证据链,而不是让单个脚本背负它无法观察的结论。
运行本节验证
静态检查不连接数据库:
cd static/labs/ch06
./quality-gate.sh static在已经确认的 ch04-v1 L1 上运行 session 正反例:
export PGSERVICEFILE=/absolute/private/path/pg_service.conf
export PGSERVICE=pg36-admin
export PG36_EVIDENCE_DIR="$PWD/evidence/ch06/session-$(date -u +%Y%m%dT%H%M%SZ)"
./quality-gate.sh live
./quality-gate.sh negative检查 session-profile.txt、negative-summary.txt 和各自 stderr。只有正确上下文通过、两个错误上下文按精确原因失败,连接规约才同时拥有正向和负向证据。
参考资料
- PostgreSQL 18:The Connection Service File
- PostgreSQL 18:The Password File
- PostgreSQL 18:Database Connection Control Functions
- PostgreSQL 18:Client Connection Defaults
- PostgreSQL 18:Statement Behavior
- PostgreSQL 18:Schemas and
search_path - PostgreSQL 18:Reporting and Logging
上一节:规约不是口号 · 返回本章目录 · 下一节:模式与 DDL 候选规则 · 查看全书目录 · 查看索引中心