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

资讯详情

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

OGG复制延迟背后:数据库锁竞争排查与死锁还原实战

OGG复制延迟背后:数据库锁竞争排查与死锁还原实战 1. 一次离奇“卡死”引发的排查做数据运维的人都知道生产环境最怕的不是直接报错的故障而是一种“一切看起来都正常但业务就是不动”的诡异状态。那天上午我盯着监控大屏OGGOracle GoldenGate的Replicat进程显示Running没有报错复制延迟却从几十秒一路飙升到三个小时。数据库的活跃会话里出现了一个“SESSION LOCK”标记v$session里blocking_session字段指向了一个模模糊糊的SESSION ID可顺着这个ID查下去却发现持有锁的那条会话没有任何SQL在执行。更让人头疼的是alert日志里搜了一圈连一条ORA-00060都没有。也就是说Oracle自己根本没识别出这是一个死锁。这种场景就是标题里说的“看不见”的死锁——数据库层面没有报出循环等待但实际系统已经卡成了一锅粥。后来我们引入了DBdoctor做全景诊断才把这个锁竞争链条从会话到SQL、从持有到等待、从OGG到另一个“隐藏角色”完整还原了出来。这篇博文就把整个排查过程、背后的机制、以及可复用的诊断思路分享出来适合正在跟OGG打交道、或者被锁等待问题折磨过的DBA和运维朋友。OGG这个工具本质上是做数据复制的它本身不产生数据业务但它需要以数据库会话的身份去读取事务日志、执行目标端SQL。一旦它和业务SQL、DDL操作、外部定时任务在同一个数据库实例里相遇就会触发锁竞争。而多数情况下这种竞争并不满足Oracle死锁检测器的“环状等待”条件于是Oracle不吭声OGG也不吭声只有业务在默默变慢。2. OGG到底在什么环节参与“抢锁”2.1 三个核心进程各自的锁行为要搞清楚OGG怎么抢锁得先看清它的架构。OGG在源端一般有Extract进程主要读取数据库的redo log或者archive log解析出事务变化后写入本地trail文件然后在源端还可能有一个Data Pump进程负责把trail文件传输到目标端目标端则是Replicat进程负责把接收到的trail文件解析为SQL并应用到目标库。这三个进程里真正会在数据库内部产生锁的就是Extract和Replicat。很多人有一个误区认为Extract读日志就不碰表所以没有锁。实际上如果不做特殊配置Extract在启动时或需要读取数据字典时会和DDL操作争夺字典锁如果用了“Critical Transaction”直接抽取表数据那它更会像普通会话一样持有表锁、行锁。而在目标端Replicat执行应用SQL时它的行为和一个业务应用几乎没有任何区别每条INSERT/UPDATE/DELETE都会走正常的锁机制。所以OGG抢锁的本质不是OGG有什么特殊的锁而是它作为一个“普通会话”参与了数据库并发控制。2.2 常见的四类锁竞争场景第一类是行锁竞争Replicat执行DML时和业务事务更新了同一行互相等待提交或回滚。这类竞争最直观Oracle的自动死锁检测往往能识别。第二类是表锁和DDL锁竞争比较隐蔽比如有人在业务库执行了ALTER TABLE修改字段即使使用在线DDLOnline DDL也会有一个短暂的瞬间需要拿表锁此时如果OGG的Replicat正好要往这张表写入数据就会形成阻塞。第三类是字典锁竞争源端Extract要解析一个刚被修改过的表的日志时需要读取数据字典而此时某个运维脚本正在执行DDL并持有相关字典锁Extract就会阻塞。第四类是OGG内部的队列锁与外部门锁的组合比如Extract写trail文件时的I/O锁和操作系统层的外部进程锁如果和数据库锁相互等待会形成一个Oracle根本感知不到的死锁闭环。2.3 为什么“死锁”在日志里看不见Oracle的锁定机制里有一个后台进程负责死锁检测。但它的检测逻辑依赖资源管理器的等待图需要形成“A等BB等CC等A”的环。而在OGG参与的很多锁竞争里等待关系往往是链状的Replicat在等业务会话持有的行锁业务会话又在等一个外部脚本站住的锁外部脚本则在等OGG进程结束后释放的部分资源——这个环绕过了数据库资源管理器或者跨越了数据库边界因此Oracle不会抛ORA-00060。这就导致一个问题你查看v$session时能看到等待看到blocking_session但往源头追几层发现最后一个blocker是一个“非数据库”实体比如一个shell脚本、一个LDAP连接、一按Java进程这条线就断了。DBdoctor这种专业的诊断工具恰恰能把断掉的线重新接起来。3. 用DBdoctor还原“看不见”的死锁现场3.1 核心思路从“等待关系”反推“持有关系”我们常说锁问题的本质不是某一个会话的错而是整个等待关系的拓扑结构出了问题。Oracle原生的v$session、v$lock可以给出单点的会话等待信息但要手工把这些信息拼成一张完整的等待图尤其在多节点RAC环境、在OGG和业务并发的情况下几乎是不现实的。DBdoctor的做法是从数据库内部采集等待事件、会话状态、锁持有者和等待者的对应关系把这些信息汇总成一张锁等待依赖图。图上每个节点是一个会话每条连线表示“谁在等谁”。死锁就不再是一个抽象概念而是图上的一个环。只要这个环出现哪怕Oracle没有识别出来你也可以人工判断这就是死锁。这个思路强调“时间线”而非“瞬时快照”。普通查询只能看到当前这一秒的状态但一个死锁的形成往往经历了几分钟甚至几小时的演变。例如OGG Replicat可能在10:00就开始等待TL锁10:05业务会话才进来抢同一行锁10:10外部脚本才启动并占用一个关键资源这时候环才闭合。单看10:10的快照你看到的是业务会话在等外部脚本却看不到OGG其实才是那个最初被卡住、后续又卡住别人的角色。DBdoctor能按时间线回放锁等待关系的形成过程中这就能完整还原现场。3.2 一个可复现的“死锁”模拟环境为了说清楚这个还原过程我搭了一个简单的模拟环境。一台Oracle 19c测试库开了一张业务表T_ORDER主键ORDER_ID。OGG目标端配置了一个Replicat映射关系是T_ORDER整表同步。业务侧用一个PL/SQL事务同时更新T_ORDER里三行数据与此同时用一个外部shell脚本模拟运维操作脚本里执行了一条LOCK TABLE T_ORDER IN EXCLUSIVE MODE等待特定条件最后让OGG的Replicat也执行更新。三者同时启动就会出现一种“看不见”的死锁Oracle的等待图里能看到A等B、B等C但C的会话被识别为background或application类型等来等去没有形成闭环。需要强调一点OGG Replicat默认会在一个事务里批量应用多条更改。如果其中一个批次里的第一条语句被阻塞整个批次都会挂起表现成一个长时间运行的会话但实际上它只是拿着一个未提交事务的锁在等待。这种情况下你从v$session看到的OGG会话状态非常像“正常执行中”很多人会忽略它。3.3 还原死锁的四步法DBdoctor视角第一步打开DBdoctor的锁等待全景视图。先看是否存在“环形”拓扑即任意两个会话节点之间是否首尾相接。我先截取了一段锁等待图发现Replicat会话SQL*Plus /dgc? 不程序名是“OGG Replicat”在等待一个TX锁而这个TX锁的持有者是来自JDBC连接的业务会话业务会话又在等待一个程序名为“DataCollector”的外部连接DataCollector连接正阻塞在一条LOCK TABLE语句上进一步查看它等待的对象是一张被OGG写了几万条记录的缓冲表。第二步拉取时间线。在特定时间窗口内DBdoctor会按照会话的第一次等待时间、持有锁的时间、持续时间来排序。这一步能定位到“谁是第一个被锁住的”以及“最后一个加入的”。在我们的模拟环境里时间线显示OGG才是第一个被锁住的人它在业务会话启动之前就已经进入了等待状态只是因为持续等待不明显一度被排除在外。第三步关联SQL和程序名。DBdoctor的会话详单里能看到每个锁会话最近执行的SQL。OGG Replicat的SQL可能显示为“insert into T_ORDER(...) values(...)”业务会话是普通的UPDATEDataCollector则是LOCK TABLE。程序名和SQL一对应锁竞争的参与方立刻浮出水面。第四步回溯历史操作。通过DBdoctor的ASH历史活动会话历史快照可以看到几分钟前是否有人执行过DDL、是否出现过较长时间的“enq: TM - contention”等待。这些历史数据能把“看不见”的因素补全比如一个DBA曾在测试端跑过ALTER TABLE虽然SQL已经结束但它留下的锁元数据和资源状态仍然影响着后续会话。经过这四步一次数据库层面检测不到的死锁就从模糊的“卡顿”变成了一张清晰的关系图。3.4 诊断过程中需要注意的参数和配置如果准备在真实环境里复制这个诊断流程有几个配置要特别注意。对OGG Replicat来说如果开了BATCHSQL它会把一批操作打包成一个批处理事务这会让锁等待变得更为集中但也更容易隐藏“实际只有一行脏数据”的真相。在诊断时建议临时关闭BATCHSQL或者通过HANDLECOLLISIONS参数控制冲突处理让问题更容易暴露。另一个关键是数据库端的DDL_LOCK_TIMEOUT参数默认值是0即DDL拿不到锁会立即报ORA-00054但在某些版本和场景下等待行为可能被掩盖。我建议所有运行OGG的库都把DDL_LOCK_TIMEOUT设置一个较小的值比如5秒这样一旦出现潜在的锁竞争DDL会主动超时报错而不是傻傻等待并且会在alert日志里留下痕迹。4. 一次真实排查经历从延迟告警到完整还原4.1 现场采集的关键信息回到开头那次的故障。我记得当时v$session查出来的关键信息大概长这样select sid, serial#, program, blocking_session, event, seconds_in_wait from v$session where statusACTIVE and wait_class Idle;结果里有一行非常刺眼program列为“OGG Replicat”event是“enq: TX - row lock contention”blocking_session指向了一个SID256的会话。继续查SID256的program时发现它是“JDBC Thin Client”但进一步查询v$lock它手里攥着一把TM锁锁的对象是T_ORDER表。到这里直觉判断是OGG Replicat在等业务会话释放行锁这一判断看起来没毛病。于是我通过DBdoctor的会话管理功能把JDBC会话断开果然OGG Replicat立刻恢复了运行延迟也在几分钟内追平。我以为问题解决了。4.2 第一次“假解决”带来的麻烦但那只是表面。两个小时后同样的告警再次出现这次延迟涨得更快。我再查发现又是同一个组合OGG Replicat在等某个JDBC连接。当时我心想难道是业务侧写了一个定时任务持续霸占锁我再次分析了那个JDBC会话的SQL发现它执行的是一条普通的INSERT语句持续不到毫秒就结束了但在v$session里它的blocking_session却指向另一个奇怪的Program“oraclehost (J000)”——一个调度器作业Scheduler Job。顺着J000查下去发现它是一个数据库内部作业正在执行“DBMS_STATS.GATHER_TABLE_STATS”的统计信息收集作业而那个统计作业正在等待另一个资源为了采样它要在T_ORDER表上获取一个非常短暂的SN锁或SH锁但表上恰好有OGG的一个未提交事务锁互不兼容。这就构成了一个链OGG Replicat等业务INSERT的行锁业务INSERT等统计作业的SN锁统计作业又等OGG Replicat未提交事务释放锁。看到这里我终于意识到Oracle的自动死锁检测为什么没抛出ORA-00060因为统计信息收集作业并不是一个普通事务它的锁等待属于“统计锁”层面MySQL我不知道怎么做但在Oracle里这种锁等待图通常不会触发死锁检测。我们第一轮直接杀掉业务连接等于拆断了这根链条但根本矛盾没有解决过一会儿同样的闭环又会从另一个节点重新接上。4.3 DBdoctor画出的那张完整“环形等待图”这个案例里DBdoctor的锁等待拓扑图把每一个参与方都展示了出来OGG Replicat指向一个普通INSERT会话这个INSERT会话又指向自动统计信息作业自动统计信息作业反向指回OGG Replicat形成了一根封闭的环。图上每个节点还标注了等待起始时间和当前持有的锁类型。一眼看过去整个死锁链就清清楚楚。随后我点击环上的某个节点可以查看它在等待过程中切换过的所有等待事件——从TX行锁竞争切换到ENQ: TM – contention再到statistics lock这些事件切换记录是普通AWR报表里根本看不到的。DBdoctor还提供了一个“时间线回放”模式选择故障时间段它能按分钟展示所有涉及的会话状态和锁等待关系。回放后发现链条最早的一环其实是OGG的延迟而不是业务。OGG Replicat因为一条目标端慢SQL连续重试始终没有commit所以它持有的那行锁迟迟不放。这个初始问题导致后续所有会话都积聚在它的后面。等空闲统计作业一进来死锁链就闭环了。4.4 修复方案的落地最终我们做了三件事第一将OGG针对目标端慢SQL的“MAXTRANLOOP”或“GROUPTRANSOPS”参数适当调低避免单个事务积累过多操作而长时间不释放锁第二把自动统计信息收集的窗口避开OGG复制高峰并且给相关作业设置了LOCK_TIMEOUT让它拿不到锁就主动退出而不是SLEEP等待第三在DBdoctor里配置一个锁等待环检测告警一旦拓扑图中出现环状结构不管Oracle是否报错都自动通知值班人员。这个配置上线之后跟踪两周再也没有出现过同样性质的故障。5. 常见问题与排查技巧速查5.1 怎么快速判断“疑似OGG死锁”如果OGG Replicat进程正常业务也无明显报错但复制延迟持续增长第一步不是看OGG日志而是查数据库锁视图。重点关注三类信息程序名包含OGG的会话是否长时间处于非Idle等待blocking_session不为空且block_chain涉及多个Programv$lock里的LMODE为6排他或为3行排他持有者的实际SQL是否长时间没有提交。如果同时满足这三条基本可以确定有OGG参与的大型等待链。还有一个容易忽略的细节OGG Replicat在“LOGREADER”阶段时会话状态可能会显示为“SQL*Net message from client”很多人会把它误判为空闲会话。实际上那可能是OGG进程主循环在等待下一批日志提交的控制信号并不代表它真正空闲。要区分这个状态最好结合OGG自身的进程报告GGSCI里的stats来看如果行数没有变化说明它在等锁或在等更底层的队列而不是在工作。5.2 常用锁诊断工具对比下面这个表是我在实际工作中常用的排查工具和它们的适用场景工具/手段能看到的局限性适合场景v$session v$lock 手工SQL当前瞬时锁关系、blocking会话只能看单点快照很难自动发现环突发问题初步定位Oracle死锁traceORA-00060死锁涉及的两个事务和SQL对“看不见”的闭环无能为力数据库识别到的死锁AWR/ASH报告会话等待事件分布、历史活跃会话没有精确的锁持有链路关系事后分析性能瓶颈EM / Grid Control锁等待图、会话阻塞树多节点可视化较弱有时操作笨重单库或者小规模环境DBdoctor全局锁等待拓扑图、时间线回放、跨会话程序关联需要能采集到数据库诊断日志需要部署agent复杂环境中快速识别“看不见”的锁环这里想多说一句很多OCP培训资料里会让你直接查all_objects查看object_id再关联v$lock找阻塞但实际上遇到真正的多跳等待手工SQL的效率非常低。花几分钟把工具部署好比在命令行里反复join十几个视图效率高得多。5.3 与OGG共存时的三个保命习惯第一个习惯永远不要忽略OGG的未提交事务。OGG应用端默认采用“隐式事务”每批次结束才提交。任何业务查询如果在OGG的批次中访问同一行数据都会直接堵死在那。所以尽量给OGG的Replicat配置合理的GROUPTRANSOPS例如100避免一个批次攒太久。第二个习惯执行DDL之前先查OGG延迟。在线DDL虽然破坏性小但在目标库上执行时仍然会产生语言锁等待。如果OGG延迟很高先暂停Replicat或把DDL放到维护窗口否则极容易形成“DDL等OGG、OGG等DDL提交”这种闭环。别小看这个闭环Oracle极大概率不会报错。第三个习惯给所有外部脚本、定时任务、监控程序都加上“锁超时”。不要用默认的无限等待。比如在PL/SQL里使用DBMS_LOCK.SLEEP等于永久等待的写法或者Java里的setQueryTimeout设置太大都会成为死锁闭环里的隐藏节点。让每个参与方都有主动退出的时间整个系统反而更健壮。6. 写在最后共享资源场景下的锁管理思路我后来回看这次故障最大的教训不是“OGG抢锁”而是“谁也没有恶意抢锁”只是几个正常的数据库操作在并发条件下碰在了一起。OGG、业务SQL、统计作业、外部脚本每一个单独拿出来都无害连在一起就变成了数据库看不见的死锁环。过去我会习惯性地去杀阻塞会话以为断开一条链路就能解决。但现在我更倾向于先完整还原一张锁等待图看清楚谁是源头谁是被动等待哪个会话其实是“假阻塞”——它正因为锁超时即将失败只是还没走到失败那一步。如果你也想在生产环境里复制一下这个思路我建议先从最小的闭环开始练习。搭建一套OGG测试环境配置好一个持续复制的Replicat再写一个外部脚本去和它抢同一行数据最后用DBdoctor画出等待关系图。这个过程不需要太多理论知识只要你亲手看到一次“Oracle不报错但进程卡死”的样子以后遇到真问题时就不会慌。工具方面DBdoctor确实给排查提供了很大帮助你也能在官方文档里找到更细的配置说明而更重要的是保持那种“不到最后一个会话就不下结论”的耐心。
返回列表