多版本模型与一致性读
一致性读
当select 查询接触到一个被X 锁的块时,Oracle 绕开锁,并从回滚段中重构数据; 以实现数据的一致读。
Oracle 自动在查询上提供读一致性以使查询所能看到的数据均来自一个时间点上。
同样,ORACLE 也提供事务中所有查询的一致性,如你的update 就是一个事务。
Oracle 为保证一致性用回滚段来保存未提交或最近已提交的数据的旧值。
当查询进行的时候. 当前SCN 已被决定,因此, 每个查询返回所有截止于在查询执行点上的那个SCN 的所有提交的数据,在查询期间发生的其他事务改变不会被用到。
因此如果在查询前提交该事务. 那么数据是可见的。
读取操作只会读取读取查询开始时那一时刻的数据,如果数据在读取操作以后发生了变化,则从回滚段中读取old image 。
例子
假设表A 的col1 的值初始为9
时间点 | Session1 | Session2 |
T1 | 开始事务 |
|
T2 |
| Select col1 from A where id=1 ( 值为9) |
T3 | Update A set col1=2 where id=1 |
|
T4 |
| Select col1 from A where id=1 ( 值为9) |
T5 | commit |
|
T6 |
| Select col1 from A where id=1 ( 值为2) |
假设查询开始时间为 T1 ,则在查询获取的数据块中,如果数据块的提交 SCN 小于 T1 ,则 oracle 接收该数据,如果提交 SCN 大于 T1 或数据被锁定修改尚未记录 commit SCN ,则 oracle 需要通过回滚段构造前镜像来返回结果,这就是一致性读的本质含义。
当前读
为了提高事务处理的速度, oracle 采用了读一致性机制。就是当一个会话更新数据后未提交时,其它会话读取到更新前的结果。这大大提高数据并发的效率,但某些特定条件下会产生错误的结果。
下面的例子进行说明。
假设存在表 test ( A number(10,2) )其中有一行记录,字段 A 的值为 100
表 test
A |
100 |
时间点 | Session1 | Session2 |
T1 | update test set A=(select A+1 from test) |
|
T2 | commit |
|
T3 |
| update test set A=(select A+1 from test) |
T4 |
| commit |
当会话 1 提交后再执行会话 2 时,得到得结果是 102
时间点 | Session1 | Session2 |
T1 | update test set A=(select A+1 from test) |
|
T2 |
| update test set A=(select A+1 from test) |
T3 | commit |
|
T4 |
| commit |
当会话 1 执行后未提交,会话 2 就已经执行,得到得结果是 101 ,并不是 102 。这是因为 oracle 为了提供数据的并发访问速度,在一个会话更新数据后未提交时,另一个会话 select 到的将是第一个会话更新前的数据
单多个会话并发时,会产生不确定得结果,这不是我们想看到的。在并发系统开发时应多注意此类问题。
上面的两个会话可以改成以下做法,保证数据的准确更新
时间点 | Session1 | Session2 |
T1 | update test set A=A+1 |
|
T2 |
| update test set A=A+1 |
T3 | commit |
|
T4 |
| commit |
Oracle 的当前读会使 session2 看到 session1 做出的修改
更新丢失
例子
时间点 | Session1 | Session2 |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 | Update auction set status=’ 卖出 ’ where auction_id=1 |
|
T4 |
| Update auction set status=’ 卖出 ’ where auction_id=1 |
T5 | commit |
|
T6 |
| commit |
上例中,如果在业务逻辑上是在拍卖一个商品,那么同时这一个商品会被 session1 的用户与 session2 的用户同时拍到。
采用悲观锁或乐观锁可保证同一商品不会被两人同时拍到
悲观锁
时间点 | Session1 | Session2 |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 | Select * from auction where auction_id=1 for update nowait |
|
T4 | Update auction set status=’ 卖出 ’ where auction_id=1 |
|
T5 |
| Select * from auction where auction_id=1 for update nowait |
T6 |
| Update auction set status=’ 卖出 ’ where auction_id=1 |
T7 | commit |
|
T8 |
| commit |
Session2 在 T5 时刻发现有人锁定了要修改的行,则无法锁定成功, update 操作将不会执行
乐观锁
时间点 | Session1 | Session2 |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 | Update auction set status=’ 卖出 ’ where auction_id=1 and status=’ 未卖出 ’ |
|
T4 |
| Update auction set status=’ 卖出 ’ where auction_id=1 and status=’ 未卖出 ’ |
T5 | commit |
|
T6 |
| commit |
Session2 在 T4 时刻由于当前读, status=’ 未卖出 ’ 的行已读不到,所以 session2 的用户没有更新到行。