oracle锁机制的延续——并发与多版本1

开发多用户数据库应用,最大的难题之一是:一方面要力争最大的并发访问,而同时还要确保每一用户 能以一致的方式读取和修改数据。力争最大的并发访问需要用锁定机制,而确保一致读和修改数据则需要一些并发控制机制。

   1、并发控制:

    并发控制(concurrency control)是数据库提供的函数集合,允许多个人同时访问和修改数据。锁(lock)是Oracle管理共享数据库资源并发访问并防止并发数据库事务之间“相互干涉”的核心机制之一。总结一下,Oracle使用了多种锁,包括:
    TX锁:修改数据的事务在执行期间会获得这种锁。
    TM锁和DDL锁:在你修改一个对象的内容(对于TM锁)或对象本身(对应DDL锁)时,这些锁可以确保对象的结构不被修改。
    闩(latch):这是Oracle的内部锁,用来协调对其共享数据结构的访问。

    Oracle对并发的支持不仅使用高效的锁定,还实现了一种多版本体系结构,它提供了一种受控但高度并发的数据访问。这里的多版本指的是可以同时地物化多个版本的数据,这也是Oracle提供读一致性视图的机制。多版本有一个很好的副作用,即数据的读取器(reader)绝对不会被数据的写入器(writer)所阻塞。换句话说,写不会阻塞读。这是Oracle与其他数据库之间的一个根本区别。

     默认情况下,Oracle的读一致性多版本视图是应用与语句级的,即对应与每一个查询。也可以改为事务级的。数据库中事务的基本作用是将数据库从一种一致状态转变为另一种一种状态。ISO SQL标准指定了多种事务隔离级别(transaction isolation level),这些隔离级别定义了一个事务对其他事务做出的修改有多“敏感”。越是敏感,数据库在应用执行的各个事务之间必须提供的隔离程度就越高。

    

2.事务隔离级别

      ANSI/ISO SQL标准定义了4种事务隔离级别,对于相同的事务,采用不同的隔离级别分别有不同的结果。也就是说,即使输入相同,而且采用同样的方式来完成同样的工作,也可能得到完全不同的答案,这取决于事务的隔离级别。这些隔离级别是根据3个“现象”定义的,以下就是给定隔离级别可能允许或不允许的3种现象:

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

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

这里的修改是已经提交了的,与脏读不同。

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

      SQL隔离级别是根据这些现象来描述级别的,并没有强制采用某种特定的锁定机制或硬性规定的特定行为,这就允许多种不同的锁定/并发机制存在。

                                表1   ANSI隔离级别
               隔离级别                   脏读   不可重复读   幻像读
        READ UNCOMMITTED     允许    允许           允许
        READ COMMITTED                    允许           允许
        REPEATABLE READ                                   允许
        SERIALIZABLE

      SQL的隔离级别表明Read Committed不能提供一致性的结果,因为有可能产生不可重复读和幻想读,而在Oracle中,Read Committed则有得到读一致查询所需的属性。另外,Oracle还秉承了READ UNCOMMITTED的“精神”。(有些数据库)提供脏读的目的是为了支持非阻塞读,也就是说,查询不会被同一个数据的更新所阻塞,也不会因为查询而阻塞同一数据的更新。不过,Oracle不需要脏读来达到这个目的,而且也不支持脏读。但在其他数据库中必须实现脏读来提供非阻塞读。

      除了SQL定义的4个隔离级别外,Oracle还定义了另外一个级别,叫做Read Only。READ ONLY事务相对于无法在SQL中完成任何修改的REPEATABLE READ或SERIALIZABLE事务。如果事务使用READ ONLY隔离级别,只能看到事务开始那一刻提交的修改,但是插入、更新和删除不允许采用这种模式(其他会话可以更新数据,但是READ
ONLY事务不行)。如果使用这种模式,可以得到REPEATABLE READ和SERIALIZABLE级别的隔离性。

       以下分别介绍一下这几个隔离级别。

