首页 > 数据库 >Oracle 对象依赖性与对象失效

Oracle 对象依赖性与对象失效

时间:2023-05-06 10:32:32浏览次数:47  
标签:依赖 依赖性 对象 视图 编译 引用 Oracle 失效


一、 起源

开发A在从库查询视图遇到报错:

执行的SQL为: select * from V_TEST_JOIN 
具体错误信息为:java.sql.SQLSyntaxErrorException: 
ORA-04045: 在重新编译/重新验证 V_TEST_JOIN 时出错
ORA-16000: 数据库或可插入数据库是以只读访问方式打开的

二、 报错原因及处理方法

1. 报错原因

视图V_TEST_JOIN失效,在select时会自动编译,但访问的是从库,而从库是只读的,因此编译会报错ORA-16000: 数据库或可插入数据库是以只读访问方式打开的

2. 处理方法

报错处理方法极其简单,在主库编译失效对象即可。常见编译方法如下:

  • 在主库查询该对象由其自动编译
  • ALTER … COMPILE;手动编译
ALTER PACKAGE my_package COMPILE;
ALTER PACKAGE my_package COMPILE BODY;
ALTER PROCEDURE my_procedure COMPILE;
ALTER FUNCTION my_function COMPILE;
ALTER TRIGGER my_trigger COMPILE;
ALTER VIEW my_view COMPILE;

也可以生成重编译的批量脚本后一起执行:

SELECT 'ALTER '||OBJECT_TYPE||' '||OBJECT_NAME ||' COMPILE;'
  FROM USER_OBJECTS
 WHERE STATUS='INVALID';
  • utlrp.sql脚本

执行@$ORACLE_HOME/rdbms/admin/utlrp.sql脚本编译数据库失效对象。

数据库的升级迁移等操作可能会导致数据库中大量对象失效,由于对象间可能存在复杂的依赖关系,手工编译通常无法顺利通过。Oracle强烈建议在升级迁移后,运行此脚本编译失效对象。utlrp.sql中调用了$ORACLE_HOME/rdbms/admin/utlrcmp.sql来编译失效对象

此脚本需要用SQLPLUS以SYSDBA身份运行,运行时中最好不要有活动事物或DDL操作,否则极容易导致死锁的出现。

 

  • DBMS_DDL包 (只用于PL/SQL对象,不能编译视图):
EXEC DBMS_DDL('PACKAGE','MY_SCHEMA','MY_PACKAGE'); 
EXEC DBMS_DDL('PACKAGE BODY','MY_SCHEMA','MY_PACKAGE'); 
EXEC DBMS_DDL('PROCEDURE','MY_SCHEMA','MY_PROCEDURE'); 
EXEC DBMS_DDL('FUNCTION','MY_SCHEMA','MY_FUNCTION'); 
EXEC DBMS_DDL('TRIGGER','MY_SCHEMA','MY_TRIGGER');
  • 使用DBMS_UTILITY.compile_schema

ORACLE提供了自动编译的接口,调用这个过程会编译指定schema下所有失效的视图、存储过程、函数、触发器、包

exec dbms_utility.compile_schema('SCOTT')
  • UTL_RECOMP.RECOMP_PARALLEL、UTL_RECOMP.RECOMP_SERIAL分别用并行或串行方式进行编译
-- Schema level.EXEC UTL_RECOMP.recomp_serial('SCOTT');
EXEC UTL_RECOMP.recomp_parallel(4,'SCOTT');

 -- Database level.EXEC UTL_RECOMP.recomp_serial();
EXEC UTL_RECOMP.recomp_parallel(4);

 -- Using job_queue_processes value.EXEC UTL_RECOMP.recomp_parallel();
EXEC UTL_RECOMP.recomp_parallel(NULL,'SCOTT');

UTL_RECOMP包的一些使用限制:

(1) 并行执行使用的是job队列,当运行并行编译的时候所有job都会被disable直到编译完成

(2) 包必须在sqlplus中以sysdba权限用户运行

(3) UTL_RECOMP依赖于DBMS_STANDARD、DBMS_JOB、DBMS_RANDOM

(4) 在运行这个包时执行DDL语句可能会导致死锁.

 

三、 如何找到对象失效时间及原因

1. 如果对象是因为编译报错失效的,可以用以下sql查出

select object_name, status, last_ddl_time, e.line, e.text
  from dba_objects d, dba_errors e
 where d.status = 'INVALID'
   and d.OBJECT_NAME = e.name
   and d.object_type = e.type
   and d.owner = e.owner

2. 如果是因为引用对象被修改

可以使用以下方法大概判断,不一定准确(但是也能压到一个较小的时间范围去定位)

