查v$sql中executions=1且parse_calls>1的语句可精准定位硬解析源头,常见于未绑定变量或nls/优化器参数不一致导致游标无法共享,应优先应用层改写而非依赖cursor_sharing。
查v$sql中executions = 1且parse_calls > 1的语句
这类语句是硬解析的实际推手,不是“执行少”,而是每次执行都重新解析——软解析失败后退化为硬解析。它比总硬解析数更精准地指向问题源头。
- 执行
SELECT sql_id, sql_text, executions, parse_calls FROM v$sql WHERE executions = 1 AND parse_calls > 1 AND rownum 快速抓出高频单次执行SQL - 重点看
sql_text是否仅数字/字符串字面量不同(如WHERE order_id = 1001vsWHERE order_id = 1002),这是未绑定变量的典型特征 - 若存储过程里用
EXECUTE IMMEDIATE 'UPDATE ... WHERE id = ' || v_id拼接SQL,必然触发此类语句
检查v$sql_shared_cursor定位游标无法重用原因
即使写了绑定变量,也可能因环境不一致导致游标无法共享,硬解析照常发生。直接查v$sql_shared_cursor比猜更可靠。
- 对疑似
sql_id执行SELECT * FROM v$sql_shared_cursor WHERE sql_id = 'xxx' AND (UNBOUND_CURSOR = 'Y' OR OPTIMIZER_MISMATCH = 'Y' OR NLS_LENGTH_SEMANTICS_MISMATCH = 'Y') -
OPTIMIZER_MISMATCH = 'Y'常见于会话级ALTER SESSION SET optimizer_features_enable被误设,或optimizer_mode不一致 -
NLS_LENGTH_SEMANTICS、NLS_SORT等NLS参数在应用连接时漂移,也会强制生成新游标
用BULK COLLECT + FORALL替代显式游标逐行处理
传统FETCH ... INTO循环在19c中会加剧共享池碎片,间接抬高硬解析率。批量操作不仅能减少逻辑读,还能显著降低游标生命周期和解析频次。
- 反例:显式游标+逐行
UPDATE,每行触发一次解析上下文切换 - 正例:
SELECT * BULK COLLECT INTO v_tab FROM orders WHERE status = 'P'; FORALL i IN 1..v_tab.COUNT UPDATE orders SET processed = 'Y' WHERE id = v_tab(i).id; - 注意:批量大小需控制(如
LIMIT 1000),避免内存溢出或锁等待放大
CURSOR_SHARING=FORCE不是万能解药
该参数可强制将字面量SQL改写为绑定变量形式,但会掩盖真实SQL结构,可能引发执行计划劣化,尤其在统计信息陈旧或数据倾斜严重时。
- 启用前必须确认
v$sql中存在大量FORCE型游标且child_number激增(说明计划未复用) - 配合
ALTER SYSTEM FLUSH SHARED_POOL使用风险极高,可能引发短时大面积硬解析风暴 - 真正稳定的解法仍是应用层改写:把
EXECUTE IMMEDIATE 'DELETE FROM t WHERE id = ' || x改为EXECUTE IMMEDIATE 'DELETE FROM t WHERE id = :1' USING x
硬解析问题从来不是孤立的SQL语法问题,而是游标生命周期、共享池内存管理、客户端连接行为三者交织的结果。最容易被忽略的是NLS参数漂移和会话级优化器设置——它们不会报错,却让同一段代码在不同会话里生成完全不同的执行计划,最终把CPU耗在反复解析上。