2.1 READ UNCOMMITTED

       这个隔离级别允许脏读,但Oracle不利用脏读,甚至不允许脏读。其实Read Uncommitted的根本目标是提供一个基于标准的定义以支持非阻塞读。而Oracle是默认支持非阻塞读的。脏读是不是一个特性,而是一个缺点。Oracle根本不需要脏读,Oracle可以完全得到脏读的所有好处(即无阻塞),而不会带来任何不正确的结果。

        它是怎么实现的? 当我们在开始的时候查询一个表中的数据,并修改了这个数据,而在事务的过程中如果有其他事务准备查询这个数据,Oracle会使用多版本创建该块的一个副本,包含原来没修改的值,这样一来,Oracle就有效地绕过了已修改的数据,它没有读修改后的值,而是从undo段,也称为回滚(rollback)重新建立原数据。因此可以返回一致而且正确的答案,而无需等待事务提交。

        而那些允许脏读的数据库就会读到修改过的数据。

 

2.2 READ COMMITTED

        READ COMMITTED隔离级别是指,事务只能读取数据库中已经提交的数据。这里没有脏读,不过可能有不可重复读(也就是说,在同一个事务中重复读取同一行可能返回不同的答案)和幻像读(与事务早期相比,查询不光能看到已经提交的行,还可以看到新插入的行)。在数据库应用中,READ COMMITTED可能是最常用的隔离级别了,这也是Oracle数据库的默认模式,很少看到使用其他的隔离级别。

        在Oracle中,由于使用多版本和读一致查询,无论是使用READ COMMITTED还是使用READ UNCOMMITTED,对同一表进行查询得到的答案总是一样的。Oracle会按查询开始时数据的样子对已修改的数据进行重建,恢复其“本来面目”,因此会返回数据库在查询开始时的答案。

        如果采用其他数据库在Read Committed隔离级别时,别的用户在查询期间如果事务未提交,则别的用户需要等待,直到事务提交,而且最后得到的结果还可能不正确(因为不可重复读)。

         自己建了一个测试表t,发现Oracle的事务隔离级别为Read Committed时,不可重复读现象是会产生的。具体做法如下(下一行的时间比上一行后):

                           会话1:                                                会话2

            create table t(x int);

            insert into t values(1);

            insert into t values(2);

            commit;

                                                                      delete  from t where x=2(开始事务)

            update t set x=10 where x=1

                                                                      select * from t (x=1)

            commit;

                                                                      select * from t (x=10)

                                                                      commit;

 

          可见Oracle的Read Commited还是会返回不同的结果的。不知道书中为什么说会返回同样的结果。望高手解答!

          (这里要得到一致的结果只能设为 SEAIALIZABLE才能得到,而且还要每次transaction 开始的时候设定)

 

2.3 REPEATABLE READ

       REPEATABLE READ的目标是提供这样一个隔离级别,它不仅能给出一致的正确答案,还能避免丢失更新。

       一致性读:

       如果隔离级别是REPEATABLE READ,从给定查询得到的结果相对于某个时间点来说应该是一致的。大多数数据库(不包括Oracle)都通过使用低级的共享读锁来实现可重复读。共享读锁会防止其他会话修改我们已经读取的数据。当然,这会降低并发性。Oracle则采用了更具并发性的多版本模型来提供读一致的答案。
       在Oracle中,通过使用多版本,得到的答案相对于查询开始执行那个时间点是一致的。在其他数据库中,通过使用共享读锁,可以得到相对于查询完成那个时间点一致的答案,也就是说,查询结果相对于我们得到的答案的那一刻是一致的.

       但是使用共享读锁来得到一致性的结果有副作用之一:数据的读取器会阻塞数据的写入器。它会影响并发性。还有一个副作用是数据的读取器经常和写入器互相死锁。

       可以看到,Oracle中可以得到语句级的读一致性,而不会带来读阻塞写的现象,也不会导致死锁。Oracle从不使用共享读锁,从来不会。Oracle选择了多版本机制,尽管更难实现,但绝对更具并发性。

       丢失更新:

       在采用共享读锁的数据库中,REPEATABLE READ的一个常见用途是防止丢失更新。在一个采用共享读锁(而不是多版本)的数据库中,如果启用了REPEATABLE READ,则不会发生丢失更新错误。这些数据库中之所以不会发生丢失更新,原因是:这样选择数据就会在上面加一个锁,数据一旦由一个事务读取,就不能被任何其他事务修改。如此说来,如果你的应用认为REPEATABLE
