《Oracle编程艺术》学习笔记(13)-Oracle的并发与多版本控制

来源:互联网 发布:mac os 10.11 cdr 编辑:程序博客网 时间:2024/05/16 07:55

 事务隔离级别
ANSI/ISO SQL 标准定义了4 种事务隔离级别,这些隔离级别是根据3个“现象”定义的
1)脏读(dirty read)
   能读取未提交的数据,也就是脏数据。
2)不可重复读(nonrepeatable read)
   如果在T1时间读取某一行,在T2时间重新读取这一行时,这一行可能已经被修改,被删除,或者被更新等等。
3)幻像读(phantom read)
   如果在T1 时间执行一个查询,而在T2时间再执行这个查询,返回结果中可能增加了另外的行。
   它与不可重复读的区别在于:在幻像读中,已经读取的数据不会改变,只是与以前相比,会有更多的数据满足你的查询条件。
各个事务级别的定义->http://blog.csdn.net/fw0124/article/details/6227376

隔离级别

 
脏读
 
不可重复读
 

幻像读

READ UNCOMMITTED

允许

允许

允许

READ COMMITTED

 

允许

允许

REPEATABLE READ

 

 

允许

SERIALIZABLE

 

 

 
Oracle支持READ COMMITTED和SERIALIZABLE这2种隔离级别,默认为EAD COMMITTED。
可以通过alter session set isolation_level =  { SERIALIZABLE | READ COMMITTED }语句修改当前会话的隔离级别。

此外,Oracle还提供了READ ONLY(只读)事务。
READ ONLY 事务相对于无法在SQL中完成任何修改的REPEATABLE READ或SERIALIZABLE事务。
如果事务使用READ ONLY 隔离级别,只能看到事务开始那一刻提交的修改,但是不允许插入、更新和删除(其他会话可以更新数据,但是READONLY 事务不行)。
set transaction read only语句可以将事务设置成只读事务,即将数据库"冻结"到该事务开始的那一点上,直至显式地发布了COMMIT或ROLLBACK命令或隐式提交(执行DDL)。

另外,还可以将数据库更改成read only方式,只能查询数据而不允许对数据进行任何DML操作。
SQL> STARTUP MOUNT
SQL> ALTER DATABASE OPEN READ ONLY;
如果要将数据库再按读写方式打开,可以用STARTUP FORCE命令重启数据库。

多版本读一致性(multi-version readconsistency)
Oracle现了一种多版本(multi-versioning)体系结构,这种体系结构提供了一种受控但高度并发的数据访问。
多版本是指,Oracle能同时物化多个版本的数据,这也是Oracle提供数据读一致视图的机制(read-consistent view 即相对于某个时间点有一致的结果)。
多版本有一个很好的副作用,即数据的读取器(reader)绝对不会被数据的写入器(writer)所阻塞。换句话说,写不会阻塞读。这是Oracle 与其他数据库之间的一个根本区别。在Oracle中,如果一个查询只是读取信息,那么永远也不会被阻塞。它不会与其他会话发生死锁,而且不可能得到数据库中根本不存在的答案。

默认情况下,Oracle的读一致性多版本模型应用于语句级(statement level),对应于READ COMMITTED隔离级别,也就是说,至少提交到数据库的每一条SQL 语句都会看到数据库的一个读一致视图。
另外还可以应用于事务级(transaction level),对应于SERIALIZABLE隔离级别。

通过下面的例子来理解多版本,读一致性和非阻塞读的含义:
需要查询一个ACCOUNTS表,其中包含了银行的账户余额,
create table accounts(
account_number number primary key,
account_balance number
);

现在ACCOUNTS表格里的数据如下:
ACCOUNT_NUMBER ACCOUNT_BALANCE
-------------- ---------------
             1             500
             2             250
             3             400
             4             100

通过下面的语句查询帐户总额,
select sum(account_balance) from accounts;