思路:对象失效是由于其引用对象更改,可以通过DBA_DEPENDENCIES找到失效对象的引用对象,再由dba_objects找到引用对象的last_ddl_time (ldt1),大于失效对象last_ddl_time (ldt2)并且离其最近的时间一般就是该对象失效的时间(ldt3)。去找ldt3时对应的ddl审计日志,对应sql语句一般就是导致对象失效的原因。

 

下面是个实际的例子

-- 启用enable_ddl_logging
SQL> show parameter ddl

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
ddl_lock_timeout                     integer     0
enable_ddl_logging                   boolean     FALSE

SQL> alter system set enable_ddl_logging=true;
System altered.

SQL> show parameter ddl

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
ddl_lock_timeout                     integer     0
enable_ddl_logging                   boolean     TRUE

有一个失效对象V_TEST_JOIN,last_ddl_time (ldt2)为2019/9/26 15:21:58

Oracle 对象依赖性与对象失效_SQL

查看其引用对象

select * from dba_dependencies d where d.name='V_TEST_JOIN';

Oracle 对象依赖性与对象失效_重新编译_02

查看引用对象last_ddl_time (ldt1)

select d.object_name,d.status,d.last_ddl_time from dba_objects d where d.OBJECT_NAME in('TEST01','TEST02');

Oracle 对象依赖性与对象失效_重新编译_03

可以看到大于ldt2且离其最近的时间ldt3是2019/9/26 15:23:38,这个时间应该就是对象真正失效的时间。

根据ldt3去找ddl日志

cd $ORACLE_BASE/diag/rdbms/conuat/conuat/log

less ddl_conuat.log
…
Thu Sep 26 15:23:38 2019
diag_adl:
alter table TEST02 add testcol number

所以失效的原因是基表TEST02加了列

这里有另外一个问题,在反向测试给表加列会不会导致视图失效的时候,发现基表加列后测试的视图并没有失效,甚至给V_TEST_JOIN视图基表TEST01加列的时候视图也不会失效。

Oracle 对象依赖性与对象失效_SQL_04

Oracle 对象依赖性与对象失效_EXEC_05

为什么会有这种现象,这就需要去看对象失效究竟是怎么定义的,究竟什么操作才会导致对象失效。

这个答案我们放在最后。

 

四、 对象依赖性

1. 依赖关系概述

某些类型的对象可以在其定义中引用其他对象。如果对象A的定义引用了对象B,则A是(B的)依赖对象,而B是(A的)引用对象。( If the definition of object A references object B, then A is a dependent object (of B) and B is a referenced object (of A).)

Oracle自动跟踪和管理依赖关系,以确保依赖对象A相对于其引用对象B而言始终是最新的。当B以可能影响A的方式更改时(通常是DDL语句),A会被标记为失效。在使用A之前,必须针对B的新定义重新编译失效的依赖对象。当引用失效的依赖对象时,它会自动重新编译。由于这种自动管理,A永远不会使用过时的B版本,并且在更改B之后几乎不需要显式重新编译A。

 

2. 依赖关系查看

通过USER_DEPENDENCIES,ALL_DEPENDENCIES和DBA_DEPENDENCIES视图查询对象间依赖关系

Oracle 对象依赖性与对象失效_EXEC_06

utldtree.sql脚本可创建视图DEPTREE和IDEPTREE。DEPTREE中包含对象依赖关系树的信息,而IDEPTREE则为格式化后的DEPTREE

3. 对象状态

状态

含义

Valid

对象已成功编译

Compiled with errors

编译对象时产生了错误

Invalid

对象被标记为失效,因为它引用的对象已更改(只有依赖对象状态才会为Invalid)

Unauthorized

对引用对象的访问权限已被回收(只有依赖对象状态才会为Unauthorized)

注意:USER_OBJECTS,ALL_OBJECTS和DBA_OBJECTS不能区分“Compiled with errors、Invalid和“Unauthorized,而将这些状态统一描述为INVALID

五、 对象失效

1. 失效影响

对象失效通过以下方式影响应用程序:

  • 失效对象必须先重新验证/编译才能被应用程序使用,会增加应用程序执行的延迟。如果失效对象的数量很大,则首次执行时增加的延迟可能会很大
  • 存储过程、函数和包的失效可能会导致并发执行这些对象的其他会话异常(通常是在等待失效对象编译而被大量阻塞)
  • 如果会话A运行应用程序的同时,在另一个会话B打了补丁,则执行应用程序的会话A会发现使用中的对象已失效,并引发以下4个异常之一:ORA-04061,ORA-04064,ORA-04065或ORA-04068,必须在重启应用程序会话来解决

2. 级联失效

如果对象A依赖于对象B,而对象B依赖于对象C,则A直接依赖于B,B直接依赖于C,而A间接依赖于C。A->B->C

  • 对于直接依赖,只有更改了对影响它们的引用对象操作会使它们失效。
  • 对于间接依赖,它们可能因为引用对象的失效而失效。例如更改C使B失效,则也将使A(还有B的所有直接和间接依赖对象)失效。这就是所谓的级联失效

