oracle(enquences & latches )lock (oracle 锁大全)

资料来自官方文档:

https://docs.oracle.com/database/121/CNCPT/consist.htm#CNCPT1333

https://docs.oracle.com/database/121/SQLRF/statements_9016.htm#SQLRF01605

latch主要内容来自:https://blog.csdn.net/yangshangwei/article/details/53725617

本章节主要介绍的oracle锁总主要包含两种类型的锁:

1.enquences 队列类型的锁,通常跟业务相关的锁(本章主要讨论的就是这种锁)

2.latches 系统锁,比如内存使用,sql解析方面的问题。

实验环境oracle版本都是19.3

1.为什么会有锁?

为了数据并发与一致性

在单用户数据库中,一个用户可以修改数据而不必担心其他用户同时修改相同的数据。但是,在多用户数据库中,多个并发事务中的语句可能会更新相同的数据。同时执行的事务必须产生有意义且一致的结果。

多用户数据库必须提供以下内容:

  • 保证用户可以同时访问数据(数据并发

  • 保证每个用户看到数据的一致视图(数据一致性),包括用户自己的事务和其他用户提交的事务所做的可见更改

为了在事务并发运行时描述一致的事务行为,数据库研究人员定义了一个称为可串行化的事务隔离模型可序列化事务在一种环境中运行,使其看起来好像没有其他用户正在修改数据库中的数据。

虽然事务之间的这种隔离程度通常是可取的,但在可序列化模式下运行许多应用程序会严重影响应用程序吞吐量。并发运行事务的完全隔离可能意味着一个事务不能执行插入到另一个事务正在查询的表中。简而言之,现实世界的考虑通常需要在完美的事务隔离和性能之间进行折衷。

Oracle 数据库通过使用多版本一致性模型和各种类型的锁和事务来维护数据一致性通过这种方式,数据库可以向多个并发用户呈现数据视图,每个视图都与一个时间点保持一致。由于不同版本的数据块可以同时存在,事务可以读取查询所需时间点提交的数据版本,并返回与单个时间点一致的结果。

 

2.Oracle 数据库锁定机制概述

锁是一种防止破坏性交互的机制,破坏性交互是在访问共享数据的事务之间错误地更新数据或错误地改变底层数据结构的交互。锁在维护数据库并发性和一致性方面起着至关重要的作用。

 

2.1锁定行为总结

数据库维护几种不同类型的锁,具体取决于获取锁的操作。

一般来说,数据库使用两种类型的锁:排他锁和共享锁。一个资源(例如行或表)只能获得一个排他锁,但在单个资源上可以获得多个共享锁。

锁会影响读者和作者的交互。以下规则总结了 Oracle 数据库对读写器的锁定行为:

  • 一行只有在被修改时才被锁定。

    当语句更新一行时,事务只为这一行获取锁。通过在行级别锁定表数据,数据库最大限度地减少了对相同数据的争用。在正常情况下,数据库不会将行锁升级到块级或表级。

  • 一行的写入阻塞同一行的并发写入者。

    如果一个事务正在修改一行,那么行锁可以防止不同的事务同时修改同一行。

  • 读取永远不会阻塞写。

    因为一行的读取不会锁定它,所以写入可以修改该行。唯一的例外是SELECT ... FOR UPDATE语句,它是一种特殊类型的SELECT语句,它会锁定正在读取的行。

  • 写入永远不会阻塞读。

    当写入更改一行时,数据库使用undo数据为读提供该行的一致视图(即利用回滚段一致性读)。

 值得注意的是:分布式事务的非常特殊的情况下,数据的读取可能不得不等待相同数据块的写入。

 

2.2锁的使用

在单用户数据库中,不需要锁定,因为只有一个用户在修改信息。但是,当多个用户访问和修改数据时,数据库必须提供一种防止对同一数据进行并发修改的方法。

锁实现了以下重要的数据库要求:

  • 一致性

    在用户完成提交之前,会话正在查看或更改的数据不得被其他会话更改。

  • 完整性

    数据和结构必须以正确的顺序反映对它们所做的所有更改。

Oracle 数据库通过其锁定机制在事务之间提供数据并发性、一致性和完整性。锁定自动发生,无需用户操作。

请感受下面这个案例:

 

 

 

sid为197的建立一张表t,且插入一条记录,不提交这条记录。

 

 sid为205的插入同样的一条记录。

set lin 200 pages 200
select distinct sid from v$mystat;
select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;
select sid,seq#,event FROM v$session_wait where sid in (197,205);

 

常见的TM表级锁,ID1表示被锁定的对象的OBJECT_ID,ID2此时为0;对于 TX 锁,这两个字段构成了事务在回滚段中的位置。

当锁类型为TX事务锁时ID1和ID2的含义如下:
ID1对应视图V$TRANSACTION中的XIDUSN字段和XIDSLOT字段。其中ID1的高16位为XIDUSN,低16位为XIDSLOT。
ID2对应视图V$TRANSACTION中的XIDSQN字段。

 select OBJECT_ID,owner,OBJECT_NAME,OBJECT_TYPE from dba_objects where OBJECT_ID in (107036);

 

 可以查看的是sid197,205的首先加了一个表级锁,sid为205会话被sid197的阻塞,TX行级锁;后面会详细介绍。

例如查看TX,AE,TM锁类型:

select distinct type,name,DESCRIPTION from v$lock_type a where a.TYPE in ('TX','AE','TM');

 Oracle 数据库在执行 SQL 语句时会自动获取必要的锁。例如,在数据库允许会话修改数据之前,会话必须先锁定数据。锁赋予会话对数据的独占控制权,因此在释放锁之前没有其他事务可以修改锁定的数据。

LMODE的数字0-6代表的是:

  • 0 - none 没有锁

  • 1 - null (NULL) 空

  • 2 - row-S (SS)行共享 (RS)

  • 3 - row-X (SX)也叫行级排他锁 (RX)

  • 4 - share (S)共享表锁 (S)

  • 5 - S/Row-X也叫共享行级排他锁SRX)

  • 6 - exclusive (X)表级排他锁 (X) alter table, drop table, drop index, truncate table操作等

 

