分类

链接

2011 年 12 月
 1234
567891011
12131415161718
19202122232425
262728293031  

近期文章

热门标签

新人福利,免费薅羊毛

现在位置:    首页 > Oracle > 正文
共享办公室出租
Oracle事务示例及注意事项
Oracle 暂无评论 阅读(2,930)

ORACLE事务学习笔记

 

有关事务的语句

commit, rollback, savepoint, settransaction

 

drop table t2 purge;

create table t2 (x int);

 

 

drop table t3 purge;

create table t3 (x int check(x>0));

 

 

create or replace trigger t_trigger

  before insert or delete on t3  for each row

declare

begin

if(inserting) then

 update t2 set x=x+1;

else

 update t2 set x=x-1;

 end if;

 

 dbms_output.put_line(sql%rowcount||'条记录已经被触发并且更新');

endt_trigger;

 

insert into t2 values(1);

 

insert into t3 values(1);

--1条记录已经被触发并且更新

 

insert into t3 values(-1);

--1条记录已经被触发并且更新

 

select * from t2

----------

2

 

我们发现即使insert into t3 values(-1);失败,触发器也被触发一次,但是被回滚了

 

如果在触发器中增加一个提交

 

insert into t3 values(-1);

 

insert into t3 values(-1)

 

ORA-04092: COMMIT 不能在触发器中

ORA-06512: 在"SCOTT.T_TRIGGER", line 10

ORA-04088: 触发器 'SCOTT.T_TRIGGER' 执行过程中出错;

 

如果一定要在触发器中记录操作,则可以采用自主事务

create or replace trigger t_trigger

  before insert or delete on t3  for each row

declare

pragma autonomous_transaction;

begin

if(inserting) then

 update t2 set x=x+1;

else

 update t2 set x=x-1;

 end if;

 

 dbms_output.put_line(sql%rowcount||'条记录已经被触发并且更新');

 commit;

end t_trigger;

 

insert into t3 values(-1)

 

select * from t2

-----------

3

 

如果改变触发器before为after又如何?

 

 

Commits时,到底发生了什么?

 

Commit之前的操作

在内存SGA产生了回滚段

在内存SGA产生了修改数据块

在内存SGA产生重做

取决于前面3项的大小与消耗的时间,一部分数据已经刷新到磁盘

已经获得所有的锁定

commit;---------

为系统产生一个系统更改好SCN

进程LGWR将余下的缓冲区中的redo存储在磁盘上;并在redo中记录SCN;

(LGWR的刷新条件1、3s;  2、满1/3或者1m; 3、commit)

释放所有的锁定;释放队列中所占用的所有事项;

访问多个修改事务块,对缓存中的快速访问或者“清除”;

 

基于commit的内部触发动作(如基于commit的mv的快速刷新)

 

如果理解上面的话,应该理解下面的一些事实:

1、 commit是一个极快的动作;

2、 在一个过程中,平衡地使用commit;

 

 

[察看当前session的事务所产生redo的语句]

 

selecta.sid,a.statistic#,a.value

fromv$sesstat a, v$statname b

wherea.STATISTIC#=b.STATISTIC# and

      b.NAME like 'redo size' and

      sid=( select sid from v$mystat where rownum=1)

 

 

 

drop table t4 purge;     

create table t4

(

a char(200),

b char(200)

);

 

declare

  -- Local variables here

  i integer;

  redo_amount_start  integer;

  redo_amount_end    integer;

  start_time         integer;

  v_a                integer;

  v_b                integer;

begin

select a.value into redo_amount_start

fromv$sesstat a, v$statname b

wherea.STATISTIC#=b.STATISTIC# and

      b.NAME like 'redo size' and

      sid=( select sid from v$mystat where rownum=1);

     

start_time:=dbms_utility.get_time;

 

 

for i in 1..10000 loop

v_a:=i;

v_b:=i+100;

insert into t4 values(v_a,v_b);

commit;

end loop;

 

select a.value into redo_amount_end

fromv$sesstat a, v$statname b

wherea.STATISTIC#=b.STATISTIC# and

      b.NAME like 'redo size' and

      sid=( select sid from v$mystat where rownum=1);

 

dbms_output.put_line('redo_size='||(redo_amount_end-redo_amount_start));

dbms_output.put_line((dbms_utility.get_time-start_time)/100);

 

execute immediate 'truncate table t4';

select a.value into redo_amount_start

from v$sesstata, v$statname b

wherea.STATISTIC#=b.STATISTIC# and

      b.NAME like 'redo size' and

      sid=( select sid from v$mystat where rownum=1);

    

