欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Oracle锁机制深度分析

程序员文章站 2022-06-04 11:33:12
...

一、概述

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

二、Oracle两种锁机制

在Oracle数据库中锁机制分为两种:独占锁与共享锁,数据库利用这两种基本的锁机制来对数据库的事务进行并发控制。 
**独占锁(Exclusive Lock)**:即X锁,又称排它锁,是用来防止同时共享相同资源的锁。加了独占锁的数据库对象不能被其它事务读取和修改。
**共享锁(Share Lock)**:即S锁,是通过对数据存取的高并行性来实现的。加了共享锁的数据库对象可以被其它事务读取,但是不能被其它事务修改。

三、Oracle锁类型

根据保护的对象不同,Oracle数据库锁可以分为以下几大类:DML锁、DDL锁、内部锁和闩,前两种我们经常遇到。
DML锁(data locks,数据锁):用于保护数据的完整性,能够防止同步冲突的DML和DDL操作的破坏性交互。
DDL锁(dictionary locks,字典锁):用于保护数据库对象的结构,如表、索引等的结构定义。
内部锁和闩(internal locks and latches):保护数据库的内部结构,如数据文件,对用户是不可见的。

1、 DML锁

用来保证并行访问数据的完整性。能够防止同步冲突的DML和DDL操作的破坏性交互。在Oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。

1)、TM锁

当Oracle执行DML语句时,系统自动在所要操作的表上申请TM类型的锁。当TM锁获得后,系统再自动申请TX类型的锁,并将实际锁定的数据行的锁标志位进行置位。这样在事务加锁前检查TX锁相容性时就不用再逐行检查锁标志,而只需检查TM锁模式的相容性即可,大大提高了系统的效率。TM锁包括了SS、SX、S、SSX 等多种模式,在数据库中用0-6来表示。不同的SQL操作产生不同类型的TM锁。
锁模式 锁描述 解释 SQL操作
0 None
1 NULL Select
2 SS (Row-S) 行级共享锁,其他对象只能查询这些数据行;是锁的类型中限制最少的锁,也是在表的并发程度中使用最多的 Select for update、Lock for update、Lock row share
3 SX (Row-X) 行级排它锁,在提交前不允许做DML操作 Insert、Update、 Delete、Lock row share
4 S(Share) 共享锁 Create index、Lock share
5 SSX (Share-Row-X) 共享行级排它锁 Lock share row exclusive
6 X(Exclusive) 排它锁 Alter table、Drop able、Drop index、Truncate table 、Lock exclusive

2)、TX锁

TX锁是Transaction Exclusive Lock行级排它锁,对一条记录加上TX锁后,其他用户不能修改、删除该记录
在数据行上只有X锁(排他锁)。在Oracle数据库中,当一个事务首次发起一个DML语句时就获得一个TX锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行 DML语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后,TX锁被释放,其他会话才可以加锁。
当Oracle数据库发生TX锁等待时,如果不及时处理常常会引起Oracle数据库挂起,或导致死锁的发生,产生ORA-60的错误。这些现象都会对实际应用产生极大的危害,如长时间未响应,大量事务失败等。 

2、 DDL锁

当 DDL命令发出时,Oracle会自动在被处理的对象上添加DDL锁定,从而防止对象被其他用户所修改。当DDL命令结束以后,则释放DDL锁定。DDL锁定不能显式的被请求,只有当对象结构被修改或者被引用时,才会在对象上添加DDL锁定。比如创建或者编译 存储过程时会对引用的对象添加DDL锁定。在创建视图时,也会对引用的表添加DDL锁定等。
在执行DDL命令之前,Oracle会自动添加一个隐式提交命令,然后执行具体的DDL命令,在DDL命令执行结束之后,还会自动添加一个隐式提交命令。实际上,Oracle在执行DDL命令时,都会将其转换为对数据字典表的DML操作。比如我们发出创建表的DDL命令时,Oracle会将表的名称插入数据字典表tab$里,同 时将表里的列名以及列的类型插入col$表里等。因此,在DDL命令中需要添加隐式的提交命令,从而提交那些对数据字典表的DML操作。即使DDL命令失 败,它也会发出提交命令。DDL锁包括三种类型:**排它的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锁定,则解析锁定也会被打破。

