Oracle锁机制介绍
根据保护对象的不同,单实例 Oracle 数据库锁可以分为以下几大类: DML lock(data locks,数据锁):用于保护数据的完整性; DDL lock(dictionary locks,字典锁):用于保护数据库对象的结构(例如 表、视图、索引的结构定义); internal locks,latches,mutex,pin:保护内部数据库结构;Oracle DML 锁共有两个层次,即行级锁(TX)和表级锁(TM)。
Part1Lock 锁

锁介绍
我们在谈到性能优化的时候,通常是讲系统缓慢的时候需要性能优化,系统缓慢通常是由什么原因导致的呢?大概上讲分为两种
- 系统的资源耗尽了 (cpu 耗尽了,IP,吞吐量上不去) 性能枯竭,性能达到了极限。
- 锁定,阻塞,发现系统性能很高,但应用程序性能就是上不去,这种情况通常来讲是阻塞。
模拟锁的情况:
窗口一:建一张表,插入一条数据,id 上有主键
窗口二:再往这张表 id 列插入一条同样的数据
由于 primary key 本质上是 unique key + not null ,由于第二个事务不知道第一个事务是提交还是回滚,如果提交则事务二插入失败,如果回滚则插入成功,此时会造成锁阻塞。
窗口一:

窗口二:

Oracle 中锁的大体分类
Enqueues : 队列类型的锁,通常和业务相关的锁。
Latches: 系统资源方面的锁,比如内存结构,SQL 解析等
锁的所有类型可以根据视图 v$lock_type 查看
环境 oracle 19c 共 291 种 锁。

