本文共 8214 字,大约阅读时间需要 27 分钟。
Oracle本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库,这就涉及两个重要的问题
1、这些用户之间的操作不会互相破坏,比如两个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况,这叫做串行化,也就是说,即便两个用户同时在写,也必须有先后,一个用户写完,另一个用户继续写,串行化会降低系统的并发性,但这对保护数据结构不被破坏来说则是必须的
2、在满足串行化的前提下,如何将并发性提升到最大
Oracle中通过使用闩锁(latch)和锁定(lock)
Latch和lock都是实现串行化的方法
latch是一个低级别的,轻量级的锁,获得和释放的速度都很快
lock可能持续很长时间,通过使用队列,先进先出的方式实现
锁的白话意义:一种资源,如果可能发生多个进程同时访问的情况,造成资源的破坏,那么就需要给这种资源上一个锁,如果这种资源很简单,例如就是内存的分配和释放,那么就使用latch,如果这种资源相对复杂,有一定的逻辑判断,那么就需要使用lock
资源的意义很广泛,因为进程总是通过内存来做修改、读取,因此资源都分布在内存中。
闩锁的概述(latch)
Oracle使用闩锁来实现内存的分配和释放
例如
某个用户进程A发出一条update语句,要去更新58号数据块里的某条记录,则该用户进程对应的服务器进程在写内存的时候,找到58号数据块,并往里写内容,A在写58号数据块的过程中,这时另外一个用户进程B发出insert语句,要将某个新的记录插入到58号数据块中,如果没有一定的保护机制,A正要写入的空间可能被B抢先写入,B要写入的空间可能被A抢先写入,不管哪个用户先抢先写入,造成的结果就是,58号数据块的数据都混乱了
如果使用latch来进行保护,简单的描述,任何进程要写数据块时,都必须先获得latch,在写入过程中,一直持有latch,写完以后,释放latch
当A在写入58号数据块的时候,先获得latch,B也要写58号数据块,这时B尝试获得latch,发现该latch被A持有,B进入等待状态,直到A释放latch,B获得latch以后,才能在58号数据块中写入数据
不只是写数据块需要使用latch,shared pool中就没有数据块。
凡是涉及内存的读和写,就需要通过获得latch来实现,一次只能有一个服务器进程在读或写内存。
Oracle在实例管理中,不管是buffer cache、shared pool还是log buffer,都引入了各种各样的latch。
可以这样来理解latch,通过某个变量值的变化来实现,变量值为0则说明latch没有被其它进程获取,变量值为非0则说明latch被其他进程获取
Latch是以微秒来计算的(百万分之一秒),操作非常快
凡是涉及内存里的某个地址读写的,都要涉及到latch
所谓轻量级锁,就是实现非常的简单,起的作用也是非常的初级。
Latch 分为两种类型
愿意等待(willing – to - wait)
大部分的latch都属于这种
这种类型的latch都是通过test-and-set的方式获得的,也就是说,如果当前进程不能获得latch的时候,会绕着CPU旋转,而不放弃CPU,这就是所谓的SPIN CPU
实际上就是执行一段空循环,通过执行空循环,一只占用着CPU
1、进程没有获得latch,为什么还要占用着CPU呢?因为latch本身是一个很快速的动作,因此可能等一会就能够获得latch,进程一旦获得CPU,但是没有获得latch,如果放弃CPU,那么需要进行上下文的切换,下次再次尝试获得latch时,又要进行上下文切换,可能消耗更多的时间,因此进程不能获得CPU的时候,会通过执行一段空代码绕着CPU转,然后再次尝试获得latch。
2、如果仍然不能够获得,继续旋转,当反复旋转CPU并尝试获得latch的次数超过某个上限时(该上限由隐藏参数控制),这时进程会释放CPU,并进入sleep状态,进程一旦进入sleep状态,就会抛出一个等待事件,并记录在v$session_wait里,进程正在等待latch的信息会出现在这个视图中
3、一个进程会睡眠0.01秒,然后醒来,并再次尝试获得latch,如果旋转CPU的次数达到上限值,仍然不能获得latch,则再次进入睡眠,这时会睡眠两倍的时间,以此类推,直到达到睡眠的最大值:0.2秒
上面的情况是数据库服务器具有多个CPU的情况,如果只有一个CPU,就不存在旋转CPU的情况,一旦获得不了latch,就进入睡眠
不等待(no-wait)
这种类型的latch较少,对于这种类型的latch来说,都会有很多个可用的latch
当一个进程请求其中的一个latch时,会以no-wait模式开始请求,如果所请求的latch不可用,则进程不会等待,而是立即请求另外一个latch,只有当所有的latch都不能获得的时候,才会进入等待
如果latch资源被争用,通常都表现为CPU资源使用过高,如果CPU资源很紧张,利用率总是在90%以上,甚至总是在100%,主要原因如下
1、SQL语句没有使用绑定变量,如果没有使用绑定变量,或者书写SQL时随意性过大,比如大小写混用、则Oracle对每一条SQL语句都要进行解析,也就是非常频繁地读写shared pool里的内存块,从而导致与解析SQL相关的latch争用
2、执行sql语句时,扫描的数据块过多,或者说SQL语句写的比较低效,导致扫描很多的数据块才能返回所要的记录。因为在查找、扫描数据块的过程中,进程也要获得latch,直到找到数据块为止。
一旦发生latch资源争用,就会导致CPU繁忙
假设进程A执行一条SQL语句需要访问10000个数据块,那么该进程在扫描数据块的过程中,一直持有latch,而另一个进程B也要执行SQL语句,但是A持有了latch,导致B无法获得,于是旋转一会CPU,再去获得latch,直到睡眠才释放CPU,接下来C进程也要执行SQL,同样的,由于A持有了latch,导致C无法获得,于是旋转一会CPU,再去获得latch,直到进入睡眠才释放CPU,如果类似进程很多的话,CPU总是在旋转,也就是在做空的循环,而无法做其他的事情,因此体现出CPU使用率过高
因此:要解决latch争用,关键在于共享SQL语句(比如绑定变量、规范SQL语句的书写),优化SQL语句,使其搜索以及扫描的数据块的个数降到最低
锁定lock
Lock用来控制多个用户对表里相同数据的并行访问
如果没有使用锁定来管理事务,则在9:04的时候,用户A检索id=1的c1列值时,显示为20,用户A的修改被B覆盖了
使用锁定以后的情况。
在锁定中,存在下面的两种基本情况
1、排他锁(X锁),一旦用户对某个资源添加了排他锁,则其他用户都不能再对该资源添加任何类型的锁,直到该用户释放了资源上的X锁
2、共享锁(S锁),一旦用户对某个资源添加了共享锁,则其他用户都不能在该资源上添加X锁,只能添加S锁,直到该用户释放了S锁为止
DML事务锁定机制
能够保证当某个用户正在更新表里的一行数据时,其他用户不能同时更新相同的数据行,而且也不能删除或修改被更新的表
行级锁(TX锁)
update employee set last_name=‘bear’ where employee_id=100;
1、Oracle对该SQL语句进行解析,找到employee_id为100的记录所在的数据块(假设为58号数据块),并找到一个可用的undo数据块,将last_name列上被更新前的旧值放入该undo数据块
2、在58号数据块的头部分配一个ITL槽,在该槽里存放当前的是事务ID、SCN号、所使用的undo数据块的地址,以及未提交的标记等信息
3、在58号数据块中,找到被更新的数据行,在数据行头部设置一个锁定标记,并在头部记录当前事务所使用的ITL槽号,做完这些工作
4、控制权交给用户,该锁定说明当前用户在被修改的数据行上已经添加了X锁
如果这时,另一个用户B也对employee_id为100的记录进行修改,则其过程和上面描述一样,只不过B在对数据行的头部设置锁定标记的时候,发现该数据行头部已经有一个锁定标记了,说明该记录已经添加了X锁,于是用户B必须等待,等待X锁被释放。
锁定一条记录,并不影响其他用户对该记录的读取,例如
如果用户发出一条SQL语句,检索employee_id为100的记录信息,这时服务器进程发现被检索的记录有锁定标记,说明该记录已经被其他用户修改了,但是还没有提交。于是根据数据行头部记录到ITL槽的号码,在数据块头部找到ITL槽,并根据其中记录的undo数据块的地址,找到该undo数据块,将其中所保存的改变前的旧值取出来,并构建CR(consistent read 读一致性)块,根据CR块的内容,将用户所需要的信息进行返回。
对于Oracle来说,行级锁只有X锁定模式,没有S锁定模式,更新数据行,只是锁定被更新的数据行
表级锁
用户A已经发出了更新employee_id为100的记录的SQL语句,当A还没有提交之前,另外一个用户D发出下面的语句
drop table employee
由于用户A还没有提交所做的事务,该事务还没有结束,其他用户不能删除该表,否则A所发出的事务就无法正常结束,为了阻止用户D的删除操作,我们能够想到的最直观的方法是
在执行删除表的命令以前,先依次检查employee表里的每一条记录,查看每一条数据行的头部是否存在锁定标记,如果是,则说明正有当前事务在更新表,删除表的操作必须等待
显然这样存在很大的性能问题
实际上,当我们在对employee表的数据进行更新时,不仅会在数据行的头部记录行级锁,而且还会在表的级别上添加一个表级锁,这样可以提高锁管理的性能
表级锁共有5种模式
1、行级排他锁(Row exclusive)RX锁
当我们进行DML时,会自动在被更新的表上添加RX锁,可以执行LOCK命令显式的在表上添加RX锁
允许其他事务通过DML语句修改相同表里的其他数据行
允许使用lock命令对表添加RX锁定
不允许其他事务对表添加X锁
A事务
B事务,显然不允许添加X锁。
A事务
如何结束锁定,使用rollback
允许B事务使用lock添加一个行级排他锁
行级共享锁(row shared RS锁)
在表上添加了一个行级的共享锁,这里面的select可以是多条记录,往往是多条记录。
用户在执行select的时候,不希望其他用户对这些记录进行更新,经常发出这些命令。
查询完成以后,发出rollback命令解锁
表上添加了RS锁以后,不允许其他事务对相同的表添加X锁,但是允许其他事务通过DML对表里面的其他数据进行修改,也允许添加行级排他锁
事务A
事务B
不允许对锁定的数据进行修改。
允许对没有锁定的数据进行修改
在对表进行DML的时候,已经在表上添加了行级排他锁,因此在有行级共享锁的表上可以添加行级排他锁
手工的可以添加行级排他锁,和上面的效果一样。
不允许添加排他锁
事务A回滚后,可以重新添加排他锁
共享锁(shared)S锁
不允许任何用户更新表,但是可以添加行级共享锁RS
共享锁和排他锁的区别是可以允许RS
不允许进行DML操作
允许进行RS
排他锁(exclusive )X锁
其他事务不能对表进行任何的DML和DDL操作,只能进行查询。
共享行级排他锁(shared row exclusive)SRX锁
不能对相同的表进行DML操作,也不能添加共享锁
锁之间的兼容性,有了这个锁,能不能有另外一个锁。
lock table <table_name> in [row share][row exclusive][share][share row exclusive][exclusive] mode;
可以通过DML进行自动的加锁,也可以使用LOCK手工加锁。
解决DML事务锁定的冲突
如果多个用户同时更新相同表的相同记录或者多个用户需要在表上添加不兼容的锁定,就会发生锁定冲突的现象。
锁定相关视图
v$transaction
记录了当前每个活动事务(也就是还没有提交或者回滚的事务)的信息。其中比较重要的字段包括XIDUSN表示当前事务使用的回滚段的编号、XIDSLOT说明该事务在回滚段头部的事务表中对应的记录编号(也可以叫做槽号)、XIDSQN说明序列号、STATUS说明该事务是否为活动的。
v$lock
记录了当前session已经获得的锁定以及正在请求的锁定的信息。其中比较重要的字段包括SID说明session的ID号、TYPE说明锁定级别,主要关注TX和TM、LMODE说明已经获得的锁定的模式,以数字编码表示、REQUEST说明正在请求的锁定的模式,以数字编码表示。BLOCK说明是否阻止了其他用户获得锁定,大于0说明是,等于0说明否。
v$enqueue_lock
该视图中包含的字段以及字段含义与v$lock中的字段一模一样。只不过该视图中只显示那些申请锁定,但是无法获得锁定的session信息。其中的记录按照申请锁定的时间先后顺序排列,先申请锁定的session排在前面,排在前面的session将会先获得锁定。
v$locked_object
记录了当前已经被锁定的对象的信息。其中比较重要的字段包括XIDUSN表示当前事务使用的回滚段的编号、XIDSLOT说明该事务在回滚段头部的事务表中对应的记录编号、XIDSQN说明序列号、OBJECT_ID说明当前被锁定的对象的ID号,可以根据该ID号到dba_objects里查找被锁定的对象名称、LOCKED_MODE说明锁定模式的数字编码。
v$session
记录了当前session的相关信息。其中比较重要的字段包括SID表示session的编号、SERIAL#表示序列号。SID和SERIAL#可以认为是v$session的主键,它们共同唯一标识一个session。
会话A
会话A的session ID是1
会话A
该会话使用了6号回滚段
该会话在回滚段事务表中的槽号是3
事务是活动的,也就是没有提交
TX:行级锁,就一种模式,排他锁
TM:表级锁,行级排他锁
ID1:对于TM锁来说,被锁定对象的ID号
被锁定对象是test13
另外启动一个session B
Session ID是34
这个会话被锁定了。
回到session A,查看一下会话的锁的情况。
会话34请求的锁是行级排他锁,但是该记录被1会话锁定,因此锁定模式为NONE
34在TM上获得了行级排他锁,因为这个锁和已有的锁并不冲突
1的锁定阻塞了另外一个锁定
再次启动一个session C
回到session A执行这个命令
上面的会话中,34会话首先进入队列,然后才是47会话
1会话阻塞了34和47两个会话
首先提交1,才能让34提交,然后才是47
在A会话中,提交事务。再次运行查询。
34阻止了47,需要提交34
如果无法提交34,那么可以kill 34
再次查询,发现已经没有了锁定。
一个事务不管更新多少行数据,都只获得了一个TX锁。
如果一个表上有TX锁定,那么会有一个RX锁定在TM上
如果要删除这个表,那么需要得到这个表的X锁,RX和X是冲突的,因此会报错。
会话A
会话B
会话A是可以的
在数据库系统中,我们同时可以获得的TX锁定的总个数由初始化参数transactions决定,而可以获得的TM锁定的个数则由初始化参数dml_locks决定
默认情况下,同时可以启动271个事务,也就是获得271个TX锁定,以及获得1084个TM锁定。也就是说,平均每个事务更新4个表(1084/271=4)
假设当前存在两个session(以A和C来表示),如果A持有C正在申请的锁定,同时C也持有A正在申请的锁定时,这时发生死锁现象。死锁是典型的“双输”情况,如果任其发展,则会出现A和C这两个session正在执行的事务都无法结束的现象。因此,在Oracle数据库中,造成死锁的那个DML语句会被撤销。死锁总是由于应用程序设计不合理引起的。
DDL锁定
当我们发出DDL命令时,会自动在被处理的对象上添加DDL锁定,从而防止对象被其他用户所修改。当DDL命令结束以后,则释放DDL锁定。我们不能显式地请求一个DDL锁定,只有当对象结构被修改或者被引用时,才会在对象上添加DDL锁定。比如创建或者编译存储过程时会对引用的对象添加DDL锁定。在创建视图时,也会对引用的表添加DDL锁定等。
在执行DDL命令之前,Oracle会自动添加一个隐式提交命令,然后执行具体的DDL命令,在DDL命令执行结束之后,还会自动添加一个隐式提交命令。实际上,Oracle在执行DDL命令时,都会将其转换为对数据字典表的DML操作。比如我们发出创建表的DDL命令时,Oracle会将表的名称插入数据字典表tab$里,同时将表里的列名以及列的类型插入col$表里等。因此,在DDL命令中需要添加隐式的提交命令,从而提交那些对数据字典表的DML操作。即使DDL命令失败,它也会发出提交命令。
DDL锁定具有以下三种类型
1、排他的DDL锁定 Exclusive DDL Lock
大部分的DDL操作都会在被操作的对象上添加排他的DDL锁定,从而防止在DDL命令期间,对象被其他用户修改,当对象添加了排他的DDL锁定以后,该对象上不能再添加任何其他的DDL类型,如果是对表进行DDL,则其它进程也不能修改表里的数据
2、共享的DDL锁定 Shared DDL Lock
用来保护被DDL的对象不被其他用户进程更新,但是允许其它进程在对象上添加共享的DDL锁定,如果是对表进行DDL命令,则其它进程可以同时修改表里的数据
例如create view命令创建视图的时候,在视图中所引用的表上添加的就是共享的DDL锁定,创建视图的时候,其他用户不允许修改表结构,但是可以更新数据
3、可打破的解析锁定Breakable Parsed Lock
在shared pool里缓存的SQL游标、PL/SQL代码都会获得引用对象上的解析锁定,如果我们发出DDL命令修改了某个表的结构时,该对象相关的、位于shared pool里的解析锁定就会被打破,从而导致引用了该对象的SQL游标或者PL/SQL程序代码全都失效,下次再执行相同的SQL语句时,需要重新解析,这就是所谓的SQL语句的reload,可打破的解析锁定不会阻止其他的DDL锁定,如果发生与解析锁定相冲突的DDL锁定,则解析锁定也会被打破
注意:DDL语句有很多种,不只是建立和删除表
1、最主要的还是排他的DDL锁定,建立、删除、修改对象的时候,特别是表、索引等,这些操作花费的时间长,发生锁定冲突的几率高
发生这个锁定的时候,要注意锁定后的效果是什么?
2、另外就是一些类似于create view之类的语句,在建立的时候,需要被引用的对象的结构不要发生变化,但是数据可以发生变化,这种非常适合共享的DDL锁定
3、还有一类就是可打破的解析锁定
Shared pool里的SQL游标、PL/SQL等,引用的对象,如果对象发生了变化,例如被删除,那么解析锁定被打破,这个锁定只是起到了连接的作用,并没有锁定的意义,勉强算作锁定
整体思路就是
1、Oracle提供了DBMS_LOCK包
2、使用包里面的函数
1、申请一个锁定、得到一个handle
2、根据这个handle请求这个锁定,请求锁定的时候,可以指定 锁定的模式,例如上面的例子就是exclusive模式
3、根据请求是否成功,进行下面的操作,例如打印
具体的打印操作和上面的申请锁定没有任何关系,完全独立的两套体系,申请就是为了实现串行化,和具体打印没有任何关系,申请了锁定以后,开始打印。
串行化的例子可以使用上面的例子,基本都一样。
转载地址:http://kslkx.baihongyu.com/