《Oracle编程艺术》学习笔记(14)-写一致性

先解释2个概念:
· 一致读(Consistent read):“发现”要修改的行时,所完成的获取就是一致读。
· 当前读(Current read):得到块来实际更新所要修改的行时,所完成的获取就是当前读。
可以通过tkprof报告看到每条语句一致读和当前读的统计。
tkprof:http://blog.csdn.net/fw0124/article/details/6899162

用一个例子来引出问题。如果运行以下语句
update t set x = x + 1 where y = 5;
在该语句运行时,其他会话将这条语句已经读取的一行从Y=5更新为Y=10,并提交,如果是这样会发生什么?

时间  会话1         会话2           注释
-----------------------------------------------------------------------------------------
T1   Update t                      这会更新与条件匹配的一行
     set y = 10
     where y = 5;
T2                 Update t        使用一致读,这会找到会话1修改的记录,但是无法更新这个记录,
                   set x = x+1     因为会话1已经将其阻塞。会话2将被阻塞,并等待这一行可用
                   where y = 5;
T3   commit;                       这会解除对会话2的阻塞。会话2终于可以在包含这一行
                                   (会话1开始更新时Y等于5的那一行)的块上完成当前读。
                                   当前读发现Y现在等于10,不是5
-----------------------------------------------------------------------------------------


在这种情况下,如果你使用了SERIALIZABLE 隔离级别,此时这个事务就会收到一个ORA-08177: can’t serialize access 错误。
如果采用READ COMMITTED模式,事务会回滚你的更新后,重新启动更新。Oracle会进入SELECT FOR UPDATE模式,并试图为你的会话锁住所有Y=5的行。一旦完成了这个锁定,它会对这些锁定的数据运行UPDATE,这样可以确保这一次就能完成而不必(再次)重启动。
但是如果重启动更新,并进入SELECT FOR UPDATE模式(与UPDATE一样,同样有读一致块获取(read-consistent block get)和读当前块获取(read current block get)),执行SELECT FOR UPDATE的一致读时Y=5的一行等到你得到它的当前版本时却发现Y=11,会发生什么呢?SELECT FOR UPDATE会重启动,而且这个循环会再来一遍。

通过在表格T上创建1个BEFORE UPDATE FOR EACH ROW的触发器,在触发器中打印输出,就能够很清楚观察到上述现象。例如,
首先准备数据:
create table t ( x int, y int );
insert into t values ( 1, 1 );
commit;

然后创建触发器:
create or replace trigger t_print
before update on t for each row
begin
    dbms_output.put_line( 'old.x = ' || :old.x || ', old.y = ' || :old.y );
    dbms_output.put_line( 'new.x = ' || :new.x || ', new.y = ' || :new.y );
end;
/

在会话1中运行:
update t set x = x + 1 where x > 0;

在会话2中也运行:
update t set x = x + 1 where x > 0;
这个时候会话2会被阻塞,

回到会话1中进行提交:
commit;

在会话2中可以看到输出:
old.x = 1, old.y = 1
new.x = 2, new.y = 1
old.x = 2, old.y = 1
new.x = 3, new.y = 1

可以看到触发器会被触发2次。可以看出发生了重启动。

有意思的是如果把上面的
update t set x = x + 1 where x > 0;
语句换成
update t set x = x + 1 where y > 0;
还是会看到相同的结果。现在我们是在搜索Y>0,根本没有修改Y,为什么还会重新启动更新?
原来是因为在触发器中引用:NEW.X和:OLD.X时,会比较X的一致读值和当前读值,并发现二者不同。这就会带来一个重启动。

现在重新创建触发器:
create or replace trigger t_print
before update on t for each row
begin
 dbms_output.put_line( 'fired' );
end;
/
然后使用update t set x = x + 1 where y > 0;语句重新进行上面的测试,可以看到现在触发器只会触发1次了。
(所以,使用AFTER FOR EACH ROW 触发器比使用BEFORE FOR EACH ROW 更高效。AFTER 触发器不存在上面由于触发器导致的更新重新启动的问题。)

如果在触发器中做任何非事务性的工作,就会受到重启动的影响。考虑以下影响:
· 考虑一个触发器,它维护着一些PL/SQL 全局变量,如所处理的个数。重启动的语句回滚时,对PL/SQL变量的修改不会“回滚“。
· 一般认为,以UTL_开头的几乎所有函数(UTL_FILE、UTL_HTTP、UTL_SMTP等)都会受到语句重启动的影响。语句重启动时,UTL_FILE 不会“取消“对所写文件的写操作。
· 触发器里如果作为自治事务,语句重启动并回滚时,自治事务无法回滚。
结论就是不要在触发器里面做任何非事务性的工作。

你可能感兴趣的:(oracle,编程,工作,File,insert,each)