start_time:=dbms_utility.get_time;

 

 

for i in 1..10000 loop

v_a:=i;

v_b:=i+100;

insert into t4 values(v_a,v_b);

end loop;

commit;

select a.value into redo_amount_end

fromv$sesstat a, v$statname b

wherea.STATISTIC#=b.STATISTIC# and

      b.NAME like 'redo size' and

      sid=( select sid from v$mystat where rownum=1);

 

dbms_output.put_line('redo_size='||(redo_amount_end-redo_amount_start));

dbms_output.put_line((dbms_utility.get_time-start_time)/100);

 

end;

 

-------------------

redo_size=9191340

1.24

redo_size=6641732

.45

 

rollback会发生什么事情;

 

 

 

在游标中使用commit是一个很不好的习惯

create table t5

as

select cast(substr(object_name,1,1) as varchar2(2)) first_char from all_objects

 

declare

  i integer;

begin

  for x in (select * from t5) loop

   delete from t5 where first_char= x.first_char;

   dbms_output.put_line(sql%rowcount);

   commit;

  end loop;

end;

 

自主事务

关键字

PRAGMA AUTONOMOUS_TRANSACTION;

表示为一个自主事务,独立于父事务,就是说自主事务的提交与否不影响父事务的提交或者回滚。

 

举例说明

create table emp_dup as select * from empwhere 1=2

 

自主事务

create or replace procedure p_log(p_row_count in integer) is

pragma autonomous_transaction;

begin

  insert into log_table values(p_row_count);

  commit;

end p_log;

 

主事务

create or replace procedure p_main is

begin

  insert into emp_dup select * from emp;

  p_log(sql%rowcount);

  rollback;

end p_main;

 

 

执行测试

begin

p_main;

end;

 

select * from log_table

-------

15

 

 

自主事务的主要用途

1、审计

2、避免变异表错误

3、躲开ora-14552错误

 

避免变异表错误

[在9i下执行][在11g没有出现04091错误]

create or replace trigger t_mutate_table

  before update on emp_dup 

  for each row

declare

row_count int;

begin

select count(*)into row_count from emp where empno=:new.empno;

ifrow_count>0 then

insert into log_table values(:new.empno);

end if; 

end t_mutate_table;

 

update emp_dup

set sal=sal+10

where empno=5555

 

 

select * from log_table

 

解决方法

create or replace trigger t_mutate_table

  before update on emp_dup 

  for each row

declare

pragma autonomous_transaction;

row_count int;

begin

select count(*)into row_count from emp where empno=:new.empno;

ifrow_count>0 then

insert into log_table values(:new.empno);

commit;

end if; 

 

 

躲开ora-14552错误

 

truncate table emp_dup

 

create type o_emp as object

(

EMPNO    NUMBER(4)   ,                       

ENAME    VARCHAR2(10) ,                        

JOB      VARCHAR2(9) ,                        

MGR      NUMBER(4)   ,                        

HIREDATE DATE         ,                        

SAL      NUMBER(7,2) ,                        

COMM     NUMBER(7,2) ,                        

DEPTNO   NUMBER(2)   

);

 

select empno,ename, job, mgr, hiredate, sal, comm, deptno from emp_dup

 

create type t_emp_dup is table of o_emp

 

create or replace function f_get_emp_dup(p_deptnoin integer) return t_emp_dup is

  Result t_emp_dup;

begin

  insert into emp_dup select * from emp where deptno=p_deptno;

  select o_emp(empno, ename, job, mgr,hiredate, sal, comm, deptno)

  bulk collect into Result from emp_dup; 

  return(Result);

endf_get_emp_dup;

 

select * from table(f_get_emp_dup(20))

 

ORA-14551: 无法在查询中执行 DML 操作

ORA-06512:在"SCOTT.F_GET_EMP_DUP", line 5

 

修改函数

create or replace functionf_get_emp_dup(p_deptno in integer)

  return t_emp_dup is

  pragma autonomous_transaction;

  Result t_emp_dup;

begin

  insert into emp_dup

    select * from emp where deptno = p_deptno;

  commit;

  select o_emp(empno, ename, job, mgr,hiredate, sal, comm, deptno) bulk collect

    into Result

    from emp_dup;

  return(Result);

end f_get_emp_dup;

 

 

select * from table(f_get_emp_dup(20));

 

EMPNO ENAME     JOB         MGR HIREDATE          SAL      COMM DEPTNO