3. 粗粒度失效与细粒度失效

在11g前的版本,对象依赖项是在对象级别进行管理的,更改引用对象会自动使所有依赖对象失效,这称为粗粒度失效。11g开始引入了细粒度依赖管理新特性,更改引用对象时,只有直接影响依赖对象的更改才会导致依赖对象失效。

下面是会导致粗、细粒度失效的操作列表:

  • 未在表中列出的所有DDL操作均会导致粗粒度失效
  • 左列中的操作会导致细粒度的失效(操作对象不符合右列情况)
  • 当执行左列操作且对象符合右列情况时,会导致粗粒度失效

操作

例外

ALTER TABLE table ADD column

  1. 依赖对象使用SELECT * from table(视图除外)
  2. 依赖对象使用table%rowtype
  3. 依赖对象执行INSERT时未指定具体列名(insert into test values(1,2,3);)
  4. 依赖对象是查询中join连接的非基础表(不是全部列都显示的那张表)
  5. 依赖对象在查询中使用了PL/SQL变量

ALTER TABLE table {MODIFY|RENAME|DROP|SET UNUSED} column

ALTER TABLE table DROP CONSTRAINT not_null_constraint

  1. 依赖对象直接引用column
  2. 依赖对象使用SELECT * from table
  3. 依赖对象用使用table%rowtype
  4. 依赖对象执行INSERT时未指定具体列名
  5. 依赖对象是依赖于整行的触发器(即未在定义中指定具体列)

CREATE OR REPLACE VIEW view

 

Online Table Redefinition(DBMS_REDEFINITION)

  1. 新旧定义列的列表不同,且符合下列至少一项:
  2. 新视图或表定义中的依赖对象引用列已被修改或删除
  3. 依赖对象使用view%rowtype或table%rowtype
  4. 依赖对象执行INSERT时未指定具体列名
  5. 新视图定义引入了新列,查询中的依赖对象引用视图或包含联接的表。
  6. 新视图定义引入了新列,且依赖对象是查询中join连接的非基础表
  7. 新视图定义引入了新列,且依赖对象在查询中使用了PL/SQL变量
  8. 依赖对象使用RELIES ON子句引用视图或表。

CREATE OR REPLACE SYNONYM synonym

  1. 新旧synonym目标不同,其中一个不是表。
  2. 新旧synonym目标都是表,但这些表的列不同或授权不同。
  3. 新旧synonym目标都是表,且依赖对象是一个视图,该视图引用了一个列,该列在旧目标上是唯一索引的一部分,而在新目标上不是。

DROP INDEX

  1. 该索引是基于函数的索引且依赖对象是触发器。依赖对象是依赖于整行或者使用了函数列的触发器
  2. 索引是唯一索引,依赖对象是视图,且该视图引用了该唯一索引中的列

CREATE OR REPLACE {PROCEDURE|FUNCTION}

  1. 更改调用签名。调用签名是指参数列表(参数顺序、名称和类型)、返回类型,ACCESSIBLE BY子句(白名单),纯度、确定性(determinism),并行性、流水线(pipelining)和实现属性(需要存储过程或函数是用C或Java实现)

CREATE OR REPLACE PACKAGE

  1. ACCESSIBLE BY 子句、包纯度更改
  2. 依赖对象引用已删除或重命名的包或子项
  3. 依赖对象引用的存储过程或函数调用签名或entry-point number已更改
  4. 如果引用的存储过程或函数具有多个重载候选,任何重载候选的调用签名或entry-point number发生了更改,或者添加或删除了一个候选,依赖对象将失效
  5. 依赖对象引用了调用签名、entry-point number或行类型已更改的包游标
  6. 依赖对象引用了定义已更改的package type or subtype
  7. 依赖对象引用了名称、数据类型、初始值或偏移数已更改的包变量或常量

 

六、 对象重新验证

失效的对象必须经过验证才能重新使用。访问对象时,验证会自动进行,不需用户操作。

1. 重新验证编译有错误的对象

编译器无法自动重新验证编译时出错的对象。使用编译器重新编译该对象,如果重新编译没有错误,将对其进行重新验证,否则它仍旧失效。

2. 重新验证未授权的对象

编译器检查依赖对象是否有对引用的访问权限。若有,编译器将重新验证未经授权的对象,而无需重新编译它,否则编译器会报错。

3. 重新验证失效的SQL对象

SQL编译器重新编译失效的对象。如果重新编译没有错误,将对其进行重新验证,否则它仍旧失效。

4. 重新验证失效的PL/SQL对象

