文档库 最新最全的文档下载
当前位置:文档库 › Oracle_lock_latch

Oracle_lock_latch

Oracle_lock_latch
Oracle_lock_latch

第10章闩锁、锁定和并发性

数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库。这里就涉及两个很重要的问题。

这些用户之间的操作不会互相破坏。比如两个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况。这叫串行化,也就是说,即便两个用户同时写,也必须有先后,一个用户写完,另一个用户继续写。串行化会降低系统的并发性,但这对于保护数据结构不被破坏来说则是必需的。

在满足串行化的前提下,如何将并发性提升到最大。

在Oracle数据库中,通过闩锁(latch)和锁定(lock)来解决这两个问题。闩锁和锁定既有相同点又有不同点。相同点在于它们都是用于实现串行化的资源。而不同点则在于闩锁是一个低级别、轻量级的锁,获得和释放的速度很快,以类似于信号灯的方式实现。而锁定则可能持续的时间很长,通过使用队列,按照先进先出的方式实现。也可以简单地理解为闩锁是微观领域的,而锁定则是宏观领域的。

读完本章以后,我们能够了解到:

闩锁是什么,以及latch是如何保护资源的;

锁定(包括TX锁和TM锁)是如何保护资源的;

如何检测并解决锁定冲突;

DDL锁定的概念;

如何创建我们自己的锁定。

10.1 闩锁(latch)概述

Oracle数据库使用闩锁来管理内存的分配和释放。假设,某个用户进程(假设其为A)发出一条update 语句,要去更新58号数据块里的某条记录。则该用户进程对应的服务器进程在写内存的时候,找到58号数据块,并往里写内容。A在写58号数据块的过程中,这时,另一个用户进程B发出insert语句,要将某个新的记录插入到58号数据块里。如果没有一定的保护机制,A正要写入的空间可能会被B抢先写入,或者相反,B正要写入的空间也可能会被A抢先写入。不管哪个用户先抢先写入,造成的结果就是,58号数据块里的数据都混乱了,因为这时,A和B之间的数据互相交织在一起了。

因此,必须使用latch对此进行保护。简单来说,任何进程要写数据块时,都必须先获得latch,在写入过程中,一直持有该latch,写完以后,释放该latch。对于上面的例子来说,当A在写入58号数据块时,先获得latch,然后开始写。而当A正在写入的过程中,B也要写58号数据块。这时B在尝试获得latch时,发现该latch正被其他用户(也就是A)持有,因此B进入等待状态。直到A写完数据块并释放latch以后,B才能获得latch,获得latch以后,才能在58号数据块里写入数据。

这里只是以写数据块为例来说明为何要使用latch。而事实上,latch不仅仅用于写数据块,比如对于shared pool来说,其内存单位就不是数据块了。latch也不仅仅用于写操作,只要涉及内存地址的读和写,都需要通过获得latch来实现串行化,一次只能有一个服务器进程在读或者写内存地址。

Oracle在实例管理中,不管是buffer cache、shared pool还是log buffer,都引入了各种各样的latch。

实现latch时,实际是由操作系统的旗语(semaphore:也叫信号量)来完成的。为了便于理解,可以把它们想象为,通过某个变量值的变化而实现的。变量值为0则说明latch当前没有被其他进程获取,否则

进程之所以不释放CPU而是绕着CPU旋转,是由于latch操作本身是一个很快速的动作,因此可能等一会就能获得latch了。当进程一旦获得CPU,但是获得不了latch时,如果这时候立刻放弃CPU,那么需要进行上下文切换,下次再次尝试获得latch时,又要进行上下文切换,可能反而要消耗更多的时间。因此,进程在不能获得latch的时候,会执行上面这段代码,绕着CPU转一会,然后再次尝试获得latch,如果仍然不能获得,则再次旋转CPU。当反复旋转CPU并尝试获得latch的的次数超过某个上限(该上限由隐藏参数控制)时,这时进程会释放CPU,并进入睡眠(Sleep)状态。进程一旦进入睡眠状态,则会抛出一个对应的等待事件,并记录在视图v$session_wait里,说明当前该进程正在等待的latch的类型等信息。初始状态下,一个进程会睡眠0.01秒。然后醒过来,并再次尝试获得latch。如果旋转CPU的次数达到上限以后,仍然不能获得latch,则再次进入睡眠,这时会睡眠两倍的时间,依此类推,直到达到睡眠的最大值:0.2秒。