READ就意味着“丢失更新不可能发生”,等你把应用移植到一个没有使用共享读锁作为底层并发控制机制的数据库时,就会痛苦地发现与你预想的并不一样。

       尽管听上去使用共享读锁好像不错,但你必须记住,如果读取数据时在所有数据上都加共享读锁,这肯定会严重地限制并发读和修改。所以,尽管在这些数据库中这个隔离级别可以防止丢失更新,但是与此同时,也使得完成并发操作的能力化为乌有!对于这些数据库,你无法鱼和熊掌兼得。

 

2.4 SEAIALIZABLE

      一般认为这是最受限的隔离级别,但是它也提供了最高程度的隔离性。SERIALIZABLE事务在一个环境中操作时,就好像没有别的用户在修改数据库中的数据一样。我们读取的所有行在重新读取时都肯定完全一样,所执行的查询在整个事务期间也总能返回相同的结果。

      Oracle采用了一种乐观的方法来实现串行化,它认为你的事务想要更新的数据不会被其他事务所更新,而且把宝押在这上面。一般确实是这样的,所以说通常这个宝是押对了,特别是在事务执行得很快的OLTP型系统中。尽管在其他系统中这个隔离级别通常会降低并发性,但是在Oracle中,倘若你的事务在执行期间没有别人更新你的数据,则能提供同等程度的并发性,就好像没有SERIALIZABLE事务一样。另一方面,这也是有缺点的,如果宝押错了,你就会得到ORA_08177错误。

      Oracle试图完全在行级得到这种隔离性,但是即使你想修改的行尚未被别人修改后,也可能得到一个ORA-01877错误。发生ORA-01877错误的原因可能是:包含这一行的块上有其他行正在被修改。

 

2.5 READ ONLY

      READ ONLY事务与SERIALIZABLE事务很相似,惟一的区别是READ ONLY事务不允许修改,因此不会遭遇ORA-08177错误。READ ONLY事务的目的是支持报告需求,即相对于某个时间点,报告的内容应该是一致的。在其他系统中,为此要使用REPEATABLE READ,这就要承受共享读锁的相关影响。在Oracle中,则可以使用READ
ONLY事务。采用这种模式,如果一个报告使用50条SELECT语句来收集数据,所生成的结果相对于某个时间点就是一致的,即事务开始的那个时间点。你可以做到这一点,而无需在任何地方锁定数据。

      为达到这个目标,就像对单语句一样,也使用了同样的多版本机制。会根据需要从回滚段重新创建数据,并提供报告开始时数据的原样。不过,READ ONLY事务也不是没有问题。在SERIALIZABLE事务中你可能会遇到ORA-08177错误,而在READ ONLY事务中可能会看到ORA-1555:snapshot too old错误。如果系统上有人正在修改你读取的数据,就会发生这种情况。对这个信息所做的修改(undo信息)将记录在回滚段中。但是回滚段以一种循环方式使用,这与重做日志非常相似。报告运行的时间越长,重建数据所需的undo信息就越有可能已经不在那里了。回滚段会回绕,你需要的那部分回滚段可能已经被另外某个事务占用了。此时,就会得到ORA-1555错误,只能从头再来。

 

3.多版本读一致性的含义:

      我们要理解多版本的机制和含义,才能正确地得到我们想要的结果。例如在常用的数据仓库技术中,我们经常是在某个时刻如上午9点从事务系统拉出数据,作为最初填充数据仓库的数据,然后在过一段时间后,在时刻如上午10点,再从事务系统拉出自9点修改过的所有记录,并把修改合并到数据仓库中。

       这一种方式对于其他读会被写阻塞,写会被读阻塞的系统来说,确是可以很好工作。因为在t1时刻如果存在被修改的行,那么我们读的时候会堵塞,直到事务提交,那么9点到10点修改过的所有记录会包括这一行。

       但如果采用多版本,同样的一行在9点之前(如9:59:20)已被修改,如果在查询事务来到这一行读取时,她还没有提交,那么读取事务会绕过这个锁,到达undo段里去找到读取事务开始之前那个提交的版本,即使在查询到来时她已经提交了,那我们还是会去undo段里找那个数据,原因是我们只能查询读取事务开始那一瞬间的数据,这就是读一致性的特色。这样一来,我们这次拉数据的时候拉不出来这一行的新数据。但是最重要的是10点再次拉数据的时候也不会拉出这一行更改的数据,为什么呢?因为这次拉数据就是拉的是9点以后修改的数据。