3、 内部锁和闩

内部锁保护内部数据库结构,如数据文件,对用户是不可见的。

四、悲观锁和乐观锁

当一个会话保持另一个会话正在请求的资源上的锁定时,就会发生阻塞。被阻塞的会话将一直挂起,直到持有锁的会话放弃锁定的资源为止。锁机制的适当应用保证了数据的完成性,应用不当会导致死锁,从而我们又将锁分为**悲观锁**与**乐观锁**。

1、悲观*

锁在用户修改之前就发挥作用,如:Select ..for update(nowait) ,Select * from tab1 for update 用户发出这条命令之后,oracle将会对返回集中的数据建立行级*,以防止其他用户的修改。 如果此时其他用户对上面返回结果集的数据进行dml或ddl操作都会返回一个错误信息或发生阻塞。 
1)对返回结果集进行update或delete操作会发生阻塞。 
2)对该表进行ddl操作将会报:Ora-00054:resource busy and acquire with nowait specified. 
此时Oracle已经对返回的结果集上加了排它的行级锁,所有其他对这些数据进行的修改或删除操作都必须等待这个锁的释放,产生的外在现象就是其他的操作将发生阻塞,这个操作commit或rollback. 
同样这个查询的事务将会对该表加表级锁,不允许对该表的任何ddl操作,否则将会报出ora-00054错误::resource busy and acquire with nowait specified. 

2、乐观*

乐观的认为数据在select出来到update进去并提交的这段时间数据不会被更改。这里面有一种潜在的危险就是由于被选出的结果集并没有被锁定,是存在一种可能被其他用户更改的可能。因此Oracle仍然建议是用悲观*,因为这样会更安全。

五、死锁

死锁-deadlock ,当两个用户希望持有对方的资源时就会发生死锁. 即两个用户互相等待对方释放资源时,oracle认定为产生了死锁,在这种情况下,将以牺牲一个用户作为代价,另一个用户继续执行,牺牲的用户的事务将回滚。
4个常见的dml语句会产生阻塞 INSERT ,UPDATE,DELETE,SELECT…FOR UPDATE 。
INSERT:Insert发生阻塞的唯一情况就是用户拥有一个建有主键约束的表。当2个的会话同时试图向表中插入相同的数据时,其中的一个会话将被阻塞,直到另外一个会话提交或会滚。一个会话提交时,另一个会话将收到主键重复的错误。回滚时,被阻塞的会话将继续执行。 
UPDATE 和DELETE:当执行Update和delete操作的数据行已经被另外的会话锁定时,将会发生阻塞,直到另一个会话提交或会滚。 
Select …for update:当一个用户发出select..for update的错作准备对返回的结果集进行修改时,如果结果集已经被另一个会话锁定,就是发生阻塞。需要等另一个会话结束之后才可继续执行。可以通过发出 select… for update nowait的语句来避免发生阻塞,如果资源已经被另一个会话锁定,则会返回以下错误:Ora-00054:resource busy and acquire with nowait specified. 

1、死锁案例分析

1:用户1对A表进行Update,没有提交。
2:用户2对B表进行Update,没有提交。 
此时双反不存在资源共享的问题。 
3:如果用户2此时对A表作update,则会发生阻塞,需要等到用户一的事物结束。 
4:如果此时用户1又对B表作update,则产生死锁。此时Oracle会选择其中一个用户进行会滚,使另一个用户继续执行操作。 

2、解决死锁方法

