跳至内容
6.2 连接与会话候选规则

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-adminPGSERVICE=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 acquisitionDDL/DML 被冲突锁长期阻塞
idle_in_transaction_session_timeouttransaction 中无客户端活动遗忘事务长期持锁或 snapshot

本章实验值为 5 秒连接、30 秒 statement、5 秒 lock、60 秒 idle-in-transaction。它们是 L1 教学 workload 的默认,不是生产通用答案。在线请求、报表、批处理、migration 应分别从 SLO、锁风险和恢复方式推导预算。

statement_timeout=0lock_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”,而是:

  1. 定义 canonical session profile;
  2. 选择一个可重复应用的层次;
  3. 在取得连接后验证关键语义;
  4. 对 pool reuse 不做隐式假设;
  5. 在 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.sql

context.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.txtnegative-summary.txt 和各自 stderr。只有正确上下文通过、两个错误上下文按精确原因失败,连接规约才同时拥有正向和负向证据。

参考资料


上一节:规约不是口号 · 返回本章目录 · 下一节:模式与 DDL 候选规则 · 查看全书目录 · 查看索引中心

最后更新于