通过案例学调优之--和 BUFFER CACHE 相关的主要 Latch

2.1、和 BUFFER CACHE 相关的主要 Latch 有:
Latch:cache buffers lru chain
Latch: cache buffers chains既然Latch是用来保护共享内存块不被并行破坏,那就了解BUFFER CACHE的相关原理,进 而得知什么时候需要用到这个 Latch,才能够有相应的解决方案。

2.2BUFFER CACHE 是用来缓存数据块的地方,那么数据的查询和修改都要通过它来完成。 接下来就看看访问数据的流程是怎样的:


2.2.1当一个进程要访问数据时,首先要查找 BUFFER CACHE 看看数据是否已经存在?

(Y) 如果数据在 BUFFER CACHE 中存在,则根据数据的状态来判断是否可以直接访问 还是需要构造一致性读取?

(N) 如果数据在 BUFFER CACHE 中不存在,则需要从数据文件中读取数据块到 BUFFER CACHE 中去。这个时候,需要查找在 BUFFER CACHE 中寻找足够的内存空间来读取相关的数 据。
2.2.2现在 BUFFER CACHE 那么大,一个 BUFFER 一个 BUFFER 的扫描过去是相当的消耗资源以及查 询的时间,那我们怎样才能快速地查到数据了?
可以看看下面关于
BUFFER CACHE 的示图:

wKioL1RjAt2g9T7GAAIE1X17zB8435.jpg

这边我们先看下面几点:
1) 图中右边的有一块 Buffers Memory,其中每一块小格就是一个 Buffer(用来存放从数据文

件中读取的数据块 Block)

2)  图中左边的有许多 Buffer Header 用虚线指向 Buffers Memory 中的相应的 Buffer

3)  图中左边的有许多实线箭头,这些箭头(其实就是数据结构的链表结构中的指针)将不 同的 Buffer Header 连接成一条 Hash Chain,这边也就是 Cache Buffers Chain(双向链表)。

4)  另外,还有一个 Hash Bucket,其实这只是一个逻辑上的概念,即每一个 Hash Bucket 都 会有一条 Hash Chain 来将 Buffer Header(按照 HASH 算法分类后)连接起来,并由一个Cache Buffers Chains Latch 来进行管理其并发操作。

每当将一个Block读入到Buffer Cache的时候,首先会构造一个与之对应的Buffer Header, 然后根据 HASH 算法( Hash Bucket = MOD(Data Block Address, _DB_BLOCK_HASH_BUCKETS) ), 将 Buffer Header 放到对应的 Hash Bucket Cache Buffers Chain 中去,并在 Buffer Header 中 存放如下信息:

wKiom1RjA7KQLSatAAQalv6MWBk606.jpg

5) 最后,在看看 Hash Latch,即 Cache Buffers Chains Latch,在 Oracle 8i 之前,对应每一个Hash Bucket,Oracle使用一个独立的Latch来维护。Oracle 8i开始,Oracle增加了Hash Bucket 的数量,这样每个 Latch 需要维护多个 Bucket,由于每个 Hash Bucket 上的 Buffer Header 数量大大减低,也使得 Latch 的性能反而提高。其中,BUFFER CACHE Hash Bucket的个数由隐含参数_db_block_hash_buckets 决定,Cache Buffers Chains Latch 的个数由隐 含参数_db_block_hash_latches 决定。

2.2.3现在再回过头,看看怎样查找 BUFFER CACHE 看看数据是否已经存在?

(Y) 如果数据在 BUFFER CACHE 中存在的情况。
1) 根据要查找的数据块的 DBA 等信息,通过上面给的 HASH 算法( Hash Bucket = MOD(Data

Block Address, _DB_BLOCK_HASH_BUCKETS) ),得到该数据块所在的 Hash Bucket
2) 定位到对应的 Hash Bucket 上,在该 Hash Bucket 对应的 Cache Buffers Chain 中加上 Cache Buffers Chains Latch,然后从 Cache Buffers Chain 对应的第一个 Buffer Header 开始扫描查 找,直至之后一个。在这个扫描查找过程中,为防止对 Cache Buffers Chain 并发访问,

将一直持有 Cache Buffers Chains Latch
Cache Buffers Chain 上查找的具体逻辑如下:


