存储过程变慢本质是索引失效引发执行计划退化,需先查执行计划确认是否走索引;oracle无运行时索引有效性函数,应通过plan_hash_value比对监控执行计划变更,并确保sql稳定性以保障监控有效。
存储过程变慢,**几乎从不直接因为“索引失效”本身**,而是因为索引失效导致执行计划退化(比如从索引范围扫描变成全表扫描),进而拖慢了存储过程中调用的 sql。所以问题本质是:你得先确认是不是 sql 真的没走索引,再决定要不要在存储过程里加监控——而不是一上来就埋点。
怎么快速验证存储过程里的 SQL 是否走了索引
别猜,直接查执行计划。进到存储过程涉及的 SQL 执行上下文(比如用 DBMS_SQL 动态执行,或直接抽出来跑),执行:
EXPLAIN PLAN FOR SELECT ... FROM your_table WHERE indexed_col = :val; SELECT * FROM TABLE(dbms_xplan.display(null, null, 'BASIC +PREDICATE'));
重点看两处:
-
OPERATION列是否含INDEX RANGE SCAN或INDEX UNIQUE SCAN; -
PREDICATE INFORMATION里是否显示用了你的索引字段,且没有隐式转换(如TO_CHAR("COL")=:B1)。
如果看到 FULL TABLE SCAN,再结合 v$object_usage 查对应索引是否被标记为 USED = 'NO',基本就能锁定是索引未被启用或失效。
为什么不能在存储过程里直接“监控索引是否失效”
Oracle 没有提供运行时函数能返回“索引当前是否有效可用”。v$object_usage 是监控开关打开后、SQL 实际执行时才记录的,它不是实时状态视图;而 dba_indexes.status 只反映索引是否 VALID(物理结构完好),和“是否被优化器选用”完全无关。
所以你在存储过程里写个 IF (index_is_invalid) THEN RAISE_APPLICATION_ERROR... 是行不通的——这个判断条件根本不存在。
真正可落地的做法是:
- 对关键 SQL 加
/*+ INDEX(t idx_name) */提示(仅限确认该索引一定更优时); - 在存储过程开头或关键分支,用
DBMS_UTILITY.GET_TIME记录耗时,超阈值(如 >500ms)时写入自定义日志表; - 日志表里必须存下
sql_id和child_number,方便事后查v$sql_plan追溯真实执行路径。
如何让告警真正有用,而不是天天收噪音
单纯“某次执行慢了就发邮件”没意义。你要区分是偶发抖动,还是结构性退化。推荐组合动作:
- 在存储过程编译后立即执行一次基准采集:
SELECT sql_id, plan_hash_value FROM v$sql WHERE sql_text LIKE '%your_proc_name%' AND ROWNUM=1,把plan_hash_value存进配置表; - 每次执行前查
v$sql对应sql_id的最新plan_hash_value,如果和基准不一致,且executions > 10,再触发告警; - 告警内容必须带:
old_plan_hash_value、new_plan_hash_value、last_active_time、以及一条可直接执行的SELECT * FROM TABLE(dbms_xplan.display_cursor('xxx',xxx))命令。
这种告警意味着“执行计划变了”,比“慢了”更有定位价值——它大概率指向索引失效、统计信息过期、绑定变量窥探异常等根因。
最易被忽略的一点:所有这些监控逻辑,都依赖 shared_pool 里能查到对应 SQL 的游标。如果应用端用了 /*+ NO_STATEMENT_CACHE */ 或频繁拼接 SQL 导致硬解析,v$sql 就查不到历史记录,整个监控链就断了。上线前务必确认应用层的 SQL 稳定性。











