存储过程慢90%以上源于sql或执行路径问题;应查v$session中active且等待长的会话,用dbms_xplan.display_cursor分析实际执行计划,重点关注rows与e-rows偏差、等待事件及绑定变量类型匹配。

存储过程跑得慢,90% 以上的问题不在 PL/SQL 语法本身,而在它调用的 SQL 或执行路径上。直接看 v$session 和 DBMS_XPLAN.DISPLAY_CURSOR 就能定位到卡在哪一行、哪条 SQL、哪个等待事件。
怎么快速定位正在慢跑的存储过程?
别等它跑完再查——先抓“活口”。重点看 v$session 中状态为 ACTIVE 且有长时间等待的会话:
- 查当前最耗时的活跃会话:
SELECT sid, serial#, sql_id, event, seconds_in_wait FROM v$session WHERE status = 'ACTIVE' AND username IS NOT NULL ORDER BY seconds_in_wait DESC - 如果
event是db file sequential read,说明在走索引但块读太多;如果是cursor: pin S wait on X,大概率是硬解析风暴 - 拿到
sql_id后立刻查实际执行计划:SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('your_sql_id', NULL, 'ALLSTATS LAST')),注意看Rows列(实际返回行数)和E-Rows(预估行数)是否差一个数量级
为什么加了 BULK COLLECT 还是慢?
不是用了就一定快——关键看上下文。常见误区是只改了 FETCH,却没动循环体里的逻辑:
-
BULK COLLECT INTO后直接写FOR i IN 1..coll.COUNT LOOP处理,看似批量,但如果循环里每轮都执行一次UPDATE或INSERT,仍是 N 次单行 DML - 正确做法是搭配
FORALL:把更新逻辑移到循环外,用FORALL i IN 1..coll.COUNT UPDATE t SET x = coll(i).x WHERE id = coll(i).id - 别忽略内存限制:
LIMIT值设太小(如 100)会导致频繁分批,设太大(如 100000)可能触发 PGA 不足或大量内存分配开销;建议从 5000 起调,观察v$pgastat的total PGA allocated
绑定变量导致执行计划漂移怎么办?
PL/SQL 里用 :p_date 看似安全,但 Oracle 19c 的绑定变量窥探(bind peeking)可能让优化器选错索引。尤其当字段类型与传入值不一致时:
- 比如
WHERE order_id = :p_id,而p_id是VARCHAR2类型,但表中order_id是NUMBER,就会触发隐式转换,索引失效 - 验证方法:把存储过程中该 SQL 单独拿出来,把
:p_id替换成字面量(如12345),再跑EXPLAIN PLAN,如果这时走了索引,问题就出在绑定类型 - 强制走索引的临时解法是加提示:
/*+ INDEX(t idx_order_date) */,但治标不治本;长期方案是统一变量类型,或用TO_NUMBER(:p_id)显式转换
游标反复打开关闭,CPU 却很高?
PL/SQL 里写 FOR rec IN (SELECT ...) 看起来简洁,但每次循环都会重新 parse + execute —— 尤其当这个 SELECT 带动态条件又没绑定时,共享池压力会飙升:
- 显式声明带参数的游标:
CURSOR c_data(p_dt DATE) IS SELECT id FROM t_log WHERE log_time > p_dt - 在循环前
OPEN c_data(v_start_date),循环中FETCH,结束后CLOSE - 更彻底的解法是用
REF CURSOR+DBMS_SQL动态拼接,但仅限极少数必须动态列名的场景;多数情况用静态游标 + 绑定变量足够
真正难的不是找到慢 SQL,而是判断“为什么这条 SQL 在存储过程里变慢了,但在 SQL*Plus 里很快”——差异往往藏在会话级参数(如 optimizer_mode)、统计信息新鲜度、甚至客户端字符集设置里。别跳过 DBA_HIST_SQLSTAT 对比历史执行时间,那是最可靠的锚点。