wKiom1RjBKuDVLZ1AAQnHjYfDE4773.jpg

3)获得第 2)步中查到的可以直接访问的 Buffer Header 或者构造一致性读后的 Buffer Header 中的 Buffer 地址到 Buffer Memory 来获得数据。

2.2.4 如果上面在Hash Chain中查找不到对应的Buffer Header的情况下,也就前面的问题的第二 种情况:

(N) 如果数据在 BUFFER CACHE 中不存在,则需要从数据文件中读取数据块到 BUFFER CACHE 中去。这个时候,需要查找在 BUFFER CACHE 中寻找足够的内存空间来读取相关的数 据。
再看看前面给的
BUFFER CACHE 示例图:
可以看到
2 条链(LRU LRUW),这 2 条分别将 Buffer Header 连接起来,和 Cache Buffers Chain类似。下面看看这 2 条链的作用:
1)LRU 表示 Least Recently Used,也就是指最近最少使用的 Buffer Header 链表。LRU 链表串 连起来的 Buffer Header 都指向可用数据块(Free Buffer)。
2)LRUW 则表示 Least Recently Used Write,也叫做 Dirty List,也就是脏数据块链表,LRUW串起来的都是修改过但是还没有写入数据文件的内存数据块所对应的 Buffer Header(Dirty Buffer)。
3)一个 Buffer Header 要么在 LRU 上,要么在 LRUW 上,不能同时存在于这两个链表上。

所以,当前面查找数据在 BUFFER CACHE 中不存在的时候(即在 Hash Chain 中查找不到对应 的 Buffer Header 的情况下):
1) 就要扫描 LRU List 寻找 Free Buffer,在扫描过程将持有 Cache Buffers Lru Chain Latch(其

Latch 数量由隐含参数_db_block_lru_latches 决定)。扫描过程中会把已经被修改过的

Buffer 移动到 LRUW 列表上。
2) 找到足够的 Buffer 后,将数据块 Block 读入到 Buffer Cache,构造一个与之对应的 Buffer

Header , 然 后 根 据 HASH 算 法 ( Hash Bucket = MOD(Data Block Address, _DB_BLOCK_HASH_BUCKETS) ),将 Buffer Header 放到对应的 Hash Bucket Cache Buffers Chain 中去,并在 Buffer Header 中存放相关的信息。

2.3、总的在 BUFFER CACHE 中查找数据,可以简单的表示为下图:

wKiom1RjBU6CihCvAAJ69aBhzbI937.jpg

2.4 通常有 2 中情况或导致 Latch 出现竞争:
1) 某一进程长时间的持有 Latch,导致其他进程不能正常的得到 Latch,只能等待。
2) 可能存在大量的 Latch 请求。
2.4.1如果出现 Cache Buffers Chains Latch 竞用严重,根据前面的原理,那么可能有如下原因:1)当多个会话重复访问一个或多个由同一个子Cache Buffers Chains Latch保护的块时热点
块(可以关注 X$BH 中的 TCH 字段)。

2) 大量并发执行的低效 SQL。低效的 SQL 通常需要获取大量的逻辑读,而得到一次逻辑 IO

就得获得一次 Cache Buffers Chains Latch
3) Hash Bucket 中存在长的 Cache Buffers Chains,导致查询数据时候,长时间持有 Latch2.4.2
如果出现 Cache Buffers Lru Chain Latch 竞用严重,那么可能有如下原因:
1) 可能 BUFFER CACHE 分配的空间不够,导致读数据到 BUFFER CACHE 的时候,不断的扫描
LRU List

案例分析:

2.5、测试:模拟 Cache Buffers Chains Latch 竞用 

1、建立测试table,只有一条记录
14:10:47 SCOTT@ prod >select * from test;
         N
----------
        10

2、查看表存储的block信息        
14:10:50 SCOTT@ prod >select dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid) block# from test;
     FILE#     BLOCK#
---------- ----------
         6        365

3、建立测试存储过程
14:16:29 SCOTT@ prod >create or replace procedure test_latch is
14:16:30   2     i number;
14:16:30   3    begin
14:16:30   4      loop
14:16:30   5       select n into i from test;
14:16:30   6    end loop;
14:16:30   7    end;
14:16:31   8  /
Procedure created.

