Oracle 锁机制
数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作。
在数据库中有两种基本的锁类型:排它锁( Exclusive Locks ,即 X 锁)和共享锁( Share Locks ,即 S 锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
Oracle 数据库的锁类型
根据保护的对象不同, Oracle 数据库锁可以分为以下几大类: DML 锁( data locks ,数据锁),用于保护数据的完整性; DDL 锁( dictionary locks ,字典锁),用于保护数据库对象的结构,如表、索引等的结构定义;内部锁和闩( internal locks and latches ),保护 数据库的内部结构。
DML 锁的目的在于保证并发情况下的数据完整性,。在 Oracle 数据库中, DML 锁主要包括 TM 锁和 TX 锁,其中 TM 锁称为表级锁, TX 锁称为事务锁或行级锁。
当 Oracle 执行 DML 语句时,系统自动在所要操作的表上申请 TM 类型的锁。当 TM 锁获得后,系统再自动申请 TX 类型的锁,并将实际锁定的数据行的锁标志位进行置位。这样在事务加锁前检查 TX 锁相容性时就不用再逐行检查锁标志,而只需检查 TM 锁模式的相容性即可,大大提高了系统的效率。 TM 锁包括了 SS 、 SX 、 S 、 X 等多种模式,在数据库中用 0 - 6 来表示。不同的 SQL 操作产生不同类型的 TM 锁。
在数据行上只有 X 锁(排他锁)。在 Oracle 数据库中,当一个事务首次发起一个 DML 语句时就获得一个 TX 锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行 DML 语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后, TX 锁被释放,其他会话才可以加锁。
当 Oracle 数据库发生 TX 锁等待时,如果不及时处理常常会引起 Oracle 数据库挂起,或导致死锁的发生,产生 ORA-60 的错误。这些现象都会对实际应用产生极大的危害,如长时间未响应,大量事务失败等。
悲观封锁和乐观封锁
一、悲观封锁
锁在用户修改之前就发挥作用:
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.
二、乐观封锁
乐观的认为数据在 select 出来到 update 进取并提交的这段时间数据不会被更改。这里面有一种潜在的危险就是由于被选出的结果集并没有被锁定,是存在一种可能被其他用户更改的可能。因此 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.
死锁 -deadlock
定义 : 当两个用户希望持有对方的资源时就会发生死锁 .
即两个用户互相等待对方释放资源时 ,oracle 认定为产生了死锁 , 在这种情况下 , 将以牺牲一个用户作为代价 , 另一个用户继续执行 , 牺牲的用户的事务将回滚 .
例子:
1 :用户 1 对 A 表进行 Update ,没有提交。
2 :用户 2 对 B 表进行 Update ,没有提交。
此时双反不存在资源共享的问题。
3 :如果用户 2 此时对 A 表作 update, 则会发生阻塞,需要等到用户一的事物结束。
4 :如果此时用户 1 又对 B 表作 update ,则产生死锁。此时 Oracle 会选择其中一个用户进行会滚,使另一个用户继续执行操作。
起因 :
Oracle 的死锁问题实际上很少见,如果发生,基本上都是不正确的程序设计造成的,经过调整后,基本上都会避免死锁的发生。
DML 锁分类表
表 1 Oracle 的 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(S/Row-X) |
共享行级排它锁 |
Lock share row exclusive |
6 |
X(Exclusive) |
排它锁 |
Alter table 、 Drop able 、 Drop index 、 Truncate table 、 Lock exclusive |
设立封锁机制主要是为了对并发操作进行控制,对干扰进行封锁,保证数据的一致性和准确性。 Oracle 数据库封锁方式有三种:共享封锁,独占封锁,共享更新封锁
封锁类型
Oracle RDBMS 的封锁类型可分为如下三类:
1、内部级封锁
内部级封锁是用于保护 ORACLE 内部结构,由系统内部实现,用户不能访问,因此我们不必对此做过多的了解。
2、 DDL 级封锁(字典 / 语法分析封锁)
DDL 级封锁也是由 ORACLE RDBMS 来控制,它用于保护数据字典和数据定义改变时的一致性和完整性。它是系统在对 SQL 定义语句作语法分析时自动地加锁,无需用户干予。字典 / 语法分析封锁共分三类:
(1)、字典操作锁:用于对字典操作时,锁住数据字典,此封锁是独占的,从而保护任何一个时刻仅能对一个字典操作。
(2)、字典定义锁:用于防止在进行字典操作时又进行语法分析,这样可以避免在查询字典的同时改动某个表的结构。
(3)、表定义锁:用于 一个 SQL 语句正当访问某个表时,防止字典中与该表有关的项目被修改。
3、 DML 级封锁
DML 级封锁用于控制并发事务中的数据操纵,保证数据的一致性和完整性,其封锁对象可以是表或行。
对用户的数据操纵, Oracle 可以自动为操纵的数据进行封锁,但如果有操纵授权,则为满足并发操纵的需要另外实施封锁。 DML 封锁可由一个用户进程以显式的方式加锁,也可通过某些 SQL 语句隐含方式实现。
DML 锁有如下三种封锁方式:
( 1 )、共享封锁方式( SHARE )
( 2 )、独占封锁方式( EXCLUSIVE )
( 3 )、共享更新封锁( SHARE UPDATE )
其中 SHARE , EXCLUSIVE 用于表封锁, SHARE UPDATE 用于行封锁。
1 、共享方式的表封锁
共享方式的表封锁是对表中的所有数据进行封锁,该锁用于保护查询数据的一致性,防止其它用户对已封锁的表进行更更新。其它用户只能对该表再施加共享方式的锁,而不能再对该表施加独占方式的封锁,共享更新锁可以再施加,但不允许持有共享更新封锁的进程做更新。共享该表的所有用户只能查询表中的数据,但不能更新。共享方式的表封锁只能由用户用 SQL 语句来设置,基语句格式如下:
[quote:04b72348bd]LOCK TABLE < 表名 >[,< 表名 >]...
IN SHARE MODE [NOWAIT]
[/quote:04b72348bd]
执行该语句,对一个或多个表施加共享方式的表封锁。当指定了选择项 NOWAIT ,若该封锁暂时不能施加成功,则返回并由用户决定是进行等待,还是先去执行别的语句。
持有共享锁的事务,在出现如下之一的条件时,便释放其共享锁:
A 、执行 COMMIT 或 ROLLBACK 语句。
B 、退出数据库( LOG OFF )。
C 、程序停止运行。
共享方式表封锁常用于一致性查询过程,即在查询数据期间表中的数据不发生改变。
2 、独占方式表封锁
独占方式表封锁是用于封锁表中的所有数据,拥有该独占方式表封锁的用户,即可以查询该表,又可以更新该表,其它的用户不能再对该表施加任何封锁(包括共享、独占或共享更新封锁)。其它用户虽然不能更新该表,但可以查询该表。
独占方式的表封锁可通过如下的 SQL 语句来显示地获得:
LOCK TABLE < 表名 >[,< 表名 >]....
IN EXCLUSIVE MODE [NOWAIT]
独占方式的表封锁也可以在用户执行 DML 语句 INSERT 、 UPDATE 、 DELETE 时隐含获得。
拥有独占方式表封锁的事务,在出现如下条件之一时,便释放该封锁:
( 1 )、执行 COMMIT 或 ROLLBACK 语句。
( 2 )、退出数据库( LOG OFF )
( 3 )、程序停止运行。
独占方式封锁通常用于更新数据,当某个更新事务涉及多个表时,可减少发生死锁。
DML 锁有如下三种封锁方式:
( 1 )、共享封锁方式( SHARE )
( 2 )、独占封锁方式( EXCLUSIVE )
( 3 )、共享更新封锁( SHARE UPDATE )
其中 SHARE , EXCLUSIVE 用于表封锁, SHARE UPDATE 用于行封锁。
1 、共享方式的表封锁
共享方式的表封锁是对表中的所有数据进行封锁,该锁用于保护查询数据的一致性,防止其它用户对已封锁的表进行更更新。其它用户只能对该表再施加共享方式的锁,而不能再对该表施加独占方式的封锁,共享更新锁可以再施加,但不允许持有共享更新封锁的进程做更新。共享该表的所有用户只能查询表中的数据,但不能更新。共享方式的表封锁只能由用户用 SQL 语句来设置,基语句格式如下:
[quote:04b72348bd]LOCK TABLE < 表名 >[,< 表名 >]...
IN SHARE MODE [NOWAIT]
[/quote:04b72348bd]
执行该语句,对一个或多个表施加共享方式的表封锁。当指定了选择项 NOWAIT ,若该封锁暂时不能施加成功,则返回并由用户决定是进行等待,还是先去执行别的语句。
持有共享锁的事务,在出现如下之一的条件时,便释放其共享锁:
A 、执行 COMMIT 或 ROLLBACK 语句。
B 、退出数据库( LOG OFF )。
C 、程序停止运行。
共享方式表封锁常用于一致性查询过程,即在查询数据期间表中的数据不发生改变。
2 、独占方式表封锁
独占方式表封锁是用于封锁表中的所有数据,拥有该独占方式表封锁的用户,即可以查询该表,又可以更新该表,其它的用户不能再对该表施加任何封锁(包括共享、独占或共享更新封锁)。其它用户虽然不能更新该表,但可以查询该表。
独占方式的表封锁可通过如下的 SQL 语句来显示地获得:
LOCK TABLE < 表名 >[,< 表名 >]....
IN EXCLUSIVE MODE [NOWAIT]
独占方式的表封锁也可以在用户执行 DML 语句 INSERT 、 UPDATE 、 DELETE 时隐含获得。
拥有独占方式表封锁的事务,在出现如下条件之一时,便释放该封锁: <BR> ( 1 )、执行 COMMIT 或 ROLLBACK 语句。
( 2 )、退出数据库( LOG OFF )
( 3 )、程序停止运行。
独占方式封锁通常用于更新数据,当某个更新事务涉及多个表时,可减少发生死锁。
3 、共享更新封锁方式
共享更新封锁是对一个表的一行或多行进行封锁,因而也称作行级封锁。表级封锁虽然保证了数据的一致性,但却减弱了操作数据的并行性。行级封锁确保在用户取得被更新的行到该行进行更新这段时间内不被其它用户所修改。因而行级锁即可保证数据的一致性又能提高数据操作的迸发性。
可通过如下的两种方式来获得行级封锁:
( 1 )、执行如下的 SQL 封锁语句,以显示的方式获得:
LOCK TABLE < 表名 >[,< 表名 >]....
IN SHARE UPDATE MODE [NOWAIT]
( 2 )、用如下的 SELECT ...FOR UPDATE 语句获得:
SELECT < 列名 >[,< 列名 >]...
FROM < 表名 >
WHERE < 条件 >
FOR UPDATE OF < 列名 >[,< 列名 >].....[NOWAIT]
一旦用户对某个行施加了行级封锁,则该用户可以查询也可以更新被封锁的数据行,其它用户只能查询但不能更新被封锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式封锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。
当出现如下之一的条件,便释放共享更新锁:
(1)、执行提交( COMMIT )语句;
(2)、退出数据库( LOG OFF )
(3)、程序停止运行。
执行 ROLLBACK 操作不能释放行锁。
从上面讲述可见, ORACLE RDBMS 的加锁机制,解决了并发事务的相容与互斥问题。相容保证事务的并发性,互斥确保数据的一致性。不同用户锁的相容与互斥关系由下图给出。
其中最后一行最后一列为其它用户提供在不同行上设置 SHARE UPDATE 锁。但当用户1在某行上进行更新操作时,用户2只有等待用户1提交事务后,才能更新自己所封锁的行。
中最后一行最后一列为其它用户提供在不同行上设置 SHARE UPDATE 锁。但当用户1在某行上进行更新操作时,用户2只有等待用户1提交事务后,才能更新自己所封锁的行。
死锁
封锁虽然能够有效的解决并发操作,但是任何资源的独占都会有死锁的危险。例如:有两个事务 T1 , T2 , T1 对数据 A 施加独占封锁, T2 对数据 B 施加了独占封锁。再假设 T1 要对数据 B 加锁,由于 B 已被 T2 独占封锁,因此 T1 置于等待状态,等待 B 被释放;现在若 T2 也要对 A 进行封锁,由于 A 已被 T1 独占封锁,因此 T2 也被置于等待状态。这样就形成了两个事务相互等待的状态,而且永远不能结束,此种情况称为死锁。
在 Oracle 系统中能自动发现死锁,并选择代价最小的,即完成工作量最少的事务予以撤消,释放该事务所拥有的全部锁,记其它的事务继续工作下去。
从系统性能上考虑,应该尽可能减少资源竞争,增大吞吐量,因此用户在给并发操作加锁时,应注意以下几点:
1、对于 UPDATE 和 DELETE 操作,应只封锁要做改动的行,在完成修改后立即提交。
2、当多个事务正利用共享更新的方式进行更新,则不要使用共享封锁,而应采用共享更新封锁,这样其它用户就能使用行级锁,以增加并行性。
3、尽可能将对一个表的操作的并发事务施加共享更新锁,从而可提高并行性。
4、在应用负荷较高的期间,不宜对基础数据结构(表、索引、簇和视图)进行修改