尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PostgreSQL锁竞争排查:pg_blocking_pids定位阻塞者实战

PostgreSQL锁竞争排查:pg_blocking_pids定位阻塞者实战 1. 锁竞争排查的核心思路1.1 数据库“卡住”了从哪下手做 PostgreSQL 运维或者开发的同学肯定都遇到过这种情况一条简单的 UPDATE 或者 SELECT 突然就跑不动了应用侧一直转圈监控面板上的活跃会话数直线上升业务反馈“数据库响应变慢”。这时候去数据库里查pg_stat_activity能看到一大堆 state 为active的会话但对应的 SQL 其实什么都没干就这么干等着。这种“看似活跃、实则等待”的会话绝大多数情况下都是被锁阻塞了。PostgreSQL 的锁机制不像某些数据库那样用超时或排队策略自动处理它采用的是“阻塞即等待”的模型一个事务拿到了某行或某表的锁另一个事务想拿同一把锁就只能乖乖排在后面等待持有锁的事务提交或回滚。如果持有锁的事务一直不结束后面等待的事务就会无限期挂起应用侧的连接池很快被占满整个系统就这样被拖垮。问题来了怎么定位到底是谁在阻塞这条被卡住的 SQL 是哪个会话持有的锁导致的在没有专门工具的前提下很多人的第一反应是查pg_locks把这表里所有锁的持有关系和等待关系一条条列出来再手工对照pg_stat_activity去拼 PID、拼事务 ID。说实话这套做法我现在还在用但它非常繁琐尤其当系统里有几十个会话在互相牵扯的时候光靠人眼看pg_locks去判断“谁等谁”简直是一场灾难。PostgreSQL 9.6 引入的pg_blocking_pids()函数就是为了解决这个痛点。它干的事很简单传入一个正在等待的进程 PID它直接把阻塞这个 PID 的所有后端进程 PID 以数组形式返回。比如你查到某条 SQL 的 PID 是 12345执行SELECT pg_blocking_pids(12345);得到{9876}就意味着 PID 9876 这个会话正在阻塞它。一次函数调用阻塞关系一目了然。1.2 这个函数能解决什么问题pg_blocking_pids()的核心价值是把“需要多表关联加人工分析的锁等待定位”压缩成了一个函数调用。它解决的典型问题包括业务侧反馈某个功能响应慢需要快速定位是不是数据库锁竞争导致的监控系统检测到活跃会话堆积需要批量输出每个被阻塞会话对应的“元凶”PID自动化运维脚本需要判断某个会话是否可以安全 kill避免误杀排查死锁或长时间锁等待时需要在多个会话之间理清阻塞链条。这个函数在 PostgreSQL 9.6 及以上版本都可用目前主流的 12、13、14、15、16 版本完全覆盖。如果你的生产环境还在用 9.5 或更早的版本那只能老老实实回去查pg_locks了——但我建议你尽快升级不光是这个函数的问题老版本的性能和安全性都差太远了。1.3 和传统 pg_locks 排查方式对比我最早排查锁等待用的是纯pg_locks方案大致思路是SELECT blocked.pid AS blocked_pid, blocker.pid AS blocker_pid FROM pg_locks blocked JOIN pg_locks blocker ON blocker.locktype blocked.locktype AND blocker.database IS NOT DISTINCT FROM blocked.database AND blocker.relation IS NOT DISTINCT FROM blocked.relation AND blocker.pid ! blocked.pid WHERE NOT blocked.granted;这段 SQL 核心逻辑是找到所有未被授予的锁请求也就是等待锁的会话然后去pg_locks里找同一把锁上已经持有granted的会话。能跑但有两个硬伤一是自连接条件很容易漏。pg_locks里不同 locktype 的匹配字段完全不同行锁要匹配tuple和classid页锁要匹配page事务锁要匹配transactionid想把所有情况都覆盖到SQL 会变得非常长且脆弱。二是它只能找“直接阻塞者”。A 阻塞了 BB 阻塞了 C你用这个 SQL 能看到 C 被 B 阻塞但看不出来根因是 A你得继续对 B 做一次同样的查询才能往上追溯。pg_blocking_pids()把这两层问题都简化了——它内部会综合检查所有 locktype 的持有与等待关系你不需要知道锁的底层细节只需要知道“这个 PID 在等谁”。虽然它返回的仍然只是“直接阻塞者”但结合递归查询可以很轻松地把整条阻塞链挖出来。后面的章节我会给出具体的递归查询写法。2. 核心细节解析与实操要点2.1 函数签名与返回值的正确理解pg_blocking_pids()的官方签名是pg_blocking_pids(pid integer) RETURNS integer[]几个容易踩坑的点我逐个说。返回值是integer[]数组不是单个整数。这意味着一瞬间可能有多个会话同时阻塞目标进程所以返回的是一个 PID 列表。在 psql 里直接执行你会看到{123,456}这样的输出。如果返回{}说明这个 PID 当前没有被任何其他进程阻塞——注意没被锁阻塞 ≠ SQL 执行正常它可能是正常在执行也可能是在等待 IO、等待客户端输入等。入参是pg_stat_activity.pid不是内部事务 ID。有的同学会把这个函数跟pg_backend_pid()搞混。pg_backend_pid()是返回“当前会话自己的”PID用于在触发器或函数里标识自己而pg_blocking_pids()是查询“别的人”谁在阻塞某个指定的后端进程。两者一个是查自己、一个是查别人别用反了。一个常见误区是阻塞者和被阻塞者是相对的概念。pg_blocking_pids(123)返回{456}描述的是“123 这个会话在等待 456”如果 456 同时也在等待别人比如等待 789那么 456 既是阻塞者也是被阻塞者。锁竞争场景中经常出现这种链式等待甚至环形等待死锁。处理的时候要找链条最顶端的那个“根阻塞者”而不是看到谁就杀谁。2.2 配合 pg_stat_activity 联合查询的标准姿势pg_blocking_pids()单独用意义不大它只告诉你 PID 之间的关系但 PID 背后是谁、在跑什么 SQL、持锁多久了这些信息全在pg_stat_activity里。两者必须配合使用。我最常用的一个诊断查询长这样SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocked.application_name AS blocked_app, blocked.client_addr AS blocked_client, blocked.state AS blocked_state, array_agg(DISTINCT blocker.pid) AS blocker_pids, array_agg(DISTINCT blocker.query) AS blocker_queries FROM pg_stat_activity blocked LEFT JOIN LATERAL ( SELECT pid, query FROM pg_stat_activity WHERE pid ANY (pg_blocking_pids(blocked.pid)) ) blocker ON true WHERE blocked.state active GROUP BY blocked.pid, blocked.query, blocked.application_name, blocked.client_addr, blocked.state HAVING array_agg(DISTINCT blocker.pid) IS NOT NULL;走一遍你就会发现能直接列出所有“当前正在被阻塞的会话”以及对应的“阻塞者PID”和“阻塞者正在执行的SQL”。这个查询跑出来的结果里通常能一眼看到问题所在要么是某个应用连接长时间idle in transaction持锁不释放要么是一条慢 SQL 拖住了事务没提交。补充一个实用技巧如果现场会话非常多输出结果会很乱我一般加上ORDER BY blocked_pid或者把blocker_queries截断显示left(blocker.query, 80) AS blocker_query_preview这样结果集更紧凑适合快速扫一眼判断现状。2.3 如何基于返回结果判断锁类型与等待链pg_blocking_pids()本身不告诉你“等的是什么锁”但你可以结合被阻塞会话在pg_stat_activity里的wait_event_type和wait_event来推断。这两个字段是 PostgreSQL 9.6 开始提供的10 之后已经非常稳定了。当wait_event_type Lock时wait_event的值会告诉你具体的锁类型wait_event含义典型场景relation正在等待表级锁有人持有 ACCESS EXCLUSIVE 锁比如 ALTER TABLE你的 DML 被阻塞extend正在等待扩展表文件锁并发 INSERT 到同一张表时block size 扩展阶段偶发冲突transactionid正在等待行级锁两个事务更新同一行后者在等前者提交或回滚tuple正在等待行版本可见性确认与 UPDATE/DELETE 的行版本链相关virtualxid正在等待事务自身的虚拟事务 ID 锁通常与并发 INSERT 少量冲突有关举个实际例子pg_blocking_pids(100)返回{200}然后查pg_stat_activity发现 PID 200 的state idle in transactionwait_event_type Client说明 PID 200 这个会话已经开启了事务但没提交手头还握着一把事务锁PID 100 在等 transactionid 锁。这时候的处理方式就非常明确了——要么联系业务方让 PID 200 提交或回滚要么在确认无风险的情况下pg_terminate_backend(200)。判断阻塞链时我建议不要直接杀最底层的阻塞者先往上追。举个例子SELECT pid, pg_blocking_pids(pid), query FROM pg_stat_activity WHERE pid IN (100, 200, 300);假设结果是pid | pg_blocking_pids | query ------------------------------ 100 | {200} | UPDATE ... 200 | {300} | UPDATE ... 300 | {} | SELECT ...那么链条是 300 → 200 → 100根节点是 300。如果要干预先确认 300 为什么一直持锁而不是直接处理 100——因为你把 100 杀了200 可能还在等 300问题并没有彻底解决。3. 实操过程与核心环节实现3.1 复现一个典型锁阻塞场景为了把整个过程讲清楚我建议你在本地测试环境自己复现一次。方法很简单开两个 psql 会话模拟两个事务争抢同一行。会话 ABEGIN; UPDATE t_user SET status 1 WHERE id 100;不要提交保持事务打开。此时会话 A 已经拿到了 id 100 这行上的行级锁。会话 BBEGIN; UPDATE t_user SET status 2 WHERE id 100;这条语句会一直卡住因为它在等待会话 A 释放锁。现在查第三个会话SELECT pid, state, query, wait_event_type, wait_event FROM pg_stat_activity WHERE query LIKE UPDATE t_user%;你会看到 PID B 那行wait_event_type Lock、wait_event transactionid这正是行锁等待的标志。接下来用pg_blocking_pids()验证SELECT pid, pg_blocking_pids(pid) FROM pg_stat_activity WHERE pid 会话B的PID;返回结果应该是{会话A的PID}精准命中。3.2 完整排查链路从发现到解决处理生产隐患时我的标准流程如下每一步都写出来给你当模板。第一步列出所有被阻塞的会话。用 2.2 节那个联合查询输出所有处于等待状态的会话及其阻塞者。第二步找根阻塞者。对被阻塞 PID 逐个执行pg_blocking_pids()筛选出“没有被任何人阻塞但同时阻塞了别人”的会话。这些就是根阻塞者。可以直接用一条 SQL 找出来SELECT pid, query, state, age(now() - xact_start) AS xact_age FROM pg_stat_activity WHERE pid IN ( SELECT unnest(pg_blocking_pids(pid)) FROM pg_stat_activity ) AND pid NOT IN ( SELECT pid FROM pg_stat_activity WHERE pg_blocking_pids(pid) {} );原理是嵌套查询里面先对所有 PID 调用pg_blocking_pids()把返回的所有阻塞者 PID 收集起来外层再筛选那些本身没被任何人阻塞的 PID这些就是根节点。第三步分析根阻塞者在干什么。重点看三个字段state如果idle in transaction说明事务开了没提交xact_start事务开启时间和当前时间比对能算出持锁时长query最近执行的 SQL能看出它做了啥。第四步决策。如果根阻塞者是一个正常执行但耗时很长的任务可以等如果它是一个遗留的僵尸事务建议直接终止。第五步终止会话收尾。用pg_terminate_backend()SELECT pg_terminate_backend(根阻塞者PID);这个函数对普通用户只能终止自己的会话但超级用户或具备pg_signal_backend权限的角色可以终止任何会话。生产环境我一般用监控账号操作权限最小化原则。关于权限多说一句pg_terminate_backend()只能终止“后端进程”对后台 worker 和辅助进程是无效的。如果你发现某个会话怎么杀都杀不掉先看看它是不是state idle正在等待客户端连接或者查一下它是不是并行 worker。并行 worker 通常在并行查询时出现parent 进程终止后它们会自动退出。3.3 递归查询实现阻塞链一站式输出前面反复提到“向上追根”手工一次一次执行pg_blocking_pids()效率太低。我写了一个递归 CTE用来一次性输出完整的阻塞链WITH RECURSIVE blocking_chain AS ( -- 锚点找出所有被阻塞的叶子节点即最下游等待者 SELECT s.pid, { || s.pid::text || }::int[] AS chain, pg_blocking_pids(s.pid) AS blockers, s.query FROM pg_stat_activity s WHERE pg_blocking_pids(s.pid) {} UNION ALL -- 递归把阻塞者追加到链条中 SELECT b.pid, bc.chain || b.pid, pg_blocking_pids(b.pid), b.query FROM blocking_chain bc JOIN LATERAL unnest(bc.blockers) AS blocker_pid ON true JOIN pg_stat_activity b ON b.pid blocker_pid ) SELECT DISTINCT chain AS blocking_chain, query FROM blocking_chain;这段 SQL 的输出每一行就是一条完整的等待链从最下游的等待者一路追溯到最上游的持锁者。chain 列里数组的最后一位就是根阻塞者。实际使用的时候注意两点一是递归 CTE 可能会因为重复的 pid 组合导致结果膨胀所以外层加了DISTINCT二是如果环存在死锁递归会一直循环需要加上深度限制或者用CYCLE子句。PostgreSQL 14 及以上版本支持直接在 CTE 后加CYCLE pid SET is_cycle USING path来检测环如果你用的是 14 以下版本建议加一个深度计数器来控制递归层数。WITH RECURSIVE blocking_chain AS ( SELECT s.pid, { || s.pid::text || }::int[] AS chain, pg_blocking_pids(s.pid) AS blockers, s.query, 1 AS depth FROM pg_stat_activity s WHERE pg_blocking_pids(s.pid) {} UNION ALL SELECT b.pid, bc.chain || b.pid, pg_blocking_pids(b.pid), b.query, bc.depth 1 FROM blocking_chain bc JOIN LATERAL unnest(bc.blockers) AS blocker_pid ON true JOIN pg_stat_activity b ON b.pid blocker_pid WHERE bc.depth 10 ) SELECT DISTINCT chain, query FROM blocking_chain;加depth 10就是一个暴力但有效的防环手段。正常业务场景不会出现超过 10 层的锁等待链如果真出现了那基本可以断定系统存在严重的锁竞争设计问题不是一条 SQL 能解决的。3.4 基于 pg_blocking_pids 的自动化监控脚本排查是临时的预防才是根本。生产环境里我建议大家写一个简单的定时脚本把“当前是否有根阻塞者”这个指标暴露给监控系统。比如用 cron 每两分钟执行一次下面的 SQLSELECT count(*) FROM pg_stat_activity WHERE pid IN ( SELECT unnest(pg_blocking_pids(pid)) FROM pg_stat_activity ) AND pid NOT IN ( SELECT pid FROM pg_stat_activity WHERE pg_blocking_pids(pid) {} ) AND state idle in transaction AND age(now() - xact_start) interval 5 minutes;这个查询统计的是被其他会话阻塞、且自己是 idle in transaction、且持锁超过 5 分钟的根阻塞者数量。一旦这个数字大于 0说明有事务卡了超过 5 分钟不释放锁需要告警了。阈值你可以根据自己的业务来调比如核心业务表持锁超过 1 分钟就该告警了普通报表表可以放宽到 10 分钟。再进阶一点可以把超时的根阻塞者直接自动终止但我不建议一上来就做全自动 kill风险太高。稳妥的做法是监控脚本先告警人工确认后手动处理等流程跑顺了再针对特定 application_name 的会话做半自动处理——比如把某个固定应用名的僵尸事务自动终止其他情况仍然转人工。4. 常见问题与排查技巧实录4.1 为什么函数返回空数组但 SQL 还是卡住这个坑我踩过好几次。某次线上一个报表查询长期不返回pg_stat_activity里state activewait_event_type IOwait_event DataFileRead看着不像锁的问题。当时我直接调用了pg_blocking_pids()返回{}一时还以为是函数出了问题或者版本不对。后来才意识到pg_blocking_pids()只反映“锁等待”这一类阻塞。如果会话是在等待 IO、等待锁存器、等待扩展、或者其他非锁资源这个函数一概返回空。所以在实际排查时一定要先看wait_event_typeLock锁等待用pg_blocking_pids()能查到阻塞者IO磁盘 IO 瓶颈去查慢盘、IOPS 耗尽、临时文件落盘Client等待客户端读取数据通常是持有锁的一方还没提交Extension、LWLock等内部锁竞争得从参数调优和索引优化入手。判断方向错了后面全白费。先看wait_event_type再决定用什么工具这是排查数据库问题的基本素养。4.2 死锁检测到死锁自动回滚还需要手动干预吗很多人一听“死锁”就紧张但实际上 PostgreSQL 的死锁检测机制是自动运行的。默认每隔deadlock_timeout默认 1 秒检查一次锁等待图发现存在环形等待时会选择牺牲其中一个事务来回滚然后向客户端抛出ERROR: deadlock detected。所以如果死锁只是偶发数据库系统自己就能消化掉不需要人工介入。真正需要人为干预的是“长时间锁等待”而不是“死锁”——也就是某个事务一直持有锁不释放导致后续所有需要这把锁的事务全部排队但又不构成环因为没有相互等待关系系统不会自动处理。我见过最典型的案例一个开发在 psql 里执行了BEGIN; UPDATE ...;后直接关闭了窗口没有提交也没有回滚。这个会话在服务端并不会立刻断开它会变成idle in transaction状态锁继续持有所有想更新同一行数据的业务请求全部卡住。这种情况下pg_blocking_pids()能精准定位出这个僵尸连接然后pg_terminate_backend()直接结束它。针对这类问题防患于未然的做法是设置两个参数SET idle_in_transaction_session_timeout 5min; SET lock_timeout 10s;第一个参数强制空闲事务 5 分钟后自动回滚第二个参数让一次性锁等待超过 10 秒就主动放弃。但注意lock_timeout在业务侧设置时需要谨慎高并发场景下如果设置太短业务偶发冲突就会直接报错反而影响可用性。建议先在非核心实例上验证效果。4.3 批量处理多个阻塞会话时如何避免误杀生产环境最忌讳的就是“看到阻塞者就杀”。实际操作中要分三个层次来判断第一层看会话是不是长事务。age(now() - xact_start)如果超过几个小时大概率是僵尸事务如果只有几秒钟可能是正常业务操作需要观察一下再决定。第二层看会话的application_name和client_addr。如果是应用服务器的连接池会话杀掉之后连接池会自动重建影响可控如果是数据库管理员的 psql 会话可能是有人在手工维护贸然杀掉会打断操作。第三层看阻塞者是否在等待其他资源。如果阻塞者本身也是wait_event_type Lock说明它也在等锁你杀它没意义得继续往上追根。另外强烈建议终止会话前先记录现场。人肉操作时可以在 psql 里先执行SELECT pid, application_name, client_addr, xact_start, now() - xact_start AS xact_duration, query FROM pg_stat_activity WHERE pid IN (需要终止的PID列表);把结果截图或者记录到日志里再去执行终止命令。这样事后如果业务方来问“为什么我的事务被杀了”你有据可查。4.4 从 pg_stat_activity 的 state 判断阻塞会话的可杀性pg_stat_activity.state字段其实透露了大量信息配合pg_blocking_pids()使用效果极佳state含义锁持有情况处理优先级active正在执行 SQL可能持锁也可能只是等待需结合 wait_event 判断idle空闲等待客户端新指令如果是无事务的 idle则无锁无需处理idle in transaction事务已开启但未提交/回滚高危极可能持锁优先处理idle in transaction (aborted)事务内发生错误但未回滚高危持锁且无法继续执行其他 SQL优先处理fastpath function call正在执行并行查询的快路径函数可能持锁结合查询内容判断disabled该 PID 被禁用少见特殊状态一般不用管我遇到过最麻烦的情况是idle in transaction (aborted)。这种会话属于“卡死的僵尸事务”事务中一条 SQL 报错了但应用没有走回滚流程后续所有 SQL 在这个事务里都无法继续执行锁却还死死握着。如果业务方没能及时发现这个会话可以挂一整天。遇到idle in transaction (aborted)的根阻塞者基本不用犹豫直接终止它就是典型的有百害而无一利SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state idle in transaction (aborted) AND pid IN (根阻塞者列表);4.5 实战速查锁等待定位速查表下面这张表是我把多年排查经验压缩成的速查表放在手边非常实用场景排查入口关键 SQL处理方式单条 UPDATE 卡住pg_blocking_pids()查阻塞者SELECT pg_blocking_pids(pid);终止根阻塞者或等其释放大量会话堆积2.2 联合查询列出所有被阻塞会话定位根阻塞者按 3.2 流程处理idle in transaction 持锁state 持锁时长age(now() - xact_start)超阈值终止会话并设置idle_in_transaction_session_timeout多个会话互相等待递归 CTE 查阻塞链3.3 的递归查询找链首优先终止加索引 / ALTER 卡住看wait_event relationpg_blocking_pids()终止持锁 DML 会话并行查询卡住查 parent PIDpg_blocking_pids(pid)为空检查 parent 会话并终止死锁报错应用侧收到死锁错误不需要pg_blocking_pids()系统自动回滚优化 SQL 顺序4.6 SQL 层的额外建议从源头减少锁竞争排查工具再顺手也只是“治已病”。如果一个系统频繁出现锁等待最终还是要回到业务侧和 SQL 侧去优化。几个我自己的经验一是尽量缩短事务持续时间。PostgreSQL 里事务开得越久持锁越久被阻塞的概率越大。业务代码里不要让一个事务里混着网络请求、外部接口调用等耗时操作事务里只做必要的数据库操作。二是控制 UPDATE 的影响范围。能走索引过滤就绝不走全表扫。全表扫意味着可能拿上 ACCESS EXCLUSIVE 锁这种锁一拿连 SELECT 都要被阻塞。三是批量更新时注意排序。多个进程同时更新同一张表的不同行如果更新顺序不一致就可能在极低概率下产生死锁。统一按主键排序可以显著减少死锁的概率。四是合理设置事务隔离级别。READ COMMITTED是默认值适合绝大多数业务如果用了REPEATABLE READ或SERIALIZABLE虽然并发一致性更强但锁竞争范围也更广需要更强的运维保障。五是用逻辑复制或分区表拆分热点。某些高并发场景下单表单行更新是绝对瓶颈怎么调 SQL 都没用只能从架构层面把数据拆开。5. 从锁排查到锁治理的落地延伸5.1 将 pg_blocking_pids 融入日常巡检有很多团队是“出事才查”平时对数据库的锁状态完全不闻不问。我建议把锁等待巡检做成一个日常任务每 5 分钟跑一次“根阻塞者数量”查询超过阈值就告警。这样就能在锁等待发生的最早期去介入而不是等业务反馈“系统崩溃了”才半夜爬起来排查。另外巡检出的历史数据可以沉淀下来哪个时间段锁冲突最多、哪张表最常被锁、哪个应用最容易持锁超时。这些数据积累一段时间后你能非常明确地知道下一步应该去优化哪张表的索引、调整哪个应用的连接池参数。5.2 锁监控与连接池、限流策略的配合锁问题是数据库的“果”连接池管理才是很多场景下的“因”。应用侧连接池太小一旦有几个会话被锁阻塞连接池会迅速被占满其他健康请求也挤不进来形成连锁反应。所以有了锁监控之后最好和连接池监控联动锁等待告警触发时同时观察连接池使用率、活跃连接数、等待队列长度综合判断问题的影响范围。我自己实践过的一个有效策略是数据库侧做“慢持锁”检测应用侧做“短锁超时”兜底。数据库侧每 5 分钟扫一次持锁超过 10 分钟的根阻塞者应用侧把数据库驱动的 socketTimeout 和事务超时配置好。两边一起守大部分锁问题都能被提前掐灭而不是等业务崩溃了才去救火。5.3 未来扩展从单实例判断到集群视角如果你的 PostgreSQL 环境已经做了读写分离或者使用集群方案锁问题的排查会稍微复杂一些。写节点上产生的锁等待通过 pg_blocking_pids 可以看到但备节点上的冲突等待比如备库 replay 被长查询阻塞就需要查 pg_stat_activity 里backend_xmin相关的字段了。对于集群环境我建议把每台实例的锁等待指标都采集到统一的监控平台横向对比。这一点做深了你就能给业务方一个很清晰的视图某个时间段内哪台节点因为锁等待导致延迟上升进而影响了哪个读写分离链路。这已经不仅仅是函数使用层面的事而是把锁视作系统健康的核心维度之一了。把pg_blocking_pids()用好锁竞争这个问题在 PostgreSQL 体系里就不再是黑盒。它把排查从一个繁琐的推理过程降维成了一个直接、确定的函数调用。我在实际生产中见过太多因为一条锁等待没及时发现而拖垮整个系统的案例有时候卡住系统的甚至是开发人员随手开的一个事务窗口。掌握这个函数配合一套清晰的处置流程相当于给数据库加了一层保险——排查也好、自动化运维也好都能从容很多。
返回列表