2.7 实战:把人工操作变成可重跑任务
现在把连接保护、可靠脚本、确定性数据、最小负载和证据清单组合成一个任务。它会创建并覆盖 shop.ch02_fixture,因此只能在明确的 L1 教学数据库运行,不能把“表名前缀看起来安全”当作生产授权。
风险分级:
setup:R1·可逆变更,创建或重建本章专属 100 行夹具;verify、baseline:R0·观察,其中 pgbench 只读;inject-error:R2·破坏性演练,故意制造语法错误,但由单事务回滚隔离;reset:R2·破坏性演练,只删除shop.ch02_fixture,需要双重确认令牌。
2.7.1 生成 pg36_shop 初始数据与校验摘要
下载本章全部实验文件到同一目录,至少包括:
context.sql
setup.sql
verify.sql
workload.sql
broken.sql
reset.sql
task.sh复制service file 示例,替换主机并设置私有权限:
chmod 600 "$PWD/pg_service.conf"
export PGSERVICEFILE="$PWD/pg_service.conf"
export PGSERVICE=pg36-admin为 dbuser_dba 准备 passfile 或等价的非交互凭据。本书不提供真实密码,也不要求把密码写入 service file。先人工确认落点:
psql -X -w "service=$PGSERVICE" <<'PSQL'
\conninfo
SELECT current_database(), session_user, pg_is_in_recovery();
PSQL必须是 pg36_shop、受控管理员且 pg_is_in_recovery() = false。
setup 怎样收敛
setup.sql先包含 context.sql,然后在事务内:
- 创建
shop.ch02_fixture(若不存在); - 从
pg_attribute计算五列的名称、类型与非空形状; - 发现同名表形状漂移则抛出异常;
- 截断本章专属表并按确定公式生成 100 行;
- 给
pg36_app写权限、给pg36_ro只读权限; - 提交事务。
夹具故意不是电商领域模型:
| 列 | 用途 |
|---|---|
fixture_id | 稳定排序键与 pgbench 选择范围 |
sku | 可读、可计算的唯一字符串 |
label | 哈希派生文本 |
amount | 确定的 numeric(10,2) 值 |
payload | 校验与读取负载 |
ch03 会从业务规则重新设计正式模型;本表只训练工作流,避免在建模之前偷渡随意业务约束。
运行:
chmod +x task.sh
export PG36_EVIDENCE_DIR="$PWD/evidence/ch02/setup-$(date -u +%Y%m%dT%H%M%SZ)"
./task.sh setup
./task.sh verify也可直接运行 SQL:
psql -X -w "service=pg36-admin" \
-v ON_ERROR_STOP=1 \
-f setup.sql
psql -X -w "service=pg36-admin" \
-v ON_ERROR_STOP=1 \
-f verify.sql基线版本实测摘要:
status=ok
database=pg36_shop
effective_role=pg36_owner
row_count=100
min_id=1
max_id=100
checksum=00ed4599a6ed75e4441f5211909480fa再次执行 setup 与 verify,应得到相同状态。若校验和不同,先检查脚本版本哈希、服务端主要版本与本地是否修改过生成公式;不要更新“期望值”来迁就未知漂移。
形状漂移为什么要失败
假如已有 shop.ch02_fixture 只是同名、列却不同,CREATE TABLE IF NOT EXISTS 会发 NOTICE 后继续。形状保护会随后抛出异常,整个事务不再 TRUNCATE。这才是可重入:认识并拒绝未知中间状态,而不是把所有错误压成“对象已存在”。
2.7.2 从 Pigsty 服务端点执行并保存证据
综合入口是 task.sh。它采用 Linux Shell 的严格模式和 umask 077,检查 psql、pgbench 与 sha256sum,再把每类输出写入独立文件。
export PGSERVICEFILE="$PWD/pg_service.conf"
export PGSERVICE=pg36-admin
export PG36_EVIDENCE_DIR="$PWD/evidence/ch02/all-$(date -u +%Y%m%dT%H%M%SZ)"
./task.sh allall 的顺序固定:
sequenceDiagram
participant T as task.sh
participant H as Pigsty 5436 / HAProxy
participant P as PostgreSQL primary
T->>H: service=pg36-admin
H->>P: direct primary connection
T->>P: capture manifest
T->>P: setup deterministic fixture
T->>P: verify state
T->>P: pgbench 20 read-only transactions
T->>P: run broken.sql in one transaction
P-->>T: syntax error; rollback
T->>P: verify fixture_id=999 is absent
T-->>T: write exit code and evidence path
任务使用的是 Pigsty default 服务,而不是固定实例 5432。服务层提供“当前主库直连”意图,PostgreSQL 仍负责事务、角色、目录和数据。若 5436 在你的配置中含义不同,必须修改 service file 并在清单中记录,不要改书中预期输出来掩盖端点差异。
清单与证据
成功后目录应包含:
manifest.txt
setup.stdout
setup.stderr
verify.txt
verify.stderr
pgbench.txt
pgbench.stderr
broken.stdout
broken.stderr
broken.statusmanifest.txt 记录 UTC 时间、任务动作、service 名、客户端版本、七个执行文件的 SHA-256,以及服务端版本、数据库、登录角色和恢复状态。它有意不打印 host、密码或 passfile 内容;如组织审计需要记录脱敏端点,可在外层清单增加。
验证重点:
sed -n '1,120p' "$PG36_EVIDENCE_DIR/verify.txt"
sed -n '1,160p' "$PG36_EVIDENCE_DIR/pgbench.txt"
sed -n '1,80p' "$PG36_EVIDENCE_DIR/broken.status"期望:
row_count=100
checksum=00ed4599a6ed75e4441f5211909480fa
number of transactions actually processed: 20/20
number of failed transactions: 0 (0.000%)
exit_code=3
rollback_marker_count=0不验收具体 latency 或 TPS。它们会随环境变化,保留在证据中供观察,不作为通过条件。
分动作重跑
./task.sh setup
./task.sh verify
./task.sh baseline
./task.sh inject-error每次最好给 PG36_EVIDENCE_DIR 一个新路径,防止覆盖上次失败证据。verify 和 baseline 假设夹具已经存在;inject-error 会先验证正常基线,再注入错误。
task 的目标是把协议做显式,并不替代通用工作流平台。生产上的 CI、Ansible、Kubernetes Job 或调度器仍应保留同样语义:输入、目标保护、超时、失败状态、证据、重试策略和回退边界。
2.7.3 注入脚本错误,验证停止、修复与复位
broken.sql先插入一行标记,再故意把 SELECT 写成 SELEC:
INSERT INTO shop.ch02_fixture
(fixture_id, sku, label, amount, payload)
VALUES
(999, 'SKU-0999', 'must-be-rolled-back', 9.99, md5('broken'));
SELEC 'intentional syntax error';任务调用:
psql -X -w \
--single-transaction \
"service=pg36-admin" \
-v ON_ERROR_STOP=1 \
-f broken.sql必须同时满足三项:
- stderr 含明确语法错误与位置;
psql返回状态3;- 新连接查询
fixture_id = 999得到0行。
只满足前两项不够。若忘记 --single-transaction,ON_ERROR_STOP 会停止后续发送,却无法撤销已经自动提交的 INSERT。错误退出与状态回滚是两个独立性质。
修复并不自动等于正确
把 broken.sql 复制成临时 repaired.sql,将 SELEC 改为 SELECT 后再次以单事务运行,标记行会成功提交。此时语法已修复,但 verify.sql 会因为行数变成 101、确定公式不匹配而失败。
这说明:
- 修复执行错误,只证明脚本能跑完;
- 状态验证才证明结果符合任务契约;
- 幂等 setup 可以把本章拥有的夹具重新收敛到 100 行;
- 未经定义的数据不能因为“是成功 SQL 写进去的”就留在基线。
运行:
./task.sh setup
./task.sh verify确认校验和恢复。不要在有业务价值的表上用 TRUNCATE + 重建 套用这个教学复位模式。
显式 reset
默认 all 不删除夹具。若要回到 ch01 末尾状态,需要两个一致令牌:
export PG36_RESET_TOKEN=RESET_CH02_FIXTURE
./task.sh reset
unset PG36_RESET_TOKENShell 先检查环境变量,SQL 文件再检查 confirm_reset。脚本只执行:
DROP TABLE IF EXISTS shop.ch02_fixture;它不会删除 pg36_shop、shop 模式或 ch01 的角色。完成后:
SELECT to_regclass('shop.ch02_fixture') IS NULL AS removed;应返回 true。若下一章继续使用案例,重新执行 ./task.sh setup,不要 reset。
本章最终验收
逐项打勾:
- service file 与 passfile 分离,Git 中没有密码;
- 正确目标通过 context,错误数据库返回状态
3; - setup 连续执行两次仍得到 100 行和固定校验和;
- 人读探索使用元命令,机器证据查询明确目录字段;
- 所有脚本设置
ON_ERROR_STOP,调用方保存原始退出码; - CSV 的 NULL、编码、顺序和错误策略明确;
- pgbench 完成 20/20、零失败,且未宣称固定 TPS;
- custom dump 能列清单、恢复到隔离数据库并验证;
- 故障注入返回
3,标记行回滚; - reset 需要令牌且只删除本章表。
达到这些条件后,读者拥有的不只是几个命令,而是一套后续 34 章都能复用的执行语法:先验证上下文,再应用动作;用状态而不是屏幕感觉验收;把失败当作需要设计的正常路径。
下一章进入 ch03《正本清源:从业务规则到关系模型》。ch02_fixture 只作为确定性输入与反例,正式业务表将从业务不变量重新推导。
参考资料
- PostgreSQL 18:psql
- PostgreSQL 18:pgbench
- PostgreSQL 18:pg_dump
- PostgreSQL 18:pg_restore
- Pigsty v4.4:服务与接入
上一节:最小逻辑备份闭环 · 返回本章目录 · 下一章:正本清源:从业务规则到关系模型 · 查看全书目录 · 查看索引中心