Oracle的隔离级别
隔离级别(isolation level)
隔离级别定义了事务与事务之间的隔离程度。
隔离级别与并发性是互为矛盾的:隔离程度越高,数据库的并发性越差;隔离程度越低,数据库的并发性越好。
ANSI/ISO SQL92标准定义了一些数据库操作的隔离级别:
未提交读(read uncommitted)
提交读(read committed)
重复读(repeatable read)
序列化(serializable)
通过一些现象,可以反映出隔离级别的效果。这些现象有:
更新丢失(lost update):当系统允许两个事务同时更新同一数据是,发生更新丢失。
脏读(dirty read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
非重复读(nonrepeatable read):同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。(A transaction rereads data it has previously read and finds that another committed transaction has modified or deleted the data.)
幻像(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。(A transaction reexecutes a query returning a set of rows that satisfies a search condition and finds that another committed transaction has inserted additional rows that satisfy the condition.)
下面是隔离级别及其对应的可能出现或不可能出现的现象:
isolation level
|
Dirty Read
|
NonRepeatable Read
|
Phantom Read
|
Read uncommitted | Possible | Possible | Possible |
Read committed | Not possible | Possible | Possible |
Repeatable read | Not possible | Not possible | Possible |
Serializable | Not possible | Not possible | Not possible |
ORACLE的隔离级别
ORACLE提供了SQL92标准中的read committed和serializable,同时提供了非SQL92标准的read-only。
read committed:
这是ORACLE缺省的事务隔离级别。
事务中的每一条语句都遵从语句级的读一致性。
保证不会脏读;但可能出现非重复读和幻像。
serializable:
简单地说,serializable就是使事务看起来象是一个接着一个地顺序地执行。
仅仅能看见在本事务开始前由其它事务提交的更改和在本事务中所做的更改。
保证不会出现非重复读和幻像。
Serializable隔离级别提供了read-only事务所提供的读一致性(事务级的读一致性),同时又允许DML操作。
如果有在serializable事务开始时未提交的事务在serializable事务结束之前修改了serializable事务将要修改的行并进行了提交,则serializable事务不会读到这些变更,因此发生无法序列化访问的错误。(换一种解释方法:只要在serializable事务开始到结束之间有其他事务对serializable事务要修改的东西进行了修改并提交了修改,则发生无法序列化访问的错误)
If a serializable transaction contains data manipulation language (DML) that attempts to update any resource that may have been updated in a transaction uncommitted at the start of the serializable transaction, (并且修改在后来被提交而没有回滚), then the DML statement fails. 返回的错误是ORA-08177: Cannot serialize access for this transaction。
ORACLE在数据块中记录最近对数据行执行修改操作的N个事务的信息,目的是确定是否有在本事务开始时未提交的事务修改了本事务将要修改的行。具体见英文:Oracle permits a serializable transaction to modify a data row only if it can determine that prior changes to the row were made by transactions that had committed when the serializable transaction began. To make this determination efficiently, Oracle uses control information stored in the data block that indicates which rows in the block contain committed and uncommitted changes. In a sense, the block contains a recent history of transactions that affected each row in the block. The amount of history that is retained is controlled by the INITRANS parameter of CREATE TABLE and ALTER TABLE. Under some circumstances, Oracle may have insufficient history information to determine whether a row has been updated by a "too recent" transaction. This can occur when many transactions concurrently modify the same data block, or do so in a very short period. You can avoid this situation by setting higher values of INITRANS for tables that will experience many transactions updating the same blocks. Doing so will enable Oracle to allocate sufficient storage in each block to record the history of recent transactions that accessed the block.
The INITRANS Parameter:Oracle stores control information in each data block to manage access by concurrent transactions. Therefore, if you set the transaction isolation level to serializable, you must use the ALTER TABLE command to set INITRANS to at least 3. This parameter will cause Oracle to allocate sufficient storage in each block to record the history of recent transactions that accessed the block. Higher values should be used for tables that will undergo many transactions updating the same blocks.
read-only:
遵从事务级的读一致性,仅仅能看见在本事务开始前由其它事务提交的更改。
不允许在本事务中进行DML操作。
read only是serializable的子集。它们都避免了非重复读和幻像。区别是在read only中是只读;而在serializable中可以进行DML操作。
Export with CONSISTENT = Y sets the transaction to read-only.
read committed和serializable的区别和联系:
事务1先于事务2开始,并保持未提交状态。事务2想要修改正被事务1修改的行。事务2等待。如果事务1回滚,则事务2(不论是read committed还是 serializable方式)进行它想要做的修改。如果事务1提交,则当事务2是read committed方式时,进行它想要做的修改;当事务2是serializable方式时,失败并报错“Cannot serialize access”,因为事务2看不见事务1提交的修改,且事务2想在事务一修改的基础上再做修改。具体见英文:Both read committed and serializable transactions use row-level locking, and both will wait if they try to change a row updated by an uncommitted concurrent transaction. The second transaction that tries to update a given row waits for the other transaction to commit or roll back and release its lock. If that other transaction rolls back, the waiting transaction (regardless of its isolation mode) can proceed to change the previously locked row, as if the other transaction had not existed. However, if the other (blocking) transaction commits and releases its locks, a read committed transaction proceeds with its intended update. A serializable transaction, however, fails with the error "Cannot serialize access", because the other transaction has committed a change that was made since the serializable transaction began.
read committed和serializable可以在ORACLE并行服务器中使用。
关于SET TRANSACTION READ WRITE:read write和read committed 应该是一样的。在读方面,它们都避免了脏读,但都无法实现重复读。虽然没有文档说明read write在写方面与read committed一致,但显然它在写的时候会加排他锁以避免更新丢失。在加锁的过程中,如果遇到待锁定资源无法锁定,应该是等待而不是放弃。这与read committed一致。
语句级的读一致性
ORACLE保证语句级的读一致性,即一个语句所处理的数据集是在单一时间点上的数据集,这个时间点是这个语句开始的时间。
一个语句看不见在它开始执行后提交的修改。
对于DML语句,它看不见由自己所做的修改,即DML语句看见的是它本身开始执行以前存在的数据。
事务级的读一致性
事务级的读一致性保证了可重复读,并保证不会出现幻像。
设置隔离级别
设置一个事务的隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION READ ONLY;
设置增个会话的隔离级别
ALTER SESSION SET ISOLATION_LEVEL SERIALIZABLE;
ALTER SESSION SET ISOLATION_LEVEL READ COMMITTED;
事务的隔离性研究
SQL 标准用三个必须在并行的事务之间避免的现象定义了四个级别的事务隔离。 这些不希望发生的现象是:
脏读(dirty reads):一个事务读取了另一个未提交的并行事务写的数据。
不可重复读(non-repeatable reads ): 一个事务重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务修改过。
幻读(phantom read ): 一个事务重新执行一个查询,返回一套符合查询条件的行,发现这些行因为其他最近提交的事务而发生了改变。
这四种隔离级别和对应的行为在表Table 12-1中描述:
Table 12-1 SQL事务隔离级别
隔离级别
|
脏读 (Dirty Read)
|
不可重复读 (NonRepeatable Read)
|
幻读 (Phantom Read)
|
读未提交(Read uncommitted) |
可能
|
可能
|
可能
|
读已提交(Read committed) |
不可能
|
可能
|
可能
|
可重复读(Repeatable read)
|
不可能
|
不可能
|
可能
|
可串行化(Serializable) |
不可能
|
不可能
|
不可能
|
在 PostgreSQL 里,你可以请求四种可能的事务隔离级别中的任意一种。但是在内部,实际上只有两种独立的隔离级别,分别对应读已提交和可串行化。如果你选择了读未提交的级别,实际上你用的是读已提交,在你选择可重复的读级别的时候,实际上你用的是可串行化,所以实际的隔离级别可能比你选择的更严格。这是 SQL 标准允许的:四种隔离级别只定义了哪种现象不能发生,但是没有定义那种现象一定发生。 PostgreSQL 只提供两种隔离级别的原因是,这是把标准的隔离级别与多版本并发控制架构映射相关的唯一的合理方法。可用的隔离级别的行为在下面小节里描述。
1、读已提交隔离级别
读已提交(Read Committed) 是 PostgreSQL 里的缺省隔离级别。当一个事务运行在这个隔离级别时,一个 SELECT 查询只能看到查询开始之前提交的数据而永远无法看到未提交的数据或者是在查询执行时其他并行的事务提交做的改变。(不过 SELECT 的确看得见同一次事务中前面更新的结果。即使它们还没提交也看得到) 实际上,一个 SELECT 查询看到一个在该查询开始运行的瞬间该数据库的一个快照。 请注意两个相邻的 SELECT 命令可能看到不同的数据,哪怕它们是在同一个事务里,因为其它事务会在第一个SELECT执行的时候提交。
UPDATE、 DELETE或者 SELECT FOR UPDATE 在搜索目标行的时候的行为和SELECT 一样:它们只能找到在命令开始的时候已经提交的行。不过,这样的目标行在被找到的时候可能已经被其它并发的事务更新 (或者删除,或者标记为更新的)。在这种情况下,即将进行的更新将等待第一个更新事务提交或者回滚 (如果它还在处理)。 如果第一个更新回滚,那么它的作用将被忽略,而第二个更新者将继续更新最初发现的行。如果第一个更新者提交,那么如果第一个更新者删除了该行,则第二个更新者将忽略该行,否则它将试图在该行的已更新的版本上施加它的操作。系统将重新计算命令搜索条件(WHERE 子句),看看该行已更新的版本是否仍然符合搜索条件。如果是,则第二个更新继续其操作,从该行的已更新版本开始。
因为上面的规则,正在更新的命令可能会看到不一致的快照 —— 它们可以看到影响它们试图更新的并发更新命令的效果,但是它们看不到那些命令对数据库里其它行的作用。这样的行为令读已提交模式不适合用于哪种涉及复杂搜索条件的命令。不过,它对于简单的情况而言是正确的。比如,假设我们用类似下面这样的命令更新银行余额:
BEGIN; UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345; UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534; COMMIT;
如果两个并发事务试图修改帐号 12345 的余额,那我们很明显希望第二个事务是从帐户行的已经更新过的版本上进行更新。因为每个命令只是影响一个已经决定了的行,因此让它看到更新后的版本不会导致任何不一致的问题。
因为在读已提交模式里,每个新的命令都是从一个新的快照开始的,而这个快照包含所有到该时刻为止已经提交的事务,因此同一个事务里的后面的命令将看到任何已提交的并发事务的效果。 这里要考虑的问题是我们在一个命令里是否看到数据库里绝对一致的视图。
读已提交模式提供的部分事务隔离对于许多应用而言是足够的,并且这个模式速度快,使用简单。不过,对于做复杂查询和更新的应用,可能需要保证数据库有比读已提交模式提供的更加严格的一致性视图。
2、可串行化隔离级别
可串行化(Serializable) 级别提供最严格的事务隔离。这个级别模拟串行的事务执行,就好象事务将被一个接着一个那样串行的,而不是并行的执行。不过,使用这个级别的应用必须准备在串行化失败的时候重新发动事务。
当一个事务处于可串行化级别,一个 SELECT 查询只能看到在事务开始之前提交的数据而永远看不到未提交的数据或事务执行中其他并行事务提交的修改。(不过,SELECT 的确看得到同一次事务中前面的更新的效果。即使事务还没有提交也一样) 这个行为和读已提交级别是不太一样,它的 SELECT 看到的是该事务开始时的快照,而不是该事务内部当前查询开始时的快照。这样,一个事务内部后面的 SELECT 命令总是看到同样的数据。
UPDATE、DELETE和 SELECT FOR UPDATE 在搜索目标行上的行为和 SELECT 一样:它们将只寻找在事务开始的时候已经提交的目标行。但是,这样的目标行在被发现的时候可能已经被另外一个并发的事务更新了(或者是删除或者是标记为更新)。在这种情况下,可串行化的事务将等待第一个正在更新的事务提交或者回滚 (如果它仍然在处理中)。 如果第一个更新者回滚,那么它的影响将被忽略, 而这个可串行化的就可以继续更新它最初发现的行。但是如果第一个更新者提交了 (并且实际上更新或者删除了该行,而不只是为更新选中它) 那么可串行化事务将回滚,并返回下面信息:
ERROR: Can't serialize access due to concurrent update
因为一个可串行化的事务在可串行化事务开始之后不能更改被其他事务更改过的行。
当应用收到这样的错误信息时,它应该退出当前的事务然后从头开始重新进行整个事务。第二次运行时,该事务看到的前一次提交的修改是该数据库初始的样子中的一部分, 所以把新版本的行作为新事务更新的起点不会有逻辑冲突。
请注意只有更新事务才需要重试,只读事务从来没有串行化冲突。
可串行化事务级别提供了严格的保证:每个事务都看到一个完全一致的数据库的视图。不过,如果并行更新令数据库不能维持串行执行的样子,那么应用必须准备重试事务。因为重做复杂的事务的开销可能是非常可观的,所以我们只建议在更新命令中包含足够复杂的逻辑,在读已提交级别中可能导致错误的结果的情况下才使用。最常见的是,可串行化模式只是在这样的情况下是必要的:一个事务连续做若干个命令, 而这几个命令必须看到数据库完全一样的视图。
3、可串行化隔离与真正的可串行化之比较
执行的“可串行化”的直观含义(以及数学定义)是两个成功提交的并发事务将显得好像严格地串行执行一样,一个跟着一个 —— 尽管我们可能无法预期哪个首先执行。我们必须明白,禁止那些在 Table 12-1 里面列出的行为并不能保证真正的可串行化。并且,实际上 PostgreSQL 的可串行化模式并不保证在这种含义下的可串行化。举例来说,假设一个表 mytab,最初包含
class | value -------+------- 1 | 10 1 | 20 2 | 100 2 | 200
假设可串行化事务 A 计算: SELECT SUM(value) FROM mytab WHERE class = 1; 然后把结果(30)作为 value 到表中,class = 2。 同时,一个并发的可串行化的事务 B 进行下面计算: SELECT SUM(value) FROM mytab WHERE class = 2; 并且获取结果 300,然后它插入一行新行,class = 1
然后两个事务都提交。所有列出的禁止行为都不会发生,但是我们拿到的结果是不可能在任何一种串行执行下看到的。如果 A 在 B 之前执行,B 应该计算出总和 330,而不是 300,如果是另外一种顺序,那么 A 计算出的总和也会不同。
为了保证真正数学上的可串行化,一个数据库系统必须强制谓词锁定, 这就意味着一个事务不能插入或者更改这样的数据行:这个数据行的数据匹配另外一个并发事务的 WHERE 条件。 比如,一旦事务 A 执行了查询 SELECT ... WHERE class = 1,那么一个谓词锁定系统将禁止事务 B 插入任何 class 为 1 的新行,直到 A 提交。 实际上,一个谓词锁定系统避免了幻读,方法是约束写入的东西,而 MVCC 避免幻读的方法是约束它读取的东西。这样的锁系统实现起来非常复杂,并且执行起来代价高昂,因为每个会话都必须要知道每个并发事务的每个查询的执行细节。并且这样大量的开销在大部分情况下都是浪费掉的,因为在实际情况下大部分应用都不做会导致问题的这种事情。(当然,上面的例子是静心设计的,不能代表真实的软件。) 因此,PostgreSQL 并未实现谓词锁定,而就我们所知,没有其它的生产中的 DBMS 实现了这个。
在那些非串行化执行真的可能有危险的场合,可以通过使用明确的锁定来避免问题的发生。
posted on 2013-10-27 15:43 heartstage 阅读(4320) 评论(0) 编辑 收藏 举报