2.3锁定模式

Oracle 数据库自动使用最低的适用的限制级别以提供最高程度的数据并发性,同时还提供故障安全数据完整性。级别限制越少,其他用户访问的数据就越多。相反,级别越严格,其他事务在它们可以获取的锁类型方面就越有限。

Oracle 数据库在多用户数据库中使用两种锁定模式:

  • 独占锁定模式

    此模式可防止共享关联的资源。事务在修改数据时获得排他锁第一个独占锁定资源的事务是唯一可以更改资源的事务,直到独占锁被释放。

  • 共享锁定模式

    此模式允许共享关联的资源,具体取决于所涉及的操作。多个读取数据的用户可以共享数据,每个用户都持有一个共享锁,以防止需要排他锁的写入者进行并发访问。多个事务可以在同一资源上获取共享锁。

假设事务使用SELECT ... FOR UPDATE语句来选择单个表行。事务获取排他行锁和行共享表锁。行锁允许其他会话修改锁定行以外任何行,而表锁防止会话更改表的结构。因此,数据库允许执行尽可能多的语句。

 

2.3锁转换和升级 

Oracle 数据库执行锁转换有必要的。在锁转换中,数据库会自动将限制性较低的表锁转换为限制性较高的表锁。

例如,假设一个事务SELECT ... FOR UPDATE ,然后更新了锁定的行。在这种情况下,数据库会自动将行共享表锁转换为行排他表锁。事务为事务中插入、更新或删除的所有行持有排它行锁。因为行锁是在最高级别的限制下获取的,所以不需要或执行锁转换。

锁转换与锁升级不同,锁升级是在一个粒度级别(例如,行)持有大量锁并且数据库将锁提升到更高粒度级别(例如,表)时发生的。如果用户锁定一个表中的许多行,那么某些数据库会自动将行锁定升级到单个表。锁的数量减少了,但锁定的限制增加了。

Oracle 数据库从不升级锁。锁升级大大增加了死锁的可能性。假设系统试图代表事务 1 升级锁,但由于事务 2 持有锁而无法升级。如果事务 2 还需要对相同数据进行锁升级才能继续进行,则会创建死锁。

 

 

 

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;
select OBJECT_ID,owner,OBJECT_NAME,OBJECT_TYPE from dba_objects where OBJECT_ID in (107036);

 

 

  

 可以看到的是,sid为35首先发出SELECT ... FOR UPDATE 语句,数据库会自动将共享表锁转换为表级排他锁 LMOD为6和行排他表锁LMOD为3;sid的会话一直在等待资源释放。

 

2.4锁定持续时间

当某些事件发生时,Oracle 数据库会自动释放锁,以便事务不再需要该资源。通常,数据库在事务期间持有事务内语句获取的锁。这些锁可防止并发事务中的破坏性干扰,例如脏读、更新丢失和破坏性DDL

由于未使用索引的外键而对子表采取的表锁定在语句期间保持,而不是在事务期间保持。此外,如用户定义锁概述”中所述,该DBMS_LOCK包使用户定义的锁能够随意释放和分配,甚至可以跨越事务边界。

