oracle中的lock modes

来源:互联网 发布:flash编辑软件 编辑:程序博客网 时间:2024/04/28 03:37

    本文为翻译文章,原文地址:原文链接

    我们在使用Oracle时,一般并不会过多的关注其中的lock机制。因为大多数情况下,我们不会使用显式的lock。当我们执行DML操作(insertupdatedeleteselect for update)时,oracle内部的隐式锁对用户来说是透明的。当我们执行select操作时(不是select for update),oracle根本不会加任何的锁。另外,deadlockenqueue wait event等情况更是很少出现。

但是,当你需要深入的分析一个deadlock发生的原因、分析为什么session会被阻塞、分析怎样才能有效的保证数据的引用完整性(referential integrity)时,事情就没有那么简单了。Oracle中的lock机制不是很好理解,甚至一些专有名词经常的被人们混淆。举几个例子:在行上加的锁我们也称为事物锁(transaction lock),在表上加的锁我们也称为行共享锁(row share lock)或行排它锁(row exclusive lock),但是表上加的锁并不会锁住任何一行。而且虽然名字叫做行排他锁,但是这种锁仍然允许多个session并发的访问,并没有像其名字一样进行排他。另外,行排他锁也称为子排他锁(sub exclusive lock),类似的,行共享锁也称为子共享锁(sub share lock)。

可能你已经被这些名词搞混了,所以接下来,我们会分成几个部分讲解oracle中的锁。

Lock Types

   锁是一种序列化访问资源的机制。对同一个资源进行并发访问的session会排队等待。Oracle中使用到的锁有几种:

1、保护数据的锁称为DML锁,这里的数据可能是表、行、索引...

2、保护数据字典的锁称为数据字典锁。

3、保护内存中的internal objects的锁,有latch(闩)、mutextes(互斥器)、其他的internal lock

在本文中我们只讨论数据锁,也就是DML 锁。数据锁之所以称之为DML锁,是因为其被DML(数据操作语言)所使用。需要注意的是,DML锁不光被DML所使用,也会被DDL(数据定义语言)所使用。

3种类型的DML锁:

1、行级别锁(row level lock),也被称为事物锁(TX)。为什么称为事物锁?尽管TX锁是由于在同一行上并发操作所触发,但是其锁住的资源是事物,不是并发操作的那一行。TX锁并不会在行上排队等待,而是等待操作该行的事物执行完成。

2、表级别锁(table level lock),也被称为表锁(TM)。TM锁住的资源是数据库对象(tableindexpartition...)。除了DMLDDL能获取TM外,显式的执行lock table 命令也能获取到TM

3、用户自定义的锁(UL)。

Lock Modes

   通常意义下,一个资源可以用2种方式加锁,分别是排他模式(阻止任何的并发访问)和共享模式(只阻止排他访问)。但是oracle定义了6种模式(包括不加锁模式),如下表:

 

Share/Exclusive

   一个排它锁(X)不允许共享资源,排它锁会阻止其他的session获取当前资源的共享锁(S)或者排他锁(X)。

   一个共享锁(S)允许共享资源,多个session可以获取当前资源的共享锁(S)。但是共享锁会阻止其他的session获取当前资源的排他锁(X)。在读取数据的过程中,我们通常不想数据被其他的session改写,这种情况下,我们需要对访问的资源加上共享锁。

   请一定记住一个规则:S/S是兼容的,S/XX/X是不兼容的。

使用SX的原则如下:

1、当我们需要写数据的时候,我们需要排他锁(X)。

2、当我们需要读取数据,并且希望读取过程中不会被其他人修改数据。我们就需要对资源加上共享锁(S)。如果有一个session正在更新数据(对资源加了X锁),我们就需要等待直到session提交。

   NOTE:oracle中,读取数据的时候可以不加共享锁(S),如果不加共享锁,oracle读取到时保证了数据一致性的版本的数据(不一定是最新版本)。如果加了共享锁,oracle读取到的是当前版本的数据(最新的版本)

