原文链接: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技术社区,南大通用致力于成为用户最信赖的数据库产品供应商。