Oracle 数据库在提交或回滚时释放事务中的语句获取的所有锁。当回滚到savepoint时,Oracle 数据库还会释放在savepoint之后获取的锁但是,只有不等待先前锁定资源的事务才能获取对现在可用资源的锁定。等待事务继续等待,直到原始事务完全提交或回滚。

 

2.5死锁

死锁是其中两个或更多用户正在等待由相互锁定的数据的情况。死锁会阻止某些事务继续工作。

Oracle 数据库自动检测死锁并通过回滚死锁中涉及的一条语句来解决死锁,释放一组冲突的行锁。数据库向进行语句级回滚的事务返回相应的消息回滚的语句属于检测到死锁的事务。通常,发出信号的事务应该显式回滚,但它可以在等待后重试回滚语句。

 

 

 

3.锁的分类

Oracle 数据库代表事务自动锁定资源,以防止其他事务执行需要对同一资源进行独占访问的操作。数据库根据资源和正在执行的操作自动获取不同级别限制的不同类型的锁。

执行简单读取时,数据库从不锁定行。

官方分为以下三类,没有找到官方有乐观,悲观的分类(有的关,可以留言提示我),虽然看到网络上有很多这种分类的,官方有提到显示,当然对立的就是隐示了。

  1. 自动锁    

  2. 手动锁        

  3. 用户自定义锁   

3.1 自动锁

 

一个DML锁,也称为数据锁定,保证多个用户同时访问数据的完整性。例如,DML 锁可防止两个客户购买在线书商提供的最后一本图书。DML 锁可防止同时发生冲突的 DML 或 DDL 操作的破坏性干扰。

DML 语句自动获取以下类型的锁: 

  • 行锁 (TX)

  • 表锁 (TM)

 

3.1.1.1行锁 (TX)

一个行锁,也称为TX锁,上表中的单个行的锁。事务获取用于通过修改的每个行的行锁INSERTUPDATEDELETEMERGE,或SELECT ... FOR UPDATE语句。行锁一直存在,直到事务提交或回滚。

行锁主要用作一种排队机制,以防止两个事务修改同一行数据库始终以独占模式锁定已修改的行,以便其他事务在持有锁的事务提交或回滚之前无法修改该行。行锁提供了最好的粒度锁,因此提供了最好的并发性和吞吐量。

如果事务因数据库实例故障而终止,则块级恢复会在整个事务恢复之前使一行可用。

如果一个事务获得了某行的锁,那么该事务也会为包含该行的表获得一个锁。表锁可防止冲突的 DDL 操作覆盖当前事务中的数据更改。下图说明了表中第三行的更新。Oracle 数据库自动在更新的行上放置一个排他锁,并在表上放置一个子排他锁。

 

修改employees这张表的employee_id 为100的这行, 会有一个表锁和行锁,这就是为啥SELECT ... FOR UPDATE 语句,据库会自动将共享表锁转换为独占表锁 LMOD为6和行排他表锁LMOD为3;一直等提交或者回滚资源才释放。

行锁和并发

这个场景说明了 Oracle 数据库如何使用行锁来实现并发。每个会话看到自己的未提交更新,但看不到任何其他会话的未提交更新。

行锁的存储

与某些使用锁管理器在内存中维护锁列表的数据库不同,Oracle 数据库将锁信息存储在包含锁定行的数据块

数据库使用队列机制来获取行锁。如果事务需要锁定未锁定的行,则事务会在数据块中放置一个锁。此事务修改的每一行都指向存储在块头

当交易结束时,交易 ID 保留在区块头中。如果不同的事务想要修改一行,则它使用事务 ID 来确定锁是否处于活动状态。如果锁处于活动状态,则会话要求在释放锁时得到通知。否则,事务获取锁。

 

3.1.1.2表锁 (TM)

表锁,也称作TM锁,通过一个事务被获得当一个表INSERTUPDATEDELETEMERGESELECTFOR UPDATE子句或LOCK TABLE语句。DML 操作需要表锁来代表事务保留对表的 DML 访问,并防止与事务冲突的 DDL 操作。

