oracle的锁与并发机制
锁是并发访问的时候用于保护不共享资源不被同时并发修改的机制。
oracle锁分为DML锁,DDL锁,内部锁和latch
DML锁确保一次只能只有一个人修改某一行(TX锁),而且正在处理一个表时别人不能删除(TM锁)。
DDL锁,在DDL操作是系统会自动为对象加上DDL锁,保护这些对象不被其他会话锁修改。
latch是轻量级的串行化设备,用于协调对共享数据结构、对象、文件的多用户访问,一般都是保护共享内存结构使用的锁,在此不做讨论。
一般的锁管理器工作过程:
1.找到想要锁定的那一行地址
2.在锁管理器排队
3.锁定列表
4.搜索列表,查看别人是否锁定这一行
5.在列表中创建一个新的条目,表明已经锁定这一行
6.对列表解锁
接下里修改,之后提交修改后,继续过程:
7.再次排队
8.锁住锁的列表
9.在这个列表中锁定,并释放所有的锁
10.对列表解锁
oracle锁管理方式:
找到需要锁定的那行地址
到达那一行
锁定这一行
通常lock有三个组件:Resource Structure(资源)、Lock Structure(锁)和Enqueue(排队机制)
Resource和lock是数据结构,而Enqueue是算法。
Resource Structure每一个需要并发控制的资源都有用这个数据结构来描述,先关的成员为:owner、waiter和converter,这是三个指针,分别指向3个由Lock Structure组成的链表。
Lock Structure
每当进程需要访问共享资源时,必须先“锁定”该资源,这个动作实际上是从内存中申请一个Lock Structure,
,在其中记录“锁模式、进程ID”等重要信息。然后看是否立即能够获得资源的访问权,如果不能的话将这个Lock structure挂到Resource Structure的Waiter链表中,如果能够获得,则把Lock Structure的owner链表中。
最常用的锁模式
Share 拥有这对资源进行只读访问,允许其他用户并发只读访问
Exclusive 拥有者对资源进行修改访问,不允许其他用户并发访问
Enqueue 算法
Lock使用的是Enqueue算法,可以理解为“先入先出队列”,如果进程的锁定请求不能满足,该进程的Lock Structure就被加到Waiter链表的末端。当占用进程释放锁时,会检查Waiter和Converter队列,把锁分配给先入对的请求者。converter和waiter两个等待队列,算法的有些区别:如果某个操作先后需要2中不同模式的锁,比如先是share mode然后是exclusive mode,则进程会先请求share mode 后获得lock structure会挂在owner队列上,当需要exclusive
mode锁时,进程先释放share mode的锁,然后再次申请exclusive mode的锁,但是可能无法立即获得,这时请求会挂在converter队列下,converter队列会被优先于waiter队列处理。
oracle行级锁机制
首先明白三个概念:
ITL:每个数据块的头部有一个叫做ITL的数据结构,用于记录那些事务修改了这个数据块的内容。
记录头ITL索引:每条记录的记录头部有一个字段,用于记录ITL表项号,可以看做指向ITL表的指针
TX锁,事务锁
TM锁:保护表或视图定义不被修改的锁
当一个事务开始时,必须申请一个TX锁,这种锁保护资源是回滚段、回滚段数据块,因此这个这个申请意味着:用户进程必须先申请到回滚段资源后才能开始一个事务,才能执行DML语句修改数据。
申请到回滚段资源后,用户事务就可以开始修改数据了,事务信息可在v$transaction中查到,在修改数据表的记录时,需要遵守如下操作顺序:
首先获得这个表的TM锁,这个锁用于保护事务执行过程中其他用户不能修改表结构;
事务修改某个数据块记录时,首先需要在改数据块块头的ITL表中申请一个空闲表项,并在其中记录事务号,实际就是在记录这个事物要使用的回滚段地址;
事务修改该数据块的某条记录时,会设置该记录头部的ITL索引指向上一步申请到的表项,然后再修改记录内容,修改前先在回滚段对记录修改前的状态做一个拷贝,然后才能修改数据记录,这个拷贝用于以后的回滚、恢复和一致性读。当其他用户并发修改这条记录时,会根据记录头的ITL索引读取ITL表项内容,查看这个事务是否已经提交,如果没有提交,则这个用户的TX锁会等待前一个用户的TX锁的释放。
例如如下式转储的一个数据块的ITL信息:
Block header dump: 0×00411819
Object id on Block? Y
seg/obj: 0x10396 csc: 0×00.d62e7 itc: 2 flg: O typ: 1 – DATA
fsl: 0 fnx: 0×0 ver: 0×01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0×0008.00b.0000029b 0x00c05271.006a.3c —- 3 fsc 0×0000.00000000
0x02 0×0000.000.00000000 0×00000000.0000.00 —- 0 fsc 0×0000.00000000
seg/obj:seg/obj id
csc:clean scn
itc:itl slots的个数,此时多少个事务在对本data block进行操作
flg: 0=on the freelist
typ:数据块类型
fsl: itl tx freelist slot
fnx: dba of next block on freelist
Itl:interested transaction list index
Xid:transaction id
Uba:undo address
Flag:事务状态标志
Lck:事物所影响行的数量
oracle 在对数据行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID会保存下来,这个事务ID时事务特有的,表示了回滚段号、槽和序列号,事务ID留在包含数据行的块中,可以告诉其他会话:一个会话拥有这个数据行。另一个会话会看到锁ID,由于锁ID表示一个事务,所以可以很快的查看持有这个锁的事务是否活动的。如果锁不活动的,则允许会话访问这个数据,如果锁还是活动的,会话会要求一旦释放锁就得到通知。所以这需要一个排队机制:请求锁的会话会排队,等待目前拥有这个锁的事务执行,然后的到这个数据。可以根据v$lock视图的lmode和request
mode判断谁是owner、waiter和converter
owner:lomode>0,request=0
waiter:lmode=0,request>0
converter:lmode>0,request>0
例如下试验可以清楚看到这些信息:
系统已更改。
SQL> create table t1 ( x int );
表已创建。
SQL> create table t2 ( x int );
表已创建。
SQL> insert into t1 values ( 1 );
已创建 1 行。
SQL> insert into t2 values ( 1 );
已创建 1 行。
SQL> select (select username
2 from v$session
3 where sid = v$lock.sid) username,
4 sid,
5 id1,
6 id2,
7 lmode,
8 request, block, v$lock.type
9 from v$lock
10 where sid = (select sid
11 from v$mystat
12 where rownum=1)
13 /
USERNAME SID ID1 ID2 LMODE REQUEST BLOCK TYPE
——– —– —- —- —– ——- —– —-
SYS 13 66455 0 3 0 0 TM
SYS 13 66456 0 3 0 0 TM
SYS 13 589840 662 6 0 0 TX
SQL> select object_name, object_id
2 from user_objects
3 where object_name in (‘T1′,’T2′)
4 /
OBJECT_NAME OBJECT_ID
———— ———
T1 66455
T2 66456
每个事务只能有一个TX锁,但是TM锁依照修改的对象个数而定,TM对应的ID1列就是DML锁定对象ID.
SQL> select username,
2 v$lock.sid,
3 trunc(id1/power(2,16)) rbs,
4 bitand(id1,to_number(‘ffff’,'xxxx’))+0 slot,
5 id2 seq,
6 lmode,
7 request
8 from v$lock, v$session
9 where v$lock.type = ‘TX’
10 and v$lock.sid = v$session.sid
11 and v$session.username = USER;
USERNAME SID RBS SLOT SEQ LMODE REQUEST
——– —– —— —– —— —— ———
SYS 13 9 16 662 6 0
SQL> select XIDUSN, XIDSLOT, XIDSQN from v$transaction;
XIDUSN XIDSLOT XIDSQN
—— ———- ———-
9 16 662
oracle事务不同于其他数据库之处,不需要专门语句显示开始事务,事务会在修改数据的第一条语句处开始,但是一定要用commit或rollback事务。
oracle的commit做了如下操作:
为事务生成一个SCN
LGWR将所有余下的缓存重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中
v$lock中记录着会话持有的锁,这些锁将被释放,而排队等待这些锁的每一个队列都会被唤醒
如果事务处理的某些块还在缓存中,则会快速的模式访问并清除
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0×0008.00b.0000029b 0x00c05271.006a.3c C— 0 scn 0×0000.000d6470
0x02 0×0005.007.00000372 0x00c0dbca.006e.2f –U- 1 fsc 0×0000.000d6584
如上的flag列,第一条ITL信息显示数据块当前事务信息已经被清除,第二个还未清除事务ITL信息标志为U;
oracle并发支持,实现了一种多版本体系,能够同时物化多个版本的数据,能够提供读一致性机制,数据读取器绝不会被写入器所阻塞,也就是写不会阻塞读。一种情况例外,那就是在分布式事务处理(2PC)期间。
另外,记住大多数DDL都带排它锁,有些DDL没有DDL锁,如create index idx on t(x) online;online关键字会改变建立索引的方法。oracle只会得到表上的TM锁,防止其他DDL发生,但是运行DML运行。oracle发生死锁的原因外键未加索引、位图索引发生更新,外键未加索引更新或删除父表都会对整个子表加锁
会话1:
create table p ( x int primary key );
create table c ( x references p );
insert into p values ( 1 );
insert into p values ( 2 );
commit;
insert into c values ( 2 );
会话2:
delete from p where x = 1;;
这个时候机会发生阻塞:
SQL> select
2 (select username from v$session where sid=a.sid) blocker,
3 a.sid,
4 ‘ is blocking ‘,
5 (select username from v$session where sid=b.sid) blockee,
6 b.sid
7 from v$lock a, v$lock b
8 where a.block = 1
9 and b.request > 0
10 and a.id1 = b.id1
11 and a.id2 = b.id2;
BLOCKER SID ‘ISBLOCKING’ BLOCKEE SID
——- —– ——— ——— ——-
SYS 142 is blocking SYS 13
不需要对外键加索引的情况:
1、没有从父表删除行
2、没有更新父表的唯一键/主键值
3、没有从父表联结子表
参考:
TOM oracle 9i&10g编程艺术