----- ---------- --------- ----- -------------------- --------- ------

 5555 武大郎     CLERK      7900 2008-07-22       0.00               20

 7369SMITH      CLERK      7902 1980-12-17     800.00               20

 7566JONES      MANAGER    7839 1981-04-02    2975.00               20

 7788SCOTT      ANALYST    7566 1987-04-19    3000.00               20

 7902FORD       ANALYST    7566 1981-12-03    3000.00               20

 7876 ADAMS     CLERK      7788 1987-04-23    1100.00               20

 

 

自主事务与父事务享用同一个进程

create or replace function return_sid return integer is

  Result integer;

  pragma autonomous_transaction;

begin

  select sid into Result from v$mystat where rownum = 1;

  return(Result);

end return_sid;

 

 

declare

  i integer;

begin

  select sid into i from v$mystat where rownum = 1;

  dbms_output.put_line('main session is '||i);

  dbms_output.put_line('autonomous_transactionsession is '||return_sid);

end;

----------------

main session is 158

autonomous_transactionsession is 158

 

这会引起一个问题:

create global temporary table table_temp

(

x int

) on commit delete rows;

 

 

create or replace procedure p_auto_trancaction is

  pragma autonomous_transaction;

begin

  for x in (select x from table_temp)

   loop

     null;

   end loop; 

end p_auto_trancaction;

 

 

insert into table_temp values(2);

 

begin

p_auto_trancaction;

end;

 

begin

p_auto_trancaction;

end;

 

ORA-14450: 试图访问已经在使用的事务处理临时表

ORA-06512: 在"SCOTT.P_AUTO_TRANCACTION", line 4

ORA-06512: 在 line 3

 

因此在自主事务避免用临时表;

 

 

在自主事务中调用正常事务,自主事务提交,会提交正常事务。

create or replace procedurenormal_transaction(x in int) is

begin

  insert intolog_table values(x);

end normal_transaction;

 

declare

  pragmaautonomous_transaction;

  i integer;

begin

insert into log_table values(10000);

commit;

normal_transaction(10);

end;

 

ORA-06519: 检测到活动的独立的事务处理, 已经回退

ORA-06512: 在 line 10

 

declare

  pragmaautonomous_transaction;

  i integer;

begin

insert into log_table values(10000);

normal_transaction(10);

commit;

end;

 

变异表中的自主事务问题:

制定一个规则:某部门每一个员工的工资不能超过该部门所有员工平均工资的2倍。

create or replace procedure sal_check(p_deptno in integer) is

avg_sal  number;

max_sal  number;

begin

select max(sal),avg(sal) into max_sal,avg_sal from emp where deptno=p_deptno;

ifmax_sal>2*avg_sal then

 raise_application_error(-200001,'wa la wa la wa la');

end if;

end sal_check;

 

建立一个触发器检查规则

create or replace trigger trigger_sal_change

  after insert or update or delete on emp 

  for each row

declare

begin

 if (inserting or updating) then

   sal_check(:new.deptno);

 end if;

 

 if (deleting or updating) then

   sal_check(:old.deptno);

 end if;    

end trigger_sal_change;

 

update emp set sal=99999 where empno=7566

 

ORA-04091: 表 SCOTT.EMP发生了变化, 触发器/函数不能读它

ORA-06512: 在"SCOTT.SAL_CHECK", line 5

ORA-06512: 在"SCOTT.TRIGGER_SAL_CHANGE", line 5

ORA-04088: 触发器'SCOTT.TRIGGER_SAL_CHANGE' 执行过程中出错

 

解决这个问题

create or replace proceduresal_check(p_deptno in integer) is

pragma autonomous_transaction;

avg_sal  number;

max_sal  number;

begin

select max(sal),avg(sal) into max_sal,avg_sal from emp wheredeptno=p_deptno;

if max_sal>2*avg_sal then

 raise_application_error(-200001,'wala wa la wa la');

end if;

endsal_check;

 

update emp set sal=99999 where empno=7566;

1 rowupdated

 

但是我们看看

select max(sal),avg(sal) from emp where deptno=20

MAX(SAL)

AVG(SAL)

99999

17983.16667

显然 最大的工资比平均工资的2倍要大的多,业务规则没有起作用

 

再来更新一次

update emp set sal=99999 where empno=7566

 

ORA-20001:wa la wa la wa la

ORA-06512: 在 "SCOTT.SAL_CHECK", line 8

ORA-06512: 在 "SCOTT.TRIGGER_SAL_CHANGE", line 5

ORA-04088: 触发器 'SCOTT.TRIGGER_SAL_CHANGE' 执行过程中出错

 

为什么会出现这个情况? 这是因为自主事务根本看不到父事务的数据变化而引起的。在开发过程中要小心

 

