Thursday, May 3, 2007

浅谈Linux与Oracle的锁(未完成)

作者:feelfall
出处:feelfall.blogspot.com
转载时请保留作者和出处。

还是由于人懒,这篇文章没有写完。有兴趣再回头研究的时候,我会把这篇文章写完的。

信号量是一种描述共享资源的方法。
信号量分两种,一种是binary semaphore,一种counting semaphore。
在操作系统中,它是一种进程间通信(IPC)的重要手段。

锁是一种序列化机制,实现并发控制的一种手段,它强制那些使用资源的实体(如线程、进程)遵守某些规范(authorized or not. etc)。
锁从强制性分,有两种,一种是advisory lock(也称协作锁),一种是mandatory lock(强制锁)。
使用协作锁,控制权交由程序员来控制,进程或线程之间需要默契配合。这种锁不会防止程序员破坏自己的数据,使数据不一致。说白了,这种锁由user mode processes控制。
强制锁是内核控制检查的锁,这种锁一定会保证其约束条件。比如io scheduler调度不过来了,bio队列已满。
那么用户程序是不会在这个时候从操作系统得到资源的,必须被挂起。
锁从读取类型来分,也分两种,一种是shared lock,一种是exclusive lock。什么作用,这里不赘述了。

一个binary semaphore就可以是一个最简单的锁。
通常,锁是要求硬件支持的,以完成原子操作。
在多处理器的环境中,为了保证原子性,这些操作由处理器的特殊指令来实现,例如test-and-set,fetch-and-add或者是compare-and-swap。
可以防止interrupt破坏atomic operation。
嗯,是不是想起了oracle中的latch了?操作系统中是spin lock,oracle中是latch,目的差不多,防止并发修改,都是抢的。
用的cpu指令就是test-and-set,这是硬件提供的最底层的原子化操作。

现在说一下锁的粒度。
锁的粒度是指lock锁了多少资源。
锁粒度太小,会导致lock overhead。系统这个时候疲于应付反复initialize和destroy locks(内存资源)。用的锁越多,这个问题就越凸显。
粒度太大,会导致锁竞争。

信号量和锁之间是什么关系呢?

当进程试图访问一个被信号量保护的资源的时候,进程被挂起(进入sleep状态)。注意,申请使用信号量的函数都要是可以sleep的(中断和可延后程序就不行)。
当所需的信号量被释放,对应进程就会进入runnable状态,随时可以调度运行。
从Linux的角度看,序列化设备有spin lock,semaphore,(还有interrupt和softirq)。
从Oracle的角度看,序列化设备有latch lock和enqueue lock。latch使用test and set或者compare and swap,


先来简单看一下Linux的spin和semaphore结构。
------------------
|spinlock_types.h|
------------------
typedef struct {
  unsigned int slock;
} raw_spinlock_t;

typedef struct {
  raw_spinlock_t raw_lock;
#if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
  unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
  unsigned int magic, owner_cpu;
  void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
  struct lockdep_map dep_map;
#endif
} spinlock_t;