表锁包括以下类型:

  • 2-行共享 (RS) --行级共享锁

    此锁,也称为子共享表锁 (SS),表示持有该表锁的事务已锁定表中的行并打算更新它们。行共享锁是限制最少的表锁模式,可为表提供最高程度的并发性。SS,行级共享锁,其他对象只能查询这些数据行,sql操作有select for update、lock for update、lock row share;

  • 3-行独占表锁 (RX)--行级排他锁

    此锁,也称为子排他表锁 (SX),通常表示持有该锁的事务已更新表行或已发出SELECT ... FOR UPDATE。SX 锁允许其他事务在同一表中同时查询、插入、更新、删除或锁定行。因此,SX 锁允许多个事务同时获取同一表的 SX 和子共享表锁。行级排他锁,commit前不允许DML操作,sql操作有insert、update、delete、lock row share; 

  • 4-共享表锁 (S)--共享锁

    SELECT ... FOR UPDATE事务持有的共享表锁允许其他事务查询该表,但仅当单个事务持有共享表锁时才允许更新。因为多个事务可能同时持有一个共享表锁,持有这个锁不足以保证一个事务可以修改表,sql操作有创建索引,锁共享

  • 5-共享行独占表锁 (SRX)--共享行级排他锁

    此锁也称为共享子排他表锁 (SSX),比共享表锁限制更多。一次只有一个事务可以获取给定表上的 SSX 锁。事务持有的 SSX 锁允许其他事务查询表(除了SELECT ... FOR UPDATE)但不允许更新表;sql操作有锁共享行排他。

  • 6-独占表锁--表级排他锁 (X)

    这个锁是最严格的,禁止其他事务执行任何类型的 DML 语句或在表上放置任何类型的锁,alter table, drop table, drop index, truncate table,等DDL操作。

v$lock对应的LMODE列的数字0-6代表的是:

  • 0 - none 没有锁

  • 1 - null (NULL) 空

  • 2 - row-S (SS)行共享 (RS)

  • 3 - row-X (SX)也叫行级排他锁 (RX)

  • 4 - share (S)共享表锁 (S)

  • 5 - S/Row-X也叫共享行级排他锁 (SRX)

  • 6 - exclusive (X)表级排他锁 (X)

锁和外键(RI)

Oracle 数据库最大限度地提高了父键相对于从属外键的并发控制。

锁定行为取决于外键列是否有索引。如果外键没有索引,那么子表可能会更频繁地被锁定,会发生死锁,并发度会降低。出于这个原因,外键应该有索引。唯一的例外是匹配的唯一键或主键从未更新或删除。

锁和无索引的外键

当子表的外键列上不存在索引时,数据库在子表上获取全表锁,并且会话修改父表中的主键(例如删除一行或修改主键属性)或将行合并到父表中。

当以下两个条件都为真时,数据库在子表上获取全表锁:

  • 子表的外键列上不存在索引。

  • 会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中。

插入到父表中不会获取阻止子表 DML 的阻塞表锁。在插入的情况下,数据库获取子表上的锁,以防止结构更改,但不会修改现有或新添加的行。

官方给的图如下:

 

数据库employees在部门 60 的主键修改期间获得了一个全表锁。这个锁使其他会话可以查询但不能更新employees表。例如,会话无法更新员工电话号码。表上employees的主键修改departments完成后立即释放表锁如果 中的多行departments进行主键修改,employees则为 中修改的每一行获取并释放一次表锁departments

子表上的 DML 不会获取父表上的表锁。

 请看测试:

select distinct sid from v$mystat;
create table ph_1(id int primary key);
create table st_1(id int, constraint fk_st_1 foreign key (id) references ph_1(id));
insert into ph_1 select rownum from dual connect by rownum <=10;
commit;
update ph_1 set id=11 where id=1; --不提交

  

select distinct sid from v$mystat;

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;

 

insert into st_1 values(2);

 update st_1 set id=3 where id=2; --不提交

 select distinct sid from v$mystat; 

 

 

select distinct sid from v$mystat;

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;

select OBJECT_ID,owner,OBJECT_NAME,OBJECT_TYPE from dba_objects where OBJECT_ID in (107257,107259);

 

 

 

 

 以上操作可以看出,当子表外键没有索引的时候,主表update操作,子表无表锁;但是当子表进行update操作时,主表有表锁。

 

锁和有索引的外键(RI)

如果子表指定ON DELETE CASCADE,则从父表中删除会导致从子表中删除。例如,部门 280 的删除可能导致employees删除部门中员工的记录的删除。在这种情况下,等待和锁定规则与从父表中删除行后从子表中删除行一样。

官方如下图:

 

create index st_1_idx on st_1(id);

update ph_1 set id=222 where id=10; --不提交

select distinct sid from v$mystat;

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;

 

 

提交后,子表进行操作;

 

 

以上操作可以看出,当子表外键有索引的时候,主表update操作,子表有表锁;但是当子表进行update操作时,主表也有表锁。

 

