Oracle数据库锁机制概述
Oracle数据库锁机制概述
锁介绍
锁是一种机制,用来防止多个共同访问共享数据的事务之间的破坏性交互,包括不正确地更新数据或不正确地更改基础数据结构。
锁在维护数据库并发性和一致性当中扮演着一个关键的角色。
锁定行为总结
1、数据库维护几种不同类型的锁,这取决于获取锁的操作。
2、一般数据库使用两种类型的锁: 独占锁和共享锁。
3、在一个资源(如行或表)上,只能获得一个独占锁,但在单个资源上可以获得很多共享锁。
锁行为规则
锁会影响读取者与写入者的交互。读取者是一个对某种资源的查询语句,而写入者是一个对某个资源的修改语句。
Oracle 数据库中读取者和写入者的锁定行为规则如下:
1、一行只有在被某个写入者修改时,才被锁定。
当一个语句更新某行时,事务只需要获取在该行上的锁。通过在行级别锁定表数据,数据库最小化对同一数据的争用。在正常情况下,数据库不会将行锁升级到块级或表级。
2、一行的写入者,会阻塞在同一行上的并发写入者。
如果一个事务正在修改某行,则行锁可防止不同的事务同时修改同一行。
3、一个读取者永远不会阻塞一个写入者。
由于行的读取者不会将它锁定,所以一个写入者可以修改该行。唯一的例外是 SELECT ... FOR UPDATE 语句,它是一种特殊类型的SELECT 语句,的确会锁定它正在读取的行。
4、一个写入者绝不会阻塞一个读取者。
当一个行正在被某个写入者更改时,数据库使用撤销数据向读取者提供一个该行的一致视图。
在某些非常特殊的情况下,在挂起的分布式事务中,数据读取者可能需要等待相同数据块上的写入者。
使用锁
在单用户数据库中,锁不是必需的,因为只有一个用户在修改信息。但是,当多个用户在访问和修改数据时,数据库必须提供一种方法,以防止对同一数据进行并发修改。
锁实现了以下重要的数据库需求:
1、一致性
一个会话正在查看或更改的数据不能被其它会话更改,直到用户会话结束。
2、完整性
数据和结构必须按正确的顺序反映对他们所做的所有更改。
Oracle 数据库通过其锁定机制,提供在多个事务之间的数据并发性、一致性、和完整性。锁定将自动执行,并且不需要用户操作。
对锁的需求,可以由对某个单一行的并发更新来说明。在下面的示例中,一个简单的基于 web 的应用程序,将某个雇员的电子邮件和电话号码呈现给最终用户。应用程序使用如下所示的 UPDATE 语句修改该数据:
UPDATE employees
SET email = ?, phone_number = ?
WHERE employee_id = ?
AND email = ?
AND phone_number = ?
在前面的 UPDATE 语句中, WHERE 子句中电子邮件和电话号码是某指定雇员的原始的、 未经修改的值。此更新可确保应用程序修改的行,在应用程序之前读取并向用户显示后,没有被更改过。以这种方式,应用程序可避免丢失更新的数据库问题,即其中一个用户覆盖了另一个用户所做的修改,
执行 SQL 语句时,Oracle 数据库自动获取所需的锁。例如,在数据库允许某个会话修改数据之前,该会话必须先锁定数据。锁给予该会话对数据的独占控制权,以便在释放该锁之前,任何其他事务都不可以修改被锁定的数据。
因为数据库的锁定机制与事务控制紧密地绑定在一起,应用程序设计人员只需要正确地定义事务,而数据库会自动管理锁定。虽然数据库也支持用户能够手动锁定数据,但用户从来就不需要显式锁定任何资源。
锁模式
Oracle 数据库自动使用最低适用的限制级别,来提供最高程度的数据并发,但还能提供非常安全的数据完整性。
限制级别越低、 则有更多的可用数据供其他用户访问。相反,限制级别越高,则其他事务为获取其所需的锁类型就将遭受更多的限制。
Oracle 数据库在多用户数据库中使用两种锁定模式:
独占锁模式
此模式可防止相关资源被共享。当一个事务修改数据时,获取一个独占锁。直到独占锁被释放之前,第一个以独占方式锁定资源的事务是唯一可以更改资源的事务。
共享锁模式
取决于所涉及的操作,此模式允许相关资源被共享。读取数据的多个用户可以共享数据,并持有共享锁,以防止企图获取独占锁的写入者并发访问。多个事务可以同时获取在同一资源上的共享锁。
假定一个事务使用 SELECT ... FOR UPDATE 语句选择一个单一表行。该事务获取一个独占行锁和行共享表锁。行锁允许其他会话修改除锁定行之外的任何行,而表锁可防止其它会话更改表结构。这样,数据库允许尽可能多的语句得以执行。
锁转换和锁升级
Oracle 数据库在必要时执行锁转换。在锁转换中,数据库自动将较低限制的表锁转换为较高限制的其它锁定。
例如,假设事务对某雇员发出 SELECT ... FOR UPDATE,并之后更新了该锁定行。在这种情况下,数据库会自动将行共享表锁转换为行独占表锁。一个事务在该事务中所有执行插入、 更新、或删除的行上持有行独占锁。因为行锁是在最高程度限制下获得的,因此不要求锁转换,也不执行锁转换。
锁转换不同于锁升级,锁升级发生在当某个粒度级别持有许多锁(例如行) ,数据库将其提高到更高粒度级别(例如表)。如果一个用户锁定了一个表中的许多行,则某些数据库自动将行锁升级到单个表锁。锁的数量减少了,但被锁定的东西却增加了。
Oracle 数据库永远不会升级锁。锁升级极大地增加了死锁的可能性。假定一个系统尝试升级事务 1 中的锁,但因为事务 2 持有该锁,故不能成功。如果事务 2 在它可以继续操作之前也需要在相同的数据上进行锁升级,则将发生一个死锁。
锁持续时间
当某些事件发生,使事务不再需要资源时,Oracle 数据库会自动释放锁。
在大多数的情况下,数据库持有语句所获取的锁,直至该语句所在事务的整个持续期间结束。
这些锁可以防止破坏性干扰,如多个并发事务中的脏读、更新丢失、和破坏性 DDL。
注意:
因为未索引外键而在子表上发生的表锁,会在语句持续期间持有,而不是在事务持续期间。此外, DBMS_LOCK 包使得用户定义的锁可以按你的意志被释放或分配,甚至跨越事务边界而持有。
当 Oracle 数据库提交或回滚事务时,会释放事务内的语句获取的所有锁。
当 Oracle 数据库回滚至保存点时,也会释放该保存点之后所获取的锁。但是,只有不在等待以前被锁定的资源的事务,可以获取现有可用资源上的锁。等待中的事务会继续等待,直到原始事务提交或完全回滚。
锁和死锁
死锁情况发生在两个或多个用户都在等待被对方锁定的数据时。死锁会阻止某些事务继续工作。
Oracle 数据库自动检测死锁,并通过回滚死锁中的一个语句以释放其冲突行锁,来解决死锁。数据库向遭遇语句级回滚的事务返回一条相应的消息。被回滚语句属于检测到死锁的事务。通常,收到通知的事务应该明确回滚,但它可以等待一会之后重试被回滚的语句。
当事务显式覆盖 Oracle 数据库的默认锁定时,最经常发生死锁。由于数据库不会升级锁,也不对查询使用读锁定,而是使用行级 (而不是页级别)锁定,所以很少会出现死锁。
自动锁的概述
Oracle 数据库会为事务自动锁定资源,以防止其他事务进行某些需要独占访问同一资源的操作。
数据库在不同的限制级别自动获取不同类型的锁,这依赖于不同的资源和正在执行的操作。
注意:
执行简单读取时,数据库绝不会锁定行。
锁分类
锁 描述
DML锁 保护数据。例如,表锁锁定整个表,而行锁锁定所选的行。
DDL锁 保护模式对象的结构 ——例如,表和视图的数据字典定义。
系统锁 保护内部数据库结构,如数据文件。闩锁、 互斥体、和内部锁是完全自动的。
DML锁
DML锁,也称为数据锁,确保由多个用户并发访问的数据的完整性。例如, DML 锁可防止两个客户从一个在线书店购买某一本书所剩的最后一个拷贝。DML 锁也可以防止多个相互冲突的 DML 或 DDL 操作产生破坏性干扰。
DML 语句自动获取下列类型的锁:
1、行锁 (TX) ,TX 是 Transaction(事务)的缩写
2、表锁 (TM)
行锁(TX)
行锁,也称为 TX 锁,是一个表中单个行上的锁。一个事务在被 INSERT、UPDATE、DELETE、MERGE、或 SELECT ... FOR UPDATE 等语句所修改的每一行上获取一个行锁。
行锁一直存在直到事务提交或回滚。
行锁主要作为一种排队的机制,以防止两个事务修改相同的行。数据库始终以独占模式锁定修改的行,以便其他事务不能修改该行,直到持有锁的事务提交或回滚。行锁定提供了近乎最细粒度的锁定,并因此提供了近乎最佳的并发性和吞吐量。
注意:
如果一个事务因为数据库实例失效而终止,会先进行块级恢复以使行可用,之后进行整个事务恢复。
如果一个事务在某行上获取了一个锁,则该事务也将在包含该行的表上获取一个锁。表锁可防止冲突性的 DDL 操作在当前事务中会覆盖数据更改。
下图演示了在一个表中第三行上的更新。Oracle 数据库将自动在更新行上置一个独占锁,且在表上置一个次独占锁。