--------
|wait.h|
--------
struct __wait_queue_head {
  spinlock_t lock;
  struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

----------
|atomic.h|
----------
typedef struct { volatile int counter; } atomic_t;

-------------
|semaphore.h|
-------------
struct semaphore {
  atomic_t count;
  int sleepers; 等待此信号量的进程数
  wait_queue_head_t wait; 等待进程队列
};


我们再来看一下linux中spin的函数。(片段)

-----------------------------------------
|linux-2.6.20.1/arch/s390/lib/spinlock.c|
-----------------------------------------
int spin_retry = 1000;

static inline void _raw_yield(void)
{
  if (MACHINE_HAS_DIAG44)
    asm volatile("diag 0,0,0x44");
}

static inline void _raw_yield_cpu(int cpu)
{
  if (MACHINE_HAS_DIAG9C)
    asm volatile("diag %0,0,0x9c"
           : : "d" (__cpu_logical_map[cpu]));
  else
    _raw_yield();
}

void _raw_spin_lock_wait(raw_spinlock_t *lp, unsigned int pc)
{
  int count = spin_retry;
  unsigned int cpu = ~smp_processor_id();

  while (1) {
    if (count-- <= 0) {
      unsigned int owner = lp->owner_cpu;
      if (owner != 0)
        _raw_yield_cpu(~owner);
      count = spin_retry;
    }
    if (__raw_spin_is_locked(lp))
      continue;
    if (_raw_compare_and_swap(&lp->owner_cpu, 0, cpu) == 0) {
      lp->owner_pc = pc;
      return;
    }
  }
}

int _raw_spin_trylock_retry(raw_spinlock_t *lp, unsigned int pc)
{
  unsigned int cpu = ~smp_processor_id();
  int count;

  for (count = spin_retry; count > 0; count--) {
    if (__raw_spin_is_locked(lp))
      continue;
    if (_raw_compare_and_swap(&lp->owner_cpu, 0, cpu) == 0) {
      lp->owner_pc = pc;
      return 1;
    }
  }
  return 0;
}

void _raw_spin_relax(raw_spinlock_t *lock)
{
  unsigned int cpu = lock->owner_cpu;
  if (cpu != 0)
    _raw_yield_cpu(~cpu);
}

void _raw_read_lock_wait(raw_rwlock_t *rw)
{
  unsigned int old;
  int count = spin_retry;

  while (1) {
    if (count-- <= 0) {
      _raw_yield();
      count = spin_retry;
    }
    if (!__raw_read_can_lock(rw))
      continue;
    old = rw->lock & 0x7fffffffU;
    if (_raw_compare_and_swap(&rw->lock, old, old + 1) == old)
      return;
  }
}

int _raw_read_trylock_retry(raw_rwlock_t *rw)
{
  unsigned int old;
  int count = spin_retry;

  while (count-- > 0) {
    if (!__raw_read_can_lock(rw))
      continue;
    old = rw->lock & 0x7fffffffU;
    if (_raw_compare_and_swap(&rw->lock, old, old + 1) == old)
      return 1;
  }
  return 0;
}

void _raw_write_lock_wait(raw_rwlock_t *rw)
{
  int count = spin_retry;

  while (1) {
    if (count-- <= 0) {
      _raw_yield();
      count = spin_retry;
    }
    if (!__raw_write_can_lock(rw))
      continue;
    if (_raw_compare_and_swap(&rw->lock, 0, 0x80000000) == 0)
      return;
  }
}

int _raw_write_trylock_retry(raw_rwlock_t *rw)
{
  int count = spin_retry;

  while (count-- > 0) {
    if (!__raw_write_can_lock(rw))
      continue;
    if (_raw_compare_and_swap(&rw->lock, 0, 0x80000000) == 0)
      return 1;
  }
  return 0;
}

这里使用了compare and swap指令。spin_retry也是可以调整的。


用下面这个命令查看semaphore的使用情况。
# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems
0xd9895640 229376     oracle    640        154

一般来说结构越复杂,管理起来就越麻烦,相应的开销就越大。
然而对于不同的应用类型,比如一个只使用0.1ms的资源和一个要使用30s的资源,它们使用的序列化设备就应该不一样。
前者用spin,后者用semaphore。(注意到volatile没有?其实semaphore的count是受cpu的原子操作指令保护的)

Oracle的enqueue其实就是advisory lock方式(让oracle构筑自己的进程之间的序列化机制,kernel不会去强行干预),它使用semaphore来进行控制消息的传递。
比如LGWR在没有等到自己的semaphore的时候处于sleep状态。当得到自己的semaphore时LGWR开始干活,写出在线日志buffer。
这里稍微扯远点,当发生log file sync的时候,这里其实不是Oracle的锁的直接作用结果,而是系统的mandatory lock的间接产物,最终反映成log file sync。
首先,磁盘没有返回'写完成'的中断信号,OS的kernel级写线程的调度数量达到了上限,不允许用户进程再往里面塞bio,这时是mandatory lock。
ps:还有几种情况没有说,不把问题扯的太远,先说到这里,只是提一个不同lock mode,以后我还会写相关文章的。

在Linux上安装oracle的时候,我们通常要配置一下kernel的参数,其中就有shared memory也有semaphore相关的参数需要调整。

SHMMAX: 定义单个共享内存段的最大值。这只是一个上界值,如果使用时超过了这个值,那么会被分成多段。

SHMALL 可用共享内存的总数量(字节或者页面) 如果是字节,就和 SHMMAX 一样;如果是页面,ceil(SHMMAX/PAGE_SIZE) 

*SEMMSL:定义一个semaphore set中的semaphore数量上限。system v ipc中不是一个一个获取semaphore的,当然了,你可以获取只包含一个semaphore的semaphore set,it's up to you。
上面看到semaphore arrays的结果就是一个semaphore set。oracle的每个instance都需要一个semaphore set。
每个instance要求这个内核参数至少等于oracle允许的进程数(oracle参数文件中的processes)。

*SEMMNI:定义系统中一共可以有多少个semaphore set。有什么用呢?
你要是想在一个机器上启动两个oracle instance,那么仅就oracle来说,这个数值就至少要为2(当然还可能有其他的应用需要semaphore set,而不仅是oracle)。
如果你在一个机器上启动了多个instance,那么你ipcs -s的时候,看到的条目就是多个了。

*SEMMNS:定义系统中允许的semaphore数量的最大值。这个就不用解释了吧。

*SEMOPM:定义每个semop()系统调用所允许完成的操作。前面这句话肯定没有说清楚问题:)。
这么说吧,你的进程申请资源的时候可以一下申请多种资源,对应的就是几个semaphore。
semop系统调用就是申请资源用的,SEMOPM这个内核参数就是限定semop这个玩意儿一次可以操作(修改)多少个semaphore。