Oracle的死锁问题实际上很少见,如果发生,基本上都是不正确的程序设计造成的,经过调整后,基本上都会避免死锁的发生。 当出现session锁时,我们要及时进行处理,处理方法如下:
**步骤一、查看被锁会话**
**方法一:针对所有类型锁**
V$LOCK视图包含所有被锁对象,被锁对象可以是表、存储过程、视图等,在不确定锁类型DDL与DML类型时,可以通过查看V$LOCK视图查看,语句如下:

代码片

SELECT L.SID         "会话ID",
       S.SERIAL#     "会话***",
       P.SPID        "会话进程号",
       L.TYPE        "锁类型",
       S.USERNAME    "所属用户",
       S.MACHINE     "客户端",
       O.OBJECT_NAME "被锁对象",
       O.OBJECT_TYPE "被锁对象类型",
       L.CTIME       "被锁时间(S)"
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O, V$PROCESS P
 WHERE L.SID = S.SID
   AND L.ID1 = O.OBJECT_ID
   AND S.PADDR = P.ADDR
   AND S.SCHEMA# <> 0
   AND S.USERNAME = 'GANGMA2';
**方法二: DML类型锁**
V$LOCKED_OBJECT视图只包DML锁信息,查询语句如下:

代码片

SELECT L.SESSION_ID "会话ID",
       S.SERIAL# "会话***",
       P.SPID "会话进程号",
       S.USERNAME "所属用户",
       S.MACHINE "客户端",
       O.OBJECT_NAME "被锁对象",
       O.OBJECT_TYPE "被锁对象类型",
       CEIL((SYSDATE - S.LOGON_TIME) * 24 * 60 * 60) "被锁时间(S)"
  FROM V$LOCKED_OBJECT L, V$SESSION S, DBA_OBJECTS O, V$PROCESS P
 WHERE L.SESSION_ID = S.SID
   AND L.OBJECT_ID = O.OBJECT_ID
   AND S.PADDR = P.ADDR
   AND S.SCHEMA# <> 0
AND S.USERNAME = 'GANGMA2';
**方法三: DDL类型锁**
DBA_DDL_LOCKS只包DML锁信息,查询语句如下:

代码片

SELECT L.SESSION_ID "会话ID",
       S.SERIAL# "会话***",
       P.SPID "会话进程号",
       S.USERNAME "所属用户",
       S.MACHINE "客户端",
       S.PROGRAM "客户端程序",
       O.OBJECT_NAME "被锁对象",
       O.OBJECT_TYPE "被锁对象类型",
       CEIL((SYSDATE - S.LOGON_TIME) * 24 * 60 * 60) "被锁时间(S)"
  FROM DBA_DDL_LOCKS L, V$SESSION S, DBA_OBJECTS O, V$PROCESS P
 WHERE L.SESSION_ID = S.SID
   AND S.ROW_WAIT_OBJ# = O.OBJECT_ID
   AND S.PADDR = P.ADDR
   AND S.SCHEMA# <> 0
   AND S.USERNAME = 'GANGMA2';
**步骤二、kill锁的进程**
方法一、数据库层面
根据上面查询到的会话ID与会话***进行杀进程:

代码片

alter system kill session 'SID,SERIAL#';
方法二、操作系统层面
根据上面结果查询的会话进程号,在操作系统上查杀进程:
windows平台:orakill <sid> <spid>
`代码片`
orakill gangma2 3876
linux平台:kill -9 SPID

代码片

 kill -9 9876

六、附录

1、V$LOCK视图结构

