漏写EXIT WHEN c%NOTFOUND是CURSOR+LOOP死循环主因,导致Oracle XE CPU飙至100%;须在FETCH后立即判断,用DBMS_JOB.BROKEN停调度任务,再Kill会话或重启实例临时缓解。
procedure里漏写EXIT WHEN引发无限循环
这是最典型的死循环起因:在cursor + loop结构中,忘记加exit when c%notfound或等效终止条件。oracle xe这类资源受限环境尤其敏感,一跑起来就卡死oracle.exe进程,cpu直接拉满到100%,连toad都连不上。
常见错误写法示例:
DECLARE
CURSOR c IS SELECT id FROM t;
r c%ROWTYPE;
BEGIN
OPEN c;
LOOP
FETCH c INTO r;
-- 这里缺了 EXIT WHEN c%NOTFOUND;
-- 后续逻辑(比如 INSERT/UPDATE)会无限执行
END LOOP;
CLOSE c;
END;- 只要游标没取完、又没显式退出,循环就永不停止
-
%NOTFOUND必须在FETCH之后立即判断,放错位置(比如放在循环末尾)仍可能多执行一轮 - 如果游标本身返回空集,
%NOTFOUND在第一次FETCH后即为TRUE,但没检查就进循环体,也会出问题
用DBMS_JOB.BROKEN停掉失控的调度任务
如果这个存储过程是通过DBMS_JOB或DBMS_SCHEDULER自动触发的,不能光杀会话——下次调度时间一到又跑起来。得先把它标记为BROKEN,阻止重复执行。
注意权限和视图范围:
- 用
SYS登录查DBA_JOBS能看到所有作业,但DBMS_JOB.BROKEN对非创建者调用会报ORA-23421 - 必须切换成该作业的创建用户登录,再查
USER_JOBS,然后执行EXEC DBMS_JOB.BROKEN(job#, TRUE) - 确认已生效:
SELECT job, broken FROM USER_JOBS WHERE job = 21,返回B表示已中断
别急着删作业——先停掉,观察CPU是否回落。如果还高,说明有其他会话或后台进程在跑,不是这个job单独导致的。
紧急情况下如何定位并终止高CPU会话
当数据库已严重卡顿,SQL*Plus连得上但响应极慢时,靠v$session关联v$process是最稳的路径:
- 先在操作系统用
top -Hp <pid>(Linux)或Process Explorer(Windows)找到占用CPU最高的线程ID(TID) - 进数据库执行:
SELECT sid, serial# FROM v$session WHERE paddr IN (SELECT addr FROM v$process WHERE spid = '<TID>'); - 拿到
sid, serial#后,立刻执行:ALTER SYSTEM KILL SESSION '<sid>,<serial#>' IMMEDIATE;
注意:IMMEDIATE参数很重要,否则可能挂住等事务回滚;但也要小心,强制KILL可能让未提交事务丢失数据。生产环境建议优先停job、改代码、再重启实例。
为什么重启实例后CPU立刻回落?
因为死循环过程是在PGA里跑的,属于会话私有内存。重启实例会清空所有会话上下文、释放PGA、重置DBMS_JOB队列状态。但这只是临时缓解——代码不修,下次部署或调度照样复现。
真正要盯住的点是:
- 所有含
LOOP的PL/SQL块,必须有且仅有一个明确的退出路径 - 用
FOR r IN c LOOP ... END LOOP语法更安全,它自动处理%NOTFOUND,但要注意隐式打开/关闭游标带来的锁和资源开销 - 上线前对含循环的存储过程做最小数据集测试,观察
v$session_longops是否有长时间未完成的操作
最易被忽略的是:开发环境数据量小,死循环几秒就跑完;一到生产环境数据量大,同一段代码就变成“隐形CPU杀手”。


















