34.3 失控查询、锁与事务
“杀慢查询”不是故障判型。一个耗时最长的 session 可能是阻塞根节点、也可能是等待者; 可能正在做有价值的恢复,也可能已经超过用户 deadline;可能可以安全 cancel,也可能 一旦 terminate 就触发数小时回滚。动作必须绑定 query、transaction、application、 owner 与业务语义。
34.3.1 识别高消耗查询和阻塞根节点
当前现场与历史重查询分开
pg_stat_activity 说明此刻有哪些 backend、状态与等待;pg_stat_statements 聚合的是
一段时间内同类语句的执行统计。前者适合回答“谁现在占着资源”,后者适合回答“哪类
语句长期贡献最多”。不能用累计榜单代替当前事故现场。
一个不导出完整 SQL 文本的当前投影:
SELECT pid,
usename,
application_name,
state,
wait_event_type,
wait_event,
clock_timestamp() - query_start AS query_age,
clock_timestamp() - xact_start AS xact_age,
backend_xid,
backend_xmin,
query_id
FROM pg_stat_activity
WHERE backend_type = 'client backend'
AND pid <> pg_backend_pid()
ORDER BY xact_start NULLS LAST, query_start NULLS LAST;对历史工作量,可按目标排序,而不是永远按 total time:
SELECT queryid,
calls,
total_exec_time,
mean_exec_time,
rows,
shared_blks_read,
shared_blks_written,
temp_blks_read,
temp_blks_written,
wal_bytes
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;版本、扩展列和统计起点应随报告一起记录。统计 reset 或重启后的短窗口不能与一周基线 直接比较。
找根阻塞者,不要只杀等待者
WITH RECURSIVE lock_tree AS (
SELECT a.pid,
a.application_name,
a.xact_start,
pg_blocking_pids(a.pid) AS blockers,
ARRAY[a.pid] AS path
FROM pg_stat_activity AS a
WHERE cardinality(pg_blocking_pids(a.pid)) > 0
UNION ALL
SELECT b.pid,
b.application_name,
b.xact_start,
pg_blocking_pids(b.pid),
t.path || b.pid
FROM lock_tree AS t
CROSS JOIN LATERAL unnest(t.blockers) AS p(pid)
JOIN pg_stat_activity AS b ON b.pid = p.pid
WHERE NOT b.pid = ANY(t.path)
)
SELECT * FROM lock_tree;根 blocker 可能显示 idle in transaction,因为它已经执行完持锁语句,正在等客户端下
一条命令。仅筛选 state='active' 会漏掉它。也要排除 autovacuum、logical worker、
备份和维护工作等不同 backend_type,不要把每个 PID 都当作应用会话。
高消耗不是自动有罪
取消前回答:
is this the root blocker or a victim?
is its client deadline already expired?
is it OLTP, migration, maintenance, backup, recovery, or batch?
what locks and objects does it own?
what rows/WAL/temp/I/O has it already produced?
does it carry a business idempotency key?
who owns the decision?对 query_id 做执行计划分析时,转到第 10、11 章的方法;在线事故中不要在主库上无界
执行 EXPLAIN ANALYZE 复现一条未知重查询。
34.3.2 cancel、terminate 与回滚成本
两个函数的边界
SELECT pg_cancel_backend(:pid);
SELECT pg_terminate_backend(:pid);pg_cancel_backend 向目标 backend 发送取消当前 query 的请求。session 通常仍存在;
当前事务会进入错误状态,客户端需要 ROLLBACK。pg_terminate_backend 终止整个
session,连接断开,未提交事务由服务器回滚。两者都需要相应权限;不要通过给应用
超级用户来获得事故处置能力。
优先级一般是:
application cooperative cancel/deadline
-> pg_cancel_backend exact PID
-> wait and verify
-> pg_terminate_backend exact PID when justified“exact”至少绑定:
pid + backend_start
database + user + application_name
query_id / transaction age
incident run id or ticketPID 会复用。先查 PID、过几分钟再裸 terminate,可能命中完全不同的新连接。执行动作的 SQL 应在同一事务/语句里重验识别字段。
cancel 不等于立即释放全部资源
- query 可能在到达可中断点前继续运行;
- client 可能自动重试同一工作;
- 事务未 rollback 前仍可能持有锁;
- parallel workers 与 leader 的收敛需要时间;
- remote/extension 调用的中断语义取决于组件;
- query 已经产生的 WAL、temp 或脏页不会凭空消失。
terminate 也不是免费的“更强 cancel”。大型事务回滚需要遍历并清理状态,可能继续消耗 CPU、I/O 与 WAL,且锁直到 transaction end 才释放。事故时最差选择是连续 terminate 多个大事务,再因为“资源没降”重启数据库,把恢复工作叠加到启动路径。
动作后必须复核
SELECT pid, backend_start, state, wait_event_type, wait_event
FROM pg_stat_activity
WHERE pid = :pid;同时观察:
root blocker disappeared?
dependent waiters made progress?
pool stopped recreating the work?
rollback/recovery still consuming I/O?
user success and tail latency recovered?如果应用立刻重建同一 session,数据库端 cancel 只是短暂擦除症状,真正控制点在 admission 和 retry。
34.3.3 长事务和大事务结束前先评估后果
“长”与“大”是两个维度
long but small
idle transaction holds snapshot/locks for hours
short but large
bulk UPDATE changes millions of rows in minutes
long and large
migration/ETL both retains old state and creates rollback work长事务主要风险是 lock、backend_xmin、vacuum 回收与连接占用;大事务还带来 WAL、
dirty buffers、replication lag、commit/rollback 延迟。prepared transaction 即使没有
活动 session,也可长期保留锁和 XID 状态:
SELECT gid,
prepared,
owner,
database,
clock_timestamp() - prepared AS age
FROM pg_prepared_xacts
ORDER BY prepared;不要看到 prepared transaction 就 ROLLBACK PREPARED。它属于两阶段提交协议,必须先
与 transaction manager/业务 ledger 对账,判断应 commit 还是 rollback。
结束前的后果清单
identity:
pid_backend_start: ...
application_owner: ...
transaction_or_job_id: ...
business:
partial_external_effects: ...
idempotency_or_reconciliation: ...
database:
locks: ...
xmin_retention: ...
rows_wal_temp_estimate: ...
replicas_and_archive_effect: ...
rollback:
expected_resource_cost: ...
observation_query: ...
escalation_timeout: ...若事务包含数据库外部副作用,PostgreSQL rollback 只能撤销数据库内未提交状态,不能 撤回已经发出的邮件、支付或消息。此时需要业务补偿,不是更强的 terminate。
更好的预防
- 为交互式事务设置合理的
idle_in_transaction_session_timeout; - 为不同工作负载设置 statement/lock timeout,而不是一个全局极小值;
- 大批处理分块提交,并让每块有可恢复 checkpoint;
- schema change 使用受控 lock timeout 和发布门;
- 统一 application_name、query tag 与业务 job id;
- 为重要操作保留可对账 token。
timeout 是保护栏,不是容量。设置后还要验证应用如何处理取消、事务错误和重试。
上一节:连接风暴与排队失控 · 返回本章目录 · 下一节:CPU、内存、I/O 与 OOM · 查看全书目录 · 查看索引中心