(这是一个网友对此次过程的表述)但如果采用多版本,同样的一行在t1时刻之前已被修改,但未提交,那么还采用这个策略的话,只会拉出t1时刻未修改的数据。然后在t2时刻拉出自t1
时刻修改过的所有记录,但是这一行是t1时刻之前修改的,所以不包含在内。所以我们永远到拉不到那个在t1时刻之前修改的数据。

       解决的方式是我们需要用稍微不同的方式来得到“现在”的时间。应该查询V$TRANSACTION,找出最早的当前时间是什么,以及这个视图中START_TIME列记录的时间。我们需要拉出自最老事务开始时间(如果没有活动事务,则取当前的SYSDATE值)以来经过修改的所有记录:

       select nvl( min(to_date(start_time,'mm/dd/rr hh24:mi:ss')),sysdate)
from v$transaction;

       这样他会得到在t1之前这样被修改的时间,并拉出自那个时间以来修改的数据,最后把修改合并到数据仓库中。

      

       还有一个例子是热表上的I/O超出期望值,这就是生产环境中在一个大负载条件下,一个查询使用的I/O比你在测试或开发系统时观察到的I/O要多得多,而你无法解释这一现象。你查看查询执行的I/O时,注意到它比你在开发系统中看到的I/O次数要多得多,多得简直不可想像。然后,你再在测试环境中恢复这个实例,却发现I/O又 降下来了。但是到了生产环境中,它又变得非常高(但是好像还有些变化:有时高,有时低,有时则处于中间)。可以看到,造成这种现象的原因是:在你测试系统
中,由于它是独立的,所以不必撤销事务修改。不过,在生产系统中,读一个给定的块时,可能必须撤销(回滚)多个事务所做的修改,而且每个回滚都可能涉及I/O来获取undo信息并应用于系统。

       

 

4.写一致性:

      现在来看看写方面会怎样,例如运行以下语句:

         Update t set x=2 where y=5

      在该语句运行时,有人将这条语句已经读取的一行从Y=5更新为Y=6,并提交,如果是这样会发生什么情况?也就是说,在UPDATE开始时,某一行有值Y=5。在UPDATE使用一致读来读取表时,它看到了UPDATE开始时这一行是Y=5。但是,现在Y的当前值是6,不再是5了,在更新X的值之前,Oracle会查看Y是否还是5。现在会发生什么呢?这会对更新有什么影响?
      显然,我们不能修改块的老版本,修改一行时,必须修改该块的当前版本。另外,Oracle无法简单地跳过这一行,因为这将是不一致读,而且是不可预测的。在这种情况下,我们发现Oracle会从头重新开始写修改。

 

 4.1 一致读和当前读

       Oracle处理修改语句时会完成两类块获取。它会执行:
    一致读(Consistent read):“发现”要修改的行时,所完成的获取就是一致读。
    当前读(Current read):得到块来实际更新所要修改的行时,所完成的获取就是当前读。

       如果两个会话按顺序执行以下语句会发生什么情况呢?例如

       Update t set y = 10 where y = 5;
       Update t Set x = x+1 Where y = 5;

       第一条语句在执行后,但没有提交,第二条语句执行就会被堵塞。当第一条语句提交时,第二条开始运行,但这时y已经不是5了,数据库会显示0 row updated.

 

       当第二个语句满足条件时,会进行重启动更新,例如:

       create table t(x int, y int);

       insert into t values(1,1);

       commit;

       然后在两个会话中分别执行以下两个语句:

       update t set x=x+1;

       update t set x=x+1 where x>0;

       第一会话执行后,没有提交,第二会话语句执行就会被堵塞。当第一条语句提交后,第二条语句开始运行,数据库会重启动查询,发现x>0还是满足的,x的值会继续被更新到3.

       即第二条语句一开始是使用一致性读(consistent read),读到提交的x数据(为1,第一个会话未提交),当实际要更新的时候再使用当前读(current read)去获取数据块的最新版本,发现与原来的值不符合时,就开始重启动update操作。

  

       我的理解是更新的时候,只要发现要更新的块被修改,都会重启动查询。重启动有时会带来麻烦,例如当使用触发器来发送邮件时,用户有可能收到2次邮件。