如果讲规则放在trigger中,也有变异表问题

这样写也有问题

create or replace trigger trigger_sal_change

  after insert or update or delete on emp 

  for each row

declare

  pragma autonomous_transaction;

  avg_sal number;

  max_sal number;

begin

  if (inserting or updating) then

    select max(sal), avg(sal)

      into max_sal, avg_sal

      from emp

     where deptno = :new.deptno;

    if max_sal > 2 * avg_sal then

      raise_application_error(-20001, 'wa la wa la wa la');

    end if;

  end if;

 

  if (deleting or updating) then

    select max(sal), avg(sal)

      into max_sal, avg_sal

      from emp

     where deptno =  :old.deptno;

    if max_sal > 2 * avg_sal then

      raise_application_error(-20001, 'wa la wa la wa la');

    end if;

  end if;

  commit;

end trigger_sal_change;

 

 

怎么解决这个问题

修改触发器

create or replace triggertrigger_sal_change

  after insert or update or delete on emp 

  for each row

declare

  pragma autonomous_transaction;

  avg_sal number;

  max_sal number;

  r_count integer;

begin

 

    select max(sal), avg(sal),count(*)

      into max_sal,avg_sal,r_count

      from emp

     where deptno =:new.deptno;

     

  if(inserting) then

   max_sal:=greatest(max_sal,:new.sal);

    avg_sal:=(avg_sal*r_count +nvl(:new.sal,0))/(r_count+1);

    if max_sal> 2 * avg_sal then

     raise_application_error(-20001, 'wa la wala wa la');

    end if;

  end if;

 

  if(updating) then

   max_sal:=greatest(max_sal,:new.sal);

    avg_sal:=(avg_sal*r_count +nvl(:new.sal,0)-nvl(:old.sal,0))/r_count;

    if max_sal> 2 * avg_sal then

     raise_application_error(-20001, 'wa la wala wa la');

    end if;

  end if;

/*

if (deleting) then

有点复杂

end if;

*/

end;

 

最好的方法是用过程代替触发器。

 

 

SAVEPOINT

 

select * from emp_dup where empno=5555;

create unique index idx_empno on emp_dup(empno);

 

DECLARE

BEGIN

  update emp_dup set sal=sal+10;

  SAVEPOINT do_insert;

  INSERT INTO emp_dup

  select * from emp_dup where empno=5555;

  commit;

EXCEPTION

  WHEN DUP_VAL_ON_INDEX THEN

    ROLLBACK TO do_insert;

    --commit;

    DBMS_OUTPUT.PUT_LINE('Insert was rolledback');

END;

 

可以设定多个savepoint

 

从上述例子看出,如果第2个dml失败,整个事务只是退到savapoint

 

DECLARE

v_sales  number;

BEGIN

  select sal into v_sales from emp_dup where empno=5555;

  dbms_output.put_line(v_sales);

  update emp_dup set sal=sal+10;

  select sal into v_sales from emp_dup where empno=5555;

 dbms_output.put_line(v_sales); 

  SAVEPOINT do_insert;

  INSERT INTO emp_dup

  select * from emp_dup where empno=5555;

  commit;

exception

WHEN others THEN

  select sal into v_sales from emp_dup where empno=5555;

 dbms_output.put_line(v_sales); 

END;

--------------

10

20

20

 

在每一个dml中都包含一个隐性的savepoint。

 

 

 

事务的完整性

scenery-1:

session1

>-----------------t1(opencursor)------------------------t2(output)------------------t3(closecursor)--------------commit-------<

session2

>------t1(insert commit newdate)----------------------------------------------------------------------------------------------------<

 

question1

does session1’s output include new dateinserted by session2 ?

question2

Is there any methods to prevevt the newdate inserted by session2 from outputting of session1?

 

scenery-2:

session1

>-----------------t1(opencursor)------------------------t2(output)------------------t3(closecursor)--------------commit-------<

session2

>--------------------------------------t1(insertcommit new date)--------------------------------------------------------------------<

 

does session1’s output include new dateinserted by session2 ?

 

 

scenery-2:

session1

>--------t1(opencursor)--------t2(output)-------- t1(insert commit new date)----------t3(closecursor)--------------commit-------<

session2

 

does session1’s output include new dateinserted by session1 ?

 

 

 

test code

declare

  CURSOR c1 IS SELECT deptno,loc from dept order by deptno;

  s c1%rowtype;