现在假设当已经读取了第1行,准备读取第2行和第3行时,一台自动柜员机(ATM)针对这个表发生了一个事务,将$400.00从账户1转到了账户4,结果怎样?
在几乎所有的其他数据库中,如果想得到“一致”和“正确”的查询答案,就必须在计算总额时对整个表加上共享读锁,但这会大大影响并发性。
通过使用会滚段(Undo段),Oracle则不需要任何锁定,就可以得到正确答案。Oracle是这样做的:

--------------------------------------------------------------时间   查询                                 转账事务--------------------------------------------------------------T1    读第1行;到目前为止sum = $500T2                                    更新第1行;对第1行加一个排他锁(也称独占锁,                                            exclusive lock),阻止其他更新。第1行现在有$100T3    读第2行;到目前为止sum = $750T4    读第3行;到目前为止sum = $1150T5                                    更新第4行;对第4 行加一个排他锁,阻止其他更新(但不                                            阻止读操作)。第4行现在有$500T6    读第4行,发现第4行已修改。这会      将块回滚到T1时刻的状态。查询从      这个块读到值$100T7                                    提交事务T8    得到答案sum = $1250

 

 SERIALIZABLE隔离级别和读一致性
使用SERIALIZABLE事务隔离级别在一个环境中操作时,就好像没有别的用户在修改数据库中的数据一样。我们读取的所有行在重新读取时都肯定完全一样,所执行的查询在整个事务期间也总能返回相同的结果。
例如,如果执行以下查询:
select * from ACCOUNTS;
exec dbms_lock.sleep( 60*60*24 );
select * from ACCOUNTS;
2次从ACCOUNTS返回的结果总是一样的,即使是在这个期间别的会话修改了ACCOUNTS的数据。这种情况下,Oracle会使用回滚段按事务开始时数据的原样来重建数据。
但是如果现在我们准备更新某一行,而在这之前(我们第一次执行select * from ACCOUNTS之后),别的事务也更新了这一行,那么就会得到一个错误
ORA-08177: can't serialize access for this transaction
(实际上并不需要是同一行上发生修改,只要包含这一行的块上有其他行已经被修改,就会发生这个错误)

保证以下几点情况下,适合使用SERIALIZABLE隔离级别:
· 一般没有其他人修改相同的数据
· 需要事务级读一致性
· 事务都很短 

READ ONLY隔离级别和读一致性
READ ONLY事务与SERIALIZABLE事务惟一的区别是READ ONLY事务不允许修改,因此不会遭遇ORA-08177错误。
在READ ONLY事务中可能会看到ORA-1555:snapshot too old错误。
如果其他的会话正在修改你在READ ONLY事务中读取的数据,就有可能发生这种情况。因为READ ONLY事务需要读取记录在回滚段中的修改前的这些数据(undo信息),把它们恢复到块缓冲区。但是回滚段以一种循环方式使用,这与重做日志非常相似。READ ONLY事务运行的时间越长,重建数据所需的undo信息就越有可能已经被覆盖了,此时,就会得到ORA-1555错误。
对于这个问题,惟一的解决方案就是为系统适当地确定回滚段大小。

热表上超出期望的I/O
使用READ ONLY事务与SERIALIZABLE事务可能导致超出期望的I/O。如果其他的事务大量修改了我们的事务需要读取的记录,就会发生这种现象。原因就是Oracle会查找undo信息,撤销其他事务所做的修改。甚至是其他事务只是很多次反复修改了少量记录,也会造成这种现象,Oracle撤销了修改,把被修改的块从回滚段恢复到块缓冲区,之后发现这个会滚块还是太新,然后继续回滚,反复进行,直到找到事务开始时的那个版本,这就会导致大量的I/O来读取回滚段。(这也说明块缓冲区中可能包含一个块的多个版本)
不仅仅是READ ONLY事务与SERIALIZABLE事务才会有这种现象,如果一个查询语句运行的时间很长,也会有可能遇到同样问题。运行时间越长,可能需要更多工作才能从块缓冲区(通过回滚)获得数据,就可能会运行的更久,这是一个恶性循环。