<oracle-6> 并发多版本控制

本文介绍了并发控制的概念及其在Oracle数据库中的实现方式,包括多种类型的锁机制和多版本控制体系结构。此外,还详细探讨了SQL标准定义的四种事务隔离级别,并解释了Oracle如何支持这些隔离级别。
[b]6.1 什么是并发控制[/b]
[b]并发控制(concurrency control)是数据库提供的函数集合[/b],允许多个人同时访问和修改数据。前面说过,锁是oracle管理共享数据库资源并发访问并阻止并发数据库事务之间“相互干涉”的核心机制之一。总结一下,oracle使用了多种锁,包括如下几种类型:
[b]TX(事务处理锁),TM(DML队列)锁和DDL锁,latch和Mutex。[/b]

不论是哪种锁,请求锁时都存在最小开销。TX事务锁在性能和基数方面可扩展性极好。TM锁和DDL锁要尽可能地采用限制最小的模式。Latch和队列锁都是轻量级的,而且都很快。如果应用设计不当,锁定的时间过长,而导致数据库中出现阻塞,就会带来问题。

但是oracle对并发的支持不只是高效的锁定。它还实现了一种多版本控制(multi-versioning)体系结构,这种体系结构提供了一种受控但高度并发的数据访问。多[color=red]版本控制是指oracle能同时物化多个版本的数据,这也是oracle提供数据读一致视图(即相对于某个时间点有一致的结果)的机制。[/color]"读一致性"反映了一个事实:Oracle中的查询会从某个一致的时间点开始返回,查询使用的每个块都从同一个时间点开始,即使它在你执行查询时被修改或锁定。多版本控制有一个相当令人惊喜的连带效果,[color=red]即数据的读取器绝对不会被数据的写入器所阻塞[/color]。它不会与其他会话发生死锁,而且不可能得到数据库中根本不存在的答案。

注意:在分布式两阶段提交的处理期间,在很短的一段时间内oracle不允许读信息。这种处理相当少见,只有当查询在准备阶段和提交阶段之间开始,而且视图在提交之前读取数据,此时才会存在这个问题,所以这里不详细介绍这种情况。

默认情况下,oracle的读一致性多版本模型应用于语句级,也就是说应用于每一个查询;另外还可以应用于事务级。这说明,至少提交到数据库的每一条sql语句都会看到数据库的一个读一致视图,如果你希望数据库的这种读一致视图是事务级的也是可以的。
[b]数据库中事务的基本作用是将数据库从一种一致状态转变为另一种一致状态[/b]。ISO SQL标准指定了多种事务隔离级别(transaction isolation level),这些隔离级别定义了一个事务对其他事务做出的修改有多敏感。越是敏感,数据库在应用执行的各个事务之间必须提供的隔离程度就越高。

[b]6.2 事务隔离级别[/b]
ANSI/ISO SQL标准定义了4种事务隔离级别,对于相同的事务,采用不同的隔离级别分别有不同的结果。也就是说,即使输入相同,而且采用同样的方式来完成同样的工作,也可能得到完全不同的答案,这取决于事务的隔离级别。这些隔离级别是根据3个现象定义的,以下就是给定隔离级别可能允许或不允许的3种现象:
[color=red][b]1. 脏读(dirty read)[/b][/color]:你能读取[b]未提交[/b]的数据,也就是脏数据。只要打开别人正在读写的一个os文件,就可以达到脏读的效果。如果允许脏读,将影响数据完整性,另外外键约束会遭到破坏,而且会忽略唯一性约束。
[color=red][b]2. 不可重复读(nonrepeatable read)[/b][/color]:这意味着,如果你在T1时间读取某一行,在T2时间重新读取这一行时,这一行可能已经有所修改。也许它已经消失,也可能被更新了,等等。
[color=red][b]3. 幻像读(phantom read)[/b][/color]:这说明,如果你在T1时间执行一个查询,而在T2时间再执行这个查询,此时可能已经向数据库中增加了另外的行,这会影响你的结果。[color=blue]与不可重复读的区别在于:在幻象读中,已经读取的数据不会改变,只是与以前相比,会有更多的数据满足你的查询条件。[/color]

