南大通用 GBase 8c 锁等待与死锁排查:从阻塞链到全局事务号

原文链接:www.gbase.cn/community/p...

更多精彩内容尽在南大通用GBase技术社区,南大通用致力于成为用户最信赖的数据库产品供应商。

业务反馈「系统卡死」,登录数据库一看,一条 UPDATE 跑了十几分钟不返回------这类现象背后多半是锁,不是慢 SQL。两类问题的处置方式完全不同:慢 SQL 要优化执行路径,锁问题要先解开等待关系 。

本文按「定性质 → 找链头 → 跨节点追踪 → 处置 → 预防」五步展开。

一、先分清三类锁问题

排查效率低,通常是因为一上来就去翻 SQL,而没先判断自己面对的是哪一类问题。

类型 特征 根源位置
简单锁等待 一个会话持锁,其他会话排队 持锁的那一个会话
阻塞链 A 阻塞 B、B 阻塞 C,形成链条 链条最头部,而非最后报错的那条 SQL
全局死锁 跨 CN / DN 的循环等待 多个节点互相等待,单节点视角看不全

还要区分锁的作用范围:本地锁 局限于单个 CN 或 DN 内部,全局锁跨节点存在,典型场景是分布式事务与 DDL 操作。分布式环境下,锁有可能由其他节点产生,只在某一个节点上查是查不全的。

二、第一步:从会话状态定性质

先不查锁表,先看会话。三个字段就能把问题定性:

vbnet 复制代码
SELECT pid, usename, application_name, state, wait_event_type, wait_event,
       now() - xact_start  AS xact_age,
       now() - query_start AS query_age,
       query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY xact_start;
​
字段 判读要点
state 出现 idle in transaction 意味着语句已执行完但事务未提交,锁仍被持有------这是最高频的根因
wait_event_type 为 Lock 即可确认是锁等待,而非 IO、网络等其他等待
xact_start 事务开始时间偏早说明存在长事务,是锁冲突的温床

约九成的锁问题,答案在这三个字段里。 只要抓到 idle in transaction 且 xact_start 很早的会话,就不必再往下深挖锁模式。

三、第二步:找到阻塞链的链头

确认存在锁等待后,用未授予锁列表把等待关系拉出来:

vbnet 复制代码
SELECT l.pid, n.nspname, c.relname, l.mode, l.granted
FROM pg_locks l
JOIN pg_class c     ON l.relation = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE NOT l.granted
ORDER BY l.pid;
​

接着把等待方与持锁方配对,直接定位链头:

vbnet 复制代码
SELECT w.query AS waiting_query, w.pid AS w_pid, w.usename AS w_user,
       l.query AS locking_query,  l.pid AS l_pid, l.usename AS l_user,
       t.schemaname || '.' || t.relname AS tablename
FROM pg_stat_activity w
JOIN pg_locks l1 ON w.pid = l1.pid AND NOT l1.granted
JOIN pg_locks l2 ON l1.relation = l2.relation AND l2.granted
JOIN pg_stat_activity l ON l2.pid = l.pid
JOIN pg_stat_user_tables t ON l1.relation = t.relid
WHERE w.waiting = true;
​

处置对象是 l_pid,不是 w_pid。 杀掉被阻塞的会话不会释放任何锁,反而会丢掉业务现场。

锁模式上优先关注强锁:

锁模式 典型来源 冲突面
AccessExclusiveLock DDL、TRUNCATE 阻塞该表所有读写
ExclusiveLock 写操作 与其他所有锁模式冲突
ShareLock 读操作 与排他锁冲突
意向锁(IS / IX) 表级意向声明 用于协调表锁与行锁的相容性

带 AccessExclusiveLock 的语句往往影响面最大,一条高峰期执行的 DDL 足以让整张表停摆。

四、第三步:跨节点追踪,取全局事务号

分布式场景有一类特殊现象:在 CN 上看到的往往只是外层调用。比如一条存储过程或批量作业,CN 上显示的是外层语句,真正卡住的 DML 早已下发到某个 DN 上执行。此时必须沿全局事务号追下去。

操作路径固定为四步:

步骤 所在节点 动作
1 CN 用 pg_stat_activity 按业务特征(库名、客户端 IP、用户名或 SQL 片段)定位可疑会话,取 PID
2 CN SELECT pid, global_sessionid, mode, granted FROM pg_locks WHERE pid = <第一步的 PID>; 取 global_sessionid
3 DN SELECT pid, global_sessionid, mode, granted FROM pg_locks WHERE global_sessionid LIKE '%<事务号>%'; 取 DN 上的 PID
4 DN SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE pid = <第三步的 PID>; 看真正卡住的 SQL

第三步的关键在于事务号的提取:global_sessionid 形如 0:818#0,含节点信息,追踪时要取 : 与 # 之间的数字串 (即上例中的 818),再拿它到 DN 上做模糊匹配。这一步取错,后面就查不到任何结果。