begin

  begin

  open c1;

 dbms_lock.sleep(6);

 dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

   update dept set deptno=75 where deptno=13;

   commit; 

  loop

 

  EXIT WHEN c1%NOTFOUND;

  fetch c1 into s;

 dbms_output.put(s.deptno);

 dbms_output.put('--------------');

 dbms_output.put(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

 dbms_output.new_line;

 dbms_lock.sleep(1);

  end loop;

  close c1;

  end;

  commit;

end;

 

 

begin

insert into dept values(18,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

end;

 

看到一个DML开始,从打开游标,到关闭游标,其间即使有其他session,对该数据集合进行dml操作,并作提交,也不会影响到当前DML。要不然会完成事务的完整性了。

 

例子 运算累计存款期间,发生了存款数据的DML操作。

 

上面有没有一个方法,设定过程(含有多个DML操作)事务,也能如此?

 

事务隔离等级

 

declare

  CURSOR c1 IS SELECT deptno,loc from dept order by deptno;

  s c1%rowtype;

begin

  commit;

  SET TRANSACTION READ ONLY NAME 'xxx';

 dbms_lock.sleep(6);  --in this period, we insertinto new date into dept is another session

  open c1;

 dbms_lock.sleep(6);

 dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

 dbms_lock.sleep(2);

 

  loop

  EXIT WHEN c1%NOTFOUND;

  fetch c1 into s;

 dbms_output.put(s.deptno);

 dbms_output.put('--------------');

 dbms_output.put(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

 dbms_output.new_line;

 dbms_lock.sleep(1);

  end loop;

  close c1;

  commit;

end;

--------------

10--------------yyyy--------------2008-10-03 12:11:26

20--------------CHICAGO--------------2008-10-0312:11:27

40--------------55--------------2008-10-03 12:11:28

55--------------55--------------2008-10-0312:11:29

 

begin

insert into dept values(18,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

dbms_lock.sleep(6);

insert into dept values(19,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

end;

 

只读事务,只能SELECTINTO, OPEN, FETCH, CLOSE, LOCKTABLE, COMMIT, and ROLLBACK语句,如果我们要

如果要让事务允许写操作(输出能够看到打开游标前的DML)

 

declare

  CURSOR c1 IS SELECT deptno,loc from dept order by deptno;

  s c1%rowtype;

begin

  commit;

  SET TRANSACTION read write NAME 'xxx';

 dbms_lock.sleep(6);  --in this period, weinsert into new date into dept is another session

  update dept set loc='gg20' where deptno=20;

  --commit;

  open c1;

 dbms_lock.sleep(6);

 dbms_output.put_line(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

  update dept set loc='gg30' where deptno=30;

  commit;

 dbms_lock.sleep(2);

  loop

 

  EXIT WHEN c1%NOTFOUND;

  fetch c1 into s;

 dbms_output.put(s.deptno);

 dbms_output.put('--------------');

 dbms_output.put(s.loc);

 dbms_output.put('--------------'); 

 dbms_output.put(to_char(sysdate,'yyyy-mm-ddhh24:mi:ss'));

 dbms_output.new_line;

 dbms_lock.sleep(1);

  end loop;

  close c1;

  commit;

end;

 

无论如何,输出结果也看不到游标打开后关闭前的DML操作提交的结果,不管这个这个DML是来自于本session还是来自于其他session。

 

serializable事务隔离

 

session_1

declare

 row_count integer;

begin

  commit;

  SET TRANSACTION isolation level serializable  NAME 'xxx';

 

  select count(*) into row_count from dept;

  dbms_output.put_line(row_count);

 

  dbms_lock.sleep(10);

  

  select count(*) into row_count from dept;

 dbms_output.put_line(row_count); 

end;

 

session_2

begin

insert into dept values(18,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'));

dbms_lock.sleep(6);

insert into dept values(19,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'));

end;

 

 

-------------------

declare

 row_count integer;

begin

  commit;

  SET TRANSACTION read only  NAME 'xxx';

 

  select count(*) into row_count from dept;

  dbms_output.put_line(row_count);

 

  dbms_lock.sleep(10);

 

  select count(*) into row_count from dept;

  dbms_output.put_line(row_count);

  commit;

end;

--------------------

begin

insert into dept values(18,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'));

dbms_lock.sleep(6);

insert into dept values(19,'yyyy','yyyy');

commit;

dbms_output.put_line(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'));

end;

 

注意:SET TRANSACTION read write  NAME 'xxx';不能保证serializable的功能

============ 欢迎各位老板打赏~ ===========

本文版权归Bruce's Blog所有,转载引用请完整注明以下信息:
本文作者:Bruce
本文地址:Oracle事务示例及注意事项 | Bruce's Blog

发表评论

留言无头像?