本文结合示例详细的介绍下Oracle中锁的机制。
使用锁的目的是什么为了解决多用户环境下并发操作相同的资源而造成的错误修改数据的问题。单用户环境下不需要考虑锁,因为所有操作都是串行的。
要点锁的分类异常复杂,enqueue、latch、mutex等,都是为了解决并发存在的,自己也有些混乱,所以也不过多解释了。下面列举一些对于lock的要点内容。
排他锁:
不允许相关的资源被共享。一个资源在一个时间点内只有一个事务能够获取该资源的排他锁,只有持有该锁的事务能够修改相关的资源,
其他想要获取锁的事务只能等待该事务因为commit或者rollback而释放排他锁。
共享锁:
允许相关的资源被共享。也就是说允许多个事务同时持有某个资源的共享锁。
对于一个dml操作,会对表以及行加锁,也就是v$lock中的TM锁和TX锁。
行级锁基本原理:
行级锁的信息是置于数据块中的,如果要修改某一条记录的值,其实就是在访问相应的block,并且分配一个ITL,然后通过rowid访问
rowpiece header ,如果第二个字节lock byte(lock byte只占用1个字节,最大值为255,这也是为什么maxtrans最大为255)为0,则将其改为分配的ITL slot number。另外一个事务如果也想要修改数据,就会发现lock byte不为0,如果第一个事务还没有结束,则第二个事务进入enqueue等待,也就是transaction enqueue。
关于transaction enqueue有一个很有趣的例子,事务不一定是按照时间的先后顺序进行的。
具体地址在:
http://docs.oracle.com/cd/E11882_01/server.112/e25789/transact.htm#autoId12
对于Table lock来说可以分为以下几种类型:
1. Row Share (RS|SS)
2. Row Exclusive Table Lock (RX|SX)
3. Share Table Lock (S)
4. Share Row Exclusive Table Lock (SRX|SSX)
5. Exclusive Table Lock (X)
以下是v$lock.LMODE字段中的数字对应的锁类型
LMODE(Lockmode in which the session holds the lock):
0 -none
1 -null (NULL)
2 -row-S (SS)
3 -row-X (SX)
4 -share (S)
5 -S/Row-X (SSX)
6 -exclusive (X)
为了更好的开展下面的内容,这里列举一下各种TM锁类型的兼容情况。
详细验证情况会在4中给出。
表1
RS|SS
RX|SX
S
SRX|SSX
X
RS|SS
√
√
√
√
×
RX|SX
√
√
×
×
×
S
√
×
√
×
×
SRX|SSX
√
×
×
×
×
X
×
×
×
×
×
顺便引用一下经典内容:
只有被修改时,行才会被锁定。
当一条语句修改了一条记录,只有这条记录上被锁定,在Oracle数据库中不存在锁升级。
当某行被修改时,它将阻塞别人对它的修改。
当一个事务修改一行时,将在这个行上加上行锁(TX),用于阻止其它事务对相同行的修改。
读永远不会阻止写。
读不会阻塞写,但有唯一的一个例外,就是select ...for update。
写永远不会阻塞读。
当一行被修改后,Oracle通过回滚段提供给数据的一致性读
1.分别模拟insert,update和delete造成阻塞 一个更新语句的简单描述当我们更新一个表的记录的时候,会有两种锁产生,一种是DML锁(TM)也可以称作table lock 还有一种事务锁(TX)也可以称作行锁
在v$lock中可以查看到。
例如下面的例子当中:
_dexter@FAKE>desc tun2_tab
Name Null? Type
------------------------------------------------------------------------- --------------------------------------------
ID NUMBER(38)
_dexter@FAKE>update tun2_tab set id =2 ;
2 rowsupdated.
_dexter@FAKE>select sid , type , lmode , request , block
from v$lock
where sid =(select sid from v$mystat where rownuminsert /*+ append_values */ into tun2_tab values (1) ;
1 rowcreated.
_dexter@FAKE>select sid , type , lmode , request , block from v$lock where sid = (select sidfrom v$mystat where rownumupdate tun2_tab set id=3 ;
waiting...
看一下锁的情况:
_sys@FAKE>select sid , type , id1 , lmode , request , block
2 from v$lock l
3 where sid in (select session_id from v$locked_object)
4 and type in ('TM', 'TX')
5 order by 1 ;
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
22 TM 82618 6 0 1 --session1 包含了表6级锁,它正在阻塞其他的事务
22 TX 524296 6 0 0
24 TM 82618 0 3 0 --session2 它正在请求表的3级锁。
Session1
Session2
Description
T1
insert /*+ append_values */ into tun2_tab values (1) ;
直接路径加载会对表加6级排他锁
T2
update tun2_tab set id=3 ;
waiting…
update需要对表加3级共享锁,因为互斥,session2陷入阻塞状态
所以在直接路径加载的时候会对表加6级锁,阻塞其他事务对表加任意类型锁的操作。
(sqlldr 并行+直接路径加载的时候会加4级锁)
因为主键|唯一键引发的阻塞_dexter@FAKE>alter table tun2_tab add primary key (id) ;
Table altered.
session1 session_id=22:
_dexter@FAKE>insert into tun2_tab values (1) ;
1 rowcreated.
session2 session_id=24:
_dexter@FAKE>insert into tun2_tab values (1) ;
waiting...
lockstatus :
_sys@FAKE>select sid , type , id1 , lmode , request , block
2 from v$lock l
3 where sid in (select session_id from v$locked_object)
4 and type in ('TM', 'TX')
5 order by 1 ;
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
22 TM 82618 3 0 0
22 TX 196635 6 0 1
24 TX 65548 6 0 0
24 TM 82618 3 0 0
24 TX 196635 0 4 0
_sys@FAKE>select sid,seq#,event from v$session_wait where sid= 24 ;
SID SEQ# EVENT
-------------------- -----------------------------------------------
24 104 enq: TX - row lock contention
这里发生了row lock等待事件。
可以看到
因为在拥有primary key 列上插入了相同的值,第二个session除了持有自己本事务的6级排他锁之外,还在请求一个4级共享锁。这里发生了阻塞。如果第一个session 提交 。
第二个session会报错。
_dexter@FAKE>insert into tun2_tab values (1) ;
insert intotun2_tab values (1)
*
ERROR atline 1:
ORA-00001:unique constraint (DEXTER.SYS_C0014094) violated
Session1
Session2
Description
T1
insert into tun2_tab values (1) ;
session1插入数据这里涉及到了主键|唯一键
T2
insert into tun2_tab values (1) ;
waiting …
session2插入相同的记录,会发生阻塞,因为session1的操作是悬而未决的状态,session2中的事务能否执行取决于session1中的事务是回滚还是提交
T3
commit
session 1 中的事务提交
T4
Raise error:
ORA-00001: unique constraint (DEXTER.SYS_C0014094) violated
Update阻塞这一部分的阻塞比较简单,只要发生update操作,就会对已有的行加6级排他锁,表上加3级共享锁。
_dexter@FAKE>select * from tun2_tab ;
ID NAME
--------------------------------------------------
1 NN
2 NN
3 NN
session1 session_id=22:
_dexter@FAKE>update tun2_tab set name = 'DEXTER' where id=1 ;
1 rowupdated.
session2 session_id=18:
_dexter@FAKE>update tun2_tab set name ='dexter' where id=2 ;
1 rowupdated.
session3 session_id=9:
_dexter@FAKE> update tun2_tab set name ='dexter' where id=1;
waiting...
来看一下锁的情况:
_sys@FAKE>/
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
9 TX 589850 0 6 0
9 TM 82618 3 0 0
18 TX 196629 6 0 0
18 TM 82618 3 0 0
22 TX 589850 6 0 1 --session1正在阻塞 session 3
22 TM 82618 3 0 0
6 rowsselected.
由上可以看到,对单个表可以加多个3级共享锁。
session2因为修改的是id=2 的记录,所以可以正常执行。
session3由于修改的是id=1 的记录,session1这个时候正在修改,并且对这一行的资源加了6级的排他锁。所以session3 发生了阻塞
需要等待session 1 释放后才可以顺利执行。
Session1
Session2
Session3
Description
T1
update tun2_tab set name = 'DEXTER' where id=1 ;
session1 update操作会对表加3级共享锁
T2
update tun2_tab set name ='dexter' where id=2 ;
session2 update操作 也会对表加3级共享锁,由于更新的记录不包括ssession1中更新的记录id=1。所以可以顺利执行
T3
update tun2_tab set name ='dexter' where id=1 ;
waiting…
session3 update操作 也会对表加3级共享锁,由于更新的记录包括ssession1中更新的记录id=1。所以无法顺利执行
Delete阻塞其实对于delete、update、insert操作加锁操作大致相同,都会对表加3级共享锁,对修改的行加排他锁。
所以只要想要并发的修改表中相同的行,在第一个获取锁的事务没有结束前,后面的时候都会发生阻塞。
_dexter@FAKE>select * from tun2_tab ;
ID NAME
--------------------------------------------------
1 dexter
2 dexter
3 NN
session1 session_id=144 :
_dexter@FAKE>delete from tun2_tab where id =1 ;
1 rowdeleted.
session2 session_id=18 :
_dexter@FAKE>delete tun2_tab where id >1 ;
2 rowsdeleted.
session3 session_id=9 :
_dexter@FAKE>delete tun2_tab ;
waiting...
_sys@FAKE>/
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
9 TX 524317 0 6 0
9 TM 82618 3 0 0
18 TX 655383 6 0 0
18 TM 82618 3 0 0
144 TX 524317 6 0 1
144 TM 82618 3 0 0
6 rowsselected.
发生了阻塞,只有当session 1 和session 2 的事务结束后,session 3 才可以顺利完成。
Session1
Session2
Session3
Description
T1
delete from tun2_tab where id =1 ;
T2
delete tun2_tab where id >1 ;
session2 delete 操作因为不包括session 1 中的id=1的记录,所以可以顺利执行
T3
delete tun2_tab ;
waiting …
session3 delete操做,因为需要获取id=1,id>1记录的事务锁,所以发生了等待。可以看到它首先是在等待id=1的事务锁。
下面有两个有趣的实验 有趣小实验1_dexter@FAKE>select * from tun2_tab ;
ID NAME
--------------------------------------------------
1 dexter
2 dexter
3 NN
session1 session_id=22:
_dexter@FAKE>delete from tun2_tab where id =2 ;
1 rowdeleted.
session2 session_id=18:
_dexter@FAKE>update tun2_tab set name ='dexter' where id>1 ;
waiting...
session3 session_id=9:
_dexter@FAKE>delete tun2_tab where id = 3 ;
1 rowdeleted.
查看一下锁的情况:
_sys@FAKE>/
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
9 TX 393228 6 0 0
9 TM 82618 3 0 0
18 TX 131089 0 6 0
18 TM 82618 3 0 0
22 TX 131089 6 0 1
22 TM 82618 3 0 0
6 rowsselected.
这里比较有趣了,因为session 2 update 的记录包括id=2这一行,所以在id=2这一行加锁的时候,这里发生了transaction enqueue,它还没来得及对任何记录加锁,就已经进入了等待中。
而session3执行的时候发现id=3 的这一行还没有锁标示,所以它顺利的对id=3 的记录加了锁。
这个时候我们rollback 第一条记录后
session1 :
_dexter@FAKE>rollback ;
Rollbackcomplete.
发现session2 依然处于等待状态中
再看一下锁的情况:
_sys@FAKE>/
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
9 TX 393228 6 0 1
9 TM 82618 3 0 0
18 TX 589840 6 0 0
18 TX 393228 0 6 0
18 TM 82618 3 0 0
这个时候我们可以看到session2又在等待session3的事务结束以便获取id=3这条记录的锁。
Session1
Session2
Session3
Description
T1
delete from tun2_tab where id =2 ;
T2
update tun2_tab set name ='dexter' where id>1 ;
waiting…
session 2 因为要获取id=2的记录的事务锁所以发生阻塞,等待session1 中的事务释放。
T3
delete tun2_tab where id = 3 ;
按照正常人的思维,比如说我。这一句应该等待session2中的事务才对。但是事实不是如此。因为session2陷入了阻塞,没还没有对id=3的记录加上事务锁,所以session3可以顺利执行。
T4
commit;
T5
still waiting
因为需要id=3的记录的事务锁,所以又被阻塞。
有趣小实验2
session1session_id=144:
_dexter@FAKE>delete from tun2_tab where id =3 ;
1 rowdeleted.
session2session_id=18:
_dexter@FAKE> update tun2_tab set name ='dexter' whereid>1 ;
waiting..
session3session_id=9:
_dexter@FAKE>delete tun2_tab where id = 2 ;
waiting..
看一下锁情况:
_sys@FAKE>/
SID TYPE ID1 LMODE REQUEST BLOCK
-------------- ---------- ---------- ---------- ----------
9 TX 196635 0 6 0
9 TM 82618 3 0 0
18 TX 196635 6 0 1
18 TM 82618 3 0 0
18 TX 458767 0 6 0
144 TM 82618 3 0 0
144 TX 458767 6 0 1
7 rowsselected.
session 3 也进入了等待中,因为session2 先获取了id=2 的行锁,然后等待id=3 的行锁。
Session1
Session2
Session3
Description
T1
delete from tun2_tab where id =3 ;
T2
update tun2_tab set name ='dexter' where id>1 ;
session 2 因为要获取id=3的记录的事务锁所以发生阻塞,但是在阻塞之前,以及对id=1|2的记录加了事务锁
T3
delete tun2_tab where id = 2 ;
waiting…
发生等待。
更多详情见请继续阅读下一页的精彩内容: