2.2 用 psql 探索与取证
psql 同时服务两种不同任务:人在终端里快速理解数据库,以及脚本稳定采集证据。交互探索可以接受对齐表格、分页与版本相关的展示;自动化取证则需要明确字段、排序、格式和退出码。把两种输出混用,是很多脆弱运维脚本的起点。
本节操作均为 R0·观察。先完成 2.1 的上下文检查,再在 pg36_shop 中执行。
2.2.1 对象、权限和会话元命令
psql 元命令以反斜杠开头,由客户端解释,不会作为 SQL 发给服务器。它们最适合回答“这里大概有什么”和“下一步该查哪个目录”,不应被误认为独立于 PostgreSQL 的另一套元数据。
一张够用的探索表
| 元命令 | 主要问题 | 推荐用法 | 常见误读 |
|---|---|---|---|
\conninfo | 当前客户端连接参数是什么? | 每次进入会话先看 | 只显示客户端视角,不替代服务器快照 |
\l+ | 有哪些数据库及其属性? | 观察 owner、编码、权限和大小 | 大小统计可能慢,也不等于磁盘总占用 |
\dn+ | 有哪些模式,谁拥有? | 确认 shop 与权限 | 模式不是数据库 |
\dt+ shop.* | shop 中有哪些普通表? | 用模式限定模式匹配 | 不会列出所有关系类型 |
\d+ shop.ch02_fixture | 一个关系如何定义? | 看列、索引、约束、存储等 | 输出格式会随版本变化 |
\df+ shop.* | 有哪些函数? | 限定模式和名称模式 | 函数重载需要参数签名区分 |
\du+ | 有哪些角色及属性? | 识别 LOGIN、SUPERUSER 等角色属性 | 不完整呈现所有成员关系语义 |
\dp shop.* / \z shop.* | 表、序列等对象的 ACL 是什么? | 快速找显式授权 | 空 ACL 与默认权限不能只看字面猜测 |
\encoding | 当前客户端编码是什么? | 与服务端编码一并记录 | 客户端编码不等于数据库编码 |
命令中的 shop.* 是 psql 模式匹配,不是 shell glob。仍建议放在交互会话内输入,或在 shell 中用单引号保护:
psql -X "service=pg36-admin" \
-c '\dt+ shop.*'若对象名包含大写字母、空格或特殊字符,模式规则与 SQL 标识符引用会变得更难读。这是本书坚持小写 snake_case 标识符的一个工程原因,而不是 PostgreSQL 的强制限制。
权限需要从三个角度看
以 shop.ch02_fixture 为例:
\d+ shop.ch02_fixture
\dp shop.ch02_fixture
\du+ pg36_app它们分别展示对象定义、对象 ACL 与角色属性。实际能否执行某项操作,还可能受对象所有权、角色成员关系、模式 USAGE、行级安全和列级权限影响。最终判断应使用权限函数验证具体动作:
SELECT
has_schema_privilege('pg36_app', 'shop', 'USAGE') AS schema_usage,
has_table_privilege(
'pg36_app',
'shop.ch02_fixture',
'SELECT'
) AS can_select,
has_table_privilege(
'pg36_ro',
'shop.ch02_fixture',
'UPDATE'
) AS ro_can_update;期望前两项为 true,最后一项为 false。这仍不是“模拟一次完整 SQL”的万能授权检查,但比肉眼解释 ACL 字符串更适合验收。
2.2.2 扩展显示、分页、计时与查询缓冲区
探索效率常常取决于“怎样看”,而不是“还能背多少元命令”。以下设置只影响当前 psql 客户端:
\x auto
\pset null '∅'
\pset pager on
\timing on\x auto在结果太宽时自动切换为逐字段显示;- 自定义空值标记能区分 SQL
NULL与空字符串; - pager 便于人在终端阅读长结果;
\timing显示客户端观察到的每条语句耗时。
这些设置不适合原样带进自动化。分页器可能等待键盘输入,装饰性空值会污染机器解析,客户端计时还包含网络传输与结果渲染。脚本应显式使用:
\pset pager off
\pset tuples_only on
\pset format unaligned或者直接采用命令行 --no-align --tuples-only。
查询缓冲区是交互式安全带
psql 会把尚未发送的 SQL 保存在查询缓冲区。常用动作是:
| 元命令 | 动作 | 何时使用 |
|---|---|---|
\p | 打印当前缓冲区 | 执行前复核长 SQL |
\e | 用编辑器修改缓冲区 | 多行查询比终端编辑更安全 |
\r | 清空缓冲区 | 放弃误输入且尚未发送的 SQL |
\g | 发送缓冲区 | 明确执行 |
\gx | 发送并用扩展格式显示 | 宽结果的一次性查看 |
\gdesc | 只描述结果列,不执行结果获取 | 预览查询输出形状 |
例如先写一个查询但不输入分号:
SELECT fixture_id, sku, amount
FROM shop.ch02_fixture
ORDER BY fixture_id
LIMIT 5随后依次输入:
\p
\gdesc
\gx\gdesc 可以检查结果列的名称和类型;它不是通用 SQL 干运行工具,更不能证明一个写语句没有副作用。不要把“描述结果形状”扩展成“可以安全预演任何 SQL”。
\gexec 会把查询结果逐单元格当作 SQL 执行,后续章节偶尔用它创建可计算的 DDL。它的默认风险很高:执行顺序取决于结果排序,生成内容按字面发送,单条失败后是否继续又受 ON_ERROR_STOP 控制。使用前必须先把同一生成查询以普通 \g 输出审查,再在受控事务或隔离环境执行。
计时、重复观察与取消
\timing on
SELECT count(*) FROM shop.ch02_fixture;
\watch 2\watch 2 每两秒重复当前查询,适合短时间观察计数或活动状态;按 Ctrl-C 取消当前查询或 watch 循环,而不是关闭整个终端。执行写语句前要先清空缓冲区,避免把它误交给 \watch。
\timing 是快速反馈,不是基准测试。第一次执行的缓存状态、返回行数、终端渲染、网络与并发噪声都会改变结果。第 2.5 节会建立最小负载协议,ch26 再讨论正式测量。
发生错误后可输入:
\errverbose它会重新显示最近一个服务端错误的完整诊断,包括 SQLSTATE、DETAIL、HINT 和错误位置(若可用)。保存证据时应同时保留标准错误,而不是只截终端最后一行。
2.2.3 元命令与系统目录查询互相验证
元命令通常在内部查询 pg_catalog。用 psql -E 启动,或在会话中设置:
\set ECHO_HIDDEN on
\d+ shop.ch02_fixturepsql 会打印它为当前服务器版本生成的目录查询。这是学习系统目录的好入口,也揭示一个重要事实:\d 的展示和内部 SQL 都可能随 PostgreSQL 版本变化,不应被 shell 脚本按列位置解析。
用目录查询复核对象
下面的查询稳定地列出本章夹具的用户列:
SELECT
a.attnum AS ordinal,
a.attname AS column_name,
pg_catalog.format_type(a.atttypid, a.atttypmod)
AS data_type,
a.attnotnull AS not_null
FROM pg_catalog.pg_attribute AS a
WHERE a.attrelid = 'shop.ch02_fixture'::regclass
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum;与 \d+ 相比,它的优势不是更“原生”,而是调用方明确选择了字段、含义和顺序。regclass 转换还能在对象不存在或解析错误时直接失败;如果希望“对象缺失返回 NULL”,则用 to_regclass('shop.ch02_fixture')。
再复核 owner 与关系类型:
SELECT
n.nspname AS schema_name,
c.relname AS relation_name,
c.relkind,
pg_catalog.pg_get_userbyid(c.relowner) AS owner
FROM pg_catalog.pg_class AS c
JOIN pg_catalog.pg_namespace AS n
ON n.oid = c.relnamespace
WHERE n.nspname = 'shop'
AND c.relname = 'ch02_fixture';pg_catalog 暴露 PostgreSQL 的完整内部元数据,字段会随版本演进;information_schema 提供更标准化、通常受当前用户可见性过滤的视图,但不会覆盖全部 PostgreSQL 特性。跨数据库工具优先考虑后者,PostgreSQL 运维与深度取证通常需要前者。
人读输出与机器证据分开
交互探索:
psql -X "service=pg36-admin" \
-c '\d+ shop.ch02_fixture'机器采集:
mkdir -p evidence/ch02
psql -X -w "service=pg36-admin" \
--set=ON_ERROR_STOP=1 \
--csv \
--command="
SELECT a.attnum, a.attname,
pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
a.attnotnull
FROM pg_catalog.pg_attribute AS a
WHERE a.attrelid = 'shop.ch02_fixture'::regclass
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum;
" > evidence/ch02/columns.csv机器输出应有固定列、显式排序和失败即停;文件名、采集时间、连接上下文与版本则写入清单。CSV 解决字段引用,不会自动赋予字段长期兼容承诺。
本节验收
- 能用元命令找到
shop.ch02_fixture、owner 和 ACL; - 能用
has_*_privilege验证pg36_app与pg36_ro的实际权限; - 能解释
\timing为什么不是正式基准; - 能用
-E找到元命令背后的目录查询; - 自动化证据不解析
\d的人读表格,而是查询明确的目录字段。
参考资料
上一节:可靠连接与上下文保护 · 返回本章目录 · 下一节:编写可靠 SQL 脚本 · 查看全书目录 · 查看索引中心