列名 类型 字段说明
ADDR RAW(4 / 8) Address of lock state object
KADDR RAW(4/8) Address of lock
SID NUMBER 会话的sid,可以和v$session 关联
TYPE VARCHAR2(2) 区分该锁保护对象的类型(表v$lock锁类型说明)TM – DML enqueue TX – Transaction enqueue UL – User supplied–我们主要关注TX和TM两种类型的锁–UL锁用户自己定义的,一般很少会定义,基本不用关注–其它均为系统锁,会很快自动释放,不用关注
ID1 ID2 NUMBER ID1,ID2的取值含义根据type的取值而有所不同。 对于TM 锁:ID1表示被锁定表的object_id 可以和dba_objects视图关联取得具体表信息,ID2 值为0;对于TX 锁:ID1以十进制数值表示该事务所占用的回滚段号和事务槽slot number号,其组形式:0xRRRRSSSS,RRRR=RBS/UNDO NUMBER,SSSS=SLOT NUMBERID2 以十进制数值表示环绕wrap的次数,即事务槽被重用的次数
LMODE NUMBER 0 – none 1 – null (NULL) 2 – row-S (SS) 3 – row-X (SX) •4 – share (S) 5 – S/Row-X (SSX) 6 – exclusive (X) 具体见表TM锁模式
REQUEST NUMBER 同LMODE–大于0时,表示当前会话被阻塞,其它会话占有改锁的模式
CTIME NUMBER 已持有或者等待锁的时间
BLOCK NUMBER 是否阻塞其他会话锁申请 1:阻塞 0:不阻塞

2、和v$lock的其它相关视图说明

视图名 描述 主要字段说明
v$session 查询会话的信息和锁的信息。 sid,serial#:表示会话信息。program:表示会话的应用程序信息。row_wait_obj#:表示等待的对象,和dba_objects中的object_id相对应。lockwait :该会话等待的锁的地址,与v$lock的kaddr对应.
v$session_wait 查询等待的会话信息。 sid:表示持有锁的会话信息。Seconds_in_wait:表示等待持续的时间信息 Event:表示会话等待的事件,锁等于enqueue
dba_locks 对v$lock的格式化视图。 Session_id:和v$ lock中的Sid对应。Lock_type:和v$ lock中的type对应。Lock_ID1: 和v$ lock中的ID1对应。Mode_held,mode_requested:和v$lock中的lmode,request相对应。
v$locked_object 只包含DML的锁信息,包括回滚段和会话信息。 Xidusn,xidslot,xidsqn表示回滚段信息。和v$ transaction相关联。Object_id:表示被锁对象标识。Session_id:表示持有锁的会话信息。Locked_mode:表示会话等待的锁模式的信息,和v$lock中的lmode一致。

3、TM锁模式

锁模式 锁描述 解释 SQL操作
0 None
1 NULL Select
2 SS (Row-S) 行级共享锁,其他对象只能查询这些数据行;是锁的类型中限制最少的锁,也是在表的并发程度中使用最多的 Select for update、Lock for update、Lock row share
3 SX (Row-X) 行级排它锁,在提交前不允许做DML操作 Insert、Update、 Delete、Lock row share
4 S(Share) 共享锁 Create index、Lock share
5 SSX (Share-Row-X) 共享行级排它锁 Lock share row exclusive
6 X(Exclusive) 排它锁 Alter table、Drop able、Drop index、Truncate table 、Lock exclusive

4、v$lock锁类型说明

System Type Description System Type Description
BL Buffer hash table instance NA…NZ Library cache pin instance (A…Z?= namespace)
CF Control file schema global enqueue PF Password File
CI Cross-instance function invocation instance PI, PS Parallel operation
CU Cursor bind PR Process startup
DF datafile instance QA…QZ Row cache instance (A…Z?= cache)
DL Direct loader parallel index create RT Redo thread global enqueue
DM Mount/startup db primary/secondary instance SC System change number instance
DR Distributed recovery process SM SMON
DX Distributed transaction entry SN Sequence number instance
FS File set SQ Sequence number enqueue
HW Space management operations on a specific segment SS Sort segment
IN Instance number ST Space transaction enqueue
IR Instance recovery serialization global enqueue SV Sequence number value
IS Instance state TA Generic enqueue
IV Library cache invalidation instance TS Temporary segment enqueue (ID2=0)
JQ Job queue TS New block allocation enqueue (ID2=1)
KK Thread kick TT Temporary table enqueue
LA … LP Library cache lock instance lock(A…P = namespace) UN User name
MM Mount definition global enqueue US Undo segment DDL
MR Media recovery WL Being-written redo log instance