防止在Oracle中删除某些行

Mik*_*sen 5 sql oracle triggers oracle11g

我想防止VERSIONID=1在某个表中删除任何行.我还想在审计表中记录它,以便我们可以看到这种情况何时发生以便进行日志记录.我试图通过触发器来做到这一点:

CREATE TRIGGER TPMDBO.PreventVersionDelete
  BEFORE DELETE ON TPM_PROJECTVERSION
  FOR EACH ROW
DECLARE
BEGIN
  IF( :old.VERSIONID = 1 )
  THEN
    INSERT INTO TPM_AUDIT VALUES ('Query has attempted to delete root project version!', sysdate);
    RAISE_APPLICATION_ERROR( -20001, 'Query has attempted to delete root project version!' );
  END IF;
END;
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

SQL> delete from TPM_PROJECTVERSION where PROJECTID=70 and VERSIONID=1;
delete from TPM_PROJECTVERSION where PROJECTID=70 and VERSIONID=1
            *
ERROR at line 1:
ORA-20001: Query has attempted to delete root project version!
ORA-06512: at "TPMDBO.PREVENTVERSIONDELETE", line 6
ORA-04088: error during execution of trigger 'TPMDBO.PREVENTVERSIONDELETE'
Run Code Online (Sandbox Code Playgroud)

但是,该表TPM_AUDIT是空的.难道我做错了什么?

Jus*_*ave 10

如果触发器引发错误,则DELETE语句将失败,并且事务将回滚到在语句运行之前创建的隐式保存点.这意味着触发器所做的任何更改也会回滚.

您可以通过使用自治事务来解决此问题.就像是

CREATE PROCEDURE write_audit
AS
  PRAGMA AUTOMOMOUS_TRANSACTION;
BEGIN
  INSERT INTO tpm_audit
    VALUES( 'Query has attempted to delete root project version!',
            sysdate );
  commit;
END;

CREATE TRIGGER TPMDBO.PreventVersionDelete
  BEFORE DELETE ON TPM_PROJECTVERSION
  FOR EACH ROW
DECLARE
BEGIN
  IF( :old.VERSIONID = 1 )
  THEN
    write_audit;
    RAISE_APPLICATION_ERROR( -20001, 'Query has attempted to delete root project version!' );
  END IF;
END;
Run Code Online (Sandbox Code Playgroud)

这将使INSERT进入TPM_AUDIT到一个单独的事务可以在上下文之外被提交DELETE声明.但是,要非常小心使用自治事务

  1. 如果您发现自己使用自治事务而不是写入日志表,那么您几乎肯定会做错事.
  2. 使用自治事务声明的PL/SQL块中的代码是真正自治的,因此无法查看当前会话所做的未提交的更改.
  3. 由于写入一致性,Oracle完全有可能部分执行DELETE语句,多次触发行级触发器,回滚该工作,然后重新执行DELETE.但是,静默回滚不会回滚自治事务所做的更改.因此,完全有可能DELETE单个行中的一个实际上会导致触发器被多次触发,因此会创建多行TPM_AUDIT.