2011年1月13日
重启动发生的原因:where条件读取时采用一致读和更新时采取当前读版本不一致造成
Oracle在执行DML语句时,会用到两种不同的方式去读取数据块:
1. 一致读:在“找到”需要修改的数据行时,会采用 consistent read
2. 当前读:在“获取”数据块来实际更新数据行时,会采用 current read
如更新这个语句update test set x=1 where y=1;
首先Oracle会利用一致读找到所有y=1的数据行,因此就算读取期间有别的会话修改了某一行的y值(如从y=1变为 y=2),
Oracle也会利用undo数据将该行的y列恢复到读取的那个时刻的值(即y=1),所以这一行还是会被读取。然后,当要实际更新这一行的时候,
Oracle会利用当前读去获取数据块的最新版本,由于被提交过了,所以读到的y值是2,与之前一致读中的y=1不符。
所以Oracle就知道了需要 “重启动”这个update操作。
Oracle重启动的流程:
1. 回滚之前的修改操作
2. 进入select for update模式,锁定需要修改的行
3. 对锁定的行进行修改操作
(这个流程是指在Oracle默认的READ COMMITED隔离级别下,如果是SERIALIZABLE模式,则会出现ORA-08177错误)
重启动查看(通过创建触发器来查看)
create table t (x int,y int);
insert into t values(1,1);commit;
create or replace trigger t_buffer 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='||:old.x||',new.y='||:old.y);
end;
update t set x=x+1 where x>0;
输出结果如下:
old.x=1,old.y=1
new.x=1,new.y=1
在第二个会话中执行
update t set x=x+1 where x>0; --被锁定
将第一个会话中提交后,输出结果如下:
old.x=1,old.y=1
new.x=1,new.y=1
old.x=2,old.y=1
new.x=2,new.y=1
where条件改成y=0也一样 在第一个会话中执行如下语句
update t set x=x+1 where y>0;
old.x=3,old.y=1
new.x=3,new.y=1
在第二个会话中执行
update t set x=x+1 where y>0;
old.x=3,old.y=1
new.x=3,new.y=1
old.x=4,old.y=1
new.x=4,new.y=1
修改触发器如下:
create or replace trigger t_buffer before update on t for each row
begin
dbms_output.put_line ('已更新');
end;
第一个会话
update t set x=x+1 where x>0;
输出结果:
已更新
第二个会话
update t set x=x+1 where x>0;
输出结果:
已更新
已更新
如果where条件改成y>0,那么就不会引发重启动
第一个会话
update t set x=x+1 where y>0;
输出结果:
已更新
第二个会话
update t set x=x+1 where y>0;
输出结果:
已更新
结果说明:说明:old和:new值在触发器引用时,也会被oracle用于完成重启动检查。where子句中查找行所用的列集会与行触发器中引用的列进行比较,行的一致读版本会与行的当前读版本比较,只要不同,就会重启动修改。
如果 after for each row 触发器比使用 before for each row 触发器更有效,如果使用after for each row 触发器就不会引发重启动问题。
Oracle在执行DML语句时,会用到两种不同的方式去读取数据块:
1. 一致读:在“找到”需要修改的数据行时,会采用 consistent read
2. 当前读:在“获取”数据块来实际更新数据行时,会采用 current read
如更新这个语句update test set x=1 where y=1;
首先Oracle会利用一致读找到所有y=1的数据行,因此就算读取期间有别的会话修改了某一行的y值(如从y=1变为 y=2),
Oracle也会利用undo数据将该行的y列恢复到读取的那个时刻的值(即y=1),所以这一行还是会被读取。然后,当要实际更新这一行的时候,
Oracle会利用当前读去获取数据块的最新版本,由于被提交过了,所以读到的y值是2,与之前一致读中的y=1不符。
所以Oracle就知道了需要 “重启动”这个update操作。
Oracle重启动的流程:
1. 回滚之前的修改操作
2. 进入select for update模式,锁定需要修改的行
3. 对锁定的行进行修改操作
(这个流程是指在Oracle默认的READ COMMITED隔离级别下,如果是SERIALIZABLE模式,则会出现ORA-08177错误)
重启动查看(通过创建触发器来查看)
create table t (x int,y int);
insert into t values(1,1);commit;
create or replace trigger t_buffer 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='||:old.x||',new.y='||:old.y);
end;
update t set x=x+1 where x>0;
输出结果如下:
old.x=1,old.y=1
new.x=1,new.y=1
在第二个会话中执行
update t set x=x+1 where x>0; --被锁定
将第一个会话中提交后,输出结果如下:
old.x=1,old.y=1
new.x=1,new.y=1
old.x=2,old.y=1
new.x=2,new.y=1
where条件改成y=0也一样 在第一个会话中执行如下语句
update t set x=x+1 where y>0;
old.x=3,old.y=1
new.x=3,new.y=1
在第二个会话中执行
update t set x=x+1 where y>0;
old.x=3,old.y=1
new.x=3,new.y=1
old.x=4,old.y=1
new.x=4,new.y=1
修改触发器如下:
create or replace trigger t_buffer before update on t for each row
begin
dbms_output.put_line ('已更新');
end;
第一个会话
update t set x=x+1 where x>0;
输出结果:
已更新
第二个会话
update t set x=x+1 where x>0;
输出结果:
已更新
已更新
如果where条件改成y>0,那么就不会引发重启动
第一个会话
update t set x=x+1 where y>0;
输出结果:
已更新
第二个会话
update t set x=x+1 where y>0;
输出结果:
已更新
结果说明:说明:old和:new值在触发器引用时,也会被oracle用于完成重启动检查。where子句中查找行所用的列集会与行触发器中引用的列进行比较,行的一致读版本会与行的当前读版本比较,只要不同,就会重启动修改。
如果 after for each row 触发器比使用 before for each row 触发器更有效,如果使用after for each row 触发器就不会引发重启动问题。