【PGSQL】无法修改表结构

PostgreSQL 数据库 DDL 变更阻塞事故分析报告

一、事故概述

  • 故障时间:2026年06月06日 19:51 - 20:34
  • 涉及对象 :PostgreSQL 数据库 TOPMES,表 pw_steps
  • 故障现象:在执行表结构变更(DDL)操作时失败。尽管设置了锁等待超时,语句仍因"Lock Timeout"报错。经排查,当时该表无活跃查询,但 DDL 始终无法获取锁。
  • 处理结果:通过系统视图定位并终止了一个持续 19 天的异常会话后,DDL 立即执行成功,业务恢复正常。

tip:这次事故里面开启审计日志无法进行排查。

二、故障根因分析

本次故障的根本原因并非高并发冲突或硬件资源不足,而是一个典型的 "僵尸事务"导致的元数据锁死

  1. 直接原因 :进程 ID (PID) 为 44756 的数据库会话持有了目标表 pw_stepsAccessShareLock(共享访问锁)。

  2. 锁冲突机制ALTER TABLE 操作需要获取最高级别的 ACCESS EXCLUSIVE LOCK(排他锁)。在 PostgreSQL 的 MVCC 机制下,即使是只读的 SELECT 操作持有的共享锁,也会阻塞后续的表结构变更操作,以保证数据快照的一致性。

  3. 隐蔽性特征 :该会话的状态为 idle in transaction(空闲但在事务中),且持续时间长达 19 天(始于 2026-05-18)。这意味着它早已完成了 SQL 执行,处于"发呆"状态,因此不会出现在常规的"正在运行 SQL"列表中,导致常规排查手段失效。

  4. 来源推测 :根据最后执行的 SQL SELECT COUNT(*) AS total FROM qa_record WHERE ... 及连接用户 sa 判断,极大概率为开发人员或运维人员在使用 Navicat/DBeaver 等客户端工具进行临时查询时,开启了事务但未提交/回滚,随后关闭了窗口或长时间未操作,导致连接被挂起。

三、处置过程与排查脚本

1. 初步尝试与现象确认

首先尝试设置较短的锁等待超时以验证锁的存在,但操作依然失败。

sql 复制代码
-- 设置锁等待超时为5秒,避免无限期卡死
SET lock_timeout = '5s';
SET statement_timeout = '10s';

-- 再次尝试加列
ALTER TABLE pw_steps ADD COLUMN abc varchar(255);
-- 结果:错误: 由于锁超时,取消语句操作
2. 深入排查:定位阻塞源

由于常规查询看不到活跃 SQL,需通过 pg_lockspg_stat_activity 关联查询来寻找持有锁的会话。

排查步骤 A:查找当前所有的锁等待关系

此 SQL 用于查看谁在等待锁,以及谁持有了锁(注意:如果持有者处于 idle 状态,可能不会显示在 waiting 列表中,但可以通过下面的步骤 B 进一步确认)。

sql 复制代码
SELECT
    blocked_locks.pid     AS blocked_pid,
    blocked_activity.usename  AS blocked_user,
    blocking_locks.pid    AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query    AS blocked_statement,
    blocking_activity.query   AS current_statement_in_blocking_process
FROM pg_catalog.pg_locks         blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks         blocking_locks
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;

排查步骤 B:针对特定表的锁详情查询(关键步骤)

当上述通用查询未能直接给出线索时,直接针对目标表 pw_steps 查询所有持有锁的会话,无论其是否处于 active 状态。

sql 复制代码
-- 这个语句可以有效找出所有持有 pw_steps 表锁的进程(不管它在干嘛)
SELECT 
    l.pid,
    l.mode,          -- 锁的模式,AccessExclusiveLock 是最高级别
    l.granted,       -- 是否已获取锁
    a.state,         -- 进程状态(可能是 idle in transaction)
    a.query_start,
    now() - a.query_start AS duration,
    a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.relation = 'pw_steps'::regclass
ORDER BY l.granted DESC, a.query_start ASC;

排查结果:

通过步骤 B 的查询,发现 PID 44756 持有 AccessShareLock,状态为 idle in transaction,且事务已持续 19 天。

3. 解决措施

强制终止该异常会话以释放锁资源。

sql 复制代码
-- 终止阻塞进程
SELECT pg_terminate_backend(44756);

-- 重新执行 DDL
ALTER TABLE pw_steps ADD COLUMN abc varchar(255);
-- 结果:执行成功

四、改进建议与预防措施

为防止此类"僵尸事务"再次阻塞核心业务变更,建议实施以下防御性配置:

  1. 设置空闲事务超时(强烈推荐)

    在数据库层面配置 idle_in_transaction_session_timeout 参数。一旦事务开启后超过指定时间(如 10 分钟)没有任何操作,数据库将自动终止该会话并回滚事务。

    sql 复制代码
    ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min';
    SELECT pg_reload_conf();
  2. 规范客户端工具使用习惯

    提醒开发和运维团队,在使用 Navicat、DBeaver 等 GUI 工具进行查询时,务必注意事务状态。查询结束后应显式点击"提交"或"回滚",或直接关闭连接,避免长时间挂起事务。

  3. 监控告警优化

    将长事务(超过 30 分钟)纳入监控告警范围,特别是状态为 idle in transaction 的连接,以便在影响业务变更前及时发现并处理。

P-MES系统事务导致

在开启下一个工艺步骤的时候,会生成对应的质量巡检任务,导致这个事务一直挂载在上面,无法结束。

事故页面

事故程序

Hermes最终事故分析的原因

相关推荐
仙俊红2 小时前
如何优化 MySQL 深分页 SQL
android·sql·mysql
胖胖胖胖胖虎16 小时前
SQL json_table 行转列方法
sql
网管NO.117 小时前
子查询进阶|EXISTS/IN/ANY/ALL,优化查询效率
数据库·sql
yuzhiboyouye20 小时前
sql增删改查怎么写?有时会不会有联表查询的增删查改
数据库·sql
IvorySQL20 小时前
【HOW 2026 分论坛演讲】PG/IvorySQL私有云中实践
数据库·人工智能·sql·postgresql
*neverGiveUp*1 天前
PostgreSql常用SQL大全
数据库·sql·postgresql
六月雨滴1 天前
SQL 索引优化
数据库·sql·oracle·dba
Boop_wu1 天前
[Java EE进阶] 博客系统
数据库·sql