ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

并行insert触发library cache lock与cursor: pin S wait on X:一次ORA-04021排查记录

并行insert触发library cache lock与cursor: pin S wait on X:一次ORA-04021排查记录 1. 凌晨五点的那条 ORA-04021把并行 insert 拖进了内存等待环先说清楚这篇要解决什么问题当你在 Oracle 里对分区表做并行insert或alter table ... move partition ... parallel时业务侧突然大面积报ORA-04021: timeout occurred while waiting to lock object同时v$session里出现library cache lock、cursor: pin S wait on X、row cache lock三个内存等待串成一条链。这篇记录的就是这样一次真实排查从等待事件查询、ASH 快照、alert 日志到参数检查最后给出可复现的验证动作和恢复手段。适合谁看日常维护分区表、跑定时move subpartition任务、又同时有高并发 DML 的 DBA 或后端同学。如果你只做单机小表、没有并行 DDL这篇可以当等待事件科普读。核心检索词先摆出来并行 insert、library cache lock、cursor: pin S wait on X、row cache lock、ORA-04021。这五个词基本就是整条故障链的骨架——业务 insert 卡在 cursor pin往上顶到 library cache lock再往上顶到 row cache lock而 row cache lock 的持有者是一个并行 DDL 的 slave 进程。我先把结论放前面方便你对号入座并行 DDLmove/exchange partition与同表 DML 并发时DDL 的 QC 进程和 PX slave 之间可能形成 row cache lock 与 PX Deq 的互相等待而 row cache lock 没有自动死锁检测于是 QC 长期以 X 模式持有 library cache lock业务 insert 的硬解析被卡住5 分钟后抛 ORA-04021。下面按排查顺序展开。2. 先看现场用等待事件 SQL 把阻塞链画出来故障发生在凌晨 5 点左右应用集中报ORA-04021且只集中在IM_MESSAGE这一张表的 insert 上其他表正常。这个“只影响一张表”的信息非常关键说明不是全局 shared pool 抖动而是针对某个对象的锁竞争。第一步永远是看谁在等谁。下面这条 SQL 直接输出阻塞链final_blocking_session能帮你跳过中间环节找到根-- 查看当前阻塞链定位最终阻塞者 select sid, serial#, status, blocking_session, final_blocking_session, event, p1, p2, p3, sql_id from v$session where blocking_session is not null order by final_blocking_session, sid;当时输出大致是这样我按记忆还原关键行SIDSTATUSBLOCKING_SESSIONFINAL_BLOCKING_SESSIONEVENTP115ACTIVE13471347row cache lock2783ACTIVE7851347cursor: pin S wait on X2660772620785ACTIVE13471347library cache lock21542875041347ACTIVE1515PX Deq: Execute Reply100看到这张表链条就清楚了783 等 785785 等 13471347 等 1515 又等 1347。1347 和 15 互相等待却没有报 ORA-60 死锁这是整件事最反直觉的地方后面第 5 节专门讲。接着确认每个会话在跑什么 SQL-- 查会话对应的 SQL 文本 select s.sid, s.sql_id, s.machine, s.program, s.osuser, q.sql_text from v$session s left join v$sql q on s.sql_id q.sql_id where s.sid in (15, 783, 785, 1347);结果一目了然SID 15 和 1347 的sql_id相同SQL 是alter table IM_MESSAGE MOVE SUBPARTITION SYS_SUBP357943 TABLESPACE DATA_WARM online PARALLEL (DEGREE 2)program 分别是(P001)和(J000)也就是 PX slave 和 PX QCjob 进程。SID 783 和 785 是业务 insertinsert into IM_MESSAGE (...) values (...)来自 JDBC Thin Client。到这里可以确定业务 insert 被一个每天 5 点跑的定时 move 子分区任务阻塞了。这个 job 已经稳定跑了半年以上所以第一反应是“是不是踩到 bug 了”。紧急处理就是 kill 掉 DDL 的 QC 会话让 slave 一起退出-- 先确认 serial# select sid, serial#, status, event from v$session where sid 15; -- 立即 kill注意 immediate 会回滚未提交的 DDL alter system kill session 15,24035 immediate;kill 完再查阻塞链no rows selected业务恢复。但根因没找到所以先留一份 ASH 快照等白天分析-- 保留现场便于事后分析 create table ash_bak_0717 as select * from v$active_session_history;注意v$active_session_history默认只保留最近约 1 小时取决于 AWR 配置故障后要尽快落表否则样本会被冲掉。3. TaoToken 前置把排查脚本和模型对话串起来排查这类问题光靠手敲 SQL 容易漏参数、漏视图。我的做法是把常用诊断脚本沉淀成模板再用一个稳定的模型对话入口来辅助解读 ASH 输出、比对 MOS 文档编号。这里我用的是 TaoToken 的模型对话能力把等待事件文本、alert 片段贴进去让它帮我梳理因果链比纯人肉翻文档快不少。如果你也想搭一套类似的排查辅助流程可以按下面几步走打开模型对话页面新建一个会话专门放 Oracle 等待事件分析https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite在控制台生成一个 API Key后续脚本调用或本地工具接入都用它https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite需要看接口参数和鉴权方式时翻接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite如果你要长期跑编码类 Agent 或自动化诊断脚本用 Coding Plan 更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteAPI 基地址是https://taotoken.net/api不带任何查询参数。把 ASH 查询结果、alert 日志片段、v$rowcache输出整理成文本贴进对话让它帮你判断“这个 p1 对应哪个 dc_ 字典”“这个 stack 落在哪个函数”比在几十个 MOS 编号里翻要省事。提示模型给的是分析思路和文档线索最终结论仍要回到数据库实测验证别直接拿模型输出当定论。4. 可复制配置ASH、row cache、library cache 三件套查询这一节是全文最实用的部分把当时用到的查询全部整理成可直接复制的脚本。建议按顺序执行。4.1 从 ASH 还原历史等待链故障已过v$session看不到现场只能靠 ASH。下面这条按 sample 时间聚合找出同一时刻的等待链-- 从 ASH 快照还原等待链按时间片聚合 select sample_time, session_id, session_serial#, event, blocking_session, blocking_session_serial#, sql_id, p1, p2, p3 from ash_bak_0717 where sample_time between to_date(2021-07-17 05:00:00,yyyy-mm-dd hh24:mi:ss) and to_date(2021-07-17 05:10:00,yyyy-mm-dd hh24:mi:ss) and (blocking_session is not null or event like %row cache% or event like %library cache% or event like %cursor: pin%) order by sample_time, session_id;重点看blocking_session和event两列把同一sample_time的行连起来就能复现第 2 节那张阻塞表。4.2 定位 row cache lock 到底等的是哪个字典row cache lock的p1是 cache#对应v$rowcache的cache#。当时 SID 15 的p12-- 根据 p1 查等待的数据字典对象 select cache#, type, parameter, count, usage, fixed, gets, getmisses from v$rowcache where cache# 2;返回PARENT dc_segments。也就是说PX slave 在等dc_segments这个数据字典行缓存而它被 QC1347持有——因为 QC 正在做move subpartition需要分配段、更新段信息。dc_segments的竞争通常出现在段分配、extent 扩展、分区维护这类操作上。你可以用下面这条看当前谁在持有-- 查看当前 row cache lock 等待明细 select s.sid, s.event, s.p1, s.p2, s.p3, r.type, r.parameter from v$session s join v$rowcache r on s.p1 r.cache# where s.event row cache lock;4.3 确认 library cache lock 的持有者library cache lock的p1是 handle 地址p2是 lock 地址p3是请求模式1Null, 2Share, 3Exclusive。当时 SID 785 的p12154287504。要找到持有者用下面这条-- 查找 library cache lock 的持有者 select s.sid, s.serial#, s.event, s.p1, s.p2, s.p3, s.blocking_session, s.sql_id from v$session s where s.event library cache lock or s.sid in (select blocking_session from v$session where event library cache lock);更精确的方式是结合x$kgllk需要一定权限但生产环境一般用v$session的阻塞关系就够定位了。当时 785 等 1347而 1347 正以 X 模式持有IM_MESSAGE的 library cache lock因为它要修改这个对象的定义move 分区会改段信息。4.4 参数检查清单并行 DDL 相关的几个参数建议逐项核对参数建议值说明parallel_degree_policyMANUAL保守AUTO 会让优化器自行决定并行度DDL 场景容易放大parallel_max_servers按 CPU 核数控制过大导致 PX slave 争抢字典资源parallel_min_servers0 或较小避免常驻 slave 占用资源_px_use_large_pool默认一般不动除非有明确 MOS 指引shared_pool_size足够大且稳定过小会频繁 resize加剧 library cache 竞争查询当前值-- 检查并行与共享池相关参数 select name, value, isdefault from v$parameter where name in (parallel_degree_policy,parallel_max_servers, parallel_min_servers,shared_pool_size, shared_pool_reserved_size) order by name;注意shared_pool_size如果频繁自动调整会在 alert 里看到Resize operation completed之类的记录配合library cache lock一起出现时优先怀疑共享池抖动。5. 验证请求与成功结果复现一次并确认恢复光看历史不够得能复现、能验证。下面这套动作在测试库上做过能稳定复现出类似的等待链生产环境请勿直接照搬务必在测试库验证。5.1 构造并发场景准备一张分区表开两个会话会话 A模拟业务 insert循环执行-- 会话 A持续 insert制造硬解析压力 begin for i in 1..100000 loop insert into IM_MESSAGE (id, content) values (i, test); commit; end loop; end; /会话 B模拟并行 move 子分区-- 会话 B并行 move 子分区 alter table IM_MESSAGE move subpartition SYS_SUBP357943 tablespace DATA_WARM online parallel (degree 2);5.2 观察等待链在会话 B 执行期间用第 2 节的阻塞链 SQL 反复查询应该能看到类似row cache lock→PX Deq: Execute Reply的互相等待以及业务侧的library cache lock、cursor: pin S wait on X。5.3 验证恢复动作确认问题后恢复动作有两个层次临时恢复kill 掉 DDL 的 QC 会话业务立即恢复。-- 找到 DDL 的 QC 会话并 kill select sid, serial#, program, sql_id from v$session where sql_id (select sql_id from v$sql where sql_text like alter table IM_MESSAGE%move% and rownum 1); alter system kill session sid,serial# immediate;长期规避把 move 语句的并行去掉或者改到业务低峰且与 DML 错开的时间窗。-- 去掉 parallel改为串行执行 alter table IM_MESSAGE move subpartition SYS_SUBP357943 tablespace DATA_WARM online;当时改成串行后因为每次 move 的是子分区、数据量不大速度完全可接受问题再没复现。这是最省事的解法。5.4 成功结果确认恢复后应满足v$session中blocking_session is not null返回空。业务侧ORA-04021报错停止。alert 日志不再出现WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK!。-- 确认无阻塞 select count(*) from v$session where blocking_session is not null; -- 确认无异常内存等待 select sid, event, p1, p2, p3 from v$session where event in (library cache lock,cursor: pin S wait on X,row cache lock);6. 本篇常见错排查为什么没报死锁、为什么只卡一张表这一节回答几个当时最困惑的点也是你排查时最容易走弯路的地方。为什么 1347 和 15 互相等待却不报 ORA-60因为row cache lock没有自动死锁检测机制。Oracle 的 enqueue 死锁检测ORA-60和 library cache 死锁检测ORA-4020/4021覆盖不到 row cache enqueue。当某进程等row cache lock超过约 3 秒会 check 一次累计 1000 次约 50 分钟还没拿到就在 alert 里输出WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK!并 dump system state但不会自动解除。所以这种“死锁”只能靠人工 kill 或 OS 层杀进程。为什么只影响 IM_MESSAGE 一张表因为 library cache lock 是对象级的。1347 以 X 模式持有的是IM_MESSAGE的 handle只有引用这个对象的 SQL 才会被阻塞。其他表的 insert 不涉及这个 handle自然不受影响。这也是判断“是不是对象级锁竞争”的重要线索。为什么业务 insert 会走到 cursor: pin S wait on X11g 之后 library cache pin 被 mutex 取代。业务 insert 在硬解析时需要以 S 模式 pin 住 cursor而 DDL 以 X 模式占着于是出现cursor: pin S wait on X。它和library cache lock往往成对出现一个管 handle 的并发一个管 heap 的一致性。alert 里的 stack 怎么读当时 stack 里有kqrget→kqrLockPo→kqrpre2这是 row cache 相关函数还有ktsla_alloc_extent、ktsla_extent_allocate说明卡在段分配。结合dc_segments的等待可以确认是段分配阶段的字典竞争。读 stack 不用全懂抓住kqr*row cache和kts*段管理前缀就能定位方向。几个容易踩的坑只 kill 业务会话没用要 kill DDL 的 QC 会话slave 才会一起退出。alter system kill session ... immediate会回滚未完成的 DDLmove 操作需要重跑。ASH 快照要第一时间落表否则样本被冲掉后只能靠 AWR 的粗粒度数据。别急着下“一定是 bug”的结论先确认参数和并发模式很多情况是并行度设置不当放大了竞争。7. 语义一致 CTA把排查脚本沉淀成可复用的诊断流这次排查最大的收获不是找到某个 bug 编号而是把“等待链查询 → ASH 还原 → row cache 字典定位 → 参数核对”这套流程固化下来。下次再遇到library cache lock或cursor: pin S wait on X直接按第 4 节的脚本跑一遍十分钟内能画出阻塞链。如果你也想把这套诊断脚本和模型对话结合起来让模型帮你解读 ASH 输出、比对等待事件文档可以从模型对话入口开始https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。需要脚本调用或本地工具接入去控制台拿 API Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。接口细节看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。长期跑自动化诊断 Agent 的话Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。最后留一个我自己的习惯每次处理完这类内存等待故障都把当时的阻塞链 SQL 输出、v$rowcache结果、alert 片段存成一个 case 文件标注日期和表名。攒到十几个 case 之后你会发现很多“疑似 bug”其实是同一类并发模式改个并行度或错开时间窗就解决了。
返回列表