这是在数据库服务器具有多个CPU时的情形,如果只有一个CPU,就不存在旋转CPU的情况,一旦获得不了latch,就进入睡眠。

总的来说,当进程尝试获取Willing-To-Wait类型的latch时,如果失败,则进程会一直尝试对latch的获取,不断循环,直到获得latch为止,或者是达到所指定的上限值为止。当达到上限值时,进程进入睡眠。

不等待(No-Wait)

这种类型的latch比较少,对于这种类型的latch来说,都会有很多个可用的latch。当一个进程请求其中的一个latch时,会以no-wait模式开始请求。如果所请求的latch不可用,则进程不会等待,而是立刻请求另外一个latch。只有当所有的latch都不能获得时,才会进入等待。

从另外一个角度来说,latch分为单个latch(Solitary latch,比如shared pool latch以及redo allocation latch 等)和latch组(比如library cache latch、cache buffers lru chain latch以及cache buffers chains latch等)。latch 组包括父latch和子latch。单个latch和父latch都是定义在数据库软件代码里的,而且都是静态分配的。对于每种类型的latch,只有一个父latch。而子latch则根据参数或默认值而动态设定,而且子latch的访问独立于父latch。通常来说,父latch只用于汇总显示报表的目的。

如果latch资源被争用,通常都会表现为CPU资源使用过高。而反过来说,如果我们发现CPU资源很紧张,利用率总是在90%以上,甚至总是在100%,其主要原因有以下几点。

Oracle在对该SQL进行解析以后,找到employee_id为100的记录所在的数据块(假设为58号数据块),并找一个可用的undo数据块,将last_name列上被更新前的旧值放入该undo数据块,然后在数据块头部分配一个ITL槽,在该ITL槽里存放当前的事务ID号、SCN号、所使用的undo数据块的地址,以及当前还未提交的标记等信息。接下来,在58号数据块中,找到被更新的数据行,在其头部设置一个锁定标记,并在头部记录当前事务所使用的ITL槽的槽号。做完这些工作以后,将控制权(也就是光标)返回给用户。该锁定标记说明当前用户在被修改的数据行上已经添加了X锁。

如果这时,另一个用户(假设为N)也对employee_id为100的记录进行修改,则其过程和上面描述的一样,只不过B在对数据行的头部设置锁定标记时,发现该数据行头部已经有一个锁定标记了,说明该记录已经被添加了X锁,于是用户进程N必须等待,等待该X锁被释放。