行锁和并发
假设3 个会话同时查询相同的行。会话 2 和会话 3 对不同的行进行未提交更新,而会话 3 没有做任何更新。每个会话可以看到自己的未提交更新,但看不到其他会话中所做的任何未提交更新。由于Oracle只在每个修改的行上获取行锁,所以每个会话之间不存在锁冲突,使得整体并发性最高。
行锁存储
与某些数据库使用锁管理器在内存中维护一个锁列表不同,Oracle 数据库将锁信息存储在包含锁定行的数据块中。
数据库使用排队机制来获取行锁。如果事务需要为某个未锁定的行获取一个锁,则事务将在数据块上置一个锁。被此事务修改的每个行指向存储在块头中的事务 ID 的一个副本(可以理解为事务 ITL )。
当事务结束时,事务 ID 仍然保留在块头中。如果一个不同的事务想要修改某行,则它使用该事务 ID 来确定锁是否处于活动状态。如果锁是活动的,则会话要求该锁被释放时得到通知。否则,事务获取该锁。
行锁获取和实现
前提知识:Oracle数据库将行锁与事务紧密绑定
1、当事务第一次修改某行时,它会在数据块的 Interested Transaction List(ITL)中记录自己的事务 ID,并获取一个 TX 锁(即事务锁)。这个 TX 锁代表了整个事务对数据修改的权利。
2、该事务随后修改的每一行,都通过指向 ITL 中的相同事务槽位来表示“该行被此事务锁定”。所有行锁都共享同一个 TX 锁,而不是每行拥有独立的锁结构。
3、其他事务想要修改被锁定的行时,会检查该行指向的 TX 锁是否仍处于活动状态。因此,判断行锁是否存在的实质,就是检查对应的 TX 锁是否活跃。
表锁(TM)
表锁,也称为 TM 锁,当一个表被 INSERT、UPDATE、DELETE、MERGE、带 FOR UPDATE 子句的 SELECT 等修改时,由相关事务获取该锁。
DML 操作需要表锁来为事务保护 DML 对表的访问,并防止可能与事务冲突的 DDL 操作。
表锁模式
行共享 (RS)--row share
这种锁也被称为子共享表锁 (SS) ,表示在表上持有锁的事务在表中有被锁定的行,并打算更新它们。行共享锁是限制最少的表级锁模式,提供在表上最高程度的并发性。
行独占表锁 (RX) --row exclusive
这种锁也被称为子独占表锁(SX) ,通常表示持有锁的事务已更新了表行或发出了 SELECT ... FOR UPDATE。
一个 RX 锁允许其他事务并发地查询、 插入、 更新、 删除、或锁定在同一个表中的其它行。因此,RX 锁允许多个事务对同一个表同时获得 SX 和子共享表锁。
默认数据库DML操作时,获取这种类型的表锁
共享表锁(S)--share
由某个事务拥有的共享表锁允许其他事务查询(而不使用SELECT ... FOR UPDATE),但是更新操作只能在仅有单个事务持有共享表锁时才允许。因为可能有多个事务同时持有共享表锁,所以持有此锁不足以确保一个事务可以修改该表。
这种锁也称为共享子独占表锁(SSX), 比共享表锁的限制性更强。一次只能有一个事务可以获取给定的表上的 SSX 锁。由某个事务拥有的 SSX 锁允许其他事务查询该表 (除 SELECT ... FOR UPDATE)但不能更新该表。
独占表锁(X)--exclusive
这种锁是最严格的锁,禁止其他事务执行任何类型的 DML 语句,或在表上放置任何类型的锁。
DDL 锁
当某个运行中的 DDL 操作正在操作或引用某模式对象时,数据字典(DDL)锁保护该模式对象的定义。
在 DDL 操作的过程中,只有被修改或引用的单个模式对象被锁定。数据库绝不会锁定整个数据字典。
Oracle 数据库将为任何要求锁的 DDL 事务自动获取 DDL 锁。用户不能显式请求 DDL 锁。例如,如果用户创建一个存储过程,则数据库自动为过程定义中引用的所有模式对象获取 DDL 锁。DDL 锁防止在过程编译完成之前,这些对象被更改或删除。
独占 DDL 锁
独占 DDL 锁可防止其他会话获取 DDL 或 DML 锁。除了那些在"共享 DDL锁"中所述操作之外,绝大多数 DDL 操作需要对资源获取独占锁,以防止和其他可能会修改或引用相同模式对象的 DDL 之间的破坏性干扰。例如,当ALTER TABLE 正在将一列添加到表时,不允许 DROP TABLE 删除表,反之亦然。
独占 DDL 锁在整个 DDL 语句执行期间一直持续,并自动提交。在独占 DDL锁获取过程中,如果另一个操作在该模式对象上持有另一个 DDL 锁,则这个锁获取将一直等待,直到前一个 DDL 锁被释放,才能继续。
共享 DDL 锁
在资源上的共享 DDL 锁可防止与冲突的 DDL 操作发生破坏性干扰,但允许类似的 DDL 操作的数据并发。
例如,当 CREATE PROCEDURE 语句运行时,所在事务将为所有被引用的表获取共享 DDL 锁。其他事务可以同时创建引用相同表的过程,并在相同的表上同时获得共享 DDL 锁,但没有任何事务能在任何被引用表上获取独占 DDL 锁。
共享 DDL 锁在整个 DDL 语句执行期间持续存在,并自动提交。因此,持有一个共享 DDL 锁的事务,可保证在事务过程中,被引用模式对象的定义保持不变。
可中断的解析锁
SQL 语句或 PL/SQL 程序单元,为每个被其引用的模式对象持有一个解析锁。
获取解析锁的目的是,如果被引用的对象被更改或删除,可以使相关联的共享 SQL 区无效。
解析锁被称为可中断的解析锁,因为它并不禁止任何DDL 操作,并可以被打破以允许冲突的 DDL 操作。
解析锁是在执行 SQL 语句的分析阶段,在共享池中获取的。只要该语句的共享 SQL 区仍保留在共享池中,该锁就一直被持有。
系统锁
Oracle 数据库使用各种类型的系统锁,来保护数据库内部和内存结构。由于用户不能控制其何时发生或持续多久,这些机制对于用户几乎是不可访问的。
闩锁(Latches)
闩锁是简单、 低级别的串行化机制,用于协调对共享数据结构、 对象、和文件的多用户访问。闩锁防止共享内存资源被多个进程访问时遭到破坏。具体而言,闩锁在以下情况下保护数据结构:
1、被多个会话同时修改
2、正在被一个会话读取时,又被另一个会话修改
3、正在被访问时,其内存被释放(换出)
通常,一个单一的闩锁保护 SGA 中的多个对象。例如,后台进程(如DBWn 和 LGWR)从共享池分配内存来创建数据结构。为分配此内存,这些进程使用共享池闩锁来串行化对内存的访问,以防止两个进程同时尝试检查或修改共享池。内存分配后,其他进程可能需要访问共享池区域,如用于解析所需的库高速缓存。在这种情况下,进程只在库缓存获取闩锁,而不是在整个共享池。
闩锁获取
与行锁之类的入队闩锁不同,闩锁不允许会话排队。
当闩锁可用时,请求闩锁的第一个会话将获得它的独占访问权限。
闩锁旋转发生在当一个进程不断地循环来请求一个闩锁时,而闩锁睡眠发生在重新发起闩锁请求之前,释放CPU 时。
通常,一个 Oracle 进程在操作或查看一种数据结构时,只需在一个极短的时间内获得闩锁。例如,仅仅为某一名员工处理工资更新,数据库就可能需要获取并释放成千上万个闩锁。
闩锁的实现依赖于操作系统,特别是在一个进程是否会在闩锁上等待以及会在闩锁等待多长时间方面。
闩锁的增加意味着并发的降低。例如,过度硬解析操作会产生库缓存闩锁争用。
V$LATCH 视图包含每个闩锁的详细使用情况的统计信息,包括每个闩锁被请求和被等待的次数。
互斥体(Mutexes )
互斥对象 (mutex) 是一种底层机制,用于防止在内存中的对象在被多个并发进程访问时,被换出内存或遭到破坏。
互斥体类似于闩锁,但闩锁通常保护一组对象,而互斥体通常保护单个对象。
互斥体提供以下几个优点:
1、互斥体可以减少发生争用的可能性。
2、由于闩锁保护多个对象,当多个进程试图同时访问这些对象的任何一个时,它可能成为一个瓶颈。而互斥体仅仅串行化对单个对象的访问,而不是一组对象,因此互斥体提高了可用性。
3、互斥体比闩锁消耗更少的内存。
4、在共享模式下,互斥体允许被多个会话并发引用。
内部锁
内部锁是比闩锁和互斥体更高级、 更复杂的机制,并用于各种目的。
数据库使用以下类型的内部锁:
字典缓存锁
这些锁的持续时间很短,当字典缓存中的条目正在被修改或使用时被持有。他们保证正在被解析的语句不会看到不一致的对象定义。字典缓存锁可以是共享的或独占的。共享锁在解析完成后被释放,而独占锁在 DDL 操作完成时释放。
文件和日志管理锁
这些锁保护各种文件。例如,一种内部锁保护控制文件,以便一次只有一个进程可以对其进行更改。而另一种锁用于协调联机重做日志文件的使用和归档。数据文件被锁定,确保数据库被多个实例以共享模式装载,或以独占模式被单个实例装载。
因为文件和日志锁表示文件的状态,这些锁必要时会被持有较长一段时间。
表空间和撤销段锁
这些锁保护的表空间和撤销段。
例如,访问数据库的所有实例对一个表空间是否处于联机或脱机必须保持一致。
撤销段被锁定,以便只能有一个数据库实例可以写入该段。
手动数据锁概述
Oracle 数据库自动执行锁定,以确保数据并发性、数据完整性、和语句级读取一致性。但是,也可以手动覆盖 Oracle 数据库的默认锁定机制。
覆盖默认锁定在以下情况下很有用:
1、应用程序需要事务级读取一致性或可重复读取。
在这种情况下,查询在整个事务持续期间必须产生一致的数据,但不反映其他事务所做的更改。通过使用显式锁定、 只读事务、 可串行化事务、或覆盖默认锁定,可以实现事务级别的读取一致性。
2、应用程序需要事务对资源具有独占访问权限,以便该事务不必等待其他事务完成就可以继续。
可以在会话级或事务级覆盖数据库的自动锁定。
在会话级,会话可以使用ALTER SESSION 语句设置需要的事务隔离级别。
在事务级别,包括以下 SQL 语句的事务,会覆盖数据库的默认锁定:
1、SET TRANSACTION ISOLATION LEVEL 语句
2、LOCK TABLE 语句 (锁定一张表,或与视图一起使用时则锁定其基表)
3、SELECT ... FOR UPDATE 语句
由上述语句获取的锁在事务结束后被释放,或回滚到保存点后释放。
如果在任何级别覆盖了 Oracle 数据库的默认锁定,则数据库管理员或应用程序开发人员应确保重写锁定过程能正常运行。
锁定过程必须满足以下标准:可以保证数据完整性、可以接受的数据并发性、死锁不会发生(若发生,能被适当地处理)。
用户定义的锁的概述
使用 Oracle 数据库锁定管理服务,可以为特定应用程序定义自己的锁。例如,可以创建一个锁,来串行化到文件系统上的一个消息日志的访问。
保留的用户锁与 Oracle 数据库锁一样,它具有包括死锁检测在内的 Oracle 数据库锁的所有功能。
用户锁永远不会与数据库锁发生冲突,因为他们用前缀 UL 标识。
用户定义锁的实现方式:
通过 DBMS_LOCK 包中的过程来使用 Oracle 数据库的锁定管理服务。
可以在 PL/SQL 块中包含如下语句:
1、请求一个特定类型的锁
2、给锁起一个唯一的名称,以与(同一实例或不同实例中)另一个过程中的某个锁相区别
3、更改锁类型
4、释放锁
锁和外键
Oracle 数据库将与外键相关的父键的并发控制最大化。
锁定行为将取决于外键列是否已被索引。如果外键未被索引,则子表将更可能被频繁锁定,或发生死锁,并降低并发性。为此,外键几乎总是应该被索引的。唯一的例外是当匹配的唯一键或主键永远不会被更新或删除。
锁定和未索引外键
当下列两个条件成立时,数据库在子表上获取一个全表锁定:
1、在子表的外键列上没有索引存在。
2、会话修改了父表中的主键 (例如,删除了行或修改了主键属性)或将行合并到父表。在父表中插入行不获取子表表锁。
假设 hr.departments 表是 hr.employees 表的父表,hr.employees 表包含未索引的外键 department_id。
下图显示了一个修改 departments 表中的部门 60 的主键属性的会话。

