zoukankan      html  css  js  c++  java
  • Take Advantage of CONTEXT (dbms_session.set_context) in trigger to sync data bidirectionally

    假设我有如下两张一模一样的表... 

    create table tab1 (id number);

    create table tab2 (id number);

    我现在想做的事情是这样的,如果在tab1里面增加记录的同时把这些记录也增加到tab2中,反之tab2中增加的记录也要同步到tab1中去。很自然地想到可以通过在两张表上都创建一个trigger, 让Trigger来进行数据的同步工作。

    如下所示...

     
    create or replace trigger tri_tab1
    before
    insert on tab1
    for each row
    begin
    insert into tab2(id) values(:new.id);
    end;
    /

    create or replace trigger tri_tab2
    before
    insert on tab2
    for each row
    begin
    insert into tab1(id) values(:new.id);
    end;
    /

    “大功告成”了, 测试下...

    SQL> insert into tab1 values(1);
    insert into tab1 values(1)
    *
    ERROR at line
    1:
    ORA
    -00036: maximum number of recursive SQL levels (50) exceeded
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088:


    SQL
    > insert into tab2 values(1);
    insert into tab2 values(1)
    *
    ERROR at line
    1:
    ORA
    -00036: maximum number of recursive SQL levels (50) exceeded
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088:

    oops! 很不幸,碰到问题了。从错误信息看出来貌似是出现了“死循环”。 其实仔细想想这个是很正常的,我在两张表都创建了往对方表插入数据的trigger, 这个肯定会陷入万劫不复之地的。比如说我向tab1插入一条记录的时候,由于tab1上有个trigger会向表tab2插入数据,但是tab2上也有个trigger,这个时候也会触发,同时向tab1插入数据,这个时候就又绕回来了,于是乎就这么一直“纠缠”下去。好在oracle不笨,检测到这个情况(ORA-00036: maximum number of recursive SQL levels (50) exceeded)就退出了,不然一直累死不成。

    OK,问题知道出在什么地方了,那么接下来自然是要想办法来解决问题了。其实我们是希望这两个trigger不是同时执行的,当tri_tab1执行的时候,tri_tab2就不要执行,因为在任一时刻数据流向是“单向”的,我在表tab1中插入数据的时候是希望数据同时插入tab2中,在tab2中插入数据的时候同时将数据同步到tab1中,仅此而已,不能出现说数据在同一时刻是“双向”流动的。

    那么解决完问题的思路应该就是我在执行tri_tab1的时候,把tri_tab2给禁掉(disable),在tri_tab1执行完毕的时候再把tri_tab2恢复(enable)过来。反之依然。很容易想到可以通过"alter trigger xxx disable/enable'来办到,但是因为这是条ddl语句,会影响到transaction的完整性。如果不想影响到当前的transaction,当然可以把上面的开关语句(alter...disable/enable)放到一个单独的autonomous_transaction的procedure中执行,但是这个好像不是很好,把事情搞复杂了。

    其实好好研究下定义trigger的语法,可以发现row level的Trigger是有个when语句用来判断何时执行trigger的。那么自然而然地想到可以充分运用这个when语句,但是还有个问题是需要在某个地方设置个flag,这样通过读取这个flag来判断要不要执行这个trigger. 当然这个flag也会被Trigger来更改,这样才能起到开关的作用。很显然,如果借助表来存储flag的话,这个同样会遇到transaction完整性的问题。

    那么该怎么办呢? 其实Oracle的context就提供了一种存/取“全局”变量的这样一种机制,我们可以把这个flag设置到一个context中,这样可以通过读取/设置context来更改flag的状态。

    OK,下面就可以开工了.... 把trigger改成如下形式...

    create or replace trigger tri_tab1
    before
    insert on tab1
    for each row
    when
    (
    NVL(sys_context(
    'TEST_CONTEXT', 'TRI_TAB1'), 'Y') <> 'N'
    )
    begin
    dbms_session.set_context(
    'TRI_TAB2','N');
    insert into tab2(id) values(:new.id);
    dbms_session.set_context(
    'TRI_TAB2','Y');
    end;
    /

    create or replace trigger tri_tab2
    before
    insert on tab2
    for each row
    when
    (
    NVL(sys_context(
    'TEST_CONTEXT', 'TRI_TAB2'), 'Y') <> 'N'
    )
    begin
    dbms_session.set_context(
    'TRI_TAB1','N');
    insert into tab1(id) values(:new.id);
    dbms_session.set_context(
    'TRI_TAB1','Y');
    end;
    /

    通过增加WHEN语句,读取context ('TEST_CONTEXT') 中设置的值来判断是否运行Trigger,然后再Trigger中设置这个context的值来更改flag。 但是在执行的时候发现如下错误...

     SQL> insert into tab2 values(1);

    insert into tab2 values(1)
    *
    ERROR at line
    1:
    ORA
    -01031: insufficient privileges
    ORA
    -06512: at "SYS.DBMS_SESSION", line 90
    ORA
    -06512: at "FRANK.TRI_TAB1", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB1'
    ORA
    -06512: at "FRANK.TRI_TAB2", line 2
    ORA
    -04088: error during execution of trigger 'FRANK.TRI_TAB2'

     居然说没有权限执行DBMS_SESSION. 但是即使通过sys用户赋予FRANK这个权限,这个错误同样存在。

    后来了解到关于context的设置,oracle是有限制的,也就是dbms_session.set_context必须要在一个独立的package中定义,因此需要将上面的代码改成如下形式...

    SQL> create context test_context using context_package;

    Context created.

    SQL
    > create package context_package as
    2 procedure name_value(n varchar2, v varchar2);
    3 end;
    4 /

    Package created.

    SQL
    > create package body context_package as
    2 procedure name_value(n varchar2, v varchar2) as
    3 begin
    4 -- can only be called within the package to which it belongs
    5 -- If you try to execute DBMS_SESSION.SET_CONTEXT you'll get an error, as shown here:
    6 -- ORA-01031: insufficient privileges
    7 dbms_session.set_context('TEST_CONTEXT',n,v);
    8 end;
    9 end;
    10 /

    Package body created.

    SQL
    > create or replace trigger tri_tab1
    2 before insert on tab1
    3 for each row
    4 when
    5 (
    6 NVL(sys_context('TEST_CONTEXT', 'TRI_TAB1'), 'Y') <> 'N'
    7 )
    8 begin
    9 context_package.name_value('TRI_TAB2','N');
    10 insert into tab2(id) values(:new.id);
    11 context_package.name_value('TRI_TAB2','Y');
    12 end;
    13 /

    Trigger created.

    SQL
    > create or replace trigger tri_tab2
    2 before insert on tab2
    3 for each row
    4 when
    5 (
    6 NVL(sys_context('TEST_CONTEXT', 'TRI_TAB2'), 'Y') <> 'N'
    7 )
    8 begin
    9 context_package.name_value('TRI_TAB1','N');
    10 insert into tab1(id) values(:new.id);
    11 context_package.name_value('TRI_TAB1','Y');
    12 end;
    13 /

    Trigger created.

    现在再来测试一下...

    SQL> select * from tab1;

    no rows selected

    SQL
    > select * from tab2;

    no rows selected

    SQL
    > insert into tab1 values(1);

    1 row created.

    SQL
    > select * from tab2;

    ID
    ----------
    1

    SQL
    > select * from tab1;

    ID
    ----------
    1

    SQL
    > insert into tab2 values(3);

    1 row created.

    SQL
    > select * from tab1;

    ID
    ----------
    1
    3

    SQL
    > insert into tab2 select * from tab1;

    2 rows created.

    SQL
    > select * from tab2;

    ID
    ----------
    1
    3
    1
    3

    SQL
    > select * from tab1;

    ID
    ----------
    1
    3
    1
    3

    这下终于大功告成了!

  • 相关阅读:
    深拷贝与浅拷贝
    图片旋转插件
    promise 小抄
    github fork项目更改后与原作者同步更新
    eslint 的配置
    css规范
    Object类
    BigIntager
    System类
    Math类和Random类
  • 原文地址:https://www.cnblogs.com/fangwenyu/p/2031488.html
Copyright © 2011-2022 走看看