Sql隔离级别是根据以下原则定义的,即是否允许上述各个现象。
[img]http://dl2.iteye.com/upload/attachment/0105/1838/ea0b9d61-8e6d-3c9a-88f8-bc15ed38ea32.jpg[/img]

Oracle明确地支持read committed和serializable隔离级别。Sql标准视图建立多种隔离级别,从而允许在各个级别上完成的查询有不同程度的一致性。Repeatable read也是sql标准定义的一个隔离级别,可以保证由查询得到读一致的结果。Read committed不能提供一致的结果,而[b]read uncommitted级别用来得到非阻塞读[/b]。
[b]不过在oracle中,read committed则有得到读一致查询所需的所有属[/b]性。在许多其他数据库中,read committed查询可能返回数据库中根本不存在的答案(即实际上任何时间点上都有这样的结果)。另外,oracle还秉承了read uncommitted的精神。有些数据库提供脏读的目的是为了支持非阻塞读,不过,oracle不需要脏读来达到这个目的,而且也不支持脏读。但在其他数据库中必须实现脏读来提供非阻塞读。
除了4个已定义的sql隔离级别外,oracle还提供了另外一个级别,称为read only。read only事务相当于无法在sql中完成任何修改的repeatable read或serializable事务。

[b]6.2.1 read uncommitted[/b]
此隔离级别允许脏读。Oracle没有利用脏读,甚至不允许脏读。Read uncommitted隔离级别的根本目标是提供一个基于标准的定义以支持非阻塞读。我们已经看到,oracle默认地就提供非阻塞读。

[b]6.2.2 read committed[/b]
Read committed隔离级别是指事务只能读取数据库中已经提交的数据。这里没有脏读,不过可能有不可重复读(也就是说,在同一个事务中重新读取同一行可能返回不同的答案)和幻象读(与事务早期相比,查询不光能看到已经提交的行,还可能看到新插入的行)。在数据库应用中,[b]read committed可能是最常用的隔离级别了,这也是oracle数据库的默认模式[/b],很少看到使用其他的隔离级别。
如果在一条语句中查询了多行,除了oracle外,在几乎所有其他的数据库中,read committed隔离都可能退化得像脏读一样,这取决于具体的实现。
在oracle中,由于使用多版本和读一致查询,无论是使用read committed还是使用read uncommitted,查询得到的答案总是一致的。Oracle会按查询开始时数据的样子对已修改的数据进行重建,恢复其“本来面目”,因此会返回数据库在查询开始时的答案。

注意:不同的数据库尽管采用相同的隔离级别,而且在完全相同的环境中执行,仍有可能返回完全不同的答案。在oracle中,非阻塞读并没有以答案不正确作为代价。
[img]http://dl2.iteye.com/upload/attachment/0105/1840/efb06d8d-1f20-3b12-92cc-4b9061830051.jpg[/img]

[b]6.2.3 repeatable read[/b]
Repeatable read的目标是提供这样一个隔离级别,它不仅能给出一致的正确答案,还能避免丢失更新。

[b]1.得到一致的答案[/b]
如果隔离级别是repeatable read,从给定查询得到的结果相对于某个时间点来说应该是一致的。大多数数据库(不包括oracle)都通过使用低级的共享读锁来实现可重复读。共享读锁会防止其他会话修改我们已经读取的数据。当然,这会降低并发性。Oracle则采用了更具并发性的多版本模型来提供读一致的答案。
[b]在oracle中,通过使用多版本控制[/b],得到的答案相对于查询开始执行那个时间点是一致的。在其他数据库中通过使用共享读锁,可以得到相对于查询完成那个时间点一致的答案,也就是说,查询结果相对于我们得到答案的那一刻是一致的。但是共享读锁有一个副作用:数据的读取器和写入器可能而且经常相互死锁。
但是oracle从不使用共享读锁,oracle选择了多版本控制机制,尽管更难实现,但绝对更具并发性。

[b]2.丢失更新:另一个可移植性问题[/b]
在采用共享读锁的数据库中,repeatable read的一个常见用途是防止丢失更新。因为,只要选择数据就会在上面加一个锁,数据一旦由一个事务读取,就不能被任何其他事务修改。但是等你把应用移植到一个没有使用共享读锁作为底层并发控制机制的数据库时,就会痛苦地发现与你预想的并不一样。
尽管听上去使用共享读锁好像不错,但必须记住,如果读取数据时在所有数据上都加共享读锁,这肯定会严重限制并发和修改。

