以前虽然在网上看到很多关于Oracle锁机制的描述,但总感觉哪里有缺陷不适合自己,因此花了点时间参考官网以及Tom Tyke的《Oracle 9i/10g/11g编程艺术》一书整理了一下Oracle锁相关的知识。

Ps:此博客经过多次编辑,以当前版为准。

官网网址参考:

11.2

​https://docs.oracle.com/cd/E11882_01/server.112/e40540/consist.htm#CNCPT020​

10.2

​https://docs.oracle.com/cd/B19306_01/server.102/b14220/consist.htm​

一、Oracle数据库的锁类型:

根据保护的对象不同,Oracle数据库锁可以分为以下几大类:    

1、DML锁(data locks,数据锁),用于保护数据的完整性;    

2、DDL锁(dictionary locks,字典锁),用于保护数据库对象的结构,如表、索引等的结构定义;    

3、内部锁和闩(internal locks and latches),保护数据库的内部结构。 


二、接下来依次讨论以上三种锁结构:

1.DML锁

DML锁主要包括TM锁和TX锁,其中TM锁称为意向锁或表级锁,TX锁称为行级锁或事务锁。我们可以认为Oracle只有如下6种LMODE的锁,只是根据锁定的对象不同而有不同的名称,如6号的X锁,既可以是用于锁表的TM锁,也可以是TX锁,也可以是DDL锁。

1.1 TM锁(也叫意向锁/表级锁)

TM锁包含如下类型:

Oracle 锁机制探究_oracle

TM锁的兼容性如下:(Y表示兼容,N表示冲突)

Oracle 锁机制探究_数据_02

1.2 TX锁

TX锁只包含一种类型的锁:

Oracle 锁机制探究_oracle_03

TX的本义是Transaction(事务),当一个事务第一次执行数据更改(Insert、Update、Delete)或使用SELECT… FOR UPDATE语句进行查询时,它即获得一个TX(事务)锁,直至该事务结束(执行COMMIT或ROLLBACK操作)时,该锁才被释放。

在同一个事务中,无论是锁定一行,还是一百万行,对于Oracle来说锁开销是一样的。这点可能与其他数据库不一样(例如SQL Server),原因是针对Oracle的每行数据都有一个标志位来表示该行数据是否被锁定。这样就极大的减小了行级锁的维护开销,也不可能出现锁升级。数据行上的锁标志一旦被置位,就表明该行数据被加X锁。

注意TX锁在v$lock的lmode也是6,但是这个6与TM锁的6号X锁只是因为锁定的对象不同而被叫做了TX锁。

1.3 举例说明

当发出一个DML命令后会话获取一个3号的TM锁,和一个针对特定行的6号TX锁。

行级只有X锁,且锁模式为6,再次重申这里的6并不是指TM的6号表锁。

此外Oracle一个事务中无论锁定多少行只会获取一个TX锁(没有锁升级),但有多少个表对象就会获取多少个TM表级锁。

验证如下:

Oracle 锁机制探究_oracle_04

查询锁的语句为:


1



​select​​​ ​​sid,type,id1,lmode,request,block ​​​​from​​​ ​​v$​​​​lock​​​ ​​l ​​​​where​​​ ​​sid ​​​​in​​​ ​​(​​​​select​​​ ​​session_id ​​​​from​​​ ​​v$locked_object) and type ​​​​in​​​ ​​(​​​​'TM'​​​​, ​​​​'TX'​​​​) order ​​​​by​​​ ​​1;​


1.4 DML锁的总结:

读永远不会阻止写。但有唯一的一个例外,就是select ...for update。

写永远不会阻塞读。当一行被修改后,Oracle通过回滚段提供给数据的一致性读。

注意:

以上select和不会被其他操作阻塞是指在锁级别不会,但在内存中依然会发生内部闩锁的争用。因为DDL锁可能会造成严重的library cache lock等待,导致select语句被阻塞。

2.DDL锁

重点:DDL是保护表结构定义的。

当DDL命令发出时,Oracle会自动在被处理的对象上添加DDL锁定,从而防止对象被其他用户所修改。当DDL命令结束以后,则释放DDL锁定。

DDL锁定不能显式的被请求,只有当对象结构被修改或者被引用时,才会在对象上添加DDL锁定。

并不是所有DDL都会触发DDL锁,例如现在的创建索引语句,就只会获取一个S模式的TM锁,因此不会阻塞读。而online模式创建索引的语句则只会获取一个2号的TM锁,因此连DML也不会被阻塞。

需要注意的是DDL总会提交,即便是执行不成功也是如此,因此如果在事务中执行了DDL语句会导致所有事物被提交。验证很容易,在一个窗口执行一条delete然后执行DDL,你会发现记录被不可逆转的删除了,RollBack无效。因此针对事务中的DDL请务必使用自治事务实现。

DDL锁有3种:

2.1 排他DDL锁  --即6号的TM锁(Exclusive DDL Locks)