3.1.2 DDL锁 

 

数据字典(DDL)锁保护模式对象的定义,同时正在进行的DDL操作作用于或指对象。在 DDL 操作期间,只有被修改或引用的单个架构对象会被锁定。数据库从不锁定整个数据字典。

Oracle 数据库任何需要它的 DDL 事务自动获取 DDL 锁。用户不能显式请求 DDL 锁。例如,如果用户创建了一个存储过程,那么 Oracle 数据库会自动为过程定义中引用的所有模式对象获取 DDL 锁。DDL 锁可防止在过程编译完成之前更改或删除这些对象。


3.1.2.1独占 DDL 锁

排他 DDL 锁可防止其他会话获得 DDL 或 DML 锁。除了共享 DDL 锁”中描述的那些操作外,大多数 DDL 操作都需要资源的排他 DDL 锁,以防止破坏性干扰其他可能修改或引用相同架构对象的 DDL 操作。例如,DROP TABLE在向表ALTER TABLE中添加列时不允许删除表,反之亦然。

独占 DDL 锁在 DDL 语句执行和自动提交期间持续。在获取排它 DDL 锁期间,如果另一个 DDL 锁被另一个操作持有在架构对象上,则获取将等待,直到较旧的 DDL 锁被释放,然后继续进行。

 

3.1.2.2共享 DDL 锁

资源的共享 DDL 锁可防止对冲突 DDL 操作的破坏性干扰,但允许类似 DDL 操作的数据并发。

例如,当一个CREATE PROCEDURE语句运行时,包含的事务为所有引用的表获取共享 DDL 锁。其他事务可以并发地创建引用相同表的过程并获取相同表上的并发共享 DDL 锁,但没有事务可以在任何引用的表上获取排他 DDL 锁。

共享 DDL 锁在 DDL 语句执行和自动提交期间持续。因此,持有共享 DDL 锁的事务可以保证所引用模式对象的定义在事务期间保持不变。


3.1.2.3可破坏的解析锁

一个解析锁是通过SQL语句或PL / SQL程序单元对每个方案对象,它引用举行。获取解析锁,以便关联如果引用的对象被更改或删除,共享 SQL 区域可能会失效。解析锁被称为可破坏解析锁,因为它不禁止任何 DDL 操作,并且可以被破坏以允许发生冲突的 DDL 操作。

解析锁是在 SQL 语句执行解析阶段的共享池只要该语句的共享 SQL 区域保留在共享池中,就会保持锁定。

 

3.1.3 系统锁(闩)latch

一般也无层面是看不到的,数据库使用各种类型的系统锁来保护内部数据库和内存结构。用户无法访问这些机制,因为用户无法控制其发生或持续时间。

锁存器

闩锁是一个简单的,保证串行化的机制,对共享数据结构,对象和文件的多用户访问。

当多个进程访问时,锁存器保护共享内存资源不被破坏。具体来说,闩锁保护数据结构免受以下情况的影响:

  • 多个会话并发修改

  • 被一个会话读取同时被另一个会话修改

  • 访问时释放(老化)内存

通常,单个闩锁保护 SGA 中的多个对象。例如,DBW 和 LGWR 等后台进程从共享池中分配内存以创建数据结构。为了分配这些内存,这些进程使用共享池锁存器来序列化访问以防止两个进程同时尝试检查或修改共享池。分配内存后,其他进程可能需要访问共享池区域,例如库缓存,这是解析所需的。在这种情况下,进程只锁存库缓存,而不是整个共享池。

与行锁等入队闩锁不同,闩锁不允许会话排队。当闩锁可用时,请求闩锁的第一个会话获得对它的独占访问。锁存自旋现象发生在一个进程在循环中重复请求锁存时,而锁存休眠发生在一个进程在更新锁存请求之前释放 CPU 时。

通常,Oracle 进程在操作或查看数据结构时会在极短的时间内获取锁存器。例如,在处理单个员工的工资更新时,数据库可能会获取并释放数千个锁存器。锁存器的实现依赖于操作系统,尤其是在进程是否等待锁存器以及等待多长时间方面。

锁存的增加意味着并发性的减少。例如,过多的硬解析操作会产生对库缓存锁存器的争用。V$LATCH视图包含每个闩锁的详细闩锁使用统计信息,包括请求和等待每个闩锁的次数。

Latch的目的

保证对资源的串行访问:
– 保护 SGA 资源访问
– 保护内存分配
保证执行的序列化:
– 保护关键资源的串行执行
– 防止内存结构损坏

Latch 是保护数据库本身的内存结构,而不是保护在业务层面。

 

