跳至内容
2.7 实战:把人工操作变成可重跑任务

2.7 实战:把人工操作变成可重跑任务

现在把连接保护、可靠脚本、确定性数据、最小负载和证据清单组合成一个任务。它会创建并覆盖 shop.ch02_fixture,因此只能在明确的 L1 教学数据库运行,不能把“表名前缀看起来安全”当作生产授权。

风险分级:

  • setupR1·可逆变更,创建或重建本章专属 100 行夹具;
  • verifybaselineR0·观察,其中 pgbench 只读;
  • inject-errorR2·破坏性演练,故意制造语法错误,但由单事务回滚隔离;
  • resetR2·破坏性演练,只删除 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,然后在事务内:

  1. 创建 shop.ch02_fixture(若不存在);
  2. pg_attribute 计算五列的名称、类型与非空形状;
  3. 发现同名表形状漂移则抛出异常;
  4. 截断本章专属表并按确定公式生成 100 行;
  5. pg36_app 写权限、给 pg36_ro 只读权限;
  6. 提交事务。

夹具故意不是电商领域模型:

用途
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,检查 psqlpgbenchsha256sum,再把每类输出写入独立文件。

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 all

all 的顺序固定:

    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.status

manifest.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 一个新路径,防止覆盖上次失败证据。verifybaseline 假设夹具已经存在;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

必须同时满足三项:

  1. stderr 含明确语法错误与位置;
  2. psql 返回状态 3
  3. 新连接查询 fixture_id = 999 得到 0 行。

只满足前两项不够。若忘记 --single-transactionON_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_TOKEN

Shell 先检查环境变量,SQL 文件再检查 confirm_reset。脚本只执行:

DROP TABLE IF EXISTS shop.ch02_fixture;

它不会删除 pg36_shopshop 模式或 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 只作为确定性输入与反例,正式业务表将从业务不变量重新推导。

参考资料


上一节:最小逻辑备份闭环 · 返回本章目录 · 下一章:正本清源:从业务规则到关系模型 · 查看全书目录 · 查看索引中心

最后更新于