《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 不会“取消“对所写文件的写操作。
· 触发器里如果作为自治事务,语句重启动并回滚时,自治事务无法回滚。
结论就是不要在触发器里面做任何非事务性的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值