oracle并发控制简述(多版本和读一致性)

一、什么是并发控制

并发控制(concurrency control)是数据库提供的函数集合,允许多个人同时访问和修改数据。前一章曾经说过,锁(lock)是oracle管理共享数据库资源并发访问,并防止并发数据库事务之间“相互干涉”的核心机制之一。

总结一下,oracle使用了多种锁,包括:

TX锁:修改数据的事务在执行期间会获得这种锁。

TM锁和DDL锁:在你修改一个对象的内容(对于TM锁)或对象本身(对应DDL锁)时,这些锁可以确保对象的结构不被修改。

闩(latch):这是oracle的内部锁,用来协调对其共享数据结构的访问。

oracle的多版本:

多版本是指,oracle能同时物化多个版本的数据,数据读相对于某个时间点有一致的结果。

多版本有一个很好的副作用,即数据的读取器(reader)绝对不会被数据的写入器(writer)所阻塞。换句话说,写不会阻塞读。

二、事务隔离级别

事务隔离级别是根据3个“现象”定义的,以下就是给定隔离级别可能允许或不允许的3种现象:

脏读(dirty read):这个词不仅不好听,实际上也确实是贬义的。你能读取未提交的数据,也就是脏数据。只要打开别人正在读写的一个OS文件(不论文件中有什么数据),就可以达到脏读的效果。如果允许脏读,将影响数据完整性,另外外键约束会遭到破坏,而且会忽略惟一性约束。

不可重复读(nonrepeatable read):这意味着,如果你在T1时间读取某一行,在T2时间重新读取这一行时,这一行可能已经有所修改。也许它已经消失,有可能被更新了,等等。

幻像读(phantom read):这说明,如果你在T1时间执行一个查询,而在T2时间再执行这个查询,此时可能已经向数据库中增加了另外的行,这会影响你的结果。与不可重复读的区别在于:在幻像读中,已经读取的数据不会改变,只是与以前相比,会有更多的数据满足你的查询条件。

ANSI/ISO SQL92标准中定义了4种事务隔离级别:

1、序列化(serializable)

最高隔离级别。系统中所有的事务都是一个接一个执行的。因此也就不会发生任何事务之间的冲突问题。

2、可重复读(repeatable read)

一个事务所读取的数据记录不允许被其他事务所修改。

3、读已提交(read committed)

该级别允许其他事务修改当前事务所读取的数据记录,并且那个事务提交之后,当前事务可以看到修改后的数据。

4、读未提交(read uncommitted)

该级别允许其他事务修改当前事务所读取的数据记录,并且那个事务尚未提交时,当前事务就可以看到修改后的数据。即允许脏读。

oracle明确支持ANSI/ISO SQL92中定义的serializable和read committed两种事务隔离级别。

同时,oracle还提供了自己独有的事务隔离级别:read only。

所以,可以说oracle共支持3种事务隔离级别:

1、serializable

2、read committed

3、read only

oracle默认的隔离级别是read committed。一个事务,不允许脏读、允许不可重复读、允许幻像读。

三、数据库读一致性

数据库的读一致性是依靠事务隔离级别和undo segment来实现的。

事务隔离级别保证了,不允许脏读、允许不可重复度。即事务在执行的那一刻开始它所读的数据是不会变的。

undo segment保证当一个事务执行过程中,记录被其它事务改后,能找回原来的值。读到的是事务开始时的值。

读一致性应用例子:对一个表用sum求和

问:假设算到第3行的时候,另一个会话把第500行结果给改了,那么sum算到第500行的时候读到的数据是之前的吗?

答:是原来的值。oracle会记录事务锁定的行,当计算到第500行时,它发现这一行的数据在事务开始执行之后被更改了。为了提供一个一致(正确)的答案,oracle在这个时刻会创建该块的一个副本,其中包含查询开始时行的原值。oracle没有读修改后的值,而是从undo段重新建立原数据。

四、隔离级别介绍
1、read uncommitted
读未提交(read uncommitted)

2、read committed
读已提交(read committed)

3、repeatable read
可重复读(repeatable read)

4、serializable
序列化(serializable)

5、read only
只读(read only)隔离级别有着serializable隔离级别的所有性质,并且read only事务不允许修改。

read only事务的目的是支持报告需求,即相对于某个时间点,报告的内容应该是一致的。所生成的报告结果相对于某个时间点就是一致的,即事务开始的那个时间点。

由于原数据是从回滚段获得的,所以如果回滚段设置的太小会遇到ORA-1555: snapshot too old错。因为回滚段也是循环使用的,事务运行时间越长,重建数据所需的undo信息就越有可能已经不在那里了。

五、热表上超出期望的I/O

考虑一种情况,一个表中的一条记录在短时间内被修改了1000次,当一个事务查询时,这个块会在回滚段回滚n次,这样会造成大量I/O负载。

所以oracle会对块的多个版本进行缓存。

六、写一致性

一般情况下,一个事务用DML语句修改一个数据块时,会获得这个块的TX锁,在事务commit或rollback之前会一直持有该锁。其它update语句被阻塞不能执行。

update是当前模式操作(delete也是?),不会启用一致性读,update会读取块的现有版本,而不是读前影像。

1、触发器和重启动

如果被修改的表有行触发器,由于“重启动”触发器的内容可能会执行两遍。

一般来讲,update或delete语句的where子句中引用的列能用于确定修改是否需要重启动。oracle使用这些列完成一个一致读,然后以当前模式获取块时,如果检测到任何列有修改,就会重启动这个语句。一般来讲,不会检查行中的其他列。

where子句中查找行所用的列集会与行触发器中引用的列进行比较。行的一致读版本会与行的当前读版本比较,只要有不同,就会重启动修改。以当前读版本为准。

如果在触发器中做任何非事务性的工作,就会受到重启动的影响,因为它不会回滚。