带*的由kernel.sem 250 32000 100 128 in /etc/sysctl.conf
means SEMMSL, SEMMNS, SEMOPM, and SEMMNI.

SEMVMX:定义......回到上面的struct semaphore,这里限定的就是count的最大值。也就是semaphore反映的资源数上限。

SEMMNU:定义系统中semaphore undo structrue的数量。当进程意外终止时,这种回滚结构可以保护相应的资源。SEMMNU这个参数值最好大于系统可能的最大进程数。

Oracle's latches and enqueues

latch:栓。
latch和spin的设计目标是一样的,都是以对象被锁定时间不长为前提的。
latch保护SGA里那些不能被并发修改的数据(排他的)。通常每种结构只需要一个latch就够了。
所以latch通常不被用来做shared access。
当处理器A持有某个资源的ltach,处理器B就不可以去使用这个资源。(有例外,不讲例外:D)
但是这个时候处理器B不会善罢甘休,它会不停的去test and set,试图得到这个资源。
为了不让B永远试下去,它进行test and set的次数是被限制的,由_SPIN_COUNT隐含参数确定(和linux中的spin_retry差不多)。
达到这个次数后,运行在B上的进程会去休息会儿。
休息多久呢?这由一个指数后退算法(这让我想起了上学时模拟的CSMA/CD)加参数_MAX_EXPONENTIAL_SLEEP来确定,
如果这个process还有别的latch,那么综合考虑,参数使用_MAX_SLEEP_HOLDING_LATCH。

我机器上的配置情况如下:
select b.KSPPINM as name,a.ksppstvl as value,b.KSPPDESC as description
  from X$KSPPCV a, X$KSPPI b
  where a.indx=b.indx and b.ksppinm = '_spin_count'

NAME                 VALUE      DESCRIPTION
-------------------- ---------- ----------------------------------------
_spin_count          2000       Amount to spin waiting for a latch


select b.KSPPINM as name,a.ksppstvl as value,b.KSPPDESC as description
  from X$KSPPCV a, X$KSPPI b
  where a.indx=b.indx and b.ksppinm = '_max_exponential_sleep'

NAME                           VALUE      DESCRIPTION
------------------------------ ---------- ----------------------------------------
_max_exponential_sleep         0          max sleep during exponential backoff


select b.KSPPINM as name,a.ksppstvl as value,b.KSPPDESC as description
  from X$KSPPCV a, X$KSPPI b
  where a.indx=b.indx and b.ksppinm = '_max_sleep_holding_latch'

NAME                           VALUE      DESCRIPTION
------------------------------ ---------- ----------------------------------------
_max_sleep_holding_latch       4          max time to sleep while holding a latch


latch的类型和开机后的统计结果可以从v$latch看。

现在的问题是,latch设计之初的确是认为被锁了的资源会马上释放。而且栓等待的时间不会很长。
但是,总有事与愿违的时候。
万一有些latch(long wait latch)的确要等一个比较长的时间的时间,那么怎么办呢?
那就可以当成队列锁来管理,这里叫latch wait posting,使用了semaphore进行管理。
如果一种latch适用latch wait posting,那么当要使用这种latch的进程由于争抢latch失败时,这个进程首先会把自己放入一个等待队列。
而当持有相应资源的进程释放latch的时候,等待进程会得到原先持有那个latch的进程的通知,从而立即进入runnable状态。
这个过程是一个再次争抢的过程,也就是说被挂起进程再次争抢相应latch。

(为什么设计之初没有直接用队列锁呢?是由于从统计的角度看,这种latch大部分时候可以通过争抢来获得,而在比较少的情况下稍长。)
那么,哪些latch在sleep的时候可以被当成队列锁(也就是用semaphore)来管理呢?
oracle 8i中有一个参数,_LATCH_WAIT_POSTING,它可以来调整适用latch wait posting的范围。
_LATCH_WAIT_POSTING有三个取值:
_LATCH_WAIT_POSTING=0,所有latch都适用latch wait posting。
_LATCH_WAIT_POSTING=1,enqueue hash chains latch,shared pool latch,library cache latch这三种latch适用。
_LATCH_WAIT_POSTING=2,所有latch都适用latch wait posting。
从9i开始,_LATCH_WAIT_POSTING这个参数没有了。但是latch wait posting这种机制却没有消失。
只不过是不让用户自行调整罢了。
你可以这样查看有哪些long wait latch:
select * from v$latch where waiters_woken>0;
系统只有在运行一段时间后才能看见所有的long wait latch,因为v$latch视图反映的是latch的统计结果。


latch可以分成三种:
child latch, parent latch, solitary latch
当latch保护的是一个结构中的子结构的时候,这种latch叫child latch。
从v$latch_parent视图中可以看见child latch的统计结果。
而同一种child latch会有一个parent latch。
没有child latch的parent latch称作solitary latch。
v$latch_children视图当中包含了parent latch和solitary latch的统计结果。

前面我们提到过的_spin_count参数可以调整一个进程每一次的争抢次数。但是这种调整的影响是全局的。
9i中采取了更为精细的做法,它相当于给用户提供8个可以单独调整的spin count。

SMART>select indx, spin, yield, waittime from x$ksllclass;

      INDX       SPIN      YIELD   WAITTIME
---------- ---------- ---------- ----------
         0      16000          0          1
         1      16000          0          1
         2      16000          0          1
         3      16000          0          1
         4      16000          0          1
         5      16000          0          1
         6      16000          0          1
         7      16000          0          1

已选择8行。

其中的每一行都与init.ora参数文件当中的_LATCH_CLASS_n相对应。
如果你觉得哪些latch需要稍短或稍长的spin count,那你就可以使用这些设备。
举个例子,我们现在的cache buffers chains latches的sleep时间过长,给cpu造成比较严重的负担。
我们决定将cache buffers chains latches的spin count单独调整,使之有别于全局的spin count。

SMART>select latch#, name from v$latchname where name = 'cache buffers chains';

    LATCH# NAME
---------- ------------------------------
        97 cache buffers chains

修改参数文件之前,先来看一下我使用的参数文件和类型。

SMART>show parameter spfile

NAME                                 TYPE                   VALUE
------------------------------------ ---------------------- ------------------------------
spfile                               string                 %ORACLE_HOME%\DATABASE\SPFILE%
                                                            ORACLE_SID%.ORA

此处得知,我使用的是spfile而不是pfile,那么我就要先从spfile生成一个pfile。

SMART>create pfile='init_new.ora' from spfile;

文件已创建。

接下来我们在参数文件当中加入:
*._latch_class_5 = "1000"  #将latch class 5的spin count设置成1000
*._latch_classes = "97:5"    #将97号latch的spin count绑定到latch class 5

shutdown后,再次启动oracle instance。

SMART> startup nomount pfile=d:\oracle\ora92\database\init_new.ora
ORACLE 例程已经启动。

Total System Global Area  135338868 bytes
Fixed Size                   453492 bytes
Variable Size             109051904 bytes
Database Buffers           25165824 bytes
Redo Buffers                 667648 bytes
SMART> alter database mount;

数据库已更改。

SMART> alter database open;

数据库已更改。

SMART> select indx, spin, yield, waittime from   x$ksllclass;

      INDX       SPIN      YIELD   WAITTIME
---------- ---------- ---------- ----------
         0      16000          0          1
         1      16000          0          1
         2      16000          0          1
         3      16000          0          1
         4      16000          0          1
         5       1000          0          1
         6      16000          0          1
         7      16000          0          1