可以看到,Oracle数据库是在物理层面上实现对数据行的锁定问题。而且锁定一条记录,并不影响其他用户对该记录的读取。比如,如果当前有一个用户(假设为C)发出SQL语句,检索employee_id为100的记录信息,这时服务器进程发现被检索的记录有锁定标记,说明当前该记录已经被其他用户修改了,但是还没提交。于是根据数据行头部记录的ITL槽的槽号,在数据块头部找到该ITL槽,并根据其中记录的undo数据块的地址,找到该undo数据块,将其中所保存的改变前的旧值取出,并据此构建CR(Consistent

由于用户A还没有提交所做的事务,因此该事务还没有结束,其他用户还不能删除该表,否则A所发出的事务就无法正常结束。为了阻止这时用户D的删除操作,我们能够想到的最直观的方法就是,在执行删除表的命令之前,先依次检查employees表里的每一条记录,查看每一条数据行的头部是否存在锁定标记,如果是,则说明当前正有事务在更新该表,删除表的操作必须等待。

显然,这种方式会引起很大的性能问题,Oracle不会采用这种方式。实际上,当我们在对employees 表的数据进行更新时,不仅会在数据行的头部记录行级锁,而且还会在表的级别上添加一个表级锁。那么当D用户要删除表时,发现employees表上具有一个表级锁,于是等待。

通过这种在表级别上添加锁定的方式,我们就能够比较容易并且高效地(因为不需要扫描表里的每一条记录来判断在表上是否有DML事务)对锁定进行管理了。表级锁共具有五种模式,如下所示。

行级排他锁(Row Exclusive,简称RX锁)

当我们进行DML时会自动在被更新的表上添加RX锁,或者也可以通过执行lock命令显式的在表上添加RX锁。在该锁定模式下,允许其他的事务通过DML语句修改相同表里的其他数据行,或通过lock 命令对相同表添加RX锁定,但是不允许其他事务对相同的表添加排他锁(X锁)。

行级共享锁(Row Shared,简称RS锁)

通常是通过select … from for update语句添加的,同时该方法也是我们用来手工锁定某些记录的主要方法。比如,当我们在查询某些记录的过程中,不希望其他用户对查询的记录进行更新操作,则可以发出这样的语句。当数据使用完毕以后,直接发出rollback命令将锁定解除。当表上添加了RS锁定以后,不允许其他事务对相同的表添加排他锁,但是允许其他的事务通过DML语句或lock命令锁定相同表里的其他数据行。

共享锁(Share,简称S锁)

通过lock table in share mode命令添加该S锁。在该锁定模式下,不允许任何用户更新表。但是允许其他用户发出select …from for update命令对表添加RS锁。

排他锁(Exclusive,简称X锁)

通过lock table in exclusive mode命令添加X锁。在该锁定模式下,其他用户不能对表进行任何的DML 和DDL操作,该表上只能进行查询。

共享行级排他锁(Share Row Exclusive,简称SRX锁)

对于通过lock table命令主动添加的锁定来说,如果要释放它们,只需要发出rollback命令即可。

10.4 解决DML事务锁定的冲突

如果多个用户同时更新相同表的相同记录,或者多个用户需要在表上添加不兼容的锁定(比如某个用户在更新一个表,而另一个用户要修改该表的结构),则这时就发生了锁定冲突的现象。

10.4.1 锁定相关视图

我们要管理并解决锁定冲突的话,需要借助以下这几个数据字典:

v$transaction

其中,150为session的SID号,而6为session的SERIAL#号。当删除159号session以后,该session 所获得的锁定就会被pmon进程释放。

由于用户A还没有提交所做的事务,因此该事务还没有结束,其他用户还不能删除该表,否则A所发出的事务就无法正常结束。为了阻止这时用户D的删除操作,我们能够想到的最直观的方法就是,在执行删除表的命令之前,先依次检查employees表里的每一条记录,查看每一条数据行的头部是否存在锁定标记,如果是,则说明当前正有事务在更新该表,删除表的操作必须等待。

显然,这种方式会引起很大的性能问题,Oracle不会采用这种方式。实际上,当我们在对employees 表的数据进行更新时,不仅会在数据行的头部记录行级锁,而且还会在表的级别上添加一个表级锁。那么当D用户要删除表时,发现employees表上具有一个表级锁,于是等待。

通过这种在表级别上添加锁定的方式,我们就能够比较容易并且高效地(因为不需要扫描表里的每一条记录来判断在表上是否有DML事务)对锁定进行管理了。表级锁共具有五种模式,如下所示。

行级排他锁(Row Exclusive,简称RX锁)

当我们进行DML时会自动在被更新的表上添加RX锁,或者也可以通过执行lock命令显式的在表上添加RX锁。在该锁定模式下,允许其他的事务通过DML语句修改相同表里的其他数据行,或通过lock 命令对相同表添加RX锁定,但是不允许其他事务对相同的表添加排他锁(X锁)。

行级共享锁(Row Shared,简称RS锁)

通常是通过select … from for update语句添加的,同时该方法也是我们用来手工锁定某些记录的主要方法。比如,当我们在查询某些记录的过程中,不希望其他用户对查询的记录进行更新操作,则可以发出这样的语句。当数据使用完毕以后,直接发出rollback命令将锁定解除。当表上添加了RS锁定以后,不允许其他事务对相同的表添加排他锁,但是允许其他的事务通过DML语句或lock命令锁定相同表里的其他数据行。

共享锁(Share,简称S锁)

通过lock table in share mode命令添加该S锁。在该锁定模式下,不允许任何用户更新表。但是允许其他用户发出select …from for update命令对表添加RS锁。

排他锁(Exclusive,简称X锁)

通过lock table in exclusive mode命令添加X锁。在该锁定模式下,其他用户不能对表进行任何的DML 和DDL操作,该表上只能进行查询。

共享行级排他锁(Share Row Exclusive,简称SRX锁)

对于通过lock table命令主动添加的锁定来说,如果要释放它们,只需要发出rollback命令即可。

10.4 解决DML事务锁定的冲突

如果多个用户同时更新相同表的相同记录,或者多个用户需要在表上添加不兼容的锁定(比如某个用户在更新一个表,而另一个用户要修改该表的结构),则这时就发生了锁定冲突的现象。

10.4.1 锁定相关视图

我们要管理并解决锁定冲突的话,需要借助以下这几个数据字典:

v$transaction

其中,150为session的SID号,而6为session的SERIAL#号。当删除159号session以后,该session 所获得的锁定就会被pmon进程释放。

相关文档