9i10g11g编程艺术——锁和闩

来源:互联网 发布:php cookie能存多少 编辑:程序博客网 时间:2024/05/01 06:25
1、锁类型
oracle中主要有3中类型锁:
DML锁(DML lock):DML代表数据操作语言。一般来讲,这表示select、insert、update、merge和delete语句。DML锁机制允许并发执行数据修改。例如,DML锁可能是特定数据行上的锁,或者是锁定表中所有行的表级锁。

DDL锁(DDL lock):DDL代表数据定义语言,如create和alter语句等。DDL锁可以保护对象结构定义。

内部锁和闩:orange使用这些锁来保护其内部数据结构。闩(latch)是orange采用的一种轻量级的低级串行化设备,功能上类似于锁。不要被“轻量级”这个词搞糊涂或蒙骗了,你会看到,闩是数据库中导致竞争的一个常见原因。轻量级指的是闩的实现,而不是闩的作用。

2、DML锁
DML锁(DML lock)用于确保一次只有一个人能修改某一行,而且你正在处理一个表时别人不能删除这个表。在你工作时,oracle会透明程度不易的为你加这些锁。

2.1、TX锁
在oracle中,闩为数据的一个属性。oracle并没有一个传统的锁管理器,不会用锁管理器为系统中锁定的每一行维护一个长长的列表。不过,其他的许多数据库却是这样做的,因为对于这些数据库来说,锁是一种稀有资源,需要对锁的使用进行监视。使用的锁越多,系统要管理的方面就越多,所以在这些系统中,如果使用了“太多的”锁就会有问题。

oracle中的锁定过程如下所示。
(1)找到想锁定的那一行的地址。
(2)到达那一行。
(3)锁定这一行——在这行的位置,而非某个大列表。(如果这一行已经锁定,则等待锁住它的事务结束,除非使用了nowait选项。)
仅此而已。由于闩为数据的一个属性,oracle不需要传统的锁管理器。事务只是找到数据,如果数据还没有被锁定,则对其锁定。有意思的是,找到数据时,它可能看上去被锁住了,但实际上并非如此。在oracle中对数据行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID却会保留下来。这个事务ID是事务所独有的,表示了撤销段号、槽和序列号。事务ID留在包含数据行的块上,可以告诉其他会话:你“拥有”这个数据(并非块上的所有数据都是你的,只是你修改的那一行“归你所有”)。另一个会话到来时,他会看到锁ID,由于锁ID表示一个事务,所以可以很快的查看持有这个锁的事务是否还是活动的。如果锁不活动,则允许会话访问这个数据。如果锁还是活动的,会话就会要求一旦释放锁就得到通知。因此,这就有了一个排队机制:请求锁的会话会排队,等待目前拥有锁的事务执行,然后的得到数据。

现在启动一个事务。
u1@ORCL> update dept set dname = initcap(dname);
已更新4行。

u1@ORCL> 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
------------------------------ ---------- ---------- ---------- ---------- ---------- ----------
U1                                     25          6          4       9397          6          0

u1@ORCL> select XIDUSN, XIDSLOT, XIDSQN from v$transaction;
    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         6          4       9397

V$lock表中的LMODE为6是一个排他锁,REQUEST为0则意味着你没有发出请求。也就是说,你拥有这个锁。
0 - none
1 - null (NULL)
2 - row-S (SS)     行共享:共享表锁
3 - row-X (SX)     行专用:用于行的修改
4 - share (S)      共享锁:阻止其他DML操作
5 - S/Row-X (SSX)  共享行专用:阻止其他事务操作
6 - exclusive (X)  专用:独立访问使用
RBS、SLOT和SEQ值与v$transaction信息匹配。这就是我的事务ID。
下面使用同样的用户启动另一个会话,更新emp表中的某些行,并希望视图更新dept:
u1@ORCL> update emp set ename = upper(ename);
已更新14行。

u1@ORCL> update dept set deptno = deptno-10;           --阻塞

现在这个会话会阻塞。如果再次运行V$查询,可以看到下面的结果:
u1@ORCL> 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
------------------------------ ---------- ---------- ---------- ---------- ---------- ----------
U1                                    143          6          4       9397          0          6
U1                                    143          1         26       6920          6          0
U1                                     25          6          4       9397          6          0

u1@ORCL> select XIDUSN, XIDSLOT, XIDSQN from v$transaction;
    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         1         26       6920
         6          4       9397

这里可以看到开始了一个新的事务,事务ID是(1,26,6920)。这一次这个新会话(SID=143)在V$LOCK中有两行。其中一行表示它所拥有的锁(LMODE=6)。另外还有一行,显示了一个值为6的REQUEST。这是一个对排他锁的请求。有意思的是,这个请求行的RBS/SLOT/SEQ值正是锁持有者的事务ID。SID=25的事务阻塞了SID=143的事务。只需要执行V$LOCK的一个自联结,就可以更明确的看出这一点:
u1@ORCL> 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
------------------------------ ---------- ------------- ------------------------------ ----------
U1                                     25  is blocking  U1                                    143