4、建立session测试
14:17:14 SCOTT@ prod >select * from v$mystat where rownum=1;
       SID STATISTIC#      VALUE
---------- ---------- ----------
         1          0          0
 
5、执行procedure进行测试         
14:17:27 SCOTT@ prod >exec test_latch;

6、查看会话信息(event)
14:20:38 SYS@ prod > select sid,event,p1,p1raw,p1text,p2,p2text from v$session where sid=1
       SID EVENT                                  P1 P1RAW            P1TEXT                       P2 P2TEXT
---------- ------------------------------ ---------- ---------------- -------------------- ---------- --------------------
         1 SQL*Net message from client    1650815232 0000000062657100 driver id                     1 #bytes

7、在执行期间可以看到 Cache Buffers Lru Chain Latch 都是 Get 成功,不存在竞争 		
14:21:13 SYS@ prod >select gets,misses,sleeps,spin_gets,wait_time from v$latch
14:21:49   2      where name='cache buffers chains';

      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME
---------- ---------- ---------- ---------- ----------
  63272697          0          0          0          0

8、再建立另一个session,执行相同的任务
14:23:07 SYS@ prod >conn scott/tiger
Connected.
14:23:09 SCOTT@ prod >select * from v$mystat where rownum=1;

       SID STATISTIC#      VALUE
---------- ---------- ----------
        56          0          0

14:23:22 SCOTT@ prod >exec test_latch;
       
9、查看session event				
1)可以看到出现latch: cache buffers chains等待事件。这就是2个session要同时访问同一
个 Block,这个时候在一个会话持有 Latch 的时候,另一个会话必须 Spin 等待获得 Latch。
2)同时也能看到 cursor: pin S等待事件。 
		
14:23:42 SYS@ prod >select sid,event,p1,p1raw,p1text,p2,p2text from v$session where sid in (1,56);
       SID EVENT                                  P1 P1RAW            P1TEXT                       P2 P2TEXT
---------- ------------------------------ ---------- ---------------- -------------------- ---------- --------------------
         1 latch: cache buffers chains     850140044 0000000032AC1B8C address                     150 number
        56 cursor: pin S                  3879408699 00000000E73B143B idn                       65536 value

14:24:34 SYS@ prod >/
       SID EVENT                                  P1 P1RAW            P1TEXT                       P2 P2TEXT
---------- ------------------------------ ---------- ---------------- -------------------- ---------- --------------------
         1 latch: cache buffers chains     850082532 0000000032AB3AE4 address                     150 number
        56 latch: cache buffers chains     850140044 0000000032AC1B8C address                     150 number

14:24:46 SYS@ prod >/
       SID EVENT                                  P1 P1RAW            P1TEXT                       P2 P2TEXT
---------- ------------------------------ ---------- ---------------- -------------------- ---------- --------------------
         1 latch: cache buffers chains     849991208 0000000032A9D628 address                     150 number
        56 latch: cache buffers chains     850231368 0000000032AD8048 address                     150 number
        
14:38:32 SYS@ prod >/

       SID EVENT                                  P1 P1RAW            P1TEXT                       P2 P2TEXT
---------- ------------------------------ ---------- ---------------- -------------------- ---------- --------------------
         1 cursor: pin S                  3879408699 00000000E73B143B idn                     3670016 value
        56 cursor: pin S                  3879408699 00000000E73B143B idn                       65536 value
10、这个时候可以看到 Cache Buffers Lru Chain Latch 都是 Get 出现严重的竞争,出现大量的Misses,Sleeps,Spin_gets: 	        
14:24:48 SYS@ prod >select gets,misses,sleeps,spin_gets,wait_time from v$latch
14:25:06   2  where name='cache buffers chains';
      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME
---------- ---------- ---------- ---------- ----------
 111686729       2358       1293       1065    1043230
Elapsed: 00:00:00.00
14:25:18 SYS@ prod >/
      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME
---------- ---------- ---------- ---------- ----------
 115457171       2667       1466       1202    1178935
Elapsed: 00:00:00.00
14:25:33 SYS@ prod >/
      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME
---------- ---------- ---------- ---------- ----------
 115987352       2722       1497       1226    1204103