.........
锁的原则
- 只有被修改时, 行才会被锁定。
- 当一条语句修改了一条记录, 只有这条记录上被锁定, 在 Oracle 数据库中不存在锁升级。
- 当某行被修改时, 它将阻塞别人对它的修改。
- 当一个事务修改一行时, 将在这个行上加上行锁 (TX), 用于阻止其它事务对相同行的修改。
- 读永远不会阻止写。
- 读不会阻塞写, 但有唯一的一个例外, 就是 select …for update。
- 写永远不会阻塞读。
- 当一行被修改后, Oracle 通过回滚段提供给数据的一致性读。
“这里其实听过其它很多人说过 Oracle 锁升级的概念,根据 Oracle 资料调查 Oracle 不存在锁升级,但确实存在锁转换。
数据库在必要时执行锁转换。在锁转换中,数据库自动将较低限制的表锁转换为较 高限制的其它锁定。(锁转换不同于锁升级,锁升级发生在当某个粒度级别持有许多锁(例如行),数据库将其提高到更高粒度级别(例如表)Oracle 数据库永远不会升级锁。Oracle 的锁是 block 里面实现的,(SQLSERVER、DB2 是内存里面实现的。内存实现有资源消耗问题, 当内存不足会引发锁升级)但是 Oracle 不会发生锁升级。 事务拥有在此事务内被插入(insert)、更新(update)、删除(delete)的数 据行的排它行级锁(exclusive row lock)。对于数据行来说,排它行级锁已经是 限制程度最高的锁,因此无需再进行锁转换(lock conversion)。
TM锁和TX锁
- TM 表锁, 发生在 insert,update,delete 以及 select for update 操作时,目的是保证操 作能够正常进行, 并且阻止其它人对表执行 DDL 操作。
- TX 锁事务锁 (行锁) 对于正在修改的数据, 阻止其它会话进行修改。
根据官方文档归纳总结: v$lock 视图列出当前系统持有的或正在申请的所有锁的情况,其主要字段说明如下:


v$locked_object 视图列出当前系统中哪些对象正被锁定,其主要字段说明如下:

inert 锁过程验证:
窗口一:
窗口二:

查看锁结构:

在整个过程中
- 首先会话 58 对表 T 加 TM(表锁 )和 TX(行锁),TM 锁的 BLOCK = 1 代表这个会话在阻塞其它会话
- 第二个会话 273 也对 T 表加了一个 TM(表锁),又加了一个 TX(行锁),这个行锁 request = 4,代表它正在被阻塞
- 中间那把锁(会话 273 的行锁)正在请求锁并且被会话 58 的行锁阻塞。会话 58 持有的行锁(
LMODE=6
)正在阻塞会话 273 对相同行的访问,导致会话 273 的请求(REQUEST=4
)被阻塞。
update 锁过程验证:
窗口一:

窗口二:

查看锁过程:

整个过程
会话 53 对表 T 加 TM 和 TX 锁,TX 锁阻塞了其它会话,会话 273 对表 T 加 TM 和 TX 锁,TX 锁 request=6 被阻塞。
delete 锁过程验证:
窗口一:

窗口二:

查看锁过程:

会话 58 对表 T 加表锁和行锁,会话 273 对表 T 加表锁和行锁,58 的行锁阻塞了 273 的行锁。
从 v$session_wait 查看阻塞详情(从会话层面查看锁阻塞):

所以 TX 锁又叫队列锁(enq 锁)
TM 锁的几种模式
ORACLE 里锁有以下几种模式: |
TM 锁几种模式的互斥关系
RI 锁
当对具有主外键关系的表做 DML 操作时,锁定不单单发生在操作表上,相应的引用表上也可能加上相应的锁定
示例:


insert 操作在往主表注入一条数据的时候会在主表和从表上同时上 TM 锁,对主表上 TX 锁


update 操作 只会在主表上加 TM 和 TX 锁
死锁
两个会话互相持有对方资源导致死锁 (Oracle 会自动检测死锁)
会话一:

会话二:

如上,Oracle 自动检测到了死锁,释放了会话一。
Part2Latch 锁(闩锁)
用中国话理解闩,就是古代插在门后面那个上锁的。(意味着获取一个资源之后,把它插住,谁也用不了,用完之后再把它打开)

Latch 和 Lock 的区别
Lock 可以想象成食堂排队打饭,是有序的。
Latch 可以想象成微信群里抢红包,抢红包不是一个一个排队的抢,是无序的。而就因为 Latch 的无序性,才需要 Latch 锁
Latch 的目的
- 保证资源的串行访问: - 保护 SGA 的资源访问 - 保护内存的分配
- 保证执行的串行化: - 保护关键资源的串行执行 - 防止内存结构损坏
Latch 在哪里 ->SGA
sharedpool -sql 解析,sql 重用....... buffercache - 数据访问,数据写入磁盘,数据读入内存,修改数据块,数据段扩展
oracle19c 共 993 个 Latch
Latch 的获取:
wait 方式 -- 如果无法获取请求的 latch , 则:
-spin
当一个会话无法获得需要的 latch 时,会继续使用 CPU( CPU 空转),达到一个间隔后, 再次尝试申请 latch , 直到达到最大的重试次数。
-sleep
当一个会话无法获得需要的 latch 时,会等待一段时间(sleep) , 达到 Y 间隔后,再次 尝试申清 latch, 如此反复,直到达到最大的重试次数。
No wait 方式 -- 如果无; 标取请求的 latch , 则:
- 不会发生 sleep 或者 spin.
- 转而去获取其它可用的 Latch
Latch 锁在 data buffer 中的应用
data buffer 存在的意义就是为了在内存中进行高速的数据查找和更新, 尽量减少磁盘的 IO 操作, Buffer Cache 中存在一个 Hash Bucket 结构, 将数据库中已经读取的数 据块放到里面, 在从数据库文件中读取到一个数据块后, Oracle 会根据这个数据块的 文件编号, 段编号, 数据块号组合到一起通过一个内部的 hash 算法运算后, 会放到不同 的 hash bucket 中, 每个 Hash Bucket 都有一个 Hash chain list, 保留 Buffer Header 中的信息, 然后通过这个 list, 把相同 hash 值的 Buffer 串起来. 结构如图:

为保护这个结构不受同步更新的破坏, Oracle 设计了一个 CBC latch 的锁结构 (Cache Buffers Chains), 一个 latch 保护 32 的桶 (Bucket), 所以为了访问 hash 列表, 必须先获得 CBC Latch. 先获取 Latch 后, 在对 Buffer 进行操作. 对 buffer 操作是一个比较耗时的操作,比如从磁盘读取 block。由于一个 latch 管理 32 个桶,所以对 buffer 操作时不能继续持有 latch。Oracle 使用两阶段加 latch 锁的 方式解决这个问题。
- 加 latch 锁
- 查找 buffer,并对 buffer 加 pin 锁,如果是读取操作则为 shared pin,若是写操 作,则 exclusive pin
- 释放 latch 锁
- 对 buffer 进行操作
- 加 latch 锁
- 释放 pin 锁
- 释放 latch 锁
参见下图,一个 buffer 拥有一个 pin 锁,每个 pin 锁对应两个列表,user's list(拥有 锁的用户列表) 和 waiter's list(等待锁的列表),如果用户等待 pin 锁的时间超过 1 秒,则认为出现死锁。buffer 和 pin 锁是 一 一对应关系

加上锁保护以后, 整体的图

读取数据块的过程如下, 当一个用户进程想要访问一个数据块
- 根据数据文件号,块号生成 hash 值
- 在 Hash Table 中找到 bucket 地址
- 获取 CBC latch, 一个 latch 保护 32 个 bucket
- bucket 有一个指针指向 bh(buffer head)
- 根据 bh 的搜索链表
- 匹配 bh 里面的内容和要找的
- 匹配到一个 bh, 给 bh 加锁 buffer pin(共享和独占)
- 释放 cbc latch, cbc latch 做两件事情: 保护链表,保护加 buffer pin 锁
- 进程根据 bh 里面的 ba 地址找内存块
- 找完了,获得 cbc latch, 在保护下 释放 buffer pin 锁
利用哈希表管理已经被缓存的 data block。为了使得哈希桶上链表尽量短,理想目 标是一个链表上最多只有一个 buffer,哈希桶的数量一般是 data buffer 数量的两 倍,并把桶分组,每个组对应一个 latch,latch 用来保护桶中的链表。一个 latch 保 护 32 个桶。
虽然和 lock 相比,latch 是轻量级的,但是使用 latch 也是有比较大的开销的,所以 oracle 尽量减少 latch 数量,所以一个 latch 管理了高达 32 个桶。
Oracle 等待事件 latch free:
Oracle 事件 latch free 定义为:当多个进程竞争一个 latch 2 次,而第 2 次的竞争者失败时,在 Statspack 或 ASH 报告中可以看到的 Oracle 事件名称。 当数据库发生 IO 瓶颈,并且出现这类事件时,就可以查看 latch free 对 Oracle 性能的影响。
latch free 是一种 Oracle 的内部技术,是用于提升性能的一种需要得到保护的数据结构。 通俗讲,在有多个几秒之间,用来竞争访问相同的资源的多个进程中,如果短时间内无法访问此特定资源,则会发生 latch free 事件。
由于 Oracle 数据库中大量组件都是由不同的进程驱动的,每一个进程都可以同时访问资源,并产生 latch free 的。 而我们的目的是保护共有的资源,因此在资源访问时,可能会出现竞争的情况。 在竞争时,如果第二个进程竞争失败,则表示该进程无法获取资源,这就引发了 latch free 事件。
解决方案:首先查看 latch free 看看哪个类型 latch 最多
select p1,p2,p3,inst_id,count(*) from gv$active_session_history where sample_time > sysdate-1/24 and event= 'latch free' group by p1,p2,p3,inst_id order by 5 desc;
Latch 锁类型较多,需要根据不同类型的 Latch 做不同的处理方案
快速查看当前 Lock 锁等待信息:
Oracle 提供一个名为 utllockt.sql 的脚本,它会给出一个树形结构的锁等待图,显示持有影响其他会话的锁的会话。使用此脚本,可以看出一个会话正在等待什么锁,哪个会话持有这些锁。该脚本位于 $ORACLE_HOME/rdbms/admin 目录下。
@?/rdbmsa/admin/utllockt.sql

执行脚本可以迅速看到 会话 51 在请求一个 Share 锁,等待 47 释放 Exclusive 锁。