已选择8行。

SMART> select a.kslldnam, b.kslltnum, b.class_ksllt
  2  from   x$kslld a, x$ksllt b
  3  where  a.kslldadr = b.addr
  4  and    b.class_ksllt > 0;

KSLLDNAM                         KSLLTNUM CLASS_KSLLT
------------------------------ ---------- -----------
process allocation                      3           2
cache buffers chains                   97           5

latch会不会引起死锁?
会!
所以oracle将进程获取latch的方式分为willing-to-wait和no-wait。
当进程要获取的latch超过一个的时候,这些latch是按顺序获取的。
那么这个顺序又是什么呢?
表示这个顺序的是一个介于0到15的数,用2个字节的位图来表示。
 0: 0000 0000 0000 0000
 1: 0000 0000 0000 0001
 2: 0000 0000 0000 0010
 3: 0000 0000 0000 0100
    ...
14: 0010 0000 0000 0000
15: 0100 0000 0000 0000
系统中,每一个latch都有一个这样的数,来表示latch的level。
如果一个进程以willing to wait模式试图获取一个latch(假设这个latch为A,level为 n),那么系统会首先确保这个进程没有持有level大于等于n的latch。
这个latch level可以从X$KSLLD内部视图的KSLLDLVL看到。
X$KSLLD  : [K]ernel [S]ervice [L]atch [L]ock [D]escriptor
KSLLDLVL : [K]ernel [S]ervice [L]atch [L]ock [D]escriptor [L]atch Le[V]e[L]
SMART>desc x$kslld;
 名称                    是否为空? 类型
 ----------------------- -------- ---------------

 ADDR                             RAW(4)
 INDX                             NUMBER
 INST_ID                          NUMBER
 KSLLDNAM                         VARCHAR2(64)
 KSLLDADR                         RAW(4)
 KSLLDLVL                         NUMBER

欲获取high level latch的进程在sleep阶段可能仍然会持有low level latch,从而使得需要low level latch的进程更加激烈的竞争。
low level latch可能会被长时间持有。
可以从v$latch视图的waits_holding_latch列得到相关的信息。
当这一字段所反映的情况十分糟糕的时候,那么需要dba进行干预。

如果当一个进程需要获取小于等于level n的latch时,采用willing to wait模式可能会引起死锁。
如果进程成功获取latch,没有死锁问题。
如果进程获得latch失败,那么它会释放掉自己的high level latches,然后立即以正确的顺序重新获取一遍latch。
这就是no wait模式了。

现在开始说一下enqueue lock。
Enqueue Lock是由Oracle Kernel Enqueue Service layer (KSQ)来负责管理的。

行锁(row level lock)
这里我们主要使用oracle的trace文件来进行分析。
开启两个session,我们这里称sesion A和session B。
session A:

SMART>create table t(a char(10), b int);

表已创建。

SMART>insert into t values('aaa',111);

已创建 1 行。

SMART>insert into t values('bbb',222);

已创建 1 行。

SMART>commit;

提交完成。

SMART>select file_id,block_id from dba_extents where owner=user and segment_name='T';

   FILE_ID   BLOCK_ID
---------- ----------
         1      50649

SMART>alter system dump datafile 1 block 50650; /*要比BLOCK_ID大一个block号*/

系统已更改。

SMART>select p.spid from v$process p, v$session s,
  2  (select sid from v$mystat where rownum<2) m
  3  where s.paddr=p.addr and s.sid=m.sid;

SPID
------------------------
2628

打开udump\demo_ora_2628.ora文件。

/*
我们这里只看transaction layer(KTB)和data layer(KD)的部分。
*/
Block header dump:  0x0040c5da
 Object id on Block? Y
 seg/obj: 0x75d9  csc: 0x00.16809e3  itc: 2  flg: O  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0003.000.00000114  0x00800036.0036.35  --U-    2  fsc 0x0000.01680a2d
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

上面两行是数据块的事物槽,每一行叫做一个ITL(Interested Transaction List) slot。

Itl:事物槽编号。
Xid:事物编号,transaction id。
Uba:Undo block address。
Flag:事物状态标志。
    ---- 事物处于活动状态中,或者提交后直接clean out了。
    C--- 已提交,锁被clean out了。
    -B-- (我不知道这个标志是做什么用的)
    --U- 已提交(也许很久之前提交的),需要clean out。
    ---T 事物在last full cleanout时仍然active。