latch 和enquence lock 对比:

 

 latch一般发生在共享池(sql解析,sql重用),buffer cache(数据的修改,访问,段扩展,数据读入内存,数据块的修改等)

查看latch的视图:

V$LATCH 

 

V$LATCHHOLDER 


V$LATCHNAME 

3.1.3.1 latch原理

 

3.1.3.2 latch的获取方式

以等待方式分为两种:

 

3.1.3.3 latch的资源争用

1.shared pool的latch 争用,绑定变量

下面测试案例:

drop table t ;
create table t as select * from dba_objects;
update t set object_id=rownum;
commit;

--没有做绑定变量
create or replace procedure ps1 as
l_cnt number;
begin
for i in 1 .. 20000 loop
execute immediate ' select count(*) from t where object_id = ' || i into l_cnt;
end loop;
end;
/

exec ps1;
select SID,EVENT from v$session_wait where wait_class <> 'Idle';

 

  生产上遇到这种,进行sql语句的改写,将复杂的SQL绑定变量语句拆解成多个简单的使用绑定变量的sql语句;或者SQL加入进行绑定变量,当然olap环境就不需要加了。

2.buffer cache latch热块争用

 

 

查看造成 LATCH BUFFER CACHE CHAINS 等待事件的热块

X$bh 表是数据库头,oracle数据库运行的基础,在数据库启动时由Oracle应用程序动态创建,是不允许sysdba之外的用户直接访问的,显示授权不被允许。

一下是都去访问同一个数据块,引起热快(内存不够大)。

 

 

访问频率非常高的数据块被称为热块( Hot Block),当很多用户一起去访问某几个数据块时,就会导致一些 Latch 争用

最常见的 latch 争用有:

  • buffer busy waits
  • cache buffer chain

Buffer busy waits 产生原因
当一个会话需要访问一个数据块,而这个数据块正在被另一个用户从磁盘读取到内存中或者这个数据块正在被另一个会话修改时,当前的会话就需要等待,就会产生一个 buffer busy waits 等待。产生这些 Latch 争用的直接原因是太多的会话去访问相同的数据块导致热块问题, 造成热块的原因可能是数据库设置导致或者重复执行的 SQL 频繁访问一些相同的数据块导致。

Cache buffer chian 产生原因
当一个会话需要去访问一个内存块时,它首先要去一个像链表一样的结构中去搜索这个数据块是否在内存中,当会话访问这个链表的时候需要获得一个Latch,如果获取失败,将会产生 Latch cache buffer chain 等待,导致这个等待的原因是访问相同的数据块的会话太多或者这个列表太长(如果读到内存中的数据太多,需要管理数据块的 hash 列表就会很长,这样会话扫描列表的时间就会增加,持有 chache buffer chain latch 的时间就会变长,其他会话获得这个 Latch 的机会就会降低,等待就会增加)。

( 1) 表数据块

( 2) 索引数据块

( 3) 索引根数据块

( 4) 段头数据块

热块产生的原因

热块产生的原因不尽相同,按照数据块的类型,可以分成一下几种类型,不同热块类型处理的方式都是不同的。

表数据块
比如在 OLTP 系统中,对于一些小表,会给出某些数据块被频繁查询或者修改的操作,这时候,这些被频繁访问的数据块就会变成热块,导致内存中 Latch争用。

处理方式:
如果出现这样热块的情况,并且表不太大,一个方法是可以考虑将表数据分布在更多的数据块上,减少数据块被多数会话同时访问的频率。

可以通过一下命令将每个数据块存放记录的数量减少到最少:

Alter table tableName minimize records_per_block;
功能:当前表所有block中容纳的最大行数,并会把这个数字记录到数据字典,以后任何导致block行数超过这个数字的插入都会被拒绝

缺点:
我们把数据分布到更多的数据块上,大大降低了一个数据块被重复读取的概率。 但是这种方法的缺点很明显,就是降低了数据的性能,在这种情况下,访问相同的数据意味着需要读取更多的数据块,性能会有所降低。

索引数据块
这样的情况通常发生在一个 RAC 架构里,某个表的索引键值出现典型的“右倾”现象,

比如一个表的主键使用一个序列来生成键值,那么在这个主键在索引数据块上的键值就是以一种顺序递增的方式排列的,比如: 1, 2, 3, 4,5….,由于这些键值分布得非常接近,当许多用户在 RAC 的不同实例来向表中插入主键时,就会出现相同的索引数据块在不同实例的内存中被调用,形成一种数据块的争用。

对于这种情况,使用反向索引可以缓解这种争用。

