Notes on ‘Expert Oracle’ — No.7.1: Concurrency–Concurrency with Multi-version

1.
Oracle除了用锁实现并发控制,还利用了“多版本机制”
一条数据除了会记在表文件里,还会记在“回滚段”里。

2.
具体的并发控制策略

   a.ANSI标准定义的并发控制级别(从低到高)

      i.
Read Uncommited (可以读到脏数据)

     ii.
Read Commited   (不能读到脏数据)

     iii.
Repeatable Read (在同一个事务里两次读某条数据,读到的值一定是一样的。一个典型的反面例子是“丢失更新”)

     iv.
Serializable (不但可以重复读,而且还可以保证没有 幻读)

    

   b.Oracle支持哪些级别?

      i.Read Uncommited — 在Oracle中想脏读都读不到!

     ii.
Read Commited  — 支持,是Oracle的默认级别。Oralce不仅支持这个级别,
而且还有所增强:可以保证 “读一致性”(见下文)

     iii.
Repeatable Read — 不直接支持。需自己编程实现。

     iv.
Serializable — 支持。但是由于未使用锁机制,因此
不能真正地实现“串行”

3. 对各级别的支持的具体实现

  
基本原则是: 不用读锁,而是用多版本机制来支持。所谓的多版本,就是某个事务修改某行后,会把该行原来的值放到回滚段(Undo Segment)中,也就是说,除了表文件中的数据版本外,还有一个版本放在回滚段中。

   这里还必须引入一个概念:
读一致性(Read Consistency).

   举例来说,帐户A =100元, 帐户B = 100元,现在一个会话把50块钱从账户A转到帐户B,如果另一个会话在读取A 和 B时,要么读到 A=100, B=100,或者A=50, B=50,那么这个数据库就支持了读一致性。

      i.Read Uncommited — N/A

     ii.Read Commited  

        A.
Oracle如果发现某行数据是脏的(被另一个事务修改,但这个事务还没有提交),就会从回滚段中读出这个版本,即修改前的版本。其它的数据库没有Oracle这么强大,它们只会通过阻塞当前的读操作,直到那个修改事务释放了写锁。

          B.
Oracle在这里支持“语句”级别的读一致性。这里必须举例说明。    
  会话甲从10点整开始读取Sum(A,B),但转账会话乙也正要运行,具体步骤是:

          10:00 会话甲读取A = 100元
                                   10:05 会话乙插进来把A改成50元,把B改成150元,这时Oracle会在回滚段里设置(10:05, A = 100元,B = 100元);接着会话乙提交事务。
          10:10 会话甲读取B。但它发现B自它读取A时已经发生了变化,于是忽略表文件里的值,而是从会话乙的回滚段里去读本条语句开始后B的值,即10:00以后B的值, 读得10:05时,B= 100元
          所以最后,Sum(A,B) 是 200元,实现了“读一致性”。


        似乎平淡无奇。但如果换了其它的数据库,最后结果会是250元,因为会话甲会把B读成150元。             

     iii.Repeatable Read

        A.Oracle不提供直接支持。
其它某些数据库可能会支持Repeatable Read ,但它们是通过加读锁来支持的。也就是我在读时你不准改。Oracle认为这样做会严重影响并发能力。

         B.Oracle也可以通过自己加悲观锁来实现。如Select … for update

        C.当然,通过乐观锁也能实现。

     iv.Serializable 

        A.
也是通过多版本机制来实现的,不过在这里,读一致性从单条语句级别扩展到了整个事务级别。举例说明。

    
10:00 会话甲用select语句在读取A = 100元
         10:05 会话乙把A改成50元,并提交事务。这时Oracle会在回滚段里设置(10:05, A = 100元)
      10:10 会话再次用select语句读取A的值。它会发现A的值已经被改变了。于是它就从回滚段里读取本事务开始后A的值,也就是10:00以后的值,该值是100元。


 

         所以,Repeatable Read就实现了。幻读也是用同样的机制实现的。

         B. 此时Oracle还会通过乐观锁机制来防止丢失更新。

        C. Oracle中的Serializable不是真正的串行化。具体的例子可以看原书。

4. Oralce还支持一种特有的事务级别叫做"Read Only".
"Read Only"的隔离性与Serializable一致,但Read Only事务只能有读操作。

5. 回滚段的问题:
回滚段的大小是有限的。如果你一个事务长达24小时,而又想在事务结束前从回滚段中读取事务开始时的某个数据的值,那你可能读不到东西了,因为你要的数据已经被其它事务覆盖掉了。这时侯你会收到一个异常。

  
另一个问题是,从回滚段里读东西也是有一定代价的。在并发程度很高的生产环境里,一个报表查询可能会多次读取回滚段,所以执行速度会稍慢于你的测试环境,因为测试环境的并发通常会低一些。

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.