对于失效的PL/SQL程序单元(过程,函数或程序包),PL/SQL编译器检查是否有引用对象以影响失效对象的方式进行更改。

  • 若有,编译器将重新编译失效的对象。如果重新编译没有错误,将对其进行重新验证,否则它仍旧失效
  • 若没有,编译器仅重新验证失效的对象而不进行重新编译

七、 疑问的答案

看到这里,我们已经找到了开头那个问题的答案。为什么给基表加列有时视图会失效有时不失效?

V_TEST的定义是

CREATE OR REPLACE VIEW V_TEST AS select * from test092502;

对于单表,加列不会导致视图失效(但是视图中也查不到新加列)

V_TEST_JOIN的定义是

CREATE OR REPLACE VIEW V_TEST_JOIN AS
SELECT a."ID",a."AGE",a."TESTCOL",b.name FROM TEST01 a left join TEST02 b on a.id=b.id;

所以给TEST02加列视图失效而给TEST01加列视图不失效

 

参考


https://docs.oracle.com/database/121/ADFNS/adfns_dependencies.htm#ADFNS99992

https://docs.oracle.com/database/121/ADMIN/general.htm#ADMIN12491

https://oracle-base.com/articles/11g/enhanced-finer-grained-dependency-management-11gr1

标签:依赖,依赖性,对象,视图,编译,引用,Oracle,失效
From: https://blog.51cto.com/u_13631369/6249118

相关文章

  • crm提示对象名CRM ProductUnit 无效
    1、服务模块操作的时候提示:--备份数据服务器上面执行脚本---产品单位IFNOTEXISTS(SELECT1FROMsysobjectsWHEREid=OBJECT_ID(N'CRM_ProductUnit')ANDOBJECTPROPERTY(id,N'IsUserTable')=1)BEGINCREATETABLECRM_ProductUnit([ID][INT]IDENTITYPRIMA......
  • Unity 2019中对象池的用法
    usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassExamplePool:MonoBehaviour{privateObjectPool<GameObject>objectPool;publicGameObjectobjectType;publicintpoolSize=10;voidStart()......
  • Java 对象数组
    数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用类型中的类时,我们称为对象数组。1、案例定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。问题一:打印出3年级(state值为3)的学......
  • 85.局部对象
      在C++语言中,名字有作用域(参见2.2.4节,第43页),对象有生命周期(lifetime)。理解这两个概念非常重要。●名字的作用域是程序文本的一部分,名字在其中可见。●对象的生命周期是程序执行过程中该对象存在的一段时间。  如我们所知,函数体是一个语句块。块构成一个新的作用域,我们可......
  • oracle中的自动增长列的插入实现
    我们知道,在oracle中自动增长类型是依靠sequence类实现的,但是我们在插入一条数据时不能像sqlServer或者mySql中那么方便必须使用sequence.nextval来取得输入的值,能不能做到和Sqlserver一样吗,比如有表:createtableEMP(EMPIDNUMBER(4)notnull,EMPNAMEVARCHAR2(4000),EMPPAS......
  • oracle比较大小函数greatest与least
    目录oracle比较大小函数greatest与least1、greatest函数取最大值2、least函数取最小值oracle比较大小函数greatest与least比较出最大或者最小值的函数,null为最大或最小的值。语法:select函数名(value_1,value_2,...,value_n)fromdual;1、greatest函数取最大值select......
  • Oracle 给用户赋予dblink权限,创建dblink
    1.判断用户是否具备创建databaselink的权限  一:查看当前登录用户(如果查询有返回行,则表示具备创建权限,否则,需要创建权限)1select*fromuser_sys_privswhereprivilegelikeupper('%LINK%');二:查询某个用户test(如果查询有返回行,则表示具备创建权......
  • oracle中用户删除不了,ORA-01940提示 “无法删除当前已连接用户”
    Oracle删除用户的提示无法删除当前已连接用户两种解决方法如下:1、先锁定用户、然后查询进程号,最后删除对应的进程、在删除对应的用户SQLalteruserXXXaccountlock;SQLSELECT*FROMV$SESSIONWHEREUSERNAME='LGDB';Oracle删除用户的提示无法删除当前已连接用户两种解决......
  • Oracle使用Impdp导入dmp文件的详细过程
    这一天为了导入这个Oracle的dmp文件,简直就是血泪史,因本人对Oracle并不是很会,随意踩了很多小白会踩的坑,因此特意记录一下过程,防备下次的使用。1、首先将你需要的dmp文件准备好,将其放在Oracle安装目录的任意位置,但是如果你想按照我的步骤来,就和我安装到相同的目录,否则会和第五步的......
  • Oracle 强行断开用户连接的方法
    1、查找目标用户的当前进程selectsid,serial#fromv$sessionwhereusername='test';2、使用上述语句会返回一个进程列表,每行有两个数字,用数字替代下面的sid和serialaltersystemkillsession'sid,serial';3、执行kill语句altersystemkillsession‘9,154......