实验演示Oracle“多版本一致读”和“Cross DDL”

实验演示Oracle“多版本一致读”和“Cross DDL”

摘要:为了select时能始终一致性读(即select所获得的数据都是在select开始执行时前提交的数据,而构造了CR块,这样就产生了多个版本的数据块,一个是当前数据块,一个是由当前数据块回滚到select开始执行前的数据内容所得的CR块)。

在各种事务级别中,Oracle实现的是“Read Committed”,也就是读取的数据都是已经提交过的数据内容。在Oracle中,select不会阻塞任何操作,同样也不会被任何其他操作阻塞。

 

Oracleselect动作是不会加锁的,也不会受到数据表已经有锁的影响。其他操作,如insertupdatedelete,通常会有两个锁定动作,一个是对数据表的共享锁,保护数据表结构不被DDL操作修改。另一个锁定动作是独占锁,独占修改删除的数据记录和对应的Undo段地址。

 

如果Oracle需要保证在其他会话在修改数据块,尚未提交的时候,一个会话select数据,还满足“Read Committed”,就必须要在一个地方保存住数据块的“前镜像”,也就是rollback segment/undo segment的作用。

 

当一个数据块进行修改的时候,Oracle会自动将前镜像信息保存在一个rollback/undo空间里面,数据块中包含一个寻址到rollback/undo位置,这个位置就是ITL(活动事务槽)。当事务没有结束,select操作到这个数据块的时候,会通过ITL找到前镜像信息,并且拼凑出数据块的前镜像信息。

 

另一个方面问题,如果一个select开始后,由于数据量的原因,持续很长时间。在这个时间段内发生了DML操作,比如删除了几条数据并且提交,那么select操作检索的范围中,会不会包括这几条数据呢?

 

下面我们通过实验来证明。

 

1、环境准备

 

我们选择11gR2环境进行实验。

 

 

SQL> select * from v$version;

 

BANNER

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

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE       11.2.0.1.0        Production

TNS for 32-bit Windows: Version 11.2.0.1.0 - Production

NLSRTL Version 11.2.0.1.0 – Production

 

--创建实验数据表

 

13:35:25 SQL> createtable t as select * from dba_objects where rownum<100;

Table created

 

Executed in 0.124 seconds

 

 

实验有一个重点,就是如何让一个数据查询持续很长时间,由于环境的限制,笔者不能拿到一个长时间操作的数据范围。所以,采用了一些变通方法。

 

 

set time on;

set serveroutput on;

declare

 coun number;

 cursor tes is select * from t;

 t_info t%rowtype;

begin

 coun := -1;

 

 open tes;

 loop

   fetch tes into t_info;

   coun := coun + 1;   

   dbms_lock.sleep(0.1);

   

   exit when tes%notfound;

 end loop; 

 dbms_output.put_line('Total Count: '||to_char(coun)); 

end;

/

 

 

代码中的部分就是使用游标逐条的fetch数据表t记录。关键部分在于dbms_lock方法sleep,它可以实现让代码终止一段时间,模拟长时间操作处理。这样,我们就可以在另一个会话中更从容的进行操作了。

 

2、一致读DML操作实验

 

下面我们进行DML操作实验,如果一个select很长时间,并且中间有已经提交的DML修改操作,如插入、删除,select的结果会有什么影响?

 

首先,我们在会话1里面执行select过程脚本。

 

 

会话1

 

14:16:22 SQL> set time on;

14:16:22 SQL> set serveroutput on;

14:16:22SQL> declare

          2   coun number;

          3   cursor tes is select * from t;

          4   t_info t%rowtype;

          5 begin

          6   coun := -1;

          7 

          8   open tes;

          9   loop

         10     fetch tes into t_info;

         11     coun := coun + 1;

         12     dbms_lock.sleep(1);

         13 

         14     exit when tes%notfound;

         15   end loop;

         16   dbms_output.put_line('Total Count: '||to_char(coun));

         17 end;

         18 /

 

Total Count: 99

 

PL/SQL procedure successfully completed

 

Executed in100.044seconds

 

 

同时,第二个会话再执行脚本,注意相应的时间。

 

 

会话2

 

14:15:54 SQL> set time on;

14:16:25SQL> delete t;

99 rows deleted

 

Executed in 0.016 seconds

 

14:16:29SQL> commit;

Commit complete

 

Executed in 0 seconds

 

14:16:31 SQL>

 

 

在第二个会话中,我们实现了14:16:29的时候提交了事务。此时任何select动作,都应该是不能访问到数据。

 

但是,第一个会话的启动时间是14:16:22,此时数据是存在的。执行了超过100秒之后,才运行结束。返回结果是99行,明显是有结果的。

 

之后,第一个会话再执行的时候,我们就再也看不到数据了。

 

 