时间: 2024-10-01 14:07:05

oracle锁机制的延续——并发与多版本1的相关文章

oracle锁机制的延续——并发与多版本2

5.一致性读和当前读的深入理解: 在CSDN里经常会遇到一些特别有学习劲头的朋友,有这样一个朋友,在学习Tom的经典书  <oracle 9i/10g编程艺术> 人民邮电出版的  P244 --7.4 写一致  在一致性读 这里遇到些困惑,如下 引用: create table t ( x int, y int );  insert into t values ( 1, 1 );  commit;  create or replace trigger t_bufer  before updat

ORACLE锁机制深入理解_oracle

数据库是一个多用户使用的共享资源.当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性. 加锁是实现数据库并发控制的一个非常重要的技术.当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁.加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作. 在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Lo

理解oracle锁和闩(2)锁机制概述

锁(lock)是一种防止多个事务访问同一资源时产生破坏性的相互影响的机制.通常,高并发数据库需要利用锁机制解决数据并发访问.一致性及完整性问题. 前面提到的资源(resource)大致可以分为两类: ● 用户对象:例如表及数据行 ● 对用户透明的系统对象:例如内存中的共享数据结构.数据字典中的信息 任何 SQL 语句执行时 Oracle 都隐式地对 SQL 所需的锁进行管理,因此用户无需显式地对资源加锁.Oracle 默认采用的锁机制能尽可能地减小对数据访问的限制,在保证数据一致性的同时实现高度

MSSQL与Oracle数据库事务隔离级别与锁机制对比_oracle

一,事务的4个基本特征 Atomic(原子性): 事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要 么全部成功,要么全部失败. Consistency(一致性): 只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初 状态. Isolation(隔离性): 事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正 确性和完整性.同时,并行事务的修改必须与其他并行事务的修改 相互独立. Durability(持久性): 事务结束后,事务处理的结果必须能够得到固化. 以上属于废话

并发编程(四):也谈谈数据库的锁机制

首先声明,本次文章基本上都是从其他人的文章中或者论坛的回复中整理而来.我把我认为的关键点提取出来供自己学习.所有的引用都附在文后,在这里也就不一一表谢了. 第二个声明,我对于Internel DB并没有研究过,所使用的也是简单的写写SQL,截止到现在最多的一个经验也就是SQL的性能调优,具体点就是通过Postgresql的执行计划,来调整优化SQL语句完成在特定场景下的数据库调优.对于锁,由于数据库支持的锁机制已经能够满足平时的开发需要.因为所从事的行业并不是互联网,没有实时性高并发的应用场景,

Mysql事务,并发问题,锁机制-- 幻读、不可重复读(转)

1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约束没有被破坏 持久性:事务的提交结果,将持久保存在数据库中   2.事务并发会产生什么问题 1)第一类丢失更新:在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了. 例如: 张三的工资为5000,事务A中获取工资为5000,事务B获取工资

Oracle数据完整性和锁机制简析_oracle

本课内容属于Oracle高级课程范畴,内容略微偏向理论性,但是与数据库程序开发和管理.优化密切相关:另外本课的部分内容在前面章节已经涉及,请注意理论联系实际. 事务 事务(Transaction)从 通讯的角度看:是用户定义的数据库操作序列,这些操作要么全做.要么全不做,是不可分割的一个工作单元.事务控制语句称为TCL,一般包括Commit和Rollback. 事务不是程序,事务和程序分属两个概念.在RDBMS中,一个事务可以有一条SQL语句.一组SQL语句或者整个程序:一个应用程序又通常包含多

深入JVM锁机制1-synchronized

[本文转载于深入JVM锁机制1-synchronized] 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理.    数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现

[转]深入JVM锁机制1-synchronized

转自:http://blog.csdn.net/chen77716/article/details/6618779    目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理.    数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