【深入解析--eygle】学习笔记
1.1.2 Cache BuffersLRU Chain闩锁竞争与解决
当用户进程需要读数据到Buffer Cache时或Cache Buffer根据LRU算法进行管理等,就不可避免的要扫描LRU List获取可用Buffer或更改Buffer状态,我们知道,Oracle的Buffer Cache是共享内存,可以为众多并发进程并发访问,所以在搜索的过程中必须获取Latch(Latch是Oracle的一种串行锁机制,用于保护共享内存结构),锁定内存结构,防止并发访问损坏内存中的数据(我们必须认识到对于数据的访问、Buffer的存取就意味着多次的Latch访问,而过于严重的Latch竞争常常是系统的瓶颈所在)。
这个用于锁定LRU的Latch就是我们经常见到的Cache Buffers Lru Chain。
select addr, latch#,name, gets, misses, immediate_gets, immediate_misses
from v$latch
where name = ‘cache buffers lru chain‘;
16:48:46 [email protected]>select addr, latch#, name, gets, misses, immediate_gets,immediate_misses
17:19:05 2 from v$latch
17:19:05 3 where name = ‘cache buffers lru chain‘;
ADDR LATCH# NAME GETS MISSES IMMEDIATE_GETS IMMEDIATE_MISSES
-------------------------- ------------------------- ------ ---------- ------------------------------
0000000060019F08 150 cache buffers lru chain 29129 2 25156 0
17:19:06 [email protected] SQL>
可以从v$latch_children视图察看当前各子Latch使用情况:
select addr,
child#,
name,
gets,
misses,
immediate_gets igets,
immediate_missesimisses
fromv$latch_children
where name= ‘cache buffers lru chain‘;
17:19:06 [email protected] SQL>select addr,
17:46:11 2 child#,
17:46:11 3 name,
17:46:11 4 gets,
17:46:11 5 misses,
17:46:11 6 immediate_gets igets,
17:46:11 7 immediate_misses imisses
17:46:11 8 from v$latch_children
17:46:11 9 where name = ‘cache bufferslru chain‘;
ADDR CHILD# NAME GETS MISSES IGETS IMISSES
---------------- ------- ----------------------------- ---------- ---------- ----------
0000000077572578 16 cache buffers lru chain 0 0 0 0
00000000775724B0 15 cache buffers lru chain 16 0 1 0
0000000077556FE0 14 cache buffers lru chain 0 0 0 0
0000000077556F18 13 cache buffers lru chain 16 0 1 0
000000007753BA48 12 cache buffers lru chain 0 0 0 0
000000007753B980 11 cache buffers lru chain 16 0 1 0
00000000775204B0 10 cache buffers lru chain 0 0 0 0
00000000775203E8 9 cache buffers lru chain 16 0 1 0
0000000077504F18 8 cache buffers lru chain 0 0 0 0
0000000077504E50 7 cache buffers lru chain 16 0 1 0
00000000774E9980 6 cache buffers lru chain 0 0 0 0
00000000774E98B8 5 cache buffers lru chain 29417 2 25624 0
00000000774CE3E8 4 cache buffers lru chain 0 0 0 0
00000000774CE320 3 cache buffers lru chain 16 0 1 0
00000000774B2E50 2 cache buffers lru chain 0 0 0 0
00000000774B2D88 1 cache buffers lru chain 16 0 1 0
16 rows selected.
17:46:13 [email protected] SQL>
如果该Latch竞争激烈,通常有如下方法可以采用:
(1) 适当增大BufferCache,这样可以减少读数据到Buffer Cache的机会,减少扫描Lru List的竞争。
(2) 可以适当增加LRULatch的数量,修改_db_block_lru_latches参数可以实现,但是该参数通常来说是足够的,除非在Oracle Support的建议下或确知该参数将带来的影响,否则不推荐修改。
(3) 通过多缓冲池技术,可以减少不希望的数据老化和全表扫描等操作对于Default池的冲击,从而可以减少竞争。
(4) 优化SQL,减少数据读取,从而减少对于LRU List的扫描。
2.1.3 Cache Buffer Chain闩锁竞争与解决
在LRU和Dirty List这两个内存结构之外,Buffer Cache的管理还存在另外两个重要的数据结构:Hash Bucket和Cache Buffer Chain。
1.Hash Bucket和Cache Buffer Chain
我们可以想象,如果所有的Buffer Cache中的所有Buffer都通过同一个结构管理,当需要确定某个Block在Buffer中是否存在时,将需要遍历整个结构,性能会相当低下。
为了提高效率,Oracle引入了Bucket的数据结构,Oracle把管理的所有的Buffer通过一个内部的Hash算法运算后存放到不同Hash Bucket中,这样通过Hash Bucket进行分割之后,众多的Buffer被分布到一定数量的Bucket之中,当用户需要在Buffer中定位数据是否存在时,只需要通过同样的算法获得Hash值,然后到相应的Bucket中查找少量的Buffer即可确定。
每个Buffer的存放的Bucket由Buffer的数据块地址(DBA,Data Block Address)运算决定。在Bucket内部,通过Cache Buffer Chain(它是一个双向链表)将所有的Buffer通过BufferHeader信息联系起来。
Buffer Header存放的是对应数据块的概要信息,包括数据块的文件号、块地址、状态等。在判断数据块在Buffer中是否存在,通过检查Buffer header即可确定。
让我们通过一个现实的场景来回顾一下这个过程。
如果大家去过老一点的图书馆,查找过手工索引,你可能记得这样的场景:树立在你面前的是一排柜子(那是相当的壮观),柜子又被分为很多小的抽屉,抽屉上按照不同的分类方法标注了相关信息,比如按开头字母顺序,如果我们要查询Oracle相关书籍,就需要找到标记有“O”的 抽 屉 。打 开 抽 屉 ,我们 会 看 到 一 系 列 的 卡 片 ,这 些 卡 片 通 常 被 一 根铁闩串起来(通常就是一个铁丝),每根卡片上会记录相关书籍的信息,可能包括书籍名称、作者、ISBN号、出版日期等,当然这些卡片上还存储了一个重要的信息,就是书籍存放的书架位置信息,有了这个信息,通过翻阅这些卡片,就可以快速地找到想要的书籍,并且
在 需 要 时 能 够 快 速 从 图 书
馆浩如烟海的图书中找到我们需要的一本。
在这里,图书馆就是我们的Buffer Cache,这个Cache可能因为“图书数量”的增加而不断扩大;每个抽屉都是一个Bucket,这个Bucket中存放了根据一定的分类方式(也就是通过Hash运算)归入的图书信息,也就是Buffer Header;抽屉中的每张卡片就是一个Buffer Header,这些Buffer Header上记录了关于数据块的重要信息,如DBA等;这些卡片在Bucket中,通过一个铁闩串接起来,这就是Cache Buffer Chain
由于每个抽屉只有一根铁闩,如果很多同学都想翻阅这个链上的卡片,那么就产生了Cache Buffer Chain的竞争,先来到那个同学持有了Latch就能不停的翻阅,其他同学只好不停的来检查,当然如果检查次数多了(超过了_spin_count), 也 可 以 去 休 息 室 小 憩 一 会 , 再来和其他同学争夺。
从Oracle 9i开始,对于Cache Buffer Chain的只读访问,其Latch可以被共享。也就是说,如果大家都只是翻一翻卡片,那么大家可以一起读,但是如果有人要借走这本书,那么就只能独享这个Latch了。这就是Buffer Cache与Latch竞争。
由于Buffer根据Buffer Header进行散列,最终决定存入那一个Hash Bucket,那么Hash Bucket的数量在一定程度上决定了每个Bucket中Buffer数量的多少,也就间接影响了搜索的性能。所以在不同版本中,Oracle一直在修改算法,优化Hash Bucket的数量。我们可以想象, Bucket的数量多一些,那么在同一时间就可以有更多的同学可以拿到不同的抽屉,进行数据访问;但是更多的抽屉,显然需要更多的存放空间,更多的管理成本,所以优化在什么时候都不是简单的一元方程。
Hash Bucket的设置受一个隐含参数_ DB_BLOCK_HASH_BUCKETS的影响。在Oracle 7和Oracle 8中,该参数缺省值为DB_BLOCK_BUFFERS/4的下一个素数。
通过以上的讨论可以知道,对应每个Bucket,只存在一个Chain,当用户试图搜索Cache Buffer Chain时,必须首先获得Cache Buffer Chain Latch。那么Cache BufferChain Latch的设置就同样值得研究了
我们看到,从Oracle 8i 开始,_db_block_hash_buckets 的数量较以前增加了8 倍,而_db_block_hash_latchs的数量增加有限,这意味着,每个Latch需要管理多个Bucket,但是由于Bucket数量的多倍增加,每个Bucket上的Block数量得以减少,从而使少量Latch管理更多Bucket成为可能。
总结一下上图中所描述的内容。
(1) 从Oracle8i开始,Bucket的数量比以前大大增加;通过增加的Bucket的“稀 释 ”使得每个Bucket上的Buffer数量大大减少。
(2) 在Oracle8i之前,_db_block_hash_latches的数量和Bucket的数量是一致的,每个Latch管理一个Bucket;从Oracle 8i开始每个Latch需要管理多个Bucket,由于每个Bucket上的Buffer数量大大降低,所以Latch的性能反而得到了提高。
(3) 每个Bucket存在一条Cache Buffer Chain。
(4) Buffer Header上存在指向具体Buffer的指针。
2.X$BH与Buffer Header
Buffer Header数据,可以从数据库的字典表中查询得到,这张字典表是:X$BH。X$BH中的BH就是指Buffer Headers,每个Buffer在x$bh中都存在一条记录
Buffer Header中存储每个Buffer容纳的数据块的文件号、块地址、状态等重要信息,可以将Buffer Header看作是Buffer的“名片”,通过这张名片,Buffer的重要信息得以展现;
下图包含了Buffer Header上记录的信息,Hash Bucket上的Chian连接起这些BH。
根据Buffer Header上记录的这些信息,结合dba_extents视图,可以很容易地找到每个Buffer对应的对象信息:
12:01:56 SQL>desc v$bh;
Name Null? Type
----------------------- --------------------------
FILE# NUMBER
BLOCK# NUMBER
CLASS# NUMBER
STATUS VARCHAR2(10)
XNC NUMBER
FORCED_READS NUMBER
FORCED_WRITES NUMBER
LOCK_ELEMENT_ADDR RAW(8)
LOCK_ELEMENT_NAME NUMBER
LOCK_ELEMENT_CLASS NUMBER
DIRTY VARCHAR2(1)
TEMP VARCHAR2(1)
PING VARCHAR2(1)
STALE VARCHAR2(1)
DIRECT VARCHAR2(1)
NEW CHAR(1)
OBJD NUMBER
TS# NUMBER
LOBID NUMBER
CACHEHINT NUMBER
12:07:19 SQL>
X$BH中还有一个重要字段TCH,TCH为Touch的缩写,表征一个Buffer的访问次数, Buffer被访问的次数越多,说明该Buffer越“抢手”,也就可能存在热点块竞争的问题。
以下通过几个简单的查询。以下查询用于获得当前数据库最繁忙的Buffer:
SELECT *
FROM(SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11;
再结合dba_extents中的信息,可以查询得到这些热点Buffer都来自哪些对象:
查询热点块对象:
[email protected] SQL>SELECT e.owner, e.segment_name,e.segment_type
FROMdba_extents e,
(SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11) b
WHEREe.relative_fno = b.dbarfil
ANDe.block_id <= b.dbablk
AND e.block_id + e.blocks > b.dbablk;
OWNER SEGMENT_NAME SEGMENT_TYPE
--------------------------------------------------- -----------------
SYS C_USER# CLUSTER
SYS I_USER1 INDEX
SYS JOB$ TABLE
SYS JOB$ TABLE
SYS SCHEDULER$_CLASS TABLE
SYS SCHEDULER$_CLASS_PK INDEX
SYS SCHEDULER$_JOB TABLE
SYS SCHEDULER$_JOB TABLE
SYS SCHEDULER$_JOB TABLE
SYS SCHEDULER$_LIGHTWEIGHT_JOB TABLE
10 rows selected.
12:16:13 [email protected] SQL>
除了查询X$BH之外,其实也可以从Buffer Cache的转储信息中,看到Buffer Header的具体内容,以下信息来自Level 1级的Buffer Dump:
*** ACTION NAME:() 2014-07-23 13:58:11.585
Dump of buffer cache at level 1 for tsn=2147483647rdba=0
BH (0x69fd83a8) file#: 2 rdba: 0x00804d72(2/19826) class: 4 ba: 0x69c08000
set: 3pool: 3 bsz: 8192 bsi: 0 sflg: 1 pwc: 0,25
dbwrid: 0obj: 58298 objn: -1 tsn: 1 afn: 2 hint: f
hash:[0x77f8d3b0,0x77f8d3b0] lru: [0x69fd85c0,0x69fd8360]
lru-flags:debug_dump on_auxiliary_list
ckptq:[NULL] fileq: [NULL] objq: [NULL] objaq: [NULL]
st: CR md:NULL tch: 1
cr: [scn:0x0.197ed5],[xid: 0x0.0.0],[uba: 0x0.0.0],[cls: 0x0.198787],[sfl: 0x0],[lc:0x0.0]
flags:
BH (0x6a3f1e88) file#: 1 rdba: 0x004084d5(1/34005) class: 1 ba: 0x6a2bc000
set: 3pool: 3 bsz: 8192 bsi: 0 sflg: 1 pwc: 0,25
dbwrid: 0obj: 37 objn: 37 tsn: 0 afn: 1 hint: f
hash:[0x77f8d3c0,0x77f8d3c0] lru: [0x6a3f20a0,0x6a3f1e40]
lru-flags:debug_dump hot_buffer
ckptq:[NULL] fileq: [NULL] objq: [0x6a3f20c8,0x6a3f1e68] objaq:[0x6a3f20d8,0x6a3f1e78]
st:XCURRENT md: NULL fpin: ‘kdiwh100: kdircys‘ tch: 3
flags:only_sequential_access
LRBA:[0x0.0.0] LSCN: [0x0.0] HSCN: [0xffff.ffffffff] HSUB: [65535]
BH (0x6d7de638) file#: 2 rdba: 0x008109aa(2/68010) class: 4 ba: 0x6d4ae000
set: 3pool: 3 bsz: 8192 bsi: 0 sflg: 1 pwc: 0,25
dbwrid: 0obj: 71803 objn: -1 tsn: 1 afn: 2 hint: f
hash:[0x77f8d3f0,0x77f8d3f0] lru: [0x6d7de5f0,0x6d7de850]
lru-flags:debug_dump
ckptq:[NULL] fileq: [NULL] objq: [NULL] objaq: [NULL]
st: CR md:NULL tch: 1
cr: [scn:0x0.197ed5],[xid: 0x0.0.0],[uba: 0x0.0.0],[cls: 0x0.199105],[sfl: 0x0],[lc:0x0.0]
flags:
在Oracle 10g之前,数据库的等待事件中,所有Latch等待被归入Latch Free等待事件中,在Statspack的report中,如果在Top 5等待事件中看到Latch Free这一等待处于较高的位置,就需要DBA介入进行研究和解决。
3.热点块竞争与解决
接下来将通过一个环境的实际情况对热点块的问题进行分析和探讨。
Top 5等待事件都值得关注。注意到这个数据库中Latch Free是最严重的竞争。
由于Latch Free是一个汇总等待事件,我们需要从v$latch视图获得具体的Latch竞争主要是由哪些Latch引起的。
如果cache buffers chains正是主要的Latch竞争。实际上,如果数据库在繁忙的时段,基本上处于停顿状态,大量进程等待latchfree竞争,这些获得Session的等待事件可以很容易地从v$session_wait视图中查询得到:
16:10:22 [email protected] SQL>select SID,EVENT,SEQ#from v$session_wait;
SIDEVENT SEQ#
---------------------------------------------------------------------- ----------
1SQL*Net message to client 7166
2pmon timer 5913
3rdbms ipc message 18352
4 VKTM Logical Idle Wait 1
5rdbms ipc message 5912
6DIAG idle wait 17714
7rdbms ipc message 5923
8DIAG idle wait 17704
9rdbms ipc message 5913
10rdbms ipc message 9958
11rdbms ipc message 11632
12rdbms ipc message 22003
13smon timer 410
14rdbms ipc message 28
15rdbms ipc message 7335
16rdbms ipc message 17710
18rdbms ipc message 549
20rdbms ipc message 565
21rdbms ipc message 3404
22rdbms ipc message 546
26Streams AQ: qmn coordinator idle wait 1270
28Streams AQ: waiting for time management or cleanup tasks 3
30Streams AQ: qmn slave idle wait 712
32rdbms ipc message 5828
33SQL*Net message from client 32
35rdbms ipc message 4902
36SQL*Net message from client 7734
37Space Manager: slave idle wait 125
38Space Manager: slave idle wait 246
40jobq slave wait 54
41jobq slave wait 54
45SQL*Net message from client 429
47Space Manager: slave idle wait 65
33 rows selected.
16:10:30 [email protected] SQL>
如果需要具体确定热点对象,可以从v$latch_children中查询具体的子Latch信息:
SELECT *
FROM(SELECT addr,
child#,
gets,
misses,
sleeps,
immediate_gets igets,
immediate_misses imiss,
spin_gets sgets
FROM v$latch_children
WHERE NAME = ‘cache buffers chains‘
ORDER BY sleeps DESC)
WHEREROWNUM < 11;
_value,address, piece;
X$BH中还存在另外一个关键字段HLADDR,即Hash Chain Latch Address,这个字段可以和v$latch_child.addr进行关联,这样就可以把具体的Latch竞争和数据块关联起来,再结合dba_extents视图,就可以找到具体的热点竞争对象。
到具体热点竞争对象之后,可以进一步地结合v$sqlarea或者v$sqltext视图,找到频繁操作这些对象的SQL,对其进行优化,就可以缓解或解决热点块竞争的问题。通过以下查询可以实现以上的思想,获取当前持有最热点数据块的Latch及Buffer信息:
SELECT b.addr,
a.ts#,
a.dbarfil,
a.dbablk,
a.tch,
b.gets,
b.misses,
b.sleeps
FROM(SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch, hladdr
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11) a,
(SELECT addr, gets, misses, sleeps
FROM v$latch_children
WHERE NAME = ‘cache buffers chains‘) b
WHEREa.hladdr = b.addr;
ADDR TS# DBARFIL DBABLK TCH GETS MISSES SLEEPS
---------------- ---------- ---------- -------------------- ---------- ---------- ----------
0000000077BF8A38 2 3 144 249 5274 0 0
0000000077FEF1F8 2 3 208 248 6702 0 0
0000000077F9FCF0 2 3 272 247 5198 0 0
0000000077FF0900 0 1 2017 246 15293 0 0
0000000077BD1018 2 3 176 245 5187 0 0
0000000077BE5128 2 3 160 245 5034 0 0
0000000077FC77D8 2 3 240 244 6431 5 3
0000000077BEEDB0 0 1 2016 244 9761 0 0
0000000077FDB0E8 2 3 224 244 6161 0 0
0000000077BBD708 2 3 192 242 5805 0 0
利用前面提到的SQL,可以找到这些热点Buffer的对象信息:
SELECT distinct e.owner, e.segment_name,e.segment_type
FROMdba_extents e,
(SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11) b
WHEREe.relative_fno = b.dbarfil
ANDe.block_id <= b.dbablk
ANDe.block_id + e.blocks > b.dbablk;
16:18:29 [email protected] SQL>/
OWNER SEGMENT_NAME SEGMENT_TYPE
------------- -----------------------------------------------
SYS _SYSSMU1_1240252155$ TYPE2 UNDO
SYS _SYSSMU2_111974964$ TYPE2 UNDO
SYS _SYSSMU5_4011504098$ TYPE2 UNDO
SYS _SYSSMU9_3945653786$ TYPE2 UNDO
SYS _SYSSMU6_3654194381$ TYPE2 UNDO
SYS _SYSSMU4_1126976075$ TYPE2 UNDO
SYS _SYSSMU8_3612859353$ TYPE2 UNDO
SYS _SYSSMU10_3271578125$ TYPE2 UNDO
SYS _SYSSMU3_4004931649$ TYPE2 UNDO
SYS _SYSSMU7_4222772309$ TYPE2 UNDO
10 rows selected.
16:25:56 [email protected] SQL>
结合v$sqltext或v$sqlarea,可以找到操作这些对象的相关SQL,让我们继续查询:
break on hash_value skip 1
SELECT /*+ rule */
hash_value,sql_text
FROMv$sqltext
WHERE(hash_value, address) IN
(SELECT a.hash_value, a.address
FROM v$sqltext a,
(SELECT DISTINCT a.owner, a.segment_name, a.segment_type
FROM dba_extents a,
(SELECT dbarfil, dbablk
FROM (SELECT dbarfil,dbablk
FROM x$bh
ORDER BY tchDESC)
WHERE ROWNUM < 11)b
WHERE a.relative_fno = b.dbarfil
AND a.block_id <= b.dbablk
AND a.block_id + a.blocks > b.dbablk) b
WHERE upper(a.sql_text) LIKE ‘%‘ || b.segment_name || ‘%‘
AND b.segment_type = ‘TABLE‘)
ORDER BYhash_value, address, piece;
找到这些SQL之后,剩下的问题就简单了,可以通过优化SQL减少数据的访问,避免或优化某些容易引起争用的操作(如connect by等操作)来减少热点块竞争。
shared pool 和buffer pool 详解(之二, Cache Buffers LRU Chain、Cache Buffers LRU Chain闩锁竞争与解决)