Lck:这个事物锁了多少行数据。
Scn/Fsc: SCN or Free space credit。如果事物提交了,那么SCN表示commit SCN或者是SCN的上界(这和SCN的计算有关系),
如果没有提交,那么这里的前2个字节表示的就是事物在此block中free(动词)的字节数。


data_block_dump,data header at 0x306105c
===============
tsiz: 0x1fa0
hsiz: 0x16
pbl: 0x0306105c
bdba: 0x0040c5da
     76543210
flag=--------
ntab=1
nrow=2
frre=-1
fsbo=0x16
fseo=0x1f7c
avsp=0x1f66
tosp=0x1f66
0xe:pti[0]  nrow=2  offs=0
0x12:pri[0]  offs=0x1f8e
0x14:pri[1]  offs=0x1f7c
block_row_dump:                                 <----数据
tab 0, row 0, @0x1f8e
tl: 18 fb: --H-FL-- lb: 0x1  cc: 2              <----看见这里的lb没有?0x1就是第一个ITL。
col  0: [10]  61 61 61 20 20 20 20 20 20 20     <----col 0占10个字节(跟我们定义的一样),数据如何存的以后讲。
col  1: [ 3]  c2 02 0c                          <----col 1所占的字节数暂时为3,因为int类型实际的存储是可变的
tab 0, row 1, @0x1f7c
tl: 18 fb: --H-FL-- lb: 0x1  cc: 2
col  0: [10]  62 62 62 20 20 20 20 20 20 20
col  1: [ 3]  c2 03 17
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 50650 maxblk 50650

我们现在打开session B
SMART>update t set b=10 where a='bbb';

已更新 1 行。

SMART>alter system dump datafile 1 block 50650;

系统已更改。

暂时没有提交,再看dump文件的结果。

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0003.000.00000114  0x00800036.0036.35  C---    0  scn 0x0000.01680a2d
0x02   0x0007.026.0000011c  0x008002a8.0032.25  ----    1  fsc 0x0001.00000000
 
block_row_dump:
tab 0, row 0, @0x1f8e
tl: 18 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [10]  61 61 61 20 20 20 20 20 20 20
col  1: [ 3]  c2 02 0c
tab 0, row 1, @0x1f6b
tl: 17 fb: --H-FL-- lb: 0x2  cc: 2
col  0: [10]  62 62 62 20 20 20 20 20 20 20
col  1: [ 2]  c1 0b
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 50650 maxblk 50650

v$lock, v$transaction, v$transaction_enqueue 中大家自己去看吧。

session B提交.
SMART>commit;

提交完成。

SMART>alter system dump datafile 1 block 50650;

系统已更改。

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0003.000.00000114  0x00800036.0036.35  C---    0  scn 0x0000.01680a2d
0x02   0x0007.026.0000011c  0x008002a8.0032.25  --U-    1  fsc 0x0001.01686955

block_row_dump:
tab 0, row 0, @0x1f8e
tl: 18 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [10]  61 61 61 20 20 20 20 20 20 20
col  1: [ 3]  c2 02 0c
tab 0, row 1, @0x1f6b
tl: 17 fb: --H-FL-- lb: 0x2  cc: 2
col  0: [10]  62 62 62 20 20 20 20 20 20 20
col  1: [ 2]  c1 0b
end_of_block_dump

行锁到此结束,下面讲讲buffer lock。
row level lock是用来控制磁盘上的并发读写的设备,而buffer lock是用来控制buffer cache中的block的并发读写。
以后讲等待事件的时候再仔细梳理一遍buffer lock。总而言之,buffer lock是一种应用在SGA的buffer cache中的lock。

字典锁(Data Dictionary Lock)
看名字就知道这种锁是用来保护什么的了。
当数据字典被引用的时候,数据字典是不能被删除的,其定义也不能被修改。
当系统对sql statement进行语法分析,语义分析,安全检查,生成备用查询计划,评估查询计划,定义输出格式,返回结果...
全部步骤完成之后,相应的字典锁才可以被释放。
一些锁可以被用来锁字典。比如row cache enqueue lock,library cache pins,DML lock。

锁模式
被锁的对象可以分成混合对象和简单对象。
复杂对象就好比一个table和table当中的rows,简单对象就好比buffer cache。