另一个会话一旦放弃锁,请求行就会消失。这个请求行就是排队机制。如何用数据本身来管理锁定和事务信息。这是块开销的一部分。数据块的最前面有一个“开销”空间,这里会存放该块的一个事务表。对于锁定了该块中某些数据的各个“实际”事务,在这个事务表中都有一个相应的条目。这个结构的大小由创建对象时CREATE语句上的两个物理属性参数决定。
INITRANS:这个结构初始的预分配大小。对于索引和表,这个大小默认为2。
MAXTRANS:这个结构可以以扩展到的最大大小。它默认为255,实际中,最小值为2。在oracle 10g及更高的版本中,这个设置已经废弃了,所以不在使用。这个版本中的MAXTRANS总是255。默认情况下,每个块最开始都有两个事务槽。

2.2、TM锁
TM锁用于确保在修改表的内容时,表的结构不会改变。
u1@ORCL> insert into t1 values ( 1 );
已创建 1 行。
u1@ORCL> insert into t2 values ( 1 );
已创建 1 行。

u1@ORCL> 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);
USERNAME         SID        ID1        ID2      LMODE    REQUEST      BLOCK TY
--------- ---------- ---------- ---------- ---------- ---------- ---------- --
U1               148        100          0          4          0          0 AE
U1               148     103209          0          3          0          0 TM
U1               148     103210          0          3          0          0 TM
U1               148     524301      10072          6          0          0 TX

u1@ORCL> select object_name, object_id
  2    from user_objects
  3   where object_name in ('T1','T2');
OBJECT_NAME      OBJECT_ID
--------------- ----------
T1                  103209
T2                  103210

尽管每个事务只能得到一个TX锁,但是TM锁则不同,修改了多少个对象,就能得到多少个TM锁。在系统中允许的TM锁总数可以由你来配置(dml_locks参数定义)。实际上,这个数可能设置为0。但这并不是说你的数据库变成了一个只读数据库,而是说不允许DDL。通过使用ALTER TABLE TABLENAME DISABLE TABLE LOCK命令,还可以逐对象的禁用TM锁。这是一种快捷方法,可以使意外删除的表“难度更大”,因为在删除表之前,你必须重新启用表锁。

3、DDL锁
在DDL操作中会自动为对象加DDL锁,从而保护这些对象不会被其他会话所修改。例如,如果我执行一个DDL操作ALTER TABLE T,在表T上就会加一个排他DDL锁,以防止其他会话得到这个表的DDL锁和TM锁。
在oracle中DDL一定会提交。每条CREATE、ALTER等语句实际上都如下执行:
begin
  commit;
  DDL-STATEMENT
  commit;
exception
  when others then rollback;
end;
DDL一开始就提交,一定要知道这一点。

4、闩
闩(latch)是轻量级的串行化设备,用于协调对共享数据结构、对象和文件的多用户访问。
闩用于保护某些内存结构,如数据库块缓冲区或共享池中的库缓存。一般会在内部以一种“愿意等待”(willing to wait)模式请求闩。这说明,如果闩不可用,请求会话会睡眠很短的一段时间,并在以后再次尝试这个操作。还可以采用一种“立即”(immediate)模式请求其他闩,这与SELECT FOR UPDATE NOWAIT的思想很相似,说明这个进程会做其他事情(如获取另一个与之相当空闲闩),而不只是坐而等待这个闩直到它可用。由于许多请求者可能会同时等待一个闩,你会看到一些进程等待的时间比其他进程要长一些。闩的分配相当随机,这要看运气好坏了。闩释放后,紧接着不论哪个会话请求闩都会得到它。等待闩的会话不会排队,只是一大堆会话在不断的重试。
闩只保持很短的时间,而且提供了一种清理机制,万一某个闩持有者在持有闩时异常的“死掉了”,就能执行清理。这个清理过程有PMON执行。

队列锁(enqueue)这也是一种更复杂的串行化设备,例如,在更新数据库表中的行时就会使用队列锁。与闩的区别在于,队列锁允许请求者“排队”等待资源。对于闩请求,请求者会话会立即得到通知是否得到了闩。而对于队列锁,请求者会话会阻塞,直至真正得到锁。

4.1、闩“自旋”
等待闩可能是一个代价很高的操作。如果闩不是立即可用的,我们就得等待(大多数情况下都是如此),在一台多CPU机器上,我们的会话就会自旋(spin),也就是说,在循环中反复的尝试来得到闩。出现自旋的原因是,上下文切换(context switching)的开销很大(上下文切换是指被“踢出”CPU,然后又必须调度回CPU)。所以,如果进程不能立即得到闩,我们就会一直呆在CPU上,并立即再次尝试,而不是先睡眠、放弃CPU,等到必须调度回CPU时才再次尝试。之所以呆在CPU上,是因为我们指望闩的持有者正在另一个CPU上忙于处理(由于闩设计为只保持很短的时间,所以一般是这样),而且会很快放弃闩。如果出现自旋并不断的尝试想得到闩,但是之后还是得不到闩,此时我们的进程才回睡眠,或者让开CPU,而让其他工作进行。我们尝试得到闩时,可能会消耗大量的CPU时间。系统看上去非常忙(因为消耗了很多CPU时间),但是并没有做多少实际的工作。
原创粉丝点击