一般对表的DDL语句都会获取一个X模式的TM锁,这是为什么在表结构更改时只能查询不能修改的原因。


2.2 共享DDL锁(Share DDL Locks)

共享DDL锁的常见情形为创建存储过程时,会尝试为所有涉及到的表添加共享DDL锁,这会允许类似的DDL操作并发,但会阻止所有想要获取排他DDL锁的会话(即更改表结构的会话)。

--我觉的可以认为这就是4号的TM表锁。


2.3 可中断解析锁(Breakable Parse Locks)

会话解析一条SQL或PLSQL代码块时,对于该语句引用的每一个对象都会施加解析锁,目的在于:如果以某种方式删除或修改了引用对象,可以将共享池中已经解析的缓存语句无效刷出。

之所以叫做可中断解析锁,是因为这种锁优先级很低可能会被其他互斥DDL操作打断。

每个SQL在解析时都会获取一个可中断解析锁,只要SQL语句还在shared pool中,这个锁就一直存在。

这里提一下:library cache lock和library cache pin就是可中断解析锁,是的他俩不是latch。其分别是针对父游标与子游标的保护体,前者有三种模式:1 null,2 S,3 X 后者只有2 S,3 X两种模式。

举例来说发生Cursor : pin S wait X则意味着持有X模式Library cache pin(硬解析),且有大量会话争用S模式的Library cache pin(version count过多或硬解析),因为无论是X还是S模式的pin获取,都需要mutex保护父游标不被修改,mutex就是一种更细粒度的latch,用于保护上层内存物理结构。


3.内部闩锁机制

Latches:

可译作闩或者栓,这是一种非常低级别的序列化结构,用于协调并发会话对于共享数据结构、文件等的访问。

闩可以防止并发会话破坏共享的内存资源,几个非常典型的场景是:

  • 多个会话同时修改数据
  • 读取的数据正在被其他会话修改
  • 重新分配内存

可能在这里会和事务锁混淆,但事务锁是锁定逻辑结构的,如表和行,但是闩却是锁定物理结构的,如内存中的数据块、执行计划链表等等。多个会话对于同一个对象的访问就需要闩来保护了,例如对于同一个数据块中不同行的访问就需要latch来保护,而闩的存在时间是极短的,因此可以实现高并发(其实Oracle高并发并非是真正的并发而是latch释放速度极快的串行访问)。

通常在SGA中一个latch可以保护多个对象,例如DBWn和LGWR进程从SGA中分配内存以便创建新的数据结构,为了防止冲突进程会使用一种叫做shared pool latch的闩来序列化分配操作,在内存分配完毕后其它进程可能会需要访问shared pool中的其他区域如library cache,此时闩只会锁住library cache而非整个shared pool。

通常情况下oracle进程获取闩的时间极短,一个简单的查询都会有成千上万次的闩的获取和释放,是完全不能由用户控制的。

闩的数目的增多,意味着并发量增大,可以通过v$latch视图来查看闩的使用情况,包括每种闩被请求和等待的次数。

Mutex:(Mutual exclusion object)

可以译作互斥体,这种结构很类似于闩,区别在于一种mutex只保护一种对象,而一种闩通常会保护多种对象。

mutex的优势在于:

  • mutex可以减少latch争用
  • mutex比latch占用更少的内存
  • share mode模式的mutex允许多个会话并发使用

其他内部锁:

这些是比mutex和latch更复杂的结构,oracle数据库使用以下几种内部锁:

1.Dictionary cache locks

2.File and log management locks

3.Tablespace and undo segment locks

这几种锁的解释有兴趣可以到官网看,平时基本观察不到,后两种基本都在多实例时出现。

总结一下:

锁就是保护逻辑结构的结构体,而latch和mutex则是保护内存中物理结构的结构体,抛开DML锁,一般来说在内存中获取内存锁和pin都需要latch和mutex的保护,常见的场景就是library cache中Library cache pin的获取需要cursor: xxx的mutex保护住父游标以及Buffer pool中的buffer pin 需要cbc latch保护住hash bucket中涉及到本SQL的链表头。

从名字上就可以看出latch和mutex的保护对象,cursor: xxx这种mutex保护的是父游标的物理结构,以此来获取library cache pin; cbc latch保护的则是cache buffer chain链表,library cahce:mutex(以前叫library cache latch)则是用于保护library cache的,具体说来就是hash bucket链表头,以此来获取parent cursor handler上的library cache lock。

mutex、latch一定是保护含有子对象的内存结构的,比如子游标级别就不可能有mutex,因为已经没有更底层结构了,所以无需在底层结构上加锁,因此也就无需mutex保护,高层结构则不然,例如父游标和hash bucket,都是含有子对象的,要对子对象加锁加pin就必须先用latch、mutex先护住自己不被修改。


Oracle官网已经没有太多更详细的关于内部闩锁的资料,更详细的关于内部latch和mutex的运作机制和种类,参考吕海波《Oracle内核技术揭秘》和Steve Adams的《Oracle 8i Internal Services》一书。



1.共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

2.共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享.