简单对象适合用下面三种锁:
Exclusive:排他锁,完全杜绝concurrent access。
Shared:共享锁,可以concurrent inspect/read,但是不可以修改。
Null:Steve Adams的书上说,这种锁啊,是当替换符用的。
如果一个会话cache了一个对象的信息,即便这个资源没有处于活动状态会话也会持有一个对应的null lock。
Null mode lock不会限制并发,但是当这个资源不再有效的时候,null lock会像trigger一样,通知会话清理相关cache。

除了上面三种锁之外,还有三种锁适用于混合对象。
Sub-shared:这种锁就是对混合对象的子对象分别使用共享锁,这样做可以提供更多的灵活性。
比如以部分区域可以共享,剩余的区域要被独占。
Sub-exclusive:与前者相反。
Shared-sub-exclusive:当会话需要一个混合对象的部分排他锁和整体共享锁时,就使用这种锁模式。

这六种锁模式适用于local lock和instance lock(也就是集群环境中,用以节点之间协作的enqueue)。

下面这张表给出了各种锁类型在dump文件中的缩写和等待事件中的数字编号。



那些锁是相互兼容的也十分重要,这里给出这种关系的矩阵。



Oracle运行时就是依据上面这个表来判定会话应该得到锁还是被enqueue。

Oracle Kernel Enqueue Service layer (KSQ)负责管理所有enqueue。
会话在请求资源的时候,首先会去请求这个资源结构的锁。
在得到锁之前,系统会给这个资源当中用来描述锁的链表(引用的容器)加入一个锁的引用。
Enqueue resource可以从X$KSQRS (kernel service enqueue resource)或者V$RESOURCE试图当中看到。
SQL> select * from v$resource;

ADDR     TY        ID1        ID2
-------- -- ---------- ----------
6F644CE8 XR          4          0
6F644D40 TS          3          1
6F644EF8 CF          0          0
6F645058 RT          1          0
6F6450B0 RS         25          1
6F645538 MR          3          0
6F645590 MR          4          0
6F645698 MR          1          0
6F6456F0 MR          2          0
6F645748 MR        201          0
6F645A08 PW          1          0
6F646468 TA          6          1

(刚启动,还没有积攒起数据,嘿嘿)
Oracle 10g中V$EVENT_NAME试图里的PARAMETER2和PARAMETER3给出了ID1和ID2的含义。
在10g以前,这些含义没有给出,但是大家可以在网上找到。
TY: 指锁类型
ID1和ID2根据TY的不同,其含义也不同。
参数ENQUEUE_RESOURCES和_ENQUEUE_LOCKS定义了SGA中最多能有多少enqueue resource structure
每个enqueue resource structure都有三种锁引用的容器,分别是owner,waiter,convertor。

enqueue lock
select * from v$enqueue_lock;
给出了会话ID,锁类型,ID1和ID2,锁模式,请求模式,当前模式时间和blocking flag
查看TX锁和TM锁
select * from x$ktcxb where  KTCXBLKP in (select kaddr from v$lock where type = 'TX');
select * from x$ktadm where  KSQLKADR in (select kaddr from v$lock where type = 'TM');

Session A
SQL> test/test
已连接。
SQL> update blank set a='v' where a='cc';

已更新 1 行。

Session B
SQL> test/test
已连接。
SQL> update blank set a='dd';

Session C
SQL> select sid, event, p1, p1raw,
  2  chr(bitand(P1,-16777216)/16777215)||chr(bitand(P1,16711680)/65535) type,
  3  mod(P1, 16) "MODE" from v$session_wait where  event like '%row lock%';

       SID EVENT                                  P1 P1RAW    TYPE       MODE
---------- ------------------------------ ---------- -------- ---- ----------
        48 enq: TX - row lock contention  1415053318 54580006 TX            6

SQL>

select * from v$enqueue_stat


参考文献:

http://www.ixora.com.au/q+a/datablock.htm

《Oracle8i Internal Services For waits, latches, locks》

《Oracle Wait Interface: A Practical Guide to Performance Diagnostics & Tuning》

《Expert Oracle Database Architechture Oracle 9i and 10g Programming Techniques and Solutions》

http://www.revealnet.com/newsletter-v6/0905_E.htm

http://groups.google.com/group/Oracle-Internal/browse_thread/thread/5864abd39b3f8ffb

No comments: