模拟软解析引发的latch: library cache:一次可复现的配置与验证

发布时间:2026/9/28 20:00:21
模拟软解析引发的latch: library cache:一次可复现的配置与验证 1. 从一次压测说起软解析为什么也会卡在 latch: library cache很多人第一次看到latch: library cache这个等待事件第一反应都是「是不是硬解析太多了」。这个直觉对了一半。硬解析确实是最典型的元凶因为每次硬解析都要去 library cache 里反复检索、还要额外分配 chunk持有 latch 的时间和次数都会飙升。但真正容易被忽略的是软解析同样要拿 library cache latch。原因不复杂。软解析虽然省掉了优化器生成执行计划那一大段开销但语法检查、语义检查、权限校验这些步骤一个都跑不掉而这些检查都要去 library cache 里比对 SQL 文本、对象定义、游标信息。也就是说只要会话在解析不管硬软都得先抢到 library cache latch 才能干活。当大量会话同时做软解析latch 就变成了一个高频争抢的热点CPU 明明没跑满业务却开始变慢。这篇就聚焦这个场景构造一个高并发软解析负载把latch: library cache稳定复现出来再用 AWR/ASH 去验证最后给出可复制的初始化参数和 SQL 脚本。适合已经在做 Oracle 性能排查、想搞清楚 Hard Parsing 和 Soft Parsing 边界的同学。整套流程我在测试库上跑过参数和脚本都能直接抄。2. 复现前的环境准备与 TaoToken 接入复现实验本身在本地 Oracle 实例上做就行但排查过程中经常需要一边查文档、一边让模型帮忙解读 AWR 片段、生成诊断 SQL这时候有个顺手的模型入口会省很多事。我平时用 TaoToken 来做这类辅助工作它的 API 兼容常见的大模型调用格式接入成本很低。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你只是想临时问几个 Oracle 等待事件的问题直接用模型对话页面就够了https://taotoken.net/deep-link/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite要是打算长期把模型接进自己的排查脚本、或者做 Agent 化的诊断助手建议走 Coding Plan额度和调用方式更适合持续使用https://taotoken.net/deep-link/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewriteAPI 的基础地址是https://taotoken.net/api注意这个地址不带任何跟踪参数配置客户端时直接填它。下面给一个最小可用的调用示例用 curl 验证 key 是否生效curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -d { model: claude-sonnet-4-5, messages: [ {role: user, content: 解释 Oracle 中 latch: library cache 与 shared pool latch 的区别} ] }返回里能看到正常的choices结构就说明通了。这一步只是把辅助工具准备好真正的主角还是 Oracle 实例本身。3. 可复制的复现配置参数、脚本与并发负载3.1 先确认 library cache child latch 的数量library cache latch 不是一把锁而是一组子 latchchild latch数量取的是大于 CPU 核数的最小质数。先看清楚自己环境里有多少把show parameter cpu_count; select rownum, name, gets, misses, sleeps from v$latch_children where name library cache order by child#;gets是获取次数misses是没抢到、需要自旋或睡眠的次数sleeps是真正睡下去的次数。复现实验里我们重点盯sleeps的增长它比gets更能说明争用。3.2 关键参数把 session_cached_cursors 关掉软解析争用的核心开关是session_cached_cursors。这个参数控制每个会话最多缓存多少个已关闭的游标。默认值通常不是 0Oracle 会把执行过三次以上的 SQL 游标信息SQL 文本 指向 library cache 的指针存到 PGA 里下次同样的 SQL 进来直接在 PGA 命中就不用再去抢 library cache latch 了。要复现争用就得把这个「后门」堵上alter session set session_cached_cursors 0;设成 0 之后每次执行都要老老实实去 library cache 里走一遍解析流程latch 争用才会暴露出来。这也是为什么生产上如果这个参数被误设成 0软解析压力会明显放大。3.3 构造软解析负载的 PL/SQL 脚本下面这段脚本用execute immediate反复执行同一条 SQL。因为是同一条语句硬解析只会在第一次发生后面全是软解析正好用来隔离出「纯软解析」场景-- 准备一张测试表 create table test as select 1 as id from dual; -- 单会话软解析压测 declare v_sql varchar2(200) : select 1 from test test; begin for i in 1 .. 1000000 loop execute immediate v_sql; end loop; end; /单会话跑起来后再开多个窗口同时执行同一段脚本模拟并发软解析。我实测下来4 个会话同时跑latch: library cache的等待就开始明显堆积了。3.4 观察等待事件的实时 SQL一边压测一边用下面这条 SQL 看等待select sid, event, p1, p2, p3, wait_time, seconds_in_wait from v$session_wait where event latch: library cache;p1是 latch 地址p2是 latch 编号p3是尝试获取的次数。如果多个会话的p1相同说明它们抢的是同一把 child latch争用就集中在这里。4. 验证请求与成功结果AWR 和 ASH 怎么读4.1 用 ASH 抓实时采样ASH 是定位 latch 争用最直接的工具因为它按秒采样能看到等待在时间轴上的分布select sample_time, session_id, session_state, event, blocking_session, p1, p2 from v$active_session_history where event latch: library cache and sample_time sysdate - 1/24/60 order by sample_time desc;如果采样里latch: library cache密集出现且session_state是WAITING就说明争用确实发生了。再看blocking_sessionlatch 等待通常没有明确的阻塞者这也是它和行锁等待的区别之一。4.2 用 AWR 看 Top 5 Timed Events压测跑够一段时间后生成两个快照对比 AWR 报告-- 生成快照 exec dbms_workload_repository.create_snapshot(); -- 稍等片刻再生成一个 exec dbms_workload_repository.create_snapshot(); -- 查看快照列表 select snap_id, begin_interval_time, end_interval_time from dba_hist_snapshot order by snap_id desc;在 AWR 报告的「Top 5 Timed Foreground Events」里如果latch: library cache排进前列且% DB time占比明显就坐实了。同时去看「Instance Activity Stats」里的parse count (total)、parse count (hard)、parse count (failures)如果 total 很高而 hard 很低就是典型的软解析主导。4.3 确认 Hard Parsing 与 Soft Parsing 的边界这一步是整篇的关键。用下面这条 SQL 把硬软解析的比例算清楚select name, value from v$sysstat where name in ( parse count (total), parse count (hard), parse count (failures), execute count );判断逻辑很简单parse count (hard)接近parse count (total)说明硬解析为主问题在 SQL 没复用。parse count (total)远大于parse count (hard)但latch: library cache依然严重说明软解析本身就成了瓶颈这时候光优化 SQL 复用没用得从session_cached_cursors、游标缓存、并发度这些角度下手。我实测的结果是关掉session_cached_cursors后parse count (hard)几乎不增长但latch: library cache的 sleeps 持续上升完美复现了「纯软解析引发争用」的场景。5. 本篇常见错误排查5.1 压测跑不起来等待事件不出现最常见的原因是session_cached_cursors没关掉或者关的只是当前会话压测脚本跑在别的会话里。确认方法select sid, value from v$sesparam where parameter session_cached_cursors;如果 value 不是 0说明没生效。注意alter session只影响当前会话压测脚本必须在同一个会话里执行。5.2 只看到 latch: shared pool没有 library cache这两个等待经常一起出现但成因不同。latch: shared pool主要和空闲 chunk 扫描、内存分配有关硬解析多的时候它更突出。如果只看到 shared pool 而看不到 library cache说明你的负载里硬解析占比太高把execute immediate换成绑定变量、或者确认 SQL 文本完全一致让硬解析降下来library cache 的争用才会浮出来。5.3 AWR 里 parse count 数据对不上有时候v$sysstat和 AWR 报告里的解析数据有出入这是因为 AWR 是按快照区间统计的而v$sysstat是实例启动以来的累计值。做对比时一定要用两个快照之间的差值别直接拿累计值去比。5.4 压测把测试库跑挂了for i in 1 .. 1000000这种循环如果并发开太多测试库的 CPU 和 latch 会被打满严重时实例响应变慢甚至 hang 住。建议并发会话控制在 4 到 8 个别一上来就开几十个。循环次数先设小一点比如 100000确认现象出现后再加大。压测前确认这是测试库别在生产上做。5.5 想进一步定位是哪把 child latch 在争用前面v$latch_children的查询按sleeps排序select child#, gets, misses, sleeps, wait_time from v$latch_children where name library cache order by sleeps desc;sleeps最高的那个 child# 就是热点。如果所有 child 的 sleeps 都很平均说明争用是全局性的不是某一把 latch 的问题这时候要从降低整体解析频率入手。6. 把工具链固定下来从复现到长期排查复现只是第一步真正有价值的是把这套方法固化成日常排查流程。我的做法是把上面这些诊断 SQL 整理成一个脚本集压测时一键采集 ASH 采样和 latch children 快照事后用 AWR 做区间对比。这样每次遇到latch: library cache不用从头想查什么。如果你也想把模型接进这套流程让它帮忙解读 AWR 片段、生成诊断 SQL、或者做 Agent 化的自动排查可以先把 API Key 配好https://taotoken.net/deep-link/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档在这里里面有各语言 SDK 的调用示例和参数说明https://taotoken.net/deep-link/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite控制台可以管理用量和查看调用记录https://taotoken.net/deep-link/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite最后留一个我踩过的坑session_cached_cursors这个参数生产上不要随便设成 0。它看起来只是个游标缓存开关但关掉之后软解析的 latch 压力会成倍放大尤其是在高并发短连接场景下。如果确实要调先在小流量环境验证观察latch: library cache的 sleeps 变化再决定要不要动。