Sub/Row

   我们重点讨论一下表级锁(table lcok)。Oracle针对于一个资源有很多锁模式,但是我们可能操作的只是这个资源的一个子部分。对于表来说,表的子部分就是行。

   举个例子,如果我们更新的只是表的一行或者几行记录,我们需要在更新之前对表加上排他锁(X)。事实上,我们并没有对整个表进行更新,所以也没必要对整个表加排他锁,只是对更新的行加锁即可,这就是行排他锁的由来(RX)。类似的,读取数据的时候,也没有必要对整个表加共享锁(S),只需要对读取的数据加行共享锁(RS)即可。注意:我们是在表级(table level)加的行排他锁(RX)和行共享锁(RS),换句话说,RXRS都是在表级别的锁。

  解释一下为什么用Sub/Row命名。对于表来说,子部分就是行(row),所以我们称之为Row-exclusiveRX)或者Row-ShareRS)。但是如果需要加锁的资源不是表,其子部分称为Sub,在这种情况下,我们对子部分加的锁称为Sub SSS)或者Sub XSX)。这里需要注意的一点,虽然表的子部分是行,但是如果对表进行了分区(partition),那么我们在分区上加的排他锁称为Sub X,而不是Row X

  再次强调,本小节我们讨论的RSSS)、RXSX)都是表级别的锁。在下面的文章中,我会尽量使用Row而不是Sub来表示子部分,因为Sub的缩写SShare的缩写S可能会让大家混淆...

Table level/Row level

   我们在上一节已经讨论了表级别的锁(TM lock)。尽管上一节的RSRX关心的是某些行,但是其是属于表级别的锁。

   DML操作(INSERTUPDATEDELETESELECTFOR UPADTE)除了要获取表级锁以外,还要获取行级锁。举个例子:两个session可以并发的操作同一张表上不同的行,每个session会先获取到表级别的行排他锁(TM-RX),然后会在其操作的行上获取行级锁:在一个数据块上,每个行都有一个lock flag,这个flag会指向一个ITl条目,这个ITL条目就是最后更新该行的事物号。

  虽然行级锁(Row-level)加在行上,但是其锁住的资源是事物(transaction),这也是为什么行级锁被称为事物锁(TX)。一个事物A操作某行数据,会找到锁住该数据的其他事物B,事物A会向事物B请求lock,并且一直等待直到事物B释放lock

  TX锁总是排他的,oracle没有事项行级别的共享锁)。其他的数据库在读取数据的时候需要事物隔离,但是oracle使用了多版本控制的策略来保证读取数据的安全性。

  好的,讲到这里,我们总结一下:当我们发起一个DML请求时,我们需要获取一个表级别的锁(TM-S或者TM-X),还要获取行级别的锁(TX lock)。那为什么还需要表级别的行锁(TM-RS或者TM-RX)呢?  

  解释一下原因:一个表可能会有几百万行记录组成,如果我们对该表加共享锁(TM-S),我们需要扫描所有的记录来检查是否有记录已经被加了行级锁,所以对表加TM-S加锁是非常耗时的。如果对该表加排他锁(TM-R),也需要扫描所有记录检查是否有记录被加了行级锁。我们只对几条记录进行更新,如果对全表进行加锁,带来的后果可能是灾难性的。

  所以我们引入了表级别的行排他锁,获取一个行排他锁不需要检查所有的行。一个行排他锁预示着又一个事物想要更新当前表的某些行。这里的“预示”这个词也引出了行排他锁的第三种名称:Intended ShareIS)或者 Intended ExclusiveIX),这种说法我们很少提及到。

  现在你应该能理解文章开头提出的现象:两个session能够并发获取到同一个表的RX锁(尽管RX中的X是排他的意思,但是并不妨碍多个session同时获取)。再次强调(我已经不知道第几次强调了),尽管RX中的R代表ROW,但是其锁住的对象是table