[b]6.2.4 serializable[/b]
一般认为这是最受限的隔离级别,但是它也提供了最高程度的隔离性。Serializable事务在一个环境中操作时,就好像没有别的用户在修改数据库中的数据一样。
Oracle中是这样实现serializable事务的:原本通常在语句级得到的读一致性现在可以扩展到事务级别。结果并非相对于语句开始的那个时间点一致,而是在事务开始的那一刻就固定了。换句话说,oracle使用undo段按事务开始时数据的原样来重建数据,而不是按语句开始时的样子重建。
这种隔离性是有代价的,可能会得到ora-08177错误(can’t serialize access for this transaction)。只要你试图更新某一行,而这一行自事务开始后已经修改,你就会得到这个消息。也就是当事务隔离级别为serializable,两个事务并发修改同一个对象,当前一个事务提交或回滚时,第二个事务会收到该错误。

[b]6.2.5 read only[/b]
Read only事务与serializable事务很相似,唯一的区别是read only事务不允许修改,因此不会遇到ORA-08177错误。
<session-factory> <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider, NHibernate</property> <!-- 每种drever_class对Oracle的性能不一样,OracleClientDriver对高并发性能支持很好;如果需要使用存储过程则必须要使用OracleManagedDataClientDriver; --> <!--<property name="connection.driver_class">NHibernate.Driver.OracleClientDriver</property>--> <!--<property name="connection.driver_class">NHibernate.Driver.OracleDataClientDriver</property>--> <property name="connection.driver_class">NHibernate.Driver.OracleManagedDataClientDriver</property> <property name="connection.connection_string"> <!-- User ID=fheap; Password=fheap; Data Source=(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT = 60)(ADDRESS=(PROTOCOL=TCP)(HOST=10.255.1.13)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=10.255.1.14)(PORT=1521))(LOAD_BALANCE=yes)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=cim)(FAILOVER_MODE=(TYPE=SELECT)(METHOD=BASIC)(RETRIES=180)(DELAY=5)))); Pooling=True;Max Pool Size = 100;Min Pool Size = 0;Connect Timeout=500;--> User ID=FHEAP; Password=fheap; Data Source=(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT = 60)(ADDRESS=(PROTOCOL=TCP)(HOST=172.17.6.28)(PORT=1521))(LOAD_BALANCE=yes)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=mesdbtest)(FAILOVER_MODE=(TYPE=SELECT)(METHOD=BASIC)(RETRIES=180)(DELAY=5)))); Pooling=True;Max Pool Size = 100;Min Pool Size = 0;Connect Timeout=500; <!--User ID=fheap; Password=123456; Data Source=(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT = 60)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.85.128)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));Pooling=True;Max Pool Size = 150;Min Pool Size = 10;Connect Timeout=500;--> <!--User ID=mesforehope; Password=mesforehope; Data Source=(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT = 60)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=10.255.1.34)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=mesdb)));--> </property> <property name="show_sql">false</property> <property name="dialect">NHibernate.Dialect.Oracle12cDialect</property> <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property> <!-- If your database setup use an ASCII charset, switch following property to true. --> <property name="oracle.use_n_prefixed_types_for_unicode">false</property> <!-- Depending on your database setup, the default cast length of 4000 may be too big. By example, if previous setting is true, NHibernate may try to use nvarchar2(4000), which will be rejected if its underlying charset is UTF16 and the database MAX_STRING_SIZE is not extended. In such case, reduce it to 2000. --> <property name="query.default_cast_length"></property> <property name="hbm2ddl.keywords">none</property> <!-- 每个线程获取到的session是不一样的。多线程并发一定要配置,否则会导致线程重用,获取一样的session,就会导致后来获取到的session失效 --> <property name="current_session_context_class">thread_static</property> <!--加载映射--> <mapping assembly="Nebula.EAP.Core.Scenario" /> </session-factory> 只更换驱动NHibernate.Driver.OracleClientDriver对其他配置有没有影响
最新发布
06-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值