14:18:03 SQL> select count(*) from t;

 

 COUNT(*)

----------

        0

Executed in 0.031 seconds

 

 

这就是典型的Oracle一致读过程。无论select持续多长时间,Oracle都会保证返回的数据SCN版本都是在发出select语句时的SCN。比如,当发出select命令的时候,SCN编号是1000,那么在检索数据表段的时候,只会去检索那些SCN小于等于1000的数据行记录。如果当前数据行正有事务信息,就会根据ITL查找Undo/Rollback中的前镜像。如果数据行的SCN大于1000,那么会去Undo/Rollback找那些Expired的记录。

 

所以,在过去手工管理Rollback的时候,如果一个select时间很长,同时数据修改频度很高,会报错1555错误,也就是Snapshot Too Old。在现在自动Undo管理的时候,这样的场景已经很少了。

 

3Cross DDL

 

那么,我们想到一个场景,如果我们在长时间select的时候,发出DDL语句,如TruncateDrop数据表。因为select操作不会加锁,所以不能组织DDL操作(独占六级锁)。

 

我们还是通过实验来证明。首先,我们恢复一下现场。

 

 

 

14:21:35 SQL> insert into t select * from dba_objects where rownum<100;

99 rows inserted

 

Executed in 0.032 seconds

 

14:23:05 SQL> commit;

Commit complete

 

Executed in 0.016 seconds

 

 

第一个会话,启动匿名块脚本。

 

 

14:23:27 SQL> set time on;

14:23:27 SQL> set serveroutput on;

14:23:27SQL> declare

          2   coun number;

          3   cursor tes is select * from t;

          4   t_info t%rowtype;

          5 begin

          6   coun := -1;

          7 

          8   open tes;

          9   loop

         10     fetch tes into t_info;

         11     coun := coun + 1;

         12     dbms_lock.sleep(1);

         13 

         14     exit when tes%notfound;

         15   end loop;

         16   dbms_output.put_line('Total Count: '||to_char(coun));

         17 end;

         18 /

Total Count: 99

 

PL/SQL procedure successfully completed

Executed in99.997seconds

 

 

第二个会话,进行drop数据表过程。

 

 

14:16:31 SQL> set time on;

14:23:30 SQL> drop table t purge;

 

Table dropped

 

Executed in 0.047 seconds

 

 

执行drop数据表动作。开始时间为14:23:30,持续不到1s。但是在第一个会话中,开始14:23:27,持续了100s。说明,在第一个会话结束之前,数据表就已经不存在了。

 

但是,从第一个会话结束的情况看,数据表还是存在的。第二次执行一次,第一个会话报错。

 

 

14:25:07 SQL>

14:25:19 SQL> set time on;

14:25:19 SQL> set serveroutput on;

14:25:19 SQL> declare

          2   coun number;

          3   cursor tes is select * from t;

          4   t_info t%rowtype;

          5 begin

          6   coun := -1;

          7 

          8   open tes;

          9   loop

         10     fetch tes into t_info;

         11     coun := coun + 1;

         12     dbms_lock.sleep(1);

         13 

         14     exit when tes%notfound;

         15   end loop;

         16   dbms_output.put_line('Total Count: '||to_char(coun));

         17 end;

         18 /

 

declare

 coun number;

 cursor tes is select * from t;

 t_info t%rowtype;

begin

 coun := -1;

 

 open tes;

 loop

   fetch tes into t_info;

   coun := coun + 1;

   dbms_lock.sleep(1);

 

   exit when tes%notfound;

 end loop;

 dbms_output.put_line('Total Count: '||to_char(coun));

end;

 

ORA-06550:3,31:

PL/SQL: ORA-01775:同义词的循环链

ORA-06550:3,17:

PL/SQL: SQL Statement ignored

ORA-06550:4,10:

PLS-00201:必须声明标识符'T'

ORA-06550:4,10:

PL/SQL: Item ignored

ORA-06550:10,20:

PLS-00320:此表达式的类型声明不完整或格式不正确

ORA-06550:10,5:

PL/SQL: SQL Statement ignored

 

 

报错。

 

说明,对于DDL操作(Truncate相同),一致读的现象依然存在。Oracle不会因为同时的DDL操作,影响到原来已经发出的select动作。从原理上,笔者认为还是和Undo/Rollback有关。

 

对于Truncate数据表,Oracle没有修改真正的数据,而是修改了段头信息,直接修改段头的分区extent信息。这部分动作其实也是会计入到Undo/Rollback空间,并且段信息data_object_id被修改。

 

当原有data_object_id访问需求出现的时候,Oracle会找Undo/Rollback上的段头信息,找到原有的Extent分区列表,进而可以范围数据。

 

4、结论

 

本篇演示了一致读现象,不仅对于DML操作有效,对DDL同样有效。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值