反向索引是将从前的索引键值按照反向的方式排列,在正常的主键 B-Tree 引中,键值会按照大小的顺序排列,比如: 1234,反向索引,键值就变成 4321.

原理:这样,本来是放在相同的索引数据块上的键值,现在分布在不同的数据块上,这样用户在 RAC 不同的实例上插入的主键值因为分布在不同的数据块上,所以不会导致热块的产生,这基本是反向索引被使用的唯一情况。

反向索引使用场合之所以如此受限制,是因为它丢弃了 B-Tree 索引的一个最重要的功能:
Index range scan

索引访问方式中,这个方式最常见,但是反向索引却不能使用这个功能,因为反向索引已经把键值的排列顺序打乱,当按照键值顺序查找一个范围时,在反向索引中,由于键值被反向存储,这些值已经不是连续存放的了。 所以 Index range scan 的方式在反向索引中没有任何意义。 在反向索引中只能通过全表扫描或者全索引扫描的方式来实现。 这就是反向索引的一个非常严重的缺陷。

索引根数据块
热块也可能发生在索引的根数据块上。

在 B-Tree 索引里,当 Oracle 访问一个索引键值时,首先访问索引的根,然后是索引的分支,最后才是索引的叶块。

索引的键值就是存储在叶块上面。

当索引的根,枝数据都集中在几个数据块上时,比如 D, G 所在的枝数据块,
当用户访问的范围从 A-F,都会访问这个数据块,如果很多用户频繁的访问这个范围的索引键值,有可能导致这个枝数据块变成热块。

当出现这种现象时,可以考虑对索引做分区,以便于使用这些根,枝数据块分布到不同的数据段(分区)上,减少数据块的并行访问的密度,从而避免由于索引根,枝数据块太集中导致热块产生。

段头数据块(修改)
从 Oracle 9i 开始,引入了一个自动段管理的技术 ASSM( Automatic SegmentSpace Management: ASSM),它让 Oracle 自动管理“ Free List”。 实际上在 ASSM里,已经没有 Free List 这样的结构, Oracle 使用位图方式来标记数据块是否可用,这种数据块的空间管理方式比用一个列表来管理效率更高。

对于 OLTP 系统,表的 DML 操作非常密集,对于这些表,使用 ASSM 方式来管理会比人工管理更加方便和准确,能有效的避免段头变成热块。

对于 OLAP 系统,这个参数并没有太大的实际意义,因为在这样的数据库中,很少有表发生频繁的修改, OLAP 系统主要的工作是报表和海量数据的批量加载。

请看下面一个测试:

create table latch_table1 as select * from dba_objects;
create or replace procedure p1 as
      l number;
   begin
      for i in 1 .. 20000 loop
        select count(*) into l from latch_table1 where object_type = 'TABLE';
      end loop;
      dbms_output.put_line('successfully');
    end;
    /

select distinct sid from v$mystat;
exec p1;
select distinct sid from v$mystat;
exec p1;

select distinct sid from v$mystat;
select * from v$latchholder;
select SID,EVENT from V$SESSION_WAIT where wait_class <> 'Idle';

 

  

 

 由于我的版本是19.3,所以是cache buffer chain,生产上我遇到过类似的latch等待事件,此种等待事件最好从业务规避,进行串行化,避免业务引起的资源争用。

 

3.1.3.4 latch 优化

 

 

3.2 手动锁

官方的文档翻译如下:

Oracle 数据库自动执行锁定以确保数据并发性、数据完整性和语句级读取一致性。但是,可以手动覆盖 Oracle 数据库默认锁定机制。在以下情况下,覆盖默认锁定很有用:

  • 应用程序需要事务级读取一致性或可重复读取。

    在这种情况下,查询必须在事务期间生成一致的数据,而不是反映其他事务的更改。可以通过使用显式锁定、只读事务、可序列化事务或覆盖默认锁定来实现事务级读取一致性。

  • 应用程序要求事务具有对资源的独占访问权限,以便事务不必等待其他事务完成。可以覆盖 Oracle 数据库在会话或事务级别自动锁定。在会话级别,会话可以使用ALTER SESSION语句设置所需的事务隔离级别。在事务级别,包含以下 SQL 语句的事务会覆盖 Oracle 数据库默认锁定:

  • SET TRANSACTION ISOLATION LEVEL声明

  • LOCK TABLE(其锁定或者一个表或者,享有使用时,基表)语句

  • SELECT ... FOR UPDATE声明

前面语句获取的锁在事务结束或回滚到保存点后释放。

