GBase 8c 里一条 SQL 卡半天,我排查锁等待时通常先盯这几个地方

张开发
2026/4/3 18:47:18 15 分钟阅读
GBase 8c 里一条 SQL 卡半天,我排查锁等待时通常先盯这几个地方
GBase 8c 里一条 SQL 卡半天我排查锁等待时通常先盯这几个地方我最近看 GBase 8c 这块资料时越来越觉得锁等待特别容易被误判。现场里最常见的说法是“数据库卡住了”或者“这条 SQL 性能太差”但真正落到排查动作上很多时候根子不是执行计划本身而是事务没提交、DDL 和 DML 撞到一起、批量过程在某个 DN 上卡住、或者分布式场景里已经出现了跨节点等待。GBase 8c 本身有 MVCC、常规锁、全局死锁解除这些机制读写不冲突不代表所有操作都不会互相挡住尤其是 DDL、批量更新、长事务、存储过程这几类场景很容易把问题拖得很隐蔽。我自己现在碰到“SQL 一直不返回”这类问题通常不会上来就改参数也不会先怀疑机器不够。更实际一点的做法是先把现场拆成三件事当前会话是不是在等锁。阻塞源到底是谁是活跃 SQL 还是idle in transaction。是单节点局部问题还是已经变成分布式阻塞链。如果这三个问题没先看清后面不管是终止会话、调超时还是改程序提交策略动作都很容易打偏。我一般先把现场现象分成这几类现场现象我优先怀疑的点第一动作UPDATE、DELETE执行很久没返回行级锁 / 表级锁冲突看pg_stat_activity、pg_locksTRUNCATE、ALTER TABLE一直挂起DDL 被未提交事务挡住先找持锁会话存储过程跑着跑着不动了某个 DN 上具体步骤被卡住先查全局事务号再到 DN 定位会话状态不是 active但别人都被堵住idle in transaction长事务先看事务开始时间和最后一条 SQL过一段时间直接报超时lockwait_timeout命中查等待链再判断要不要调超时我最近整理下来觉得这种拆法很适合现场。因为锁问题最怕“只盯当前报错 SQL”而不看前面那个真正拿着锁的人。一、先判断是不是锁等待不要先把锅甩给 SQL 性能GBase 8c 的pg_stat_activity里能直接看到会话状态waiting字段可以反映后台当前是否在等待锁state里还能区分active、idle、idle in transaction、idle in transaction (aborted)这些状态。track_activities默认就是开启的关闭时很多排查视图会看不到有效内容所以我现场里基本都会先确认它没被关掉。我自己常用的第一组 SQL 很简单selectpid,usename,application_name,client_addr,xact_start,query_start,state_change,waiting,state,queryfrompg_stat_activityorderbyxact_start nullslast,query_start nullslast;如果现场已经有人反馈“某张表一操作就卡”我通常会再缩一下范围selectpid,usename,state,waiting,xact_start,query_start,queryfrompg_stat_activitywherequerylike%orders_fact%orderbyquery_start;这个阶段我更关注两个点有没有会话处在idle in transaction这个状态特别值得盯。因为它表面上看起来不像在执行 SQL但事务其实没结束锁资源还在占着。GBase 社区的锁故障定位案例里就专门提到过这种“SQL 已经跑完但事务没提交结果把后面的 DML/DDL 都挡住”的情况。当前等待的是不是锁不是别的资源别一看到慢就默认是锁。有时候是 I/O、网络、并发队列但waitingtrue加上后续锁视图能对上基本就能确定方向。二、锁来源不要靠猜直接从pg_locks往回找我自己理解下来GBase 8c 里排锁问题最稳的还是pg_stat_activity pg_locks pg_class pg_namespace这组组合。社区里给过一个比较实用的思路如果某个表上的 DML/DDL 执行超时或者长时间不返回可以直接按 schema 和表名去反查当前有哪些会话在持锁。比如我现场里常写成这样selecta.pid,a.usename,a.application_name,a.client_addr,a.state,a.waiting,a.xact_start,a.query_start,l.mode,l.granted,n.nspname,c.relname,a.queryfrompg_namespace njoinpg_class conc.relnamespacen.oidjoinpg_locks lonl.relationc.oidjoinpg_stat_activity aona.pidl.pidwheren.nspnamesalesandc.relnameorders_factorderbyl.granteddesc,a.xact_start;这条 SQL 的好处是比较直接适合“我已经知道是哪张表有问题”的场景。如果是“我不知道谁堵谁”我更常用下面这种阻塞链查法。开发者手册里有一条现成思路把等待会话和持锁会话按pg_locks关联起来再把表名也带出来这样能直接看到谁在等、谁在挡、挡的是哪张表。selectw.queryaswaiting_query,w.pidaswaiting_pid,w.usenameaswaiting_user,l.queryaslocking_query,l.pidaslocking_pid,l.usenameaslocking_user,t.schemaname||.||t.relnameastable_namefrompg_stat_activity wjoinpg_locks l1onw.pidl1.pidandnotl1.grantedjoinpg_locks l2onl1.relationl2.relationandl2.grantedjoinpg_stat_activity lonl2.pidl.pidjoinpg_stat_user_tables tonl1.relationt.relidwherew.waiting;这类查询在现场特别有价值因为它不是只告诉你“有人在等”而是把阻塞源和对象一起带出来。真正落到处理顺序上先找到源头会话比一条条取消等待会话有效得多。三、GBase 8c 分布式场景下别只盯当前 CN这个点我自己比较在意。单机数据库里的锁等待很多时候在一个实例里就能闭环排掉但 GBase 8c 是分布式架构锁可能来自别的 CN 或 DN。社区的锁故障定位文章里就专门提醒过分布式场景下因为有多个 CN 和 DN锁有可能由其他节点产生所以排查时不能只看一个节点而且如果是批量语句或者存储过程还需要通过全局事务号继续往 DN 上追。我最近整理下来比较顺手的一套动作是这样1先在 CN 找到当前会话selectpid,usename,application_name,client_addr,query_start,state,queryfrompg_stat_activitywherequerylike%call proc_merge_orders%orderbyquery_startdesc;2拿这个 PID 去pg_locks找全局事务号selectpid,sessionid,global_sessionid,mode,granted,locktagfrompg_lockswherepid140512348217104;社区资料里提到在分布式模式下global_sessionid里会带节点信息继续定位时要取其中的事务号部分再到 DN 上去查。3登录相关 DN用全局事务号继续追selectpid,sessionid,global_sessionid,mode,granted,locktagfrompg_lockswhereglobal_sessionidlike%818%;4再回到 DN 上看具体执行到了哪一步selectpid,usename,state,waiting,xact_start,query_start,queryfrompg_stat_activitywherepid140512314560272;这套动作对“存储过程很多步不知道卡在哪一步”特别有用。因为 CN 上你看到的往往只是外层调用真正卡住的那条 DML 很可能已经下发到某个 DN 上了。四、我排锁问题时通常会把下面几个状态单独拎出来看状态 / 现象我更倾向的判断处理优先级active且waitingtrue正在申请锁但没拿到先找阻塞源idle in transaction事务没提交锁可能还占着很高idle in transaction (aborted)事务里有语句失败但会话没结束很高DDL 一直挂起常见是被未提交 DML 或其他 DDL 挡住很高批处理 / 存储过程卡住常见要跨 CN、DN 联动排查中高这里面我最怕的是idle in transaction。因为业务侧经常会说“那条 SQL 明明执行完了”但数据库这边看事务没提交就还是没完。GBase 8c 对pg_stat_activity的状态定义里也明确列出了这些状态差异所以我现在看会话时state往往比query本身更有提示意义。五、超时参数别乱拧但要知道它们分别管什么很多现场排到最后会有人问一句“要不要把锁等待时间改短一点”我自己的想法是可以调但得先分清这几个参数到底各管什么不然很容易把症状压住问题根源没解决。根据开发者手册里的参数说明deadlock_timeout默认值是1s它关系到死锁检测启动时机如果log_lock_waitson它也决定锁等待信息什么时候写日志。lockwait_timeout默认值是20min控制单个锁最长等待时间超过就报错。statement_timeout控制语句总执行时长和单纯锁等待不是一回事。我更愿意这样理解参数我更常把它用在什么场景我对它的看法deadlock_timeout想更快发现锁等待/死锁迹象、配合日志分析适合排障期观察lockwait_timeout不想让业务无限等锁适合做保护边界statement_timeout防止单条 SQL 执行太久别拿它代替锁治理现场里如果只是临时排查我一般先做会话级设置不急着全局改showdeadlock_timeout;showlockwait_timeout;showstatement_timeout;showlog_lock_waits;setdeadlock_timeout500ms;setlockwait_timeout60s;setstatement_timeout10min;setlog_lock_waitson;这里我个人更倾向于把它当成“排障辅助”和“保护机制”而不是根治办法。真正的根因通常还是长事务、提交边界不清晰、批量作业和在线业务没隔离好。六、处理动作别太猛先取消查询再考虑终止会话GBase 8c 提供了pg_cancel_backend(pid)和pg_terminate_backend(pid)。社区文章和相关函数说明里提到前者是取消当前查询后者是直接终止会话而且pg_cancel_backend更适合先做温和处理pg_terminate_backend适合确定可以中断、并且需要立即释放事务资源的场景。我现场里一般按这个顺序来-- 先尝试取消当前查询selectpg_cancel_backend(140512348217104);-- 如果取消不了且确认允许中断再终止会话selectpg_terminate_backend(140512348217104);我自己更愿意给团队定一个简单原则先确认这是不是业务关键会话。先取消 active 查询不要上来就 terminate。如果是idle in transaction且已经明确阻塞别人再考虑 terminate。终止后要复查锁是不是已经释放别以为一 kill 就完了。这个顺序看起来保守一点但生产环境里我觉得更稳。因为锁问题往往不是“结束一个 PID 就彻底结束”有时只是把第一层等待打掉后面还有第二层链路。七、我现在更认可的落地建议最后收一下。我最近重新整理 GBase 8c 事务和锁这块内容时感觉最值得坚持的一点不是记住多少锁模式而是排查顺序要稳定。锁冲突这类问题最怕靠经验拍脑袋今天怀疑 SQL明天怀疑机器后天又去调全局参数最后还是没把真正挡路的会话找出来。从落地角度看我更倾向于把这类问题当成一条完整链路来处理先确认是不是锁等待再找到谁持锁、谁在等再确认是不是跨节点阻塞最后才决定是取消查询、终止会话还是调整超时和开发侧事务边界。这个方法不花哨但我自己觉得很适合 GBase 8c 这种分布式场景。因为你只要把阻塞链找对了后面的动作其实都不复杂最怕的是根本没找到那个真正拿着锁不放的人。参考资料[1] 南大通用GBase 8c分布式问题排查之锁故障定位 https://www.gbase.cn/community/post/4964

更多文章