# 用 psql 探索与取证

LLMS 索引： [llms.txt](/llms.txt)

---

`psql` 同时服务两种不同任务：人在终端里快速理解数据库，以及脚本稳定采集证据。交互探索可以接受对齐表格、分页与版本相关的展示；自动化取证则需要明确字段、排序、格式和退出码。把两种输出混用，是很多脆弱运维脚本的起点。

本节操作均为 `R0·观察`。先完成 2.1 的上下文检查，再在 `pg36_shop` 中执行。

## 2.2.1 对象、权限和会话元命令 {#item-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 中用单引号保护：

```bash
psql -X "service=pg36-admin" \
  -c '\dt+ shop.*'
```

若对象名包含大写字母、空格或特殊字符，模式规则与 SQL 标识符引用会变得更难读。这是本书坚持小写 `snake_case` 标识符的一个工程原因，而不是 PostgreSQL 的强制限制。

### 权限需要从三个角度看

以 `shop.ch02_fixture` 为例：

```text
\d+ shop.ch02_fixture
\dp shop.ch02_fixture
\du+ pg36_app
```

它们分别展示对象定义、对象 ACL 与角色属性。实际能否执行某项操作，还可能受对象所有权、角色成员关系、模式 `USAGE`、行级安全和列级权限影响。最终判断应使用权限函数验证具体动作：

```sql
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 扩展显示、分页、计时与查询缓冲区 {#item-2-2-2}

探索效率常常取决于“怎样看”，而不是“还能背多少元命令”。以下设置只影响当前 `psql` 客户端：

```text
\x auto
\pset null '∅'
\pset pager on
\timing on
```

- `\x auto` 在结果太宽时自动切换为逐字段显示；
- 自定义空值标记能区分 SQL `NULL` 与空字符串；
- pager 便于人在终端阅读长结果；
- `\timing` 显示客户端观察到的每条语句耗时。

这些设置不适合原样带进自动化。分页器可能等待键盘输入，装饰性空值会污染机器解析，客户端计时还包含网络传输与结果渲染。脚本应显式使用：

```text
\pset pager off
\pset tuples_only on
\pset format unaligned
```

或者直接采用命令行 `--no-align --tuples-only`。

### 查询缓冲区是交互式安全带

`psql` 会把尚未发送的 SQL 保存在查询缓冲区。常用动作是：

| 元命令 | 动作 | 何时使用 |
|---|---|---|
| `\p` | 打印当前缓冲区 | 执行前复核长 SQL |
| `\e` | 用编辑器修改缓冲区 | 多行查询比终端编辑更安全 |
| `\r` | 清空缓冲区 | 放弃误输入且尚未发送的 SQL |
| `\g` | 发送缓冲区 | 明确执行 |
| `\gx` | 发送并用扩展格式显示 | 宽结果的一次性查看 |
| `\gdesc` | 只描述结果列，不执行结果获取 | 预览查询输出形状 |

例如先写一个查询但不输入分号：

```sql
SELECT fixture_id, sku, amount
FROM shop.ch02_fixture
ORDER BY fixture_id
LIMIT 5
```

随后依次输入：

```text
\p
\gdesc
\gx
```

`\gdesc` 可以检查结果列的名称和类型；它不是通用 SQL 干运行工具，更不能证明一个写语句没有副作用。不要把“描述结果形状”扩展成“可以安全预演任何 SQL”。

`\gexec` 会把查询结果逐单元格当作 SQL 执行，后续章节偶尔用它创建可计算的 DDL。它的默认风险很高：执行顺序取决于结果排序，生成内容按字面发送，单条失败后是否继续又受 `ON_ERROR_STOP` 控制。使用前必须先把同一生成查询以普通 `\g` 输出审查，再在受控事务或隔离环境执行。

### 计时、重复观察与取消

```sql
\timing on
SELECT count(*) FROM shop.ch02_fixture;
\watch 2
```

`\watch 2` 每两秒重复当前查询，适合短时间观察计数或活动状态；按 `Ctrl-C` 取消当前查询或 watch 循环，而不是关闭整个终端。执行写语句前要先清空缓冲区，避免把它误交给 `\watch`。

`\timing` 是快速反馈，不是基准测试。第一次执行的缓存状态、返回行数、终端渲染、网络与并发噪声都会改变结果。第 2.5 节会建立最小负载协议，ch26 再讨论正式测量。

发生错误后可输入：

```text
\errverbose
```

它会重新显示最近一个服务端错误的完整诊断，包括 SQLSTATE、DETAIL、HINT 和错误位置（若可用）。保存证据时应同时保留标准错误，而不是只截终端最后一行。

## 2.2.3 元命令与系统目录查询互相验证 {#item-2-2-3}

元命令通常在内部查询 `pg_catalog`。用 `psql -E` 启动，或在会话中设置：

```text
\set ECHO_HIDDEN on
\d+ shop.ch02_fixture
```

`psql` 会打印它为当前服务器版本生成的目录查询。这是学习系统目录的好入口，也揭示一个重要事实：`\d` 的展示和内部 SQL 都可能随 PostgreSQL 版本变化，不应被 shell 脚本按列位置解析。

### 用目录查询复核对象

下面的查询稳定地列出本章夹具的用户列：

```sql
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 与关系类型：

```sql
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 运维与深度取证通常需要前者。

### 人读输出与机器证据分开

交互探索：

```bash
psql -X "service=pg36-admin" \
  -c '\d+ shop.ch02_fixture'
```

机器采集：

```bash
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` 的人读表格，而是查询明确的目录字段。

## 参考资料

- [PostgreSQL 18：psql 元命令](https://www.postgresql.org/docs/18/app-psql.html#APP-PSQL-META-COMMANDS)
- [PostgreSQL 18：psql 模式](https://www.postgresql.org/docs/18/app-psql.html#APP-PSQL-PATTERNS)
- [PostgreSQL 18：系统目录](https://www.postgresql.org/docs/18/catalogs.html)
- [PostgreSQL 18：信息模式](https://www.postgresql.org/docs/18/information-schema.html)
- [PostgreSQL 18：权限查询函数](https://www.postgresql.org/docs/18/functions-info.html#FUNCTIONS-INFO-ACCESS-TABLE)

---

[上一节：可靠连接与上下文保护](../01/) · [返回本章目录](../) · [下一节：编写可靠 SQL 脚本](../03/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
