一、一个经典又诡异的现象
当年做项目时,Oracle数据库里跑着几十上百个存储过程、函数、包。整个Oracle本身就是一个庞大的逻辑处理平台。
偶尔会发现,某个功能突然报错。一查,存储过程失效了。重新编译一下,恢复正常。
后来发现一个规律:每隔一两个月,总有两三个对象会莫名其妙地失效。没人动过代码,也没人改过表结构。就像有幽灵定期光顾一样。
最头疼的是,每次出问题必须上去重新编译所有对象才能彻底解决。这种“运行了几个月突然失效”的情况,比“改完代码忘记编译”难排查得多。
今天就把这个问题的根源彻底聊透。
二、问题本质:对象变成了INVALID
Oracle里每个存储过程都有一个状态标记:VALID或INVALID。当存储过程依赖的对象发生变化时,Oracle会自动把依赖它的所有存储过程标记为INVALID。
这里的“依赖对象”不限于表结构。表、视图、其他存储过程、系统包、同义词……只要存储过程里引用了,都是依赖。
常规情况下,对象失效是因为有人改了底层表结构。更隐蔽的情况——代码没动,表结构没改,权限没变,但对象还是失效了。这就得往更深层去排查。
三、最可能的“元凶”
1. 自动维护任务导致的失效
Oracle数据库自带了几个定时任务,在晚上自动运行。其中最常见的一个叫auto optimizer stats collection(自动收集统计信息)。
这个任务在对表执行DBMS_STATS收集统计信息时,如果使用了CASCADE => TRUE参数,会强制把依赖该表的所有游标标记为失效,并尝试重新编译。如果编译过程中遇到依赖链里其他对象也有问题,它就停在那里,对象保持INVALID状态。
排查方法:查看自动任务日志,确认失效发生的时间点和自动统计信息收集的时间点是否吻合。
sql
-- 查看自动任务执行历史 SELECT * FROM dba_autotask_job_history WHERE client_name = 'auto optimizer stats collection' ORDER BY job_start_time DESC;
如果失效对象的last_ddl_time和自动任务执行时间吻合,那基本可以确定是它在“捣鬼”。
2. 系统级对象的失效传递
Oracle里有些系统包是很多存储过程的底层依赖。比如DBMS_STATS、DBMS_SCHEDULER、DBMS_CRYPTO、UTL_HTTP等。
如果数据库打过补丁、升级过版本、或者DBA手动编译过这些系统包,所有依赖它们的存储过程都会连锁失效。这种情况通常是全局性的,影响面很大。
排查方法:查看失效对象的依赖链,找到链条尽头那个最初失效的对象,看它是不是系统包。
sql
-- 查看失效对象依赖了哪些底层对象 SELECT * FROM dba_dependencies WHERE owner = 'YOUR_SCHEMA' AND name = 'YOUR_INVALID_PROC' AND referenced_type = 'PACKAGE' AND referenced_owner = 'SYS';
如果失效对象依赖了SYS下的系统包,那大概率是系统包被重新编译过。
3. 定时任务或DDL脚本的“幕后操作”
有没有定时执行的DDL脚本?比如定期TRUNCATE某个日志表,或者重建某个索引,或者对某个表执行DROP再CREATE?
这些DDL操作都会让依赖它们的存储过程失效。而且因为是定时任务自动执行的,操作往往在半夜,你根本不知道它跑过。
排查方法:查看数据库的审计日志,看近期有没有TRUNCATE、DROP、CREATE、ALTER等DDL操作。
sql
SELECT * FROM dba_audit_trail WHERE action_name IN ('TRUNCATE', 'DROP', 'CREATE', 'ALTER') ORDER BY timestamp DESC;四、如何彻底解决
1. 主动监控,早于业务发现
定期扫描失效对象,一旦发现有INVALID对象,立刻告警。可以在定时任务里加上这段查询,每天跑一次。
sql
SELECT object_name, object_type, status, last_ddl_time FROM dba_objects WHERE owner = 'YOUR_SCHEMA' AND object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY') AND status = 'INVALID';2. 自动重新编译,不用人工介入
在定时任务里加上自动编译逻辑,扫描到失效对象立刻自动编译,不等业务报错。
sql
BEGIN FOR obj IN (SELECT object_name, object_type FROM user_objects WHERE status = 'INVALID') LOOP EXECUTE IMMEDIATE 'ALTER ' || obj.object_type || ' ' || obj.object_name || ' COMPILE'; END LOOP; END; /
3. 找到根源,从源头解决
如果某个表或对象频繁导致失效,检查它是否被自动统计信息任务频繁处理。如果是,可以对这个表单独设置统计信息收集策略——比如锁定统计信息,或者调高统计信息收集的阈值。
sql
-- 锁定统计信息,避免自动任务频繁触发失效 EXEC DBMS_STATS.LOCK_TABLE_STATS('SCHEMA_NAME', 'TABLE_NAME');五、总结
| 现象 | 最可能的原因 | 解法 |
|---|---|---|
| 没改代码,几个月后突然失效 | 自动统计信息收集任务触发 | 锁定统计信息,或调高自动收集阈值 |
| 失效对象涉及系统包 | 数据库升级或补丁导致 | 检查升级历史,批量重编译 |
| 失效对象集中在某个表 | 有定时DDL操作(TRUNCATE等) | 查审计日志,找到根源操作 |
| 多个对象同时失效 | 依赖链传递失效 | 查dba_dependencies,找到链条尽头 |
最关键的一点是:下次再遇到这种“没改代码却失效”的情况,先别急着重新编译,而是立刻查一下失效对象的依赖链和最近的DDL操作。只有找到失效的根源,才能从源头解决这个问题,而不是每次被动地重新编译。
供参考。