五、全局死锁是怎么被解开的

GBase 8c 的全局死锁检测采用分布式检测算法 :每个 CN 独立发起死锁检测,检测消息沿着事务处理线程之间的等待关系在节点间传递;如果某个事务处理线程收到了自己发出的检测消息,就说明等待关系成环,判定为全局死锁,该事务回滚,死锁随之解除。

deadlock_timeout 控制检测的触发时机,缺省为 1 秒------它决定系统等多久才开始做死锁检测,而不是锁等待的上限。

全局死锁最常见的成因并不复杂:两个事务以相反顺序更新同一批数据。事务 A 先更新 101 再更新 202,事务 B 先更新 202 再更新 101,交叉执行就成环。所以「统一加锁顺序」不是一句规范口号,而是最有效的死锁预防手段。

六、处置:分三档,先温和后强硬

查到链头后,动作要分档:

scss 复制代码
-- 第一档:先取消当前查询,温和处理
SELECT pg_cancel_backend(<链头 PID>);

-- 第二档:确认该会话可以中断,再终止会话释放资源
SELECT pg_terminate_backend(<链头 PID>);
​

先 pg_cancel_backend,不要上来就 pg_terminate_backend。 前者取消当前查询,后者直接终止会话并释放事务资源。动手前先确认这是不是业务关键会话------一次误杀可能比多等两分钟代价更大。

还有一类容易被忽略的持锁者:长时间停留在 prepared 状态的两阶段事务。这类事务即使业务连接早已断开,仍然持有锁,只能从悬挂事务入手清理,杀会话解决不了。

七、参数兜底与预防

排障期先用会话级设置观察,长期生效必须走 gs_guc 统一下发,禁止手工改配置文件:

参数 作用 定位
lockwait_timeout 单个锁最长等待时间,缺省 20min 保护边界,不让业务无限等锁
statement_timeout 语句总执行时长上限 防止单条 SQL 执行过久,不能替代锁治理
deadlock_timeout 死锁检测触发时机,缺省 1s 排障期可缩短以便更快观察
log_lock_waits 是否记录锁等待日志 排障期开启,便于事后复盘

下发示例(官方 FAQ 中的做法):

css 复制代码
gs_guc reload -Z coordinator -N all -I all -c "lockwait_timeout=20min"
gs_guc reload -Z datanode    -N all -I all -c "lockwait_timeout=20min"
​

需要说明的是,参数只是兜底。真正的根因通常是长事务、提交边界不清晰、批量作业与在线业务没有隔离。 预防清单如下:

关注点 动作
事务设计 拆分长事务;事务内不要调用外部接口;尽量缩短持锁时间
加锁顺序 多表操作顺序全局统一,避免交叉更新
SQL 质量 补齐索引,减少需要扫描并锁定的行数;避免无谓的全表扫描
DDL 变更 错峰执行,变更前先确认目标表无长事务
线程池 按节点规格合理配置,线程资源不足会让事务排队、放大锁冲突
节点通信 关注节点间网络延迟与中断,通信异常会诱发全局死锁

八、小结

把流程固化成动作序列,下次遇到就不用临场发挥:看会话状态定性质 → 查未授予锁找链头 → 取 global_sessionid 跨节点追 → 先 cancel 后 terminate → 用参数划边界 → 回到事务设计做根治。

判断锁问题是否处置到位,标准不是「SQL 跑通了」,而是下一次同类业务高峰期,不再出现同样的等待链。

原文链接:www.gbase.cn/community/p...

更多精彩内容尽在南大通用GBase技术社区,南大通用致力于成为用户最信赖的数据库产品供应商。

相关推荐
道虽迩,不行不至1 小时前
数据库自动连库执行增量脚本-Linux
linux·数据库
GeW1 小时前
新质生产力发展路线图来了,Linux和数据库人该怎么接
linux·数据库
用户125758524361 小时前
GoFrame Vue3开源后台管理系统推荐:数据库与Worker要求怎么验
数据库·后端·go
MaxHua1 小时前
达梦 DTS 迁移 MySQL 报错 - 3236:此列列表已索引 解决方案
数据库·后端
用户8662978480081 小时前
Oracle 迁 KingbaseES 全流程:从评估到割接,一个都不能少
数据库
用户8662978480082 小时前
从 MySQL 迁到 KingbaseES,KDTS 这些参数不调会吃亏
数据库
库库的数据库笔记2 小时前
金融核心交易系统的数据库国产信创改造该如何做?
大数据·数据库·分布式·mysql·oracle·金融·数据库架构
这个DBA有点耶2 小时前
连接数没满,应用却连不上数据库——TCP backlog溢出与somaxconn的隐藏限制
数据库·mysql·代码规范
yunlaodacom2 小时前
华为云国际站服务代理商:BMS数据库延迟突然升高?磁盘I/O、网络与CPU排查方法
网络·数据库·华为云