sqlplus之多版本
扫描二维码
随时随地手机看文章
oracle采用了一种多版本、读一致(read-consistent)的并发模型。
读一致查询:对于一个时间点(point in time),查询会产生一致的结果
非阻塞查询:查询不会被写入器阻塞
多版本(multi-versioning),oracle能够同时物化多个版本的数据。
-----------------------------------------------------------------------------------------------
创建一个测试表T,并把all_users表的一些数据加载到T表中
scott@ORCL>create table t 2 as 3 select * 4 from all_users; 表已创建。
然后在这个表上 打开一个游标。在此没有从该游标读取数据,只是打开游标而已。
打开游标时,oracle不复制任何数据,它会边行进边回答查询。
只在获取数据时它才从表中读取数据。
scott@ORCL>variable x refcursor scott@ORCL>begin 2 open :x for select * from t; 3 end; 4 / PL/SQL 过程已成功完成。
在同一会话中,再从表中删除所有数据,甚至commit提交了删除所做的工作。
scott@ORCL>delete from t; 已删除37行。 scott@ORCL>commit; 提交完成。
记录行都没有了,但是,还是可以通过游标获取数据
scott@ORCL>print x USERNAME USER_ID CREATED ------------------------------ ---------- -------------- PERFSTAT 93 15-3月 -18 BI 90 14-3月 -18 PM 89 14-3月 -18 SH 88 14-3月 -18 IX 87 14-3月 -18 OE 86 14-3月 -18 HR 85 14-3月 -18 SCOTT 84 30-3月 -10 OWBSYS_AUDIT 83 30-3月 -10 OWBSYS 79 30-3月 -10 APEX_030200 78 30-3月 -10 APEX_PUBLIC_USER 76 30-3月 -10 FLOWS_FILES 75 30-3月 -10 MGMT_VIEW 74 30-3月 -10 SYSMAN 72 30-3月 -10 SPATIAL_CSW_ADMIN_USR 70 30-3月 -10 SPATIAL_WFS_ADMIN_USR 67 30-3月 -10 MDDATA 65 30-3月 -10 MDSYS 57 30-3月 -10 SI_INFORMTN_SCHEMA 56 30-3月 -10 ORDPLUGINS 55 30-3月 -10 ORDDATA 54 30-3月 -10 ORDSYS 53 30-3月 -10 OLAPSYS 61 30-3月 -10 ANONYMOUS 46 30-3月 -10 XDB 45 30-3月 -10 CTXSYS 43 30-3月 -10 EXFSYS 42 30-3月 -10 XS$NULL 2147483638 30-3月 -10 WMSYS 32 30-3月 -10 APPQOSSYS 31 30-3月 -10 DBSNMP 30 30-3月 -10 ORACLE_OCM 21 30-3月 -10 DIP 14 30-3月 -10 OUTLN 9 30-3月 -10 SYSTEM 5 30-3月 -10 SYS 0 30-3月 -10 已选择37行。
open命令返回的结果集那一刻(时间点)就已经确定了。
打开时,我们根本没有碰过表中的任何数据块,但答案是固定的。
获取数据之前,我们无法知道答案会是什么;但从游标角度看,结果则是固定不变了。
打开游标时,并非oracle将所有数据复制到另外某个位置;实际上是delete命令为我们把数据保留下来,把它放在一个称之为undo段(undo segment)的数据区,也称之为回滚段(rollback segment)。
-------------------------------------------------------------------------------
1.多版本和闪回
SCN(System Change Number or System Commit Number)
是oracle的内部时钟:每次发生提交时,这个时钟就会向上滴答(递增)
授权给Scott:
sys@ORCL>grant "CONNECT" to scott; 授权成功。 sys@ORCL>grant "DBA" to scott; 授权成功。 sys@ORCL>grant execute on dbms_flashback to scott; 授权成功。
scott@ORCL>variable SCN number scott@ORCL>exec :scn := dbms_flashback.get_system_change_number PL/SQL 过程已成功完成。 scott@ORCL>print scn SCN ---------- 1364868
现在可以让oracle提供SCN值所表示时间点的数据。以后再查询oracle,就能看看这一时刻表中的内容。
首先看看emp表:
scott@ORCL>select count(1) from emp; COUNT(1) ---------- 14
下面把这些信息都删除,并验证数据是否确实"没有了":
scott@ORCL>delete from emp; 已删除14行。 scott@ORCL>select count(1) from emp; COUNT(1) ---------- 0
此外,使用闪回查询(即 AS OF SCN 或 AS OF TIMESTAMP子句),可以让oracle告诉我们SCN值为1364868的时间点上表中有什么:
scott@ORCL>select count(1) from emp AS OF SCN :scn; COUNT(1) ---------- 14
不仅如此,这个功能还能跨事务边界。我们甚至可以在统一查询中得到同一个对象在"两个时间点"上的结果:
scott@ORCL>commit; 提交完成。 scott@ORCL>select * 2 from (select count(*) from emp), 3 (select count(*) from emp as of scn:scn) 4 / COUNT(*) COUNT(*) ---------- ---------- 0 14
oracle 10g以及以上版本,有个“闪回”(flashback)命令,它使用了这种底层多版本技术,可以把对象返回到以前某个时间点的状态:
scott@ORCL>flashback table emp to scn :scn; flashback table emp to scn :scn * 第 1 行出现错误: ORA-08189: 因为未启用行移动功能, 不能闪回表 scott@ORCL>alter table emp enable row movement; #允许oracle修改分配给行的rowid 表已更改。 scott@ORCL>flashback table emp to scn :scn; 闪回完成。 scott@ORCL>select * 2 from (select count(*) from emp), 3 (select count(*) from emp as of scn:scn) 4 / COUNT(*) COUNT(*) ---------- ---------- 14 14
在oracle中,插入一行时就会为它分配一个rowid,而且这一行永远拥有这个rowid。
闪回表处理 会对emp完成delete,并且重新插入行,这样就会为这些行分配一个新的rowid。
要支持闪回必须允许oracle执行这个操作。
2.读一致性和非阻塞读
假设我们读取的表在每个数据库块(数据库中最小的存储单元)中只存放一行
scott@ORCL>create table accounts 2 (account_number number primary key, 3 account_balance number 4 ); 表已创建。
scott@ORCL>insert into accounts values(123,500); 已创建 1 行。 ...
scott@ORCL>select * from accounts; ACCOUNT_NUMBER ACCOUNT_BALANCE -------------- --------------- 123 500 234 250 345 400 456 100
scott@ORCL>select sum(account_balance) from accounts; SUM(ACCOUNT_BALANCE) -------------------- 1250
场景:如果我们现在读了第1行,准备读第2行和第3行时,一台ATM针对这个表发生了一个事务,将400从账号123转到账户456,结果会怎么样?读一致性 就是oracle为了避免发生这种情况所采用的方法。
在几乎所有的其他数据库中,如果想得到"一致"和"正确"的查询答案,就必须在计算总额时锁定整个表,或者在读取记录行时对其锁定。这样一来,获取结果时 就可以防止别人再做修改。如果提前锁定表,就会得到查询开始时数据库中的结果。如果在读取数据时锁定(共享读锁,可以防止更新,但不妨碍读取器读取数据库),就会得到查询结束时数据库中的结果。这两种方法都会影响并发性。
oracle利用多版本来得到结果,也就是查询开始时那个时间点的结果,然后完成查询,而不做任何锁定(转账事务更新第1行和第4行时,这些行会对其他写入器锁定,但不会对读取器锁定,如这里的 select sum...)。实际上,oracle根本没有"共享锁"。
oracle事务机制:
只要你修改数据,oracle就会创建撤销(undo)条目,这些undo条目写至undo段(撤销段,undo segment)。
如果事务失败,需要撤销,oracle就会从这个回滚段读取"之前"的映像,并恢复数据。
除了使用回滚段数据 撤销事务外,oracle还会用它撤销 读取块时对块所做的修改,使之恢复到查询开始前的时间点。
时间查询转账事务T1读取第1行;到目前为止 sum=500 T2 更新第1行;对第1行加一个排他锁,阻止其他更新第1行。现有100T3读取第2行;到目前为止sum=750 T4读取第3行;到目前为止sum=1150 T5 更新第4行;对第4行加一个排他锁,阻止其他更新第4行(但不阻止读操作)。现有500T6读取第4行,发现第4行已经修改,这会将块回滚到T1时刻的状态。查询从这个块读到值100 T7得到答案1250
在T6时,oracle有效的"摆脱'了事务加在第4行上的锁。非阻塞性读是这样实现的:oracle只看数据是否改变,它并不关心数据当前是否锁定(锁定意味着数据已经改变)。oracle只是从回滚段中取回原来的值,并继续处理下一个数据块。
数据的读一致视图总是在sql语句级执行。sql语句的结果 对于查询开始的时间点 来说是一致的。
正因为这一点,所以下面的语句可以插入可预知的数据集:
scott@ORCL>select * from t; USERNAME USER_ID CREATED ------------------------------ ---------- -------------- yin 1 18-3月 -18 scott@ORCL>begin 2 for x in( select * from t) 3 loop 4 insert into t values(x.username,x.user_id,x.created); 5 end loop; 6 end; 7 / PL/SQL 过程已成功完成。 scott@ORCL>select * from t; USERNAME USER_ID CREATED ------------------------------ ---------- -------------- yin 1 18-3月 -18 yin 1 18-3月 -18
select * from t的结果在查询开始执行时 就已经确定了。这个select并不看insert生成的任何新数据。
oracle为所有语句提供了这种读一致性,所以如下的insert也是可预知的:
scott@ORCL>insert into t select * from t; 已创建2行。 scott@ORCL>select * from t; USERNAME USER_ID CREATED ------------------------------ ---------- -------------- yin 1 18-3月 -18 yin 1 18-3月 -18 yin 1 18-3月 -18 yin 1 18-3月 -18