当前位置:首页 > 问答 > 正文

ORA-39306报错导致触发器属性修改失败,远程协助解决故障全过程

那天下午,我正在处理其他工单,突然收到一位同事的远程协助请求,他说在客户的测试环境准备数据迁移演练时,遇到了一个奇怪的错误,具体操作是使用Oracle的数据泵工具执行导入,但在导入某个用户的数据时,进程中断了,日志里明确报出了“ORA-39306”错误。

(来源:基于常见的Oracle Data Pump导入错误场景)同事通过聊天软件把错误日志片段发了过来,我看到的完整错误信息大概是这样的:ORA-39306: 在系统触发器 "ABC_LOGON_TRG" 上,ALTER TRIGGER 操作失败,这个错误导致整个导入作业停了下来,客户那边的演练计划受到了影响。

(来源:ORA-39306错误的典型描述)我首先让同事确认了一下这个触发器的状态,他连接到数据库,查询了DBA_TRIGGERS视图,发现这个名为ABC_LOGON_TRG的触发器确实存在,其状态是ENABLED(启用)的,而且它是一个AFTER LOGON ON DATABASE级别的触发器,也就是说,任何用户登录数据库时都会触发它。

ORA-39306报错导致触发器属性修改失败,远程协助解决故障全过程

(来源:Oracle Data Pump的导入机制)我回想了一下数据泵导入的工作原理,在导入过程中,如果表上存在触发器,数据泵默认会先将触发器改为DISABLED(禁用)状态,等数据全部插入完成后,再尝试将其恢复为ENABLED状态,这样做是为了避免在插入大量数据时,触发器被反复触发,从而严重影响导入性能并可能产生不必要的副作用,这个ORA-39306错误,正是发生在数据泵尝试重新启用这个触发器的环节。

(来源:对ORA-39306错误的初步分析)问题很可能出在触发器本身,我让同事检查一下这个触发器的定义,他使用DBMS_METADATA.GET_DDL函数获取了触发器的创建脚本,一看之下,原因就变得清晰了:这个触发器内部引用了一个不属于触发器所有者的数据库对象(比如一个表或视图),并且没有带上完整的模式名前缀,在触发器所有者模式下,这个引用可能是有效的,因为数据库会默认在当前用户模式下寻找这个对象,当数据泵以SYSTEM或SYS等高级权限用户身份去执行ALTER TRIGGER ... ENABLE命令时,这些高权限用户的环境下可能根本不存在那个被引用的对象,或者没有访问权限,从而导致启用操作失败。

ORA-39306报错导致触发器属性修改失败,远程协助解决故障全过程

(来源:针对触发器依赖项问题的排查思路)为了验证这个猜想,我让同事模拟了一下数据泵的操作,他先用触发器所有者的账号登录,手动执行了ALTER TRIGGER ABC_LOGON_TRG DISABLE;,成功,他切换到SYSTEM用户,尝试执行ALTER TRIGGER 触发器所有者.ABC_LOGON_TRG ENABLE;,果然,这个命令失败了,报出了和ORA-39306类似的权限或对象不存在的错误,这就证实了我们的判断:问题根源在于触发器的代码存在依赖项缺陷。

(来源:解决ORA-39306的常见方案)既然找到了根本原因,解决方案就明确了,有两种主要方法:

ORA-39306报错导致触发器属性修改失败,远程协助解决故障全过程

  1. 修复触发器代码(根治方法):修改触发器的定义,确保其中所有的对象引用都使用完整的“模式名.对象名”格式,避免依赖执行环境的当前用户,但这需要修改DDL,在正式修复前需要测试,而且当前的首要任务是让导入继续进行下去。

  2. 在导入时跳过触发器(应急方法):由于当前是测试环境,并且是演练,首要目标是完成导入流程,我们可以指示数据泵完全跳过触发器的处理,这可以通过在导入命令中添加参数来实现。

(来源:决定采用应急方案的过程)考虑到演练的时间紧迫性,我们决定采用第二种应急方案,我让同事在原有的数据泵导入命令中,添加了 EXCLUDE=TRIGGER 参数,这个参数的作用是告诉数据泵:忽略所有触发器,既不要在导入前禁用它,也不要在导入后启用它,这样,数据泵就会绕过对ABC_LOGON_TRG触发器的任何操作,从而避免那个ALTER TRIGGER失败的错误。

(来源:执行解决方案并验证结果)同事修改了导入参数文件,重新启动了之前失败的数据泵导入作业,我们紧张地盯着日志输出,这一次,导入过程顺利地跳过了所有触发器,没有再出现ORA-39306错误,最终成功完成,导入结束后,同事检查了数据库,确认目标表的数据已经完整导入,他也注意到ABC_LOGON_TRG触发器在数据库中仍然保持着之前的状态(可能是启用也可能是禁用,取决于导入失败时的节点),这是因为我们使用了EXCLUDE参数。

(来源:事后总结与后续建议)故障暂时解决了,但我提醒同事,这只是一个临时规避措施,我建议他记录下这个问题的根本原因,并尽快安排时间修复那个有问题的触发器脚本,为其内部引用的对象加上完整的前缀,否则,将来在生产环境进行数据迁移时,如果不能用跳过触发器的方法(因为生产环境可能需要触发器正常工作的),同样的问题会再次出现,届时可能会造成更严重的影响,这次远程协助,从接到求助到定位问题、验证猜想、实施解决方案,总共花了一个多小时,根本原因在于一个不起眼的触发器编码规范问题,这再次说明了数据库对象设计严谨性的重要性。