执行计划变差需先查是否全表扫描,超10万行仍全扫即索引失效;常见因函数操作、绑定变量类型不匹配或隐式转换;验证用字面量重跑EXPLAIN PLAN;存储过程慢多因硬解析或资源不足,应查undo/临时表空间及锁。

执行计划突然变差,怎么快速确认是不是索引没走
别急着重写逻辑,先看真实执行路径里有没有 FULL TABLE SCAN——特别是目标表超 10 万行还扫全表,基本就是索引失效了。常见诱因不是没建索引,而是查询条件“绕开”了它:
-
WHERE UPPER(name) = :val:函数作用在列上,B-tree 索引直接失效 -
WHERE col_date > TO_DATE(:p_date, 'YYYY-MM-DD'):绑定变量被函数包裹,优化器无法做谓词推导,access("COL_DATE">TO_DATE(:P_DATE,'YYYY-MM-DD'))这种写法在 19c 里大概率不走索引 -
WHERE id = :p_id,但p_id是NVARCHAR2类型,而字段是VARCHAR2:隐式转换触发全表扫描
验证方法很简单:把绑定变量换成字面量(比如 WHERE order_date > DATE '2026-07-01'),再跑一次 EXPLAIN PLAN。如果这时走了索引,问题就出在绑定方式或类型匹配上。
存储过程里循环查数据,为什么越跑越慢
写 FOR rec IN (SELECT ...) 看似干净,但如果 SQL 带动态条件且没显式绑定,每次循环都会硬解析一次——共享池压力飙升,CPU 被吃光。更隐蔽的是隐式游标(比如 SELECT ... INTO v_var)也受同样影响。
- 把游标声明为带参数的静态形式:
CURSOR c_data(p_dt DATE) IS SELECT id, amt FROM t_log WHERE log_time > p_dt; - 在循环外打开:
OPEN c_data(v_start_date);,避免重复解析 - 批量处理改用
BULK COLLECT+FORALL,别在循环里拼接EXECUTE IMMEDIATE
特别注意:PL/SQL 中的绑定变量必须和字段类型严格一致,否则哪怕声明了 p_id NUMBER,调用时传入字符串 '123',也会触发隐式转换,让索引彻底失效。
怎么拿到存储过程里那条慢 SQL 的真实执行计划
EXPLAIN PLAN FOR 和 AUTOTRACE 都不可靠——前者不执行,后者用 NULL 或默认值代入绑定变量,执行计划严重失真。真正能反映运行时行为的,只有 SQL Trace。
- 先查目标会话:
SELECT sid, serial#, program FROM v$session WHERE username = 'YOUR_USER'; - 用 DBA 权限开启 trace:
EXEC DBMS_MONITOR.SESSION_TRACE_ENABLE(session_id => 123, serial_num => 4567, waits => TRUE, binds => TRUE); - 执行完后用
tkprof格式化 trace 文件:tkprof ora_123456.trc output.txt sort='(prsela,fchela,exeela)'
重点盯三处:Rows 远小于 Execs(条件写错)、MISS in library cache 多次(硬解析泛滥)、某条语句 Elapsed 高但 CPU 低、Waits 高(I/O 或锁卡住)。如果看到 UPDATE 的 Rows=1 但 Buffers 几十万,基本就是没走索引。
回滚段和表空间不足,也会让存储过程“假慢”
存储过程卡在某一步不动,未必是 SQL 慢,可能是 undo 空间或临时表空间撑爆了。Oracle 会等空间释放才继续,看起来像 hang。
- 查 undo 表空间可用率:
SELECT tablespace_name, trunc((free_space / total_space) * 100) || '%' FROM (...) WHERE tablespace_name LIKE 'UNDO%'; - 查临时表空间使用:
SELECT tablespace_name, used_blocks * block_size / 1024 / 1024 "MB USED" FROM v$sort_segment JOIN dba_tablespaces USING(tablespace_name); - 查锁等待:
SELECT sid, blocking_session, event, sql_id FROM v$session WHERE blocking_session IS NOT NULL;
这些资源类问题往往比 SQL 本身更难察觉——因为执行计划看着正常,trace 里也没明显耗时点,但整个会话就是不动。务必在排查 SQL 之前,先扫一遍空间和锁。


