在图 中,数据库在部门 60 的主键修改过程中,在 employees 表上获取了一个全表锁定。此锁使其他会话可以查询 employees 表,但不能更新employees 表。例如,雇员的电话号码不能更新。一旦 departments 表上的主键修改完成后,employees 表上的表锁立即释放。如果在部门中有多个行被修改主键,则在 departments 表中每修改一行,就需要在employees 表上获取并释放一次表锁。
注意:
在子表上的 DML 不必获取父表中的一个表锁。
锁和索引外键
当下面两个条件成立时,数据库不会获取子表上的全表锁定:
1、子表中的外键列已创建索引。
2、会话修改了父表中的主键 (例如,删除了行或修改了主键属性)或将行合并到父表。
父表上的锁可以防止其它事务获取独占表锁,但在主键修改过程中不会阻止父表或子表上的 DML。主键修改发生时,我们仍可以修改子表,这种情形正是我们想要的。
下图显示一个具有索引列 department_id 的子表 employees。事务从 departments 表中删除部门 280。此删除操作不会导致数据库在employees 表上获取全表锁定。

如果在子表指定了 ON DELETE CASCADE 选项,则从父表删除记录会导致子表中删除相应记录。例如,删除部门 280 会导致从 employees 表中删属于被删除部门的雇员。在这种情况下,等待和锁定规则等同于您先从父表中删除行,然后从子表删除相应行。
浙公网安备 33010602011771号