如果在任何级别覆盖 Oracle 数据库默认锁定,则数据库管理员或应用程序开发人员应确保覆盖锁定过程正确运行。锁定过程必须满足以下条件:保证数据完整性,可接受数据并发,不可能发生死锁或已适当处理死锁。

而实际的测试如下:

3.2.1 row share mode为2的测试

 实验如下:

create table t as select * from dba_objects;

update t set object_id=rownum;

 commit;

lock table t in row share mode;

select distinct sid from v$mystat;

 

 

select distinct sid from v$mystat;

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2;

select OBJECT_ID,owner,OBJECT_NAME,OBJECT_TYPE from dba_objects where OBJECT_ID in (107264);

 

 update t set object_id=3 where object_id=2;

 insert into t select * from t where object_id=1;

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2

delete from t where object_id=3;

commit;

 

 

在row share mode模式下,可以进行增删改查,以及for update 操作。能不能进行DDL操作呢?

 

答案是不可以的。必须等待前面锁释放了才能。排斥的是独占表锁 (X),6模式,这个锁是最严格的,禁止其他事务执行任何类型的 DML 语句或在表上放置任何类型的锁,即有了6就不会在有其他的锁,有了其他的锁就不会有6这个锁。

 

3.2.2 row exclusive mode为3的测试

 

 

 

 跟在row share mode模式下一样,row exclusive mode可以进行增删改查,以及for update 操作。

 

3.2.3 share mode为4 测试

 

 

经测试,即mode为4的share 模式,只能进行select 操作。

3.2.4 share row exclusive mode为5的模式

 

 

 

 

 

 

 

 

 可以看到,进行非select 操作,都会等待锁释放。只有select(非for  update) 操作才能进行。

 

3.2.5 exclusive mode为6的模式

 

 

 

 

 

 

 

 

 

 

 

可以看到,非select 操作,都会等待锁释放。只有select(非for  update) 操作才能进行。

 

3.2.6总结:

 从2-6是级别越来越严格的操作,所有模式都排斥6,因为6模式,这个锁是最严格的,禁止其他事务执行任何类型的 DML语句或在表上放置任何类型的锁,即有了6就不能再有其他的锁,有了其他的锁就不能再有6这个锁。

 

3.3 用户自定义

使用 Oracle 数据库锁管理服务,可以为特定应用程序定义自己的锁。例如,您可以创建一个锁来序列化对文件系统上的消息日志的访问。由于保留用户锁与 Oracle 数据库锁相同,因此它具有所有 Oracle 数据库锁功能,包括死锁检测。用户锁永远不会与 Oracle 数据库锁冲突,因为它们用前缀UL

Oracle 数据库锁管理服务可通过DBMS_LOCK包中的过程获得可以在 PL/SQL 块中包含以下语句:

  • 请求特定类型的锁

  • 给锁一个唯一的名字,这个名字可以在同一个或另一个实例的另一个过程中识别

  • 更改锁类型

  • 释放锁

 

4.锁的查看

4.1 select 锁查看

 

 

 select 是没有锁的。

以最常见的TM表级锁定为例,ID1表示被锁定的对象的OBJECT_ID,ID2此时为“0”。
当所类型为TX事务锁时ID1和ID2的含义如下:
ID1对应视图V$TRANSACTION中的XIDUSN字段和XIDSLOT字段。其中ID1的高16位为XIDUSN,低16位为XIDSLOT。
ID2对应视图V$TRANSACTION中的XIDSQN字段。

select * from t where object_id=100 for update; --不提交

 

select distinct sid,id1,id2,addr,type,lmode,block from v$lock where type in ('TX','TM') ORDER BY 1,2
select OBJECT_ID,owner,OBJECT_NAME,OBJECT_TYPE from dba_objects where OBJECT_ID in (107264);
select XIDUSN,XIDSLOT,XIDSQN from V$TRANSACTION where XIDSQN=2452;

 

2*2^16+28= 13100 即正好通过XIDUSN*2^16+XIDSLOT=ID1(TX)

select …for update 会有一个TM表行级排他锁,TX的独占锁。 

 

4.2 delete 锁查看

 

 

 

 同样的也有一个TM表行级排他锁,TX的独占锁。 

 

4.3 update锁查看

 

 

 

  同样的也有一个TM表行级排他锁,TX的独占锁。 

4.2 insert 锁查看

 

 

 

 可以发现的是,除了单纯的select没有锁,其余的包括for update DML操作,都是惊人的TM表行级排他锁,TX的独占锁。

 

posted @ 2021-08-04 11:58  翰墨文海  阅读(965)  评论(0编辑  收藏  举报