运维管理30 分钟阅读
PostgreSQL 锁等待、阻塞链与长事务 100 条命令
PostgreSQL 业务卡住时,慢 SQL 不一定真的在跑。它可能正在等表锁,也可能在等另一笔事务释放行锁;后者在 `pg_locks` 里常表现为等待 `transactionid`,不一定有一条直观的“行锁记录”。
2026年9月16日阅读—点赞—收藏—
dba100postgresqlscenario
100 条命令系列文章专栏
PostgreSQL 业务卡住时,慢 SQL 不一定真的在跑。它可能正在等表锁,也可能在等另一笔事务释放行锁;后者在 `pg_locks` 里常表现为等待 `transactionid`,不一定有一条直观的“行锁记录”。
PostgreSQL 业务卡住时,慢 SQL 不一定真的在跑。它可能正在等表锁,也可能在等另一笔事务释放行锁;后者在 pg_locks 里常表现为等待 transactionid,不一定有一条直观的“行锁记录”。
这篇先找正在等待的会话,再追是谁挡住它、对方事务开了多久。定位阻塞源以后还要核对业务影响,不能只因为某个 PID 在链头就直接杀连接。值班遇到锁问题时建议收藏,也可以分享给开发同事一起对照事务边界。
PostgreSQL 锁等待与阻塞链示意
示例使用 PostgreSQL 17。查看其他用户的 SQL 文本需要相应统计权限;终止会话和修改超时参数会影响业务,本篇先从只读诊断写起。
1SELECT pid, datname, usename, state,2 wait_event_type, wait_event, query_start,3 left(query, 160) AS query4FROM pg_stat_activity5WHERE state = 'active'6ORDER BY query_start NULLS LAST;state='active' 表示查询正在执行,也可能正在等待。先看 wait_event_type,只有 Lock 才按锁链继续追;IO 和 Client 要走各自的排查路径。
1SELECT pid, locktype, mode, database, relation,2 transactionid, virtualxid, waitstart3FROM pg_locks4WHERE NOT granted5ORDER BY waitstart NULLS LAST;granted=false 是等待锁。waitstart 在刚开始等待时可能还是 NULL;行锁冲突常显示为等待对方的 transactionid,不要以为没看到 tuple 就没有行锁问题。
1SELECT pg_blocking_pids(12345) AS blocking_pids;把 12345 换成等待会话的 PID。该函数同时考虑持锁者和队列中排在前面的等待者,比自己对 pg_locks 做简单自连接可靠;返回空数组说明当下没有锁阻塞。
1SELECT a.pid AS waiting_pid,2 pg_blocking_pids(a.pid) AS blocking_pids,3 a.wait_event, a.query_start,4 left(a.query, 120) AS waiting_query5FROM pg_stat_activity AS a6WHERE a.wait_event_type = 'Lock'7ORDER BY a.query_start NULLS LAST;结果是“这一刻”的锁链,下一秒可能已经释放。pg_blocking_pids 也可能返回重复 PID;若由 prepared transaction 持锁,会出现 0,后面要另查两阶段事务。
1SELECT w.pid AS waiting_pid, b.pid AS blocking_pid,2 w.wait_event, w.query_start AS wait_query_start,3 b.state AS blocker_state, b.xact_start AS blocker_xact_start,4 left(w.query, 100) AS waiting_query,5 left(b.query, 100) AS blocker_last_query6FROM pg_stat_activity AS w7CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS p(pid)8LEFT JOIN pg_stat_activity AS b ON b.pid = p.pid9WHERE w.wait_event_type = 'Lock'10ORDER BY w.query_start NULLS LAST;阻塞者不一定还在执行 SQL:它可能 idle in transaction,b.query 此时是最后执行过的语句。blocking_pid=0 代表 prepared transaction,不能靠 pg_stat_activity 找到会话。
1SELECT pid, usename, application_name, client_addr,2 state, xact_start, now() - xact_start AS xact_age,3 backend_xid, backend_xmin4FROM pg_stat_activity5WHERE xact_start IS NOT NULL6ORDER BY xact_start7LIMIT 30;连接开得久不等于事务开得久。这里用 xact_start 找长事务,再结合第 5 条确认它是否真的在挡当前业务。
1SELECT pid, usename, application_name, client_addr,2 xact_start, state_change,3 now() - xact_start AS xact_age,4 left(query, 160) AS last_query5FROM pg_stat_activity6WHERE state LIKE 'idle in transaction%'7ORDER BY xact_start;这类会话虽然当前没执行语句,事务仍未结束,锁和旧快照可能继续保留。先找到对应应用和事务调用链,确认为何没有提交或回滚。
1SELECT l.pid, l.relation::regclass AS relation_name,2 l.mode, l.granted, l.waitstart3FROM pg_locks AS l4WHERE l.locktype = 'relation'5 AND l.database = (SELECT oid FROM pg_database6 WHERE datname = current_database())7ORDER BY l.relation, l.granted, l.pid;pg_locks 覆盖整个集群,但 relation::regclass 应只对当前数据库的关系 OID 解释。若出现等待 AccessExclusiveLock,要查是不是 DDL、VACUUM FULL 或其他需要排他锁的操作。
1SELECT pid, transactionid, mode, granted, waitstart2FROM pg_locks3WHERE locktype = 'transactionid'4 AND NOT granted5ORDER BY waitstart NULLS LAST;行更新冲突经常落在这里。它显示当前等待哪个事务结束,不直接显示被锁行的表名;要把 PID、SQL 和第 3 条的阻塞者一起对上。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('lock_timeout', 'statement_timeout',4 'idle_in_transaction_session_timeout',5 'deadlock_timeout', 'log_lock_waits')6ORDER BY name;超时参数决定等待多久报错,以及是否留下锁等待日志。参数有全局、数据库、角色和会话层级,排查某条连接时还要看那条会话的实际设置。
1SELECT l.pid, l.locktype, l.mode, l.waitstart,2 now() - l.waitstart AS wait_age,3 a.usename, a.application_name4FROM pg_locks AS l5LEFT JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE NOT l.granted7ORDER BY l.waitstart NULLS LAST;query_start 是语句开始时间,不一定等于开始等锁的时间。这里看 waitstart;刚开始等待时它可能暂时为空,不能把空值当成无等待。
1SELECT w.pid AS waiting_pid, p.pid AS blocker_pid,2 b.wait_event_type AS blocker_wait_type,3 b.wait_event AS blocker_wait_event,4 pg_blocking_pids(p.pid) AS blocker_blocked_by5FROM pg_stat_activity AS w6CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS p(pid)7LEFT JOIN pg_stat_activity AS b ON b.pid = p.pid8WHERE w.wait_event_type = 'Lock'9 AND p.pid <> 010ORDER BY w.pid, p.pid;阻塞你的会话也可能排在另一条等待队列中。blocker_blocked_by 非空就继续向上追;这里排除了代表 prepared transaction 的 PID 0,它要另查。
1WITH edges AS (2 SELECT w.pid AS waiting_pid, p.pid AS blocker_pid3 FROM pg_stat_activity AS w4 CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS p(pid)5 WHERE w.wait_event_type = 'Lock'6)7SELECT e.blocker_pid, count(*) AS directly_blocked8FROM edges AS e9WHERE e.blocker_pid <> 010 AND NOT EXISTS (SELECT 1 FROM edges AS x11 WHERE x.waiting_pid = e.blocker_pid)12GROUP BY e.blocker_pid13ORDER BY directly_blocked DESC, e.blocker_pid;“候选链头”只说明当前没被另一条锁等待挡住。它可能同时承担关键业务事务,不能据此直接终止;先回查会话、事务开始时间和应用来源。
1SELECT pid, datname, usename, application_name, client_addr,2 state, xact_start, state_change,3 left(query, 200) AS last_query4FROM pg_stat_activity5WHERE pid = 12345;把 12345 换成第 13 条的 PID。state_change 可以帮助判断会话什么时候转入空闲;last_query 是最后一条 SQL,不保证就是最初拿锁的语句。
1SELECT pid, mode, granted, waitstart2FROM pg_locks3WHERE locktype = 'relation'4 AND relation = 'public.orders'::regclass5 AND database = (SELECT oid FROM pg_database6 WHERE datname = current_database())7ORDER BY granted, waitstart NULLS LAST, pid;关系锁只解释表级访问。普通 SELECT 会拿 AccessShareLock,它通常只会被 AccessExclusiveLock 阻塞;看到 RowExclusiveLock 不等于它已经把所有读请求挡住。
1SELECT l.pid, l.relation::regclass AS relation_name,2 l.waitstart, a.usename, a.query_start,3 left(a.query, 200) AS query4FROM pg_locks AS l5JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.locktype = 'relation'7 AND l.mode = 'AccessExclusiveLock'8 AND NOT l.granted9 AND l.database = (SELECT oid FROM pg_database10 WHERE datname = current_database())11ORDER BY l.waitstart NULLS LAST;TRUNCATE、VACUUM FULL 和不少 DDL 需要这类锁。它排在等待队列里时,后续请求也可能被它软阻塞;要结合第 4 条确认真实队列关系。
1SELECT pid, virtualxid, mode, granted, waitstart2FROM pg_locks3WHERE locktype = 'virtualxid'4 AND NOT granted5ORDER BY waitstart NULLS LAST;有些操作必须等其他事务结束,即便对方还没分配永久 XID。出现 virtualxid 等待时用 pg_blocking_pids 找具体会话,不要把虚拟 ID 当成表 OID。
1SELECT gid, "transaction" AS xid, prepared, owner, database2FROM pg_prepared_xacts3ORDER BY prepared;prepared transaction 已经离开原会话,但拿到的锁还在。若第 4 条里阻塞 PID 为 0,先检查这张视图;提交还是回滚要由事务协调方决定。
1SELECT p.gid, p.prepared, l.locktype, l.mode,2 l.database, l.relation, l.granted3FROM pg_locks AS l4JOIN pg_prepared_xacts AS p5 ON l.virtualtransaction = '-1/' || p."transaction"6WHERE l.pid IS NULL7ORDER BY p.prepared, p.gid, l.locktype;官方文档给出了 virtualtransaction 的连接方式。这里的 relation 是 OID,跨数据库时不能直接在当前库强制转成 regclass;先核对该两阶段事务属于哪个数据库。
1SELECT pid, classid, objid, objsubid, mode, granted, waitstart2FROM pg_locks3WHERE locktype = 'advisory'4 AND database = (SELECT oid FROM pg_database5 WHERE datname = current_database())6ORDER BY granted, waitstart NULLS LAST;advisory lock 的键由应用定义,不能从 classid/objid 猜出业务对象。先找使用该键的代码和持锁会话;事务级与会话级 advisory lock 的释放时机也不同。
1SELECT locktype, mode, count(*) AS waiting_count2FROM pg_locks3WHERE NOT granted4GROUP BY locktype, mode5ORDER BY waiting_count DESC, locktype, mode;高峰时先看等待主要落在 relation、transactionid 还是 advisory。这是当前瞬间的请求数,不是过去一小时的等待次数;排查时间点不对,结果可能完全为空。
1SELECT a.pid, a.datname, a.usename, a.state,2 a.wait_event, a.query_start,3 pg_blocking_pids(a.pid) AS blocking_pids,4 left(a.query, 300) AS query_text5FROM pg_stat_activity AS a6WHERE a.wait_event_type = 'Lock'7ORDER BY a.query_start NULLS LAST;先确认等待的 SQL 和直接阻塞 PID。pg_blocking_pids 能处理队列中的软阻塞,比简单地用 pg_locks 自连接更可靠;它给的是这一刻的关系,重复采样才能判断阻塞是否持续。
1SELECT locktype, database, relation, page, tuple,2 transactionid, virtualxid, mode,3 granted, waitstart4FROM pg_locks5WHERE pid = 123456ORDER BY granted, locktype, mode;把 PID 换成第 22 条看到的会话。一个事务可能持有多种锁;granted=false 才是它当前等待的请求。relation 是 OID,跨数据库不要在当前库直接解释为自己的表名。
1SELECT l.pid, l.mode, l.granted, l.waitstart,2 a.state, a.xact_start,3 left(a.query, 160) AS query_text4FROM pg_locks AS l5LEFT JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.locktype = 'relation'7 AND l.relation = 'public.orders'::regclass8 AND l.database = (SELECT oid FROM pg_database9 WHERE datname = current_database())10ORDER BY l.granted, l.waitstart NULLS LAST;同一张表上有多个锁不代表它们互相冲突,冲突要按锁模式矩阵判断。持锁者当前显示的 SQL 也可能已经执行完,只是事务尚未结束;优先看 xact_start 和应用事务边界。
1SELECT pid, locktype,2 relation::regclass AS relation_name,3 page, tuple, mode, waitstart4FROM pg_locks5WHERE locktype IN ('page', 'tuple')6 AND NOT granted7 AND database = (SELECT oid FROM pg_database8 WHERE datname = current_database())9ORDER BY waitstart NULLS LAST;这类记录出现时可以定位对象与页/元组编号,但大多数行级冲突不会直接显示为一条稳定的 tuple 锁等待,常见的是第 9 条 transactionid 等待。查不到这里不能排除行锁问题。
1SELECT pid, relation::regclass AS relation_name,2 mode, fastpath, granted3FROM pg_locks4WHERE pid = 123455 AND locktype = 'relation'6 AND database = (SELECT oid FROM pg_database7 WHERE datname = current_database())8ORDER BY relation, mode;fastpath=true 说明该关系锁经快速路径取得,并不表示“没有锁”或“不会阻塞 DDL”。排查普通读写与排他 DDL 的关系时,把它也算进去;PID 和数据库范围仍要与现场一致。
1SELECT gid, "transaction" AS xid, prepared,2 now() - prepared AS prepared_age, owner3FROM pg_prepared_xacts4WHERE database = current_database()5ORDER BY prepared;两阶段事务提交前可能一直保留锁,即使原客户端早已离开。prepared_age 很长是需要联系事务协调方的线索;千万不能只按时间长短决定 COMMIT PREPARED 或 ROLLBACK PREPARED。
1SELECT l.pid, l.classid, l.objid, l.objsubid,2 l.mode, l.waitstart,3 pg_blocking_pids(l.pid) AS blocking_pids4FROM pg_locks AS l5WHERE l.locktype = 'advisory'6 AND NOT l.granted7 AND l.database = (SELECT oid FROM pg_database8 WHERE datname = current_database())9ORDER BY l.waitstart NULLS LAST;advisory lock 常用于应用自己实现的互斥;键的含义只能回到应用代码确认。先把等待会话与持锁会话的事务/连接生命周期对上,不要把这种等待误归因于 PostgreSQL 的表锁。
1SELECT datname, deadlocks, stats_reset2FROM pg_stat_database3WHERE datname = current_database();deadlocks 是自 stats_reset 以来的累计次数。要判断今天是否新增,须和之前的取样值相比,再查服务端日志中的具体死锁 SQL 与锁对象;当前没有等待也不代表死锁没有发生。
1SHOW track_activity_query_size;第 22、24 条的 query 可能因为长度上限而截断,尤其是 ORM 生成的长 SQL。看到不完整文本时,用应用日志、SQL 标识和数据库日志补齐;改大此启动参数也不会复原之前已截断的语句。
1SELECT datname, application_name,2 count(*) AS waiting_sessions,3 min(query_start) AS earliest_query_start4FROM pg_stat_activity5WHERE wait_event_type = 'Lock'6 AND backend_type = 'client backend'7GROUP BY datname, application_name8ORDER BY waiting_sessions DESC, earliest_query_start;先看等待是否集中在一个应用,还是多个业务都被拖住。application_name 由客户端上报,空值和随意填写的名字都很常见;它不能代替调用链或连接来源。这里只是取样瞬间的连接数。
1WITH edges AS (2 SELECT w.pid AS waiting_pid,3 unnest(pg_blocking_pids(w.pid)) AS blocker_pid4 FROM pg_stat_activity AS w5 WHERE w.wait_event_type = 'Lock'6)7SELECT blocker_pid,8 count(DISTINCT waiting_pid) AS affected_sessions9FROM edges10GROUP BY blocker_pid11ORDER BY affected_sessions DESC, blocker_pid;一个阻塞者后面排了多少会话,比只看最长一条等待更能说明影响范围。pg_blocking_pids 在并行查询时可能返回重复 PID,所以这里按等待 PID 去重;阻塞 PID 为 0 时去查 prepared transaction。
1SELECT l.pid, now() - l.waitstart AS wait_age,2 l.locktype, l.mode,3 a.datname, a.application_name,4 pg_blocking_pids(l.pid) AS blocking_pids5FROM pg_locks AS l6JOIN pg_stat_activity AS a ON a.pid = l.pid7WHERE NOT l.granted8 AND l.waitstart < now() - interval '5 seconds'9ORDER BY l.waitstart;这里用真正开始等锁的 waitstart,不用 SQL 的 query_start 冒充等待时间。刚开始等待的记录可能暂时没有 waitstart,因此不会出现在结果中;五秒是示例筛选线,不是 PostgreSQL 的故障阈值。
1SELECT l.pid, l.relation::regclass AS relation_name,2 l.mode, l.waitstart,3 a.application_name, a.query4FROM pg_locks AS l5JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.locktype = 'relation'7 AND l.mode = 'AccessExclusiveLock'8 AND NOT l.granted9 AND l.database = (SELECT oid FROM pg_database10 WHERE datname = current_database())11ORDER BY l.waitstart NULLS LAST;排队的 AccessExclusiveLock 常见于 DDL 和某些维护操作,它本身也可能挡住后续请求。先看发起 SQL、目标表和窗口安排,再用第 3 条找直接阻塞者;不要对所有等在队列后的查询逐个处置。
1SELECT b.pid, b.application_name, b.client_addr,2 b.xact_start, now() - b.xact_start AS xact_age,3 b.state_change, left(b.query, 200) AS last_query,4 count(DISTINCT w.pid) AS blocked_sessions5FROM pg_stat_activity AS w6CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS p(pid)7JOIN pg_stat_activity AS b ON b.pid = p.pid8WHERE w.wait_event_type = 'Lock'9 AND b.state LIKE 'idle in transaction%'10GROUP BY b.pid, b.application_name, b.client_addr,11 b.xact_start, b.state_change, b.query12ORDER BY blocked_sessions DESC, b.xact_start;空闲事务的 query 是最后执行过的 SQL,不是当前正在运行的 SQL。它可能持锁等着应用提交,也可能已经进入 aborted 状态;联系应用查事务边界,确认影响后再讨论取消或断开连接。
1SELECT w.pid AS waiting_pid,2 w.application_name, w.wait_event,3 left(w.query, 200) AS waiting_query4FROM pg_stat_activity AS w5WHERE w.wait_event_type = 'Lock'6 AND 0 = ANY(pg_blocking_pids(w.pid))7ORDER BY w.query_start NULLS LAST;pg_blocking_pids 返回 0 表示 prepared transaction 持有冲突锁。这类事务没有原会话 PID;下一步读第 18、19 条的 GID 和锁信息,找事务协调方确认,不能把 0 当成进程号去终止。
1SELECT COALESCE(d.datname, '*') AS database_name,2 COALESCE(r.rolname, '*') AS role_name,3 s.setconfig4FROM pg_db_role_setting AS s5LEFT JOIN pg_database AS d ON d.oid = s.setdatabase6LEFT JOIN pg_roles AS r ON r.oid = s.setrole7WHERE EXISTS (8 SELECT 1 FROM unnest(s.setconfig) AS c(setting)9 WHERE c.setting LIKE 'lock_timeout=%'10 OR c.setting LIKE 'statement_timeout=%'11 OR c.setting LIKE 'idle_in_transaction_session_timeout=%'12)13ORDER BY database_name, role_name;同一实例的连接可能因为角色/数据库默认值不同,等锁多久后报错也不同。* 表示该默认值不限定数据库或角色;查询这里只读出目录里的默认设置,某个已连接会话后来执行的 SET 还需从它自己的环境或应用配置确认。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('log_lock_waits', 'deadlock_timeout',4 'log_min_error_statement')5ORDER BY name;打开 log_lock_waits 后,等待超过 deadlock_timeout 才会记锁等待日志;它不是每次取得锁都记录。log_min_error_statement 决定超时报错的 SQL 是否随错误一起留在日志。先读实际值,再判断日志里“没看到”等待是否有意义。
1SELECT pg_current_logfile() AS current_log_path;只在启用 logging collector 且日志格式符合要求时能给出路径;结果为空并不能证明没有数据库日志,可能是日志由服务管理器或外部平台接收。函数默认需要超级用户或 pg_monitor 权限;查到路径后还要按故障时间和 PID 到对应日志找锁等待、死锁记录。
1SELECT name, setting, source2FROM pg_settings3WHERE name IN ('logging_collector', 'log_destination',4 'log_directory', 'log_filename')5ORDER BY name;第 39 条返回空值时先看这些设置。log_destination 可以有多个目标,日志也可能被容器或主机服务收集;要能按 PID 追锁链,还得确认日志前缀或结构化日志中有没有进程号。
1SELECT d.datname, l.pid, l.mode, l.granted,2 (l.classid::bigint << 32) | l.objid::bigint3 AS advisory_key4FROM pg_locks AS l5JOIN pg_database AS d ON d.oid = l.database6WHERE l.locktype = 'advisory'7 AND l.objsubid = 18ORDER BY d.datname, advisory_key, l.pid;objsubid = 1 表示应用用了单个 bigint 键;高、低 32 位分别在 classid 和 objid。锁键的业务含义要找应用约定,数据库不会替你翻译成“订单号”或“任务名”。
1SELECT d.datname, l.pid, l.mode, l.granted,2 l.classid AS key1, l.objid AS key23FROM pg_locks AS l4JOIN pg_database AS d ON d.oid = l.database5WHERE l.locktype = 'advisory'6 AND l.objsubid = 27ORDER BY d.datname, key1, key2, l.pid;双整数键和单个 bigint 键是两个互不重叠的键空间。分析阻塞时必须把 database、objsubid、classid、objid 一起对照,不能只拿一个整数去猜持锁者。
1SELECT l.pid, a.application_name, l.mode,2 l.classid, l.objid, l.objsubid,3 l.waitstart,4 clock_timestamp() - l.waitstart AS waited,5 pg_blocking_pids(l.pid) AS blocking_pids6FROM pg_locks AS l7LEFT JOIN pg_stat_activity AS a ON a.pid = l.pid8WHERE l.locktype = 'advisory'9 AND NOT l.granted10ORDER BY l.waitstart NULLS LAST;waitstart 可能在刚开始等待的一瞬间还是空值。阻塞者以 pg_blocking_pids() 为准;不要把同键上的所有已授予锁行都当作直接阻塞者,共享锁与排队顺序都可能改变链条。
1SELECT a.pid, a.datname, a.application_name,2 a.state, a.xact_start, a.state_change,3 COUNT(*) AS held_lock_rows4FROM pg_locks AS l5JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.locktype = 'advisory'7 AND l.granted8 AND a.state IN ('idle', 'idle in transaction')9GROUP BY a.pid, a.datname, a.application_name,10 a.state, a.xact_start, a.state_change11ORDER BY a.state_change;idle 会话仍持有 advisory lock 时,常要确认应用是否用了会话级锁且漏了释放。pg_locks 不直接标明会话级还是事务级,也不会显示同一进程重复获取了几次;先联系应用核对锁的生命周期。
1SELECT d.datname, l.objsubid, l.classid, l.objid,2 COUNT(*) FILTER (WHERE l.granted) AS holder_rows,3 COUNT(*) FILTER (WHERE NOT l.granted) AS waiter_rows4FROM pg_locks AS l5JOIN pg_database AS d ON d.oid = l.database6WHERE l.locktype = 'advisory'7GROUP BY d.datname, l.objsubid, l.classid, l.objid8HAVING COUNT(*) FILTER (WHERE NOT l.granted) > 09ORDER BY waiter_rows DESC, d.datname;这条找热点键,holder_rows 是当前锁行数,不是锁被同一进程获取的次数。它也不判断锁模式是否冲突;具体等待者和直接阻塞者再看第 43 条。
1SELECT d.datname,2 COUNT(*) FILTER (WHERE l.granted) AS granted_rows,3 COUNT(*) FILTER (WHERE NOT l.granted) AS waiting_rows4FROM pg_locks AS l5JOIN pg_database AS d ON d.oid = l.database6WHERE l.locktype = 'advisory'7GROUP BY d.datname8ORDER BY waiting_rows DESC, granted_rows DESC;pg_locks 是集群视图,advisory lock 却按数据库区分。同一数字键在两个数据库中不是同一把锁;分析应用跨库任务时,先确认连接落在哪个库。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('max_locks_per_transaction',4 'max_connections',5 'max_prepared_transactions')6ORDER BY name;advisory lock 也占锁管理器共享内存。持有键值过多时要和连接数、预备事务一起评估容量;max_locks_per_transaction 不是“每个事务最多只能拿这么多锁”的硬上限,不要按字面解释。
1SELECT p.gid, p.prepared, p.owner, p.database,2 l.mode, l.classid, l.objid, l.objsubid3FROM pg_locks AS l4JOIN pg_prepared_xacts AS p5 ON l.virtualtransaction = '-1/' || p.transaction6WHERE l.locktype = 'advisory'7 AND l.granted8ORDER BY p.prepared, p.gid;预备事务没有普通会话 PID,却仍可能持有事务级 advisory lock。出现结果时先找两阶段事务协调方和对应 gid;直接提交或回滚预备事务会改变业务事务结果,不能为了清锁擅自操作。
1SELECT a.pid, a.datname, a.state,2 a.wait_event_type, a.wait_event,3 w.description4FROM pg_stat_activity AS a5LEFT JOIN pg_wait_events AS w6 ON w.type = a.wait_event_type7 AND w.name = a.wait_event8WHERE a.state = 'active'9 AND a.wait_event IS NOT NULL10ORDER BY a.pid;Lock、BufferPin、IO 和 LWLock 不是同一类问题。先读说明,再决定是否沿第 3—5 条找阻塞链;state = active 且有等待事件,只说明 SQL 正在执行但卡在某个等待点。
1SELECT a.pid, a.datname, a.application_name,2 a.wait_event, a.query,3 pg_blocking_pids(a.pid) AS blocking_pids4FROM pg_stat_activity AS a5WHERE a.wait_event_type = 'Lock'6 AND a.wait_event = 'spectoken';spectoken 常要结合并发插入、唯一索引和 INSERT ... ON CONFLICT 的业务 SQL 看。它与普通关系锁等待不同;仍应以直接阻塞 PID、事务状态和日志为证据,不能看到事件名就宣布唯一键冲突。
1SELECT l.pid, l.database, l.relation,2 l.mode, l.waitstart,3 pg_blocking_pids(l.pid) AS blocking_pids4FROM pg_locks AS l5WHERE l.locktype = 'extend'6 AND NOT l.granted7ORDER BY l.waitstart NULLS LAST;这类锁保护关系扩展,不等同于业务 DDL 的 AccessExclusiveLock。热点写入表持续扩展时,可对照该表的写入、文件增长和等待分布;关系 OID 只有在当前数据库中才能可靠地关联 pg_class。
1SELECT pid, datname, application_name,2 state, wait_event, query_start, query3FROM pg_stat_activity4WHERE wait_event_type = 'BufferPin'5ORDER BY query_start NULLS LAST;Buffer pin 等待不一定在 pg_locks 留下一条待授予关系锁。官方文档提到开放游标可能延长 buffer pin;先看会话、游标和执行路径,不能靠杀一个“关系锁持有者”解决。
1SELECT locktype, COUNT(*) AS predicate_lock_rows,2 COUNT(DISTINCT pid) AS backend_count3FROM pg_locks4WHERE mode = 'SIReadLock'5GROUP BY locktype6ORDER BY predicate_lock_rows DESC;SIReadLock 是 SERIALIZABLE 用来跟踪读写依赖的谓词锁,本身不会阻塞其他事务,也不会造成死锁。行数多时看访问计划和事务范围;不能把它们算进第 21 条的“未获锁请求”。
1-- 只在当前数据库解释 relation OID2SELECT l.pid, l.relation::regclass AS relation_name,3 a.application_name, a.state, a.query_start4FROM pg_locks AS l5LEFT JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.mode = 'SIReadLock'7 AND l.locktype = 'relation'8 AND l.database = (SELECT oid FROM pg_database9 WHERE datname = current_database())10ORDER BY l.relation, l.pid;顺序扫描或谓词锁升级可能让锁落到关系级,带来更多 SERIALIZABLE 冲突概率;这里只展示当前锁,不直接证明某次 40001 错误就是这张表造成的。还要核对 SQL 和计划。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('max_pred_locks_per_transaction',4 'max_pred_locks_per_relation',5 'max_pred_locks_per_page')6ORDER BY name;谓词锁过于粗粒度时,先查是否因内存容量或查询计划而升级。参数调整需要按并发事务和共享内存预算评估,不能把“看见 SIReadLock”直接转成加大参数的建议。
1SELECT a.pid, a.backend_type, a.datname,2 a.wait_event, a.query,3 pg_blocking_pids(a.pid) AS blocking_pids4FROM pg_stat_activity AS a5WHERE a.wait_event_type = 'Lock'6 AND a.wait_event = 'applytransaction';applytransaction 是并行逻辑复制应用事务的同步等待。若出现该项,先结合订阅端应用 worker、事务顺序和复制延迟查;它不是普通业务 UPDATE 行锁。空结果也不能证明订阅应用没有其他瓶颈。
1SELECT pid, datname, command, phase,2 lockers_total, lockers_done, current_locker_pid,3 blocks_total, blocks_done4FROM pg_stat_progress_create_index5WHERE command IN ('CREATE INDEX CONCURRENTLY',6 'REINDEX CONCURRENTLY')7ORDER BY pid;并发建索引要等旧事务退出几个关键阶段。phase 显示 waiting for writers 或 waiting for old snapshots 时,先查 current_locker_pid;blocks_done 只适合有扫描进度的阶段,不能拿它估算等锁还要多久。
1SELECT p.pid AS index_pid, p.command, p.phase,2 p.current_locker_pid,3 a.state AS locker_state, a.xact_start,4 a.application_name, a.query5FROM pg_stat_progress_create_index AS p6LEFT JOIN pg_stat_activity AS a7 ON a.pid = p.current_locker_pid8WHERE p.current_locker_pid IS NOT NULL9ORDER BY p.pid;current_locker_pid 是当前阶段正在等待的进程,不一定是普通关系锁意义上的直接阻塞者。若是 idle in transaction,找应用确认事务为什么未提交;取消建索引会留下待检查的无效索引,不能只看命令取消成功。
1SELECT p.pid, p.command, p.phase,2 t.oid::regclass AS table_name,3 CASE WHEN p.index_relid = 0 THEN NULL4 ELSE p.index_relid::regclass END AS index_name5FROM pg_stat_progress_create_index AS p6JOIN pg_class AS t ON t.oid = p.relid7WHERE p.datname = current_database()8ORDER BY p.pid;在当前数据库里把 relid 对上业务表,避免只拿 PID 给应用沟通。普通 CREATE INDEX 建立过程中 index_relid 可能为 0;这里保留 NULL,不伪造索引名。
1SELECT i.indrelid::regclass AS table_name,2 i.indexrelid::regclass AS index_name,3 i.indisvalid, i.indisready, i.indislive4FROM pg_index AS i5WHERE NOT i.indisvalid6ORDER BY i.indrelid, i.indexrelid;并发建索引失败或被取消后可能留下无效索引。indisvalid = false 说明规划器不能把它当作有效索引;是否仍参与更新还要看 indisready。先查失败原因、唯一索引冲突及业务写入,再安排重建或清理。
1SELECT datname, confl_lock, confl_snapshot,2 confl_bufferpin, confl_deadlock, confl_tablespace3FROM pg_stat_database_conflicts4ORDER BY datname;这是备库上因 WAL 回放冲突而取消查询的累计计数,confl_lock 不能当成主库普通锁等待次数。它也不显示是哪条 SQL 被取消;结合备库日志、查询时长和复制延迟,看计数是否持续增加。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('max_standby_streaming_delay',4 'max_standby_archive_delay',5 'hot_standby_feedback')6ORDER BY name;前两个值控制备库回放等待与取消查询的边界,不是单条 SQL 的执行超时。hot_standby_feedback 能减轻部分快照冲突,但可能让主库垃圾元组积累;调参前要同时看备库查询和主库清理压力。
1SELECT pg_is_in_recovery() AS is_standby,2 pg_last_wal_receive_lsn() AS received_lsn,3 pg_last_wal_replay_lsn() AS replayed_lsn,4 pg_last_xact_replay_timestamp() AS last_replay_time;先确认查询跑在备库上,再比较接收和回放位置。last_replay_time 是最近已回放事务的提交时间;主库若暂时没有新事务,它会变旧,不能单凭时间差判定复制故障。
1SELECT pid, backend_type, state,2 wait_event_type, wait_event,3 pg_blocking_pids(pid) AS blocking_pids,4 query5FROM pg_stat_activity6WHERE pg_is_in_recovery()7 AND wait_event_type = 'Lock'8ORDER BY pid;只在备库看当前锁等待,结合第 61 条的回放冲突计数和日志判断影响。pg_stat_activity 是即时快照,取消过的查询不会一直留在这里;空结果不代表备库没有发生过冲突。
1-- 把 12345 换成待处置 PID2SELECT pid, usename, datname, application_name,3 client_addr, state, xact_start, query_start,4 wait_event_type, wait_event, query5FROM pg_stat_activity6WHERE pid = 123457 AND pid <> pg_backend_pid();先核对用户、应用、客户端、事务开始时间和 SQL;不要凭几分钟前的阻塞截图操作 PID。目标已退出时 PID 可能被新连接复用。特别是 idle in transaction,query 是上次执行的语句,不代表它还在跑。
1-- 确认 PID 后执行;只取消当前语句2SELECT pg_cancel_backend(12345);取消的是当前查询,不是整个连接。目标若已空闲且仍处在事务中,发出取消信号不等于释放已持有的锁;操作后必须再查事务状态和阻塞链。普通角色只能取消有权限处理的 backend,超级用户会话需超级用户权限。
1SELECT a.pid, a.state, a.xact_start,2 COUNT(l.pid) FILTER (WHERE l.granted) AS granted_locks,3 COUNT(l.pid) FILTER (WHERE NOT l.granted) AS waiting_locks4FROM pg_stat_activity AS a5LEFT JOIN pg_locks AS l ON l.pid = a.pid6WHERE a.pid = 123457GROUP BY a.pid, a.state, a.xact_start;如果连接还在、xact_start 未清且仍持锁,要找应用结束事务。取消语句后事务可能处于失败状态,业务需要 ROLLBACK;只看 pg_cancel_backend 返回 true 不足以证明锁已解除。
1-- 10000 是等待退出的毫秒数;操作前确认业务事务可回滚2SELECT pg_terminate_backend(12345, 10000);这会断开连接,未提交事务随之回滚,应用也会收到连接错误。PostgreSQL 17 提供第二个等待参数:返回 true 表示超时内进程已退出;不带超时参数的 true 只表示信号已发送,不能当成连接已断开。
1SELECT w.pid AS waiting_pid,2 w.wait_event, w.query,3 pg_blocking_pids(w.pid) AS blocking_pids4FROM pg_stat_activity AS w5WHERE w.wait_event_type = 'Lock'6ORDER BY w.pid;回读所有当前等待者,而不只看原来的一个 PID。若仍有 blocking_pids,可能还有其他持锁事务或队列前面的请求;核对业务恢复情况,再决定是否继续处置。
1BEGIN;2SET LOCAL lock_timeout = '5s';3-- 在同一事务内执行本次维护 SQL4COMMIT;SET LOCAL 只对当前事务有效。维护语句若超过五秒还没获得锁,会报错;错误后事务须 ROLLBACK,上面的 COMMIT 是成功路径示意。它只限制等锁时间,不限制已经取得锁后的执行时间。
1SELECT current_setting('lock_timeout') AS lock_timeout,2 current_setting('statement_timeout') AS statement_timeout,3 current_setting('idle_in_transaction_session_timeout')4 AS idle_transaction_timeout;第 10、37 条查的是参数来源或角色默认值,这里看当前连接的生效值。statement_timeout 若比 lock_timeout 更短,语句超时可能先触发;应用连接参数也可能覆盖角色默认值。
1SELECT name, setting, unit, source2FROM pg_settings3WHERE name IN ('log_recovery_conflict_waits',4 'deadlock_timeout')5ORDER BY name;备库回放等待超过 deadlock_timeout 是否写日志,取决于 log_recovery_conflict_waits。先确认开关,再谈“日志里没有冲突”;该设置针对 WAL 回放冲突,不等于第 38 条的普通锁等待日志。
1-- job_queue(id, status) 为示例业务表2SELECT id, status3FROM job_queue4WHERE id = 10015FOR UPDATE NOWAIT;别人正锁着这行时会立即报错,适合调用方能明确重试或返回“处理中”的操作。它仍需取得表级 ROW SHARE 锁;遇到强 DDL 表锁仍可能等待,不能把 NOWAIT 当作整个语句的零等待保证。
1SELECT id, status2FROM job_queue3WHERE status = 'pending'4ORDER BY id5LIMIT 106FOR UPDATE SKIP LOCKED;查询与领取任务必须在同一个事务内完成,提交前要更新状态。SKIP LOCKED 会略过其他 worker 正锁着的行,返回的是不一致视图,不适合财务对账或精确清单;长期被锁的任务还需有单独的超时和补偿机制。
1BEGIN;2LOCK TABLE job_queue IN SHARE ROW EXCLUSIVE MODE NOWAIT;3-- 成功后在同一事务中执行维护语句;失败则 ROLLBACK4ROLLBACK;示例只做锁试探,所以最后回滚。NOWAIT 失败说明此刻有冲突,但试探成功后若释放锁,再执行维护仍可能遇到新事务;正式维护须在持锁的同一事务内完成,并评估该锁模式对业务写入的影响。
1-- job_queue(id, status, created_at) 为示例业务表2WITH picked AS (3 SELECT id4 FROM job_queue5 WHERE status = 'pending'6 ORDER BY created_at, id7 LIMIT 1008 FOR UPDATE SKIP LOCKED9)10UPDATE job_queue AS j11SET status = 'processing'12FROM picked AS p13WHERE j.id = p.id14RETURNING j.id;这一条把领取和改状态放在同一语句,减少先查后改的窗口。RETURNING 是本次实际领取的任务;空结果不代表全部处理完,可能只是候选行都被锁着。若用于有限批量的数据修复,最后还要不带 SKIP LOCKED 核对漏项。
1-- 业务预先约定资源键 1001 的含义2SELECT pg_try_advisory_xact_lock(1001::bigint) AS acquired;拿不到就返回 false,不会排队。事务级锁在提交或回滚后自动释放,适合一个事务内完成的互斥业务;若在自动提交连接里单独运行此 SELECT,语句结束锁就释放,不能据此继续执行受保护的工作。
1SELECT pg_try_advisory_lock(1001::bigint) AS acquired;返回 true 后由同一连接完成业务并调用 pg_advisory_unlock(1001::bigint)。会话级锁不会因事务回滚自动释放;连接池用它时要保证归还连接前解锁,且同一连接重复取得的锁要对应重复解锁。
1-- 只在第 78 条 acquired = true 且当前连接确实持锁时执行2SELECT pg_advisory_unlock(1001::bigint) AS released;true 才表示这次解锁成功。不能在另一条连接上替原连接解锁;如果同一键被本连接多次取得,一次解锁后仍可能持有。排查连接池遗留锁时结合第 41—48 条看持有 PID 和键值。
1SELECT pg_try_advisory_xact_lock_shared(1001::bigint)2 AS acquired_shared;多个共享持有者之间不冲突,但独占申请会与它们冲突。只适合业务已经约定同一资源键、读写双方都遵守 advisory lock 协议的场景;数据库不会替业务自动给表行加锁。
1SELECT id, status2FROM job_queue3WHERE id = 10014FOR NO KEY UPDATE NOWAIT;不准备改唯一键、主键等键值时,可以考虑 FOR NO KEY UPDATE;它仍会阻挡同一行的普通更新和删除,但不会挡 FOR KEY SHARE。先核对业务后续 SQL,别为了减少等待而把需要保护键值的操作降级。
1SELECT id2FROM job_queue3WHERE id = 10014FOR KEY SHARE NOWAIT;FOR KEY SHARE 会阻止删除该行以及修改相关键值,但允许不改键的更新。它适合检查引用对象的存在性;不适合用来保证 status 等非键字段在后续事务里完全不变。NOWAIT 冲突仍需由应用处理重试。
1SELECT pid, datname, relid, phase,2 heap_blks_total, heap_blks_scanned,3 index_vacuum_count, indexes_total, indexes_processed4FROM pg_stat_progress_vacuum5ORDER BY pid;普通 VACUUM 和 autovacuum 都在这里,VACUUM FULL 不在此视图。扫描页数只在 scanning heap 阶段前进;卡在 vacuuming indexes 时不能用 heap_blks_scanned 不变直接判断死锁。
1SELECT v.pid, v.phase, v.relid::regclass AS table_name,2 a.backend_type, a.wait_event_type, a.wait_event,3 pg_blocking_pids(v.pid) AS blocking_pids4FROM pg_stat_progress_vacuum AS v5JOIN pg_stat_activity AS a ON a.pid = v.pid6WHERE v.datname = current_database()7ORDER BY v.pid;仅在当前数据库转换 relid。若 blocking_pids 有值,再看阻塞者的事务和锁对象;若没有而 wait_event_type 为 IO 或 LWLock,别把进度停滞都解释成业务表锁。
1SELECT pid, datname, relid, command, phase,2 heap_blks_total, heap_blks_scanned,3 index_rebuild_count4FROM pg_stat_progress_cluster5WHERE command IN ('VACUUM FULL', 'CLUSTER')6ORDER BY pid;这两种操作会重写表,锁影响与普通 VACUUM 不同。先看阶段,再查目标表的关系锁和业务等待;不要因为第 83 条无结果,就认为现场没有 VACUUM FULL。
1SELECT l.pid, a.backend_type, a.query,2 l.locktype, l.mode, l.relation,3 l.waitstart, pg_blocking_pids(l.pid) AS blocking_pids4FROM pg_locks AS l5JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE NOT l.granted7 AND (a.backend_type = 'autovacuum worker'8 OR a.query ~* '^(vacuum|cluster|create index|reindex)')9ORDER BY l.waitstart NULLS LAST;这是正在排队的维护进程快照,不是所有维护进程清单。SQL 文本可能被截断、脱敏或包含注释,因此正则可能漏项;autovacuum worker 由 backend_type 识别,阻塞关系仍以 pg_blocking_pids 为准。
1-- 把 public.job_queue 换成目标表2SELECT l.pid, a.backend_type, a.state,3 l.mode, l.granted, l.waitstart4FROM pg_locks AS l5LEFT JOIN pg_stat_activity AS a ON a.pid = l.pid6WHERE l.locktype = 'relation'7 AND l.database = (SELECT oid FROM pg_database8 WHERE datname = current_database())9 AND l.relation = 'public.job_queue'::regclass10ORDER BY l.granted, l.waitstart NULLS LAST, l.pid;把 ShareUpdateExclusiveLock、ShareLock、RowExclusiveLock 等模式放在同一个对象上比较。模式名带 Row 也仍是表级锁;是否冲突要按官方兼容矩阵判断,不能只凭名字推断。
1SELECT a.pid, a.datname, a.query_start,2 a.wait_event, a.query,3 pg_blocking_pids(a.pid) AS blocking_pids4FROM pg_stat_activity AS a5WHERE a.backend_type = 'autovacuum worker'6 AND a.wait_event_type = 'Lock'7ORDER BY a.query_start;自动清理被其他操作挡住时,先找锁对象和阻塞事务,评估表膨胀或 XID 冻结风险。空结果只表示此刻没有等锁的 worker;autovacuum 也可能因 I/O、限速或其他阶段进度慢。
1SELECT w.pid AS waiting_pid, w.query_id AS waiting_query_id,2 b.pid AS blocking_pid, b.query_id AS blocking_query_id,3 w.query AS waiting_query, b.query AS blocking_last_query4FROM pg_stat_activity AS w5CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS p(pid)6LEFT JOIN pg_stat_activity AS b ON b.pid = p.pid7WHERE w.wait_event_type = 'Lock'8ORDER BY w.pid, b.pid;留存 PID、query_id 和 SQL,便于和日志及统计视图对照。阻塞者若已空闲,blocking_last_query 是最后一条 SQL,不一定是拿锁那条;query_id 也可能为 NULL。blocking_pid = 0 表示预备事务,左连接不到普通会话。
1SELECT name, setting, source2FROM pg_settings3WHERE name IN ('compute_query_id',4 'shared_preload_libraries');compute_query_id = auto 并不保证每条会话都有 query_id,还要看是否有模块启用计算。pg_stat_statements 需预加载并在目标数据库安装扩展;只看 shared_preload_libraries 不能证明视图已经可查询。
1SELECT extname, extversion, extnamespace::regnamespace AS schema_name2FROM pg_extension3WHERE extname = 'pg_stat_statements';没安装就别直接运行下一条。安装扩展只创建当前数据库可用的对象,模块还须在实例启动时预加载;补装和重启是变更操作,先评估窗口与权限。
1-- 仅在当前库已安装并启用 pg_stat_statements 时执行2SELECT dbid, userid, queryid, calls,3 total_exec_time, mean_exec_time, max_exec_time,4 query5FROM pg_stat_statements6WHERE queryid = 123456789::bigint7 AND dbid = (SELECT oid FROM pg_database8 WHERE datname = current_database())9ORDER BY total_exec_time DESC;把第 89 条的 ID 换进来,结合数据库和执行角色辨认。这里是 SQL 总执行时间,不是累计等锁时间;一条长期未结束的 SQL 也未必马上反映在累计统计中。不同大版本或对象重建后不应把 queryid 当永久稳定键。
1SELECT name, setting, source2FROM pg_settings3WHERE name = 'log_line_prefix';文本日志里有 %p(PID)或 %c(会话标识)更方便串起等待与报错;%Q 需能计算 query ID。若用 CSV/JSON 日志,按对应结构化字段检索,别套用文本前缀的规则。
1SELECT pid, backend_start,2 to_hex(trunc(EXTRACT(EPOCH FROM backend_start))::integer)3 || '.' || to_hex(pid) AS log_session_id4FROM pg_stat_activity5WHERE pid = 12345;这是官方文档给 %c 的对应算法。PID 可能被复用,带上 backend_start 或 %c 才更容易区分前后两个连接;事后会话已退出时,要从日志自身找这个标识。
1SELECT name, setting, source2FROM pg_settings3WHERE name IN ('log_min_error_statement',4 'log_min_messages');发生 lock_timeout、死锁或 NOWAIT 错误时,是否能从服务端日志看到语句,取决于这些阈值与日志出口。应用参数可能含敏感数据,调高日志量前先核对脱敏和存储策略;没日志不等于没报错。
1-- app_user、appdb 替换为现场角色和数据库2ALTER ROLE app_user IN DATABASE appdb3 SET lock_timeout = '5s';这是新连接的默认值,已有连接不自动更新;应用显式 SET 或连接参数还能覆盖它。先和业务约定超时报错、重试次数及事务回滚方式,别把五秒当作所有 SQL 的总耗时上限。
1ALTER ROLE app_user IN DATABASE appdb2 SET idle_in_transaction_session_timeout = '2min';连接在事务内空闲超过该时间会被断开,未提交工作回滚。它能防止长时间占锁的空闲事务,但也可能误伤需要人为交互的后台作业;上线前先查第 7、35 条中的应用名和事务时长。
1SELECT r.rolname, d.datname, s.setconfig2FROM pg_db_role_setting AS s3JOIN pg_roles AS r ON r.oid = s.setrole4JOIN pg_database AS d ON d.oid = s.setdatabase5WHERE r.rolname = 'app_user'6 AND d.datname = 'appdb';核对第 96、97 条是否写到了指定角色与指定数据库,而不是全库或全角色。这里只证明默认值已存到目录;要确认生效,还须用该角色新建连接并查第 71 条的当前值。
1ALTER ROLE app_user IN DATABASE appdb2 RESET lock_timeout;恢复时只撤掉这一条角色与数据库绑定设置,保留其他参数。新连接随后继承更低优先级的角色、数据库或实例默认值;不要用 RESET ALL 把现场其他配置一起清掉。
1ALTER ROLE app_user IN DATABASE appdb2 RESET idle_in_transaction_session_timeout;业务已修好事务收尾逻辑或阈值需要重新评估时,按原作用域撤销。已有连接的生效值不会因为角色默认值被重置而立即改变;回读目录设置,并用新连接验证。
现场查锁,先保存等待者、阻塞者和事务开始时间,再对上锁对象与业务连接。行锁、表锁、备库回放冲突和 BufferPin 不是一回事;动连接前先确认 PID 还属于原会话。处理完回头查阻塞链,下一次再从应用事务收尾和超时设置上减少积压。
ORA100 DBA100 命令系列海报
其他数据库运维专题收在 ORA100 · DBA100:https://ora100.com/dba100
微信小程序搜索 「三笠的百令册」,也能查这套命令。