Elapsed: 00:00:00.00

查看CPU资源信息:

[oracle@RH6 ~]$ top
top - 14:42:57 up  2:20,  4 users,  load average: 2.12, 2.14, 1.66
Tasks: 177 total,   3 running, 174 sleeping,   0 stopped,   0 zombie
Cpu(s): 67.1%us, 32.6%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.3%hi,  0.0%si,  0.0%st
Mem:   1207500k total,  1143680k used,    63820k free,   101824k buffers
Swap:  2064376k total,        0k used,  2064376k free,   778420k cached
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 6410 oracle    20   0  471m  58m  55m R 49.3  4.9  15:07.16 oracle
 6604 oracle    20   0  468m  20m  18m R 49.0  1.7   9:31.20 oracle
 1887 oracle    -2   0  468m  12m  11m S  1.0  1.1   0:23.97 oracle
 1937 oracle    20   0  483m  13m  12m S  0.3  1.2   0:00.06 oracle
    1 root      20   0  2828 1396 1196 S  0.0  0.1   0:00.81 init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd
    3 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0
    4 root      20   0     0    0    0 S  0.0  0.0   0:00.02 ksoftirqd/0
    5 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 watchdog/0
    6 root      20   0     0    0    0 S  0.0  0.0   0:00.12 events/0
    7 root      20   0     0    0    0 S  0.0  0.0   0:00.00 cpuset

内存信息:

[oracle@RH6 ~]$ vmstat 2 2
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0  69756 101928 778428  108    0     0    92  349  321 13  7 73  7  0
 2  0      0  69748 101928 778428   64    0     0     0 1208  539 63 37  0  0  0

附注:

Cursor: Pin S等待,这是一个由于频繁执行SQL共享解析时产生的竞争。当一个会话尝试以共享模式(S - Share)来获得一个游标时,需要修改相应的Mutex结构的引用计数(reference count),或者增加该计数,或者减少。修改引用技术的原子操作很快(其实和Latch的获取释放类似),但是在频繁解析的情况下,仍然产生了竞争和等待,由此就产生了 cursor : pin S 的等待。

这通常是由于某些SQL以超高频繁的频率执行导致的,当然也可能与系统的CPU能力不足有关。

Mutex机制在Oracle 10g引入,用于替代Library cache pin操作,其性能更高,其原理为在每个Child Cursor上分配一个地址空间记录Mutex,当该Cursor被共享执行时,通过将该位进行加一处理来实现。虽然是指游标共享,但是更新Mutex结构的操作需要排他,当某一个SQL被频繁共享执行时,可能就会出现Pin S的等待。

每个Library Cache对象都有一个reference count (引用计数),用来表明有多少其他对象目前正保留一个对它的引用(reference).  对象A 想要引用对象B, A 就把B 的 reference count 加 1。 当A 结束了对B 的引用, A 就把 B 的reference count 减 1.   当没有任何对象再引用 B 时, B 的 reference count就减为0, B 就被清除(deallocated), 内存就被释放。清除B的时候, 被B所用的对象的 reference count 也可能减小, 也可能使它们被清除。

最简单的解决方案是,将频繁执行的SQL分区拆解,分散竞争,如以下SQL通过注释将同一条SQL分解为2条,就分散了竞争:
    select /*SQL 1*/ a from t_a where id=?

    select /*SQL 2*/ a from t_a where id=?
这种做法在Ebay、Papal、支付宝等公司被广泛采用。

在官方文档上这样解释:

A session waits for "cursor: pin S" when it wants a specific mutex in S (share) mode on a specific cursor and there is no concurrent X holder but it could not acquire that mutex immediately. This may seem a little strange as one might question why there should be any form of wait to get a mutex which has no session holding it in an incompatible mode. The reason for the wait is that in order to acquire the mutex in S mode (or release it) the session has to increment (or decrement) the mutex reference count and this requires an exclusive atomic update to the mutex structure itself. If there are concurrent sessions trying to make such an update to the mutex then only one session can actually increment (or decrement) the reference count at a time. A wait on "cursor: pin S" thus occurs if a session cannot make that atomic change immediately due to other concurrent requests. Mutexes are local to the current instance in RAC environments.