Oracle锁机制的总结

         最近在研究Oracle锁机制的时候发现网上的资料鱼龙混杂将,很多将问题复杂化,让人感觉没有条理性。经过查询原始理论资料,总结如下:

         在数据库理论中,我们知道。我们在执行并发访问数据库表时,如果没有任何一致性控制措施,那么会出现以下几种数据不一致的情况:1)提交被覆盖;2)不可重复读(其中包括了幻读);3)读“脏数据”。经过仔细分析,发现引起这些情况的根本原因是:在对数据库公共资源访问时,出现了事务交叉的情况。因此,在数据库的理论里有“可串行化”这种衡量标准,就是说:如果我们在并发情况下通过对并发事务的控制得到的最终结果和我们按顺序一个个执行事务是一样的结果,那么我们就认为这种控制是“可串行化”的,是正确的。

          锁机制是当前解决这些问题的最有效的办法,以下的两个概念“基本锁”和“意向锁”是最基本的两个概念,是构成所有其他锁的基础。
          1)为了在并发性和一致性这对矛盾中合理取舍,提供了两种最基本的锁:共享锁(读锁)、排它锁(写锁)。
         共享锁(S):如果事务T对表A加上共享锁,则事务T可以读该表,但是不能修改该表数据。其他事务也只能对该对象加上共享锁,但是不能加上排他锁。这就保证了其他事务可以读A表数据,但是不能修改A表数据(这就不会影响到读该表的事务了,有利于并发操作)。
         排他锁(X):如果事务T对对象A加上排他锁,则只允许T对A对象读取和修改,其他事务不能对A增加任何锁,只到T释放加载A上的排他锁。
         那么如何使用这些锁解决问题呢?数据库的基本理论提出了三种级别的封锁协议,封的越高,控制的范围越大,一致性也越好,并发性也越差。具体的大家可以查资料,也很简单,这里为了节约篇幅不再叙述。
          2)可是动辄就给整个表加锁,这有点鲁莽草率。我们知道,在一个并发的环境下,我们的程序(事务)操作的常常并不是表中的同一样数据,而这样大范围的控制表数据很显然不利于并发,那么我们可以针对需要缩小控制范围,于是提出了“多粒度封锁”的概念。也就是说,程序(事务)既可以封锁整个表,也可以只封锁某些和我们有关的行,这样就不会影响其他程序(事务)到该表中其他的行。可是新的问题又来了,我们如果要对某个表进行某种操作,比如删除整个表的数据,那么数据库程序就要遍历整个表的每个行,这很显然过于消耗资源,那咋办?于是,“意向锁”应运而生。意向锁的概念基本上是这个意思:如果我们要封锁表的某个行,则也在该行所在的表上加上锁。这样的话在对整个表进行操作时,就不用遍历表里的所有行了。通过和前边提到的共享锁和排他锁,我们组合出三种锁:
           意向共享锁(IS):如果我们对表加IS锁,表示该表中的某个行被加上了S锁。
           意向排他锁(IX):如果我们对表加IX锁,表时该表中的某个行上被加上X锁。
           共享意向排他锁(ISX)。表示先对某个表加上S锁,然后再加上X锁,表示要读取整个表的数据,但是只对其中的一部分行做修改。

        那么最终一共得到5种锁:共享锁(S)、排他锁(T)、意向共享锁(IS)、意向排他锁(IX)、共享意向排他锁(SIX)。

        好,谈完理论,我们和具体的数据库Oracle结合起来,Oracle一共提供了共享锁(S)、排他锁(T)、行级共享锁(RS)、行级排他锁(RX)、共享行级排他锁(SRX)。和上一句话一对比,我们就知道了,它们是一一对应的。可是,Oracle作为一个成功的数据库,总是有自己别出心裁的地方。可能Oracle认为读数据的时候加锁这种机制,在很大程度上是浪费,比如用户修改的数据大多数是自己的不会影响到别的用户(只局限于某些行,不会影响其他行)。所以,默认情况下,Oracle在读取表中数据的时候并没有加锁,而是采用了回滚段的策略解决了这个问题,具体的原理在稍后再说。这样的确是提高了并发性,但是我认为,这其实也是种冒险行为,一旦出现用户B必须要读取用户A的数据,而且要参考读取的结果进行一些重要的操作时,这个时候就需要我们自己手动去加锁了(Oracle的确提供了这种功能,让我们自己用语句去加锁)。下边,我们详细谈下Oracle自己锁的具体行为:
        共享锁:通过lock table in share mode命令添加该S锁。在该锁定模式下,不允许任何用户更新表。但是允许其他用户发出select …from for update命令对表添加RS锁。
        排他锁:通过lock table in exclusive mode命令添加X锁。在该锁定模式下,其他用户不能对表进行任何的DML和DDL操作,该表上只能进行查询。
        行级共享锁:通常是通过select … from for update语句添加的,同时该方法也是我们用来手工锁定某些记录的主要方法。比如,当我们在查询某些记录的过程中,不希望其他用户对查询的记录进行更新操作,则可以发出这样的语句。当数据使用完毕以后,直接发出rollback命令将锁定解除。当表上添加了RS锁定以后,不允许其他事务对相同的表添加排他锁,但是允许其他的事务通过DML语句或lock命令锁定相同表里的其他数据行。
       行级排他锁:当我们进行DML时会自动在被更新的表上添加RX锁,或者也可以通过执行lock命令显式的在表上添加RX锁。在该锁定模式下,允许其他的事务通过DML语句修改相同表里的其他数据行,或通过lock命令对相同表添加RX锁定,但是不允许其他事务对相同的表添加排他锁(X锁)。
       共享行级排他锁通过lock table in share row exclusive mode命令添加SRX锁。该锁定模式比行级排他锁和共享锁的级别都要高,这时不能对相同的表进行DML操作,也不能添加共享锁。

        好了,引用别人的一个例子来描述Oracle如何通过回滚段来解决重复读和幻读的问题:
        如果有一个事务A执行以下语句:update employees set last_name='HanSijie'  where employee_id=100;如果当前有一个用户(假设为B)发出SQL语句,检索employee_id为100的记录信息,这时服务器进程发现被检索的记录有锁定标记,说明当前该记录已经被其他用户修改了,但是还没提交。于是根据数据行头部记录的ITL槽的槽号,在数据块头部找到该ITL槽,并根据其中记录的undo数据块的地址,找到该undo 数据块,将其中所保存的改变前的旧值取出,并据此构建CR(Consistent Read一致性读)块,该CR块中的数据就是被更新的数据块(也就是58号数据块)在更新前的内容。于是根据该CR块的内容,将用户所需要的信息返回给C。
        以上的例子,可以证明之前我所说的担心的地方,也就是说,假如我们这个用户B要参照这个数据来决定下一步程序重要的走向,那么我们在做这件事情的时候,必须也要对该记录加锁,因为Oracle并没有对我们的读取采取加锁的行为。我们必须要控制我们在做下一步动作前的这个“决定因素”不能被别人修改。

        另外,还有一点总结,由于我没有深入研究过多线程编程,只是在研究Struts2时,对Struts2处理Action的多线程问题时研究了下ThreadLocal模式。得出以下总结,可能不完善。个人认为,资源分为两种,一种是:多个线程必须交互的资源,比如表中的数据,再比如必须参考的某个成员变量。还有一种是:多线程可以不用交互的资源,比如我们Struts2中的成员变量。第一种情况,相当于是个十字路口,在不允许“修天桥”的情况下,多个线程必须要走这个交叉点。第二种情况是,可以通过“修天桥”来避免在同一个空间里的冲突。ThreadLocal就是基于这种思想,是为了降低线程冲突,也是为了节约资源等待的时间,本质上是计算机中的真理“空间换时间”。

        以上内容有很多借鉴了数据库基本理论,但是将抽象的对象具体到表和表中的行,这样有助于理解。主要是为了澄清一些概念,比如有人将“幻读”单独作为一种不一致情况,而省去了“提交被覆盖”这种情况。我个人认为,很显然,幻读只是不可重复读的一种情况(幻读强调的是集合)。中间也摘录了其他人文章中的部分内容。如果中间有错误,希望留言评论。

你可能感兴趣的:(Oracle锁机制的总结)