oracle数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库一致性。加锁是实现数据库并发控制的一个非常重要的技术,事务隔离级别也是基于锁实现的。
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上X锁时,其他的事务不能对它读取和修改(也就是任何事务都不能再对当前数据对象加任何类型的锁)。加了S锁的数据对象可以被其它事务读取,但不能修改(表示其它事务只能对当前数据加S锁,而不能加X锁)。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
oracle数据库的锁类型
根据保护的对象不同,oracle数据库锁可以分为以下几大类:DML锁(data locks,数据锁),用于保护数据的完整性;DDL锁(dictionary locks,字典锁),用于保护数据库对象的结构,如表、索引等的结构定义;内部锁和闩(internal locks and latches),保护数据库的内部结构。DML锁的目的在于保证并发情况下的数据完整性。在oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。
TX锁(行级锁、事务锁)
许多对oracle不太了解的技术人员可能会以为每个TX锁代表一条被封锁的数据行,其实不然。TX的本义是Transaction(事务),当一个事务第一次执行数据更改(Insert、Update、Delete)或使用SELECT ... FOR UPDATE语句进行查询时,它即获得一个TX(事务)锁,直至该事务结束(执行COMMIT或ROLLBACK操作)时,该锁才被释放。所以,一个TX锁,可以对应多个被该事务锁定的数据行。
在oracle的每行数据上,都有一个标志位来表示该行数据是否被锁定。数据行上锁标志一旦被置位,就表明该行数据被加X锁,oracle在数据行上没有S锁。
TM锁(表级锁)
TM锁(表级锁)类型共有5种:共享锁(S锁)、排它锁(X锁)、行级共享锁(RS锁)、行级排它锁(RX)、共享行级排它锁(SRX锁)。下表为oracle数据库TM锁的相容矩阵(Y=YES,N=NO):
T1/T2 | S | X | RS | RX | SRX | - |
S | Y | N | Y | N | N | Y |
X | N | N | N | N | N | Y |
RS | Y | N | Y | Y | Y | Y |
RX | N | N | Y | Y | N | Y |
SRX | N | N | Y | N | N | Y |
- | Y | Y | Y | Y | Y | Y |
当oracle执行SELECT ... FROM UPDATE、INSERT、UPDATE、DELETE等DML语句时,系统自动在所要操作的表上申请表级RX锁(注:SELECT ... FROM UPDATE在oracle 9i的早期版本中还是RS锁,之后就是RX锁了)。当表级锁获得后,系统再自动申请TX锁,并将实际锁定的数据行的锁标志位置位(指向该TX锁)。另一方面,程序或操作人员也可以通过LOCK TABLE语句来指定获得某种类型的TM锁。下表总结了oracle中各SQL语句产生TM锁的情况:
SQL语句 | 表锁模式 | 允许的锁模式 |
select * from table_name... | 无 | RS、RX、S、SRX、X |
insert into table_name ... | RX | RS、RX |
update table_name ... | RX | RS、RX |
delete from table_name ... | RX | RS、RX |
select * from table_name for update | RX | RS、RX |
lock table table_name in row share mode | RS | RS、RX、S、SRX |
lock table table_name in row exclsive mode | RX | RS、RX |
lock table table_name in share mode | S | RS、S |
lock table table_name in share row exclusive mode | SRX | RS |
lock table table_name in exclusive mode | X | 无 |
注意:在缺省情况下,单纯地读数据(SELECT)并不加锁。另外,为什么SELECT FOR UPDATE、INSERT、UPDATE、DELETE,这四个语句为什么能相互阻塞?因为在它们获得RX表级锁之后,又申请了TX锁,也就是行级锁,行级锁只支持X锁,是个排它锁,所以阻塞了其它的事务。
悲观锁与乐观锁
为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定,也就是我们上面讲的锁机制。数据的锁定分为两种:第一种叫悲观锁,第二种叫乐观锁。悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定了。而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。
oracle使用悲观锁,从SQL语句上来看,就是使用 for update,for update nowait。
oracle使用乐观锁,可以通过增加一列version或(timestamp)在应用程序中实现。当提交一条数据时,比较内存中数据和数据库中当前数据version或timestamp是否相同。如果不同,那么说明有冲突,不能提交,否则安全提交。另外,Oracle 10g 特性 ORA_ROWSCN,它好像也支持乐观锁。