Read/Write  

  总结一下前面的内容:当写入数据的时候,需要获取排它锁(X)。当读取数据,并且不想被在读取过程中被别人更新,就需要获取共享锁(S)。如果你有意向更新或者读取表中的部分数据,你只需要获取一个RX/RS而不是全局的X/S

  但是在oracle中,默认的数据读取方式是不会加锁的,意味着读取数据的时候不会被其他的并发操作阻塞。oracle读取数据不需要加锁的原因是,读取的数据不一定是当前版本的数据。如果在读取数据的过程中,又一个并发的session在修改数据,oracle会使用undo日志构建一个之前的版本,换句话说,我们读取到的是之前的版本。

  如果你想要使用阻塞的方式读取数据,你可以使用select for update。在9i之前,select for update获取到的是行共享锁(RS),其主要是为了读取数据。从9i开始,select for update 获取到的是行排他锁(RX)。就像名字中拥有update一样,select for update的设计思想变成了更新数据。除了TM外,select for update还会获取TX(行级别的排它锁)锁。

   因此我们得出了这样的结论:读操作需要获取共享锁,写操作需要获取排他锁。但是oracle中的读操作不需要获取任何锁,select for update才会获取锁(select for update的目的是为了更新数据而不是读取数据)。

   DDL(数据库定义语言)也会获取到表级锁(TM lock)。举个例子,一个alter table的操作会获取一个TM-X锁:TM-X会阻止所有的阻塞读(SRS)和所有的写操作(XRX)。然而,像create index操作,其不会修改表结构,只是读取数据用来创建索引。所以create index操作只需要获取一个TM-S锁用来保证创建索引期间数据不会被修改。

   引用一致性(referential integrity)也需要获取TM锁。举个例子,当删除、更新父表中的key时,如果未对子表中的外键创建索引,oracle会对子表加一个全表锁(S)。如果没有对子表的外键创建索引,oracle就无法找到低级别的资源加锁,就无法阻止其他事物插入数据导致“引用一致性”被破坏。如果对子表的外键创建了索引,oracle就会使用第一个index entry作为低级别的资源,对其加上TX锁,这样就能阻止其他事物往子表中插入数据。

   那么在外键上设置了级联删除,那么就会存在多个事物并发的删除子表中的记录。所以除了S锁,oracle还需要在表级加上RX锁,表示当前session有意向要删除其中的某几行,这样其他的事物的删除操作就会被阻塞住。这也是共享行排他锁的由来:S+RX=SRX。共享行排他锁和共享锁的区别就是:如果对一个资源加上了共享行排它锁(SRX),那么其他事物就无法获取该资源上的共享锁(S)。如果对一个资源加上了共享锁(S),那么其他事物仍然能获取该资源上的共享锁(S)。

Table Operations   

   SELECT(非select for update,是一种非阻塞读取操作,select不会对任何资源加锁。在读取数据的过程中,如果又一个事物删除掉了当前表,数据仍然能正常读取。

   INSERTUPDATEDELETE,这些操作会获取操作的表上的TM-RX锁,还会获取操作记录上的TX锁(准确来讲,获取到的是操作记录上的事物的锁)。

  SELECT FOR UPDATE,是一种阻塞式读取操作。在9i之前,select for update需要获取一个RS锁,从9i开始,select for update和普通的update操作一样,需要获取一个RX锁。

  DML with referential integrity,需要外加的锁。举个例子,当你需要向子表插入数据时,需要阻塞式的读取父表中存不存在记录。如果父表中不存在数据,说明在我们提交事物之前父表中的数据已经被删除了。为了避免出现父表中数据被删除的情况,我们就需要使用select for update语句对父表加上RX。另一方面,如果删除父表中的记录或者更新被子表引用的值(通常是表的主键)时,我们需要阻止子表中的数据插入。怎么阻止子表中的数据插入?如果在外键上创建了索引,oracle会对子表中的低级别的资源(the first index entry)加上一个RX锁。如果没有在外键上创建索引,oracle会对子表创建一个全表锁(S),阻止其他任何的DML操作。如果引用的外键上设置了级联删除,那么S锁会升级成为SRX锁。

  A direct path  insert, 必须阻止其他所有的并发修改。因此direct path insert需要获取一个全表的排它锁(X)。

  DDL 也会获取TM锁。DDL可能在整个操作过程中都持有锁,也可能只在一个短期内持有锁(online操作)。

  当你需要实现可重复度的事物隔离级别时(可重复度是相对幻度来说的,可重复读保证在一个事物中的多次读取的结果集不会发生变化,幻读是指在一个事物中的多次读取的结果集可能不一致),行级别的锁(TX)是无法保证可重复读的,因为oracle不能对一条不存在的记录加TX。所以oracle需要对整张表加共享锁(S)。举个例子,当你使用create index创建索引时,这个DDL操作会获取一个表级别的共享锁(S)。当然对于alter tableDDL操作,其会获取一个标级别的排他锁(X)。

  刚才我们只举了有限的几个例子,事实上,还有很多我们不能在此一一列举的例子,甚至有些在不同的版本中的实现不一样。但是在分析问题之前,请先考虑一下哪些数据集需要被写,哪些数据集需要使用阻塞方式读取,弄清楚了这些对于分析问题是很有帮助的。 

0 0
原创粉丝点击