论坛的兄弟太好了,这几个文档非常好,感谢!
Latch free等待事件
原文:oracle wait interface—a practical guide to performance diagnostics & tuning
Richmond shee
Kirtikumar deshpande
K gopalakrishnan
Mcgraw-hill/osborne
2100 powell street, 10th floor
Emeryville, california 94608
U.s.a.
Chapter 6: interpreting locks-related wait events - latch free
文档修订历史:
版本 | 时间 | 作者 |
1.00 | 2006-4-21 | NinGoo |
|
|
|
本文只是对原文的一个大概的翻译,建议最好还是阅读原文。
如果你发现有任何问题,欢迎反馈NinGoo@itpub.net
Latch free等待事件的三个参数:p1-latch的地址;p2-latch编号;p3-请求次数。从oracle10g起,latch free不再包含所有的latch等待,有些latch等待可能表现为单独的等待事件,这个后面有提到一些这样的等待事件,一般情况下我们还是统称为latch free等待事件。在处理latch free等待事件时,需要注意以下几点:
n Latch只是用来保护sga中的内存结构。对数据库中的对象的保护,使用的lock而不是latch。Oracle sga中有许多latch,用来保护sga中各种内存结构不会因为并发访问而损坏。
n 等待latch的是oracle会话。不同的latch类型会导致会话采取不同的策略。
n 在oracle9i(包括9i)之前,latch free等待事件包括了所有的latch等待,但从oracle10g起,latch被分成不同的种类,并且某些latch表现为独立的等待事件。
什么是latch
Latch是一种锁机制。你应该已经熟悉latch的概念和用法,虽然可能你自己并没有意识到。在日常的工作和交流中,latch都经常出现,比如你锁门时,需要获得一个latch;或者你坐到车里,系上安全带,你就把自己放在一个latch的保护中了。
在oracle中,latch是一种轻量级的锁。一般来说,latch由三种内存元素组成:pid(进程id),内存地址和内存长度。Latch保证对共享数据结构的排它性访问,以此来保证内存结构的完整性不受到损坏。在多个会话同时修改或者检视(inspect)sga中同一个内存结构时,必须串行化访问以保证sga中数据结构的完整性。
Latch和lock的异同
Latch和lock有许多不同之处。下表列出了latch和lock之间的比较结果。
| Latch | Lock |
目的 | 只有一个目的:保证对内存结构的排他性访问(从oracle9i开始,cache buffers chain latch可以允许只读共享访问) | 两个目的:如果锁模式是兼容的,允许多个进程共享相同的资源;如果锁模型是不兼容的,保证对共享资源的排它性访问。 |
适用场景 | 只能应用于sga中的数据结构,保护内存对象。Latch只影响单次操作,而和事务无关。 | 保护数据库对象,诸如表,数据块和状态对象等。由应用程序驱动,控制对数据库中数据和元数据的访问。 Lock是事务性的。 |
获取方式 | 两种模式:willing-to-wait和no-wait | 六种模式:null, row share, row exclusive, share, share row exclusive和exclusive |
范围 | 信息都保存在内存中,并且只在本实例可见――latch是实例级别的 | 信息保存在数据库中,并且该数据库的所有实例都可见――lock是数据库级的 |
复杂度 | 使用简单机器指令比如:test-and-set, compare-and-swap或其他简单的cpu指令实现。由于cpu指令平台相关,所以latch在不同的平台的具体实现不一样。 轻量级的。 | 需要上下文切换(context siwtch),使用一系列指令实现。 重量级的。 |
持续事件 | 非常短暂(通常是微妙级的) | 通常在整个事务中都持有。 |
排队机制 | 当一个进程获取latch失败,转入睡眠状态时,他的请求不需要按顺序排队(一个例外情况:latch wait list latch需要排队)。 | 当一个进程获取lock失败,它的请求会进入一个队列,除非指定nowait。 |
死锁 | Latch的实现方式不会产生死锁(不排队) | Lock的排队机制可能导致死锁。死锁发生时会产生相应的跟踪文件。 |
Latch家族
Latch有三种:父latch,子latch和独立latch。父latch和独立latch在oracle的内核代码中固化,子latch则在实例启动时创造。V$latch_parent和v$latch_children视图分别包含父latch和子latch的统计信息。而v$latch则包含独立latch,父latch及其相应子latch的聚合统计信息。
Latch的获取
进程获取latch有两种模式:willing-to-wait和no_wait。No-wait模式只在少数latch中使用。通过no-wait模式获取latch的统计信息记录在immediate_gets和immediate_misses列中,这些列在v$latch,v$latch_parent,v$latch_children视图中都存在。一般来说,no-wait模式在第一次获取一些有很多子latch的latch比如redo copy时使用。如果一个进程第一次获取这些子latch中的任何一个失败,它会立即使用no-wait模式询问下一个。只有当采用no-wait模式试图获取所有的子latch都失败以后,才会转而采用willing-to-wait模式。
通过willing-to-wait模式获取latch的统计信息存放在gets和misses列中。每当一个进程用willing-to-wait模式去获取一个latch时,gets都会增加。
如果进程在第一次请求latch时,latch可用,就会直接获得该latch。在修改任何受到保护的数据结构之前,进程会将一些恢复信息写入到latch恢复区,这样当获得latch的进程发生异常时,pmon进程才能够清理该进程持有的latch。
如果请求latch时,该latch不可用,进程就会在cpu中等待一小段时间(spin)然后重新请求latch。如果latch一直不可用,该过程(spin一段时间然后重新请求)会一直重复。重复的次数由隐含参数_spin_count决定,默认值2000。如果在请求_spin_count次之内获得了latch,就对spin_gets和misses列各加一,否则,进程在v$session_wait中记录latch free等待事件,然后释放cpu,转入睡眠状态。睡眠一定时间后,进程被唤醒并重复上面的过程,一直到获得latch。在成功获得latch后,才会更行sleep列得统计信息。
由于进程只有在获得latch后才会停止对latch得请求,如果某个持有latch的进程发生异常,其他请求该latch的进程该怎么办?岂不是要一直等待下去?不会的。当一个进程请求latch失败一定次数后,它会请求pmon进程查看该latch的持有者,如果持有进程异常,pmon就会清理该进程,释放latch。
每个latch都有一个从0到13的优先级编号。父latch和独立latch的优先级编号是在oracle内核代码中固定的。子latch是÷在实例启动时创建,其优先级编号从其父latch继承。使用优先级可以避免死锁。
n 当一个进程请求no-wait模式的latch时,该latch的优先级编号必须和它当前已经持有的latch的优先级编号相同。
n 当一个进程请求willing-to-wait模式的latch时,该latch的优先级编号必须比它当前已经持有的latch的优先级编号要大。
短等待latch与长等待latch
大多数latch都是短等待latch,所以,进程请求latch时不会等待太长的时间。Oracle进程请求latch失败而导致进入睡眠状态,每次睡眠时间按双指数队列增长,比如睡眠时间可能像下面的队列一样:1,1,2,2,4,4,8,8,16,16,32,32,64,64(厘秒)……,最长的睡眠时间由隐含参数_max_ exponential_sleep,默认2秒。但是如果一个进程当前已经持有其他的latch,则最长睡眠时间会减少为_max_sleep_holding_latch,默认值4厘秒。这样,如果一个进程已经持有latch,就不允许睡眠太长的时间,否则可能会使其他等待该进程所持有的latch的进程的等待时间过长。
有小部分latch属于长等待latch,这意味着这些latch被可能长久持有。如果请求该latch的进程进入睡眠状态,需要其他进程来唤醒,这会产生一个latch wait posting等待事件,该行为由隐含参数_latch_wait_posting控制。在oracle8i,只有2个长等待latch,如下面的示例sql(oracle9i和oracle10g有更多长等待latch)所示。_latch_wait_posting参数从oracle9i起已经废弃,使用latch wait posting的latch的统计信息被记录在waiters_woken列中。
Select name, immediate_gets, immediate_misses,
gets, misses, sleeps, waiters_woken
From v$latch
Where waiters_woken > 0;
immediate immediate waiters
Name gets misses gets misses sleeps woken
------------------------ --------------- -------------------- ----------------- ------------- --------------- ---------------
Shared pool 0 0 18464156 3124 1032 485
Library cache 85508 124 1564400540 4334362 1516400 690419
Latch分类
从oracle9iR2开始,latch可以被分成不同的类型,每一类latch都可以有不同的_spin_count值。在早期版本中,如果改变_spin_count值,会对系统中所有的latch造成影响。这样可能会增加cpu的负担,而latch分类则正是为解决这个问题而引入的。例如,如果cache buffers chains latch的sleep次数很多,而且cpu资源充足,我们就可以将cache buffer chains latch所在的分类的_spin_count的值调高。高_spin_count值可以降低sleeps和misses的次数,代价是花费更多cpu时间。内部视图x$ksllclass (kernel serverice lock latches class)包含了latch的所有八种类型的信息。其中indx列就是latch类型编号。
Select indx, spin, yield, waittime
from x$ksllclass;
indx spin yield waittime
---------- ---------- ---------- ----------
0 20000 0 1
1 20000 0 1
2 20000 0 1
3 20000 0 1
4 20000 0 1
5 20000 0 1
6 20000 0 1
7 20000 0 1
8 rows selected.
x$ksllclass中的每行记录都和一个隐藏参数_latch_class_n关联,通过这些隐含参数,你可以改变相应的_spin_count,yield和waittime的值(x$视图不能由用户手动更新)。例如,latch类型0由参数_latch_class_0控制,latch类型1由参数_latch_class_1控制。如果你想将cache buffers chains latch的_spin_count值改成10,000,首先你需要知道latch的编号,通过以下查询可以获得
Select latch#, name
From v$latchname
Where name = ’cache buffers chains’;
latch# name
---------- -------------------------------
97 cache buffers chains
然后,你需要修改init.ora中的下面2个参数:
_latch_class_1 = "10000"
_latch_classes = "97:1"
第一个参数_latch_class_1将类型1的spin_count值改为10,000;
第二个参数_latch_classes 将编号为97的latch分配到类型1。
再次查询x$ksllclass,我们可以发现:
Select indx, spin, yield, waittime
From x$ksllclass;
indx spin yield waittime
---------- ---------- ---------- ----------
0 20000 0 1
1 10000 0 1
2 20000 0 1
3 20000 0 1
4 20000 0 1
5 20000 0 1
6 20000 0 1
7 20000 0 1
8 rows selected.
Select a.kslldnam, b.kslltnum, b.class_ksllt
From x$kslld a, x$ksllt b
Where a.kslldadr = b.addr
And b.class_ksllt > 0;
Kslldnam kslltnum class_ksllt
------------------------- ---------- -----------
Process allocation 3 2
Cache buffers chains 97 1
注意:如果服务器的cpu资源紧张,请不要增加_spin_count的值。当然,默认值2000是很久以前定下来的值,当时的cpu比现在的cpu要慢得多。
Latch free等待事件可以告诉我们什么?
如果我们在v$session_wait中发现有latch free等待事件,就意味着,进程在请求一个willing_to_wait模式的latch,在重试了_spin_count次后还是没有获得latch,然后转入睡眠状态了。如果latch争用严重,将会由于不断的spin导致cpu资源紧张,从而增加系统响应时间。
V$system_event视图的total_waits列记录了进程获取willing-to-wait模式latch失败的次数。V$latch的sleeps列记录了进程由于等待某个latch而进入睡眠状态的次数。由于一个进程在_spin_count次尝试请求latch失败后会转入睡眠状态,total_waits列应该等于sleeps列的值的和,如以下sql所示。但是,某些时候,total_waits会比sleeps的和要大,这是因为,只有在进程获得latch后才会更新total_waits的值,而不是每次请求latch失败就更新。
Select a.total_waits, b.sum_of_sleeps
from (select total_waits from v$system_event where event = ’latch free’) a,
(select sum(sleeps) sum_of_sleeps from v$latch) b;
total_waits sum_of_sleeps
----------- -------------
414031680 414031680
由于latch free等待时间一般较短,所以在很少一段时间内,total_waits就可能变得非常大,这并不一定意味着系统有问题。只有当time_waited的值也非常显著的时候,你才需要关注latch free等待事件。
Latch失败区域(latch miss locations)
V$latch_misses视图保存了latch失败在oracle内核代码中的区域信息。这些信息对于oracle support诊断latch等待事件有帮助。你可以通过以下查询查看位置信息。Steve adams有篇非常棒的关于这方面的文章http://www.ixora.com.au/newsletter/2001_02.htm
Select location,
parent_name,
wtr_slp_count,
sleep_count,
longhold_count
from v$latch_misses
where sleep_count > 0
order by wtr_slp_count, location;
longhold
location parent_name wtr_slp_count sleep_count count
-------------------- -------------------- ------------- ----------- --------
. . .
Kglupc: child library cache 7879693 11869691 0
kghupr1 shared pool 8062331 5493370 0
kcbrls: kslbegin cache buffers chains 9776543 14043355 0
kqrpfl: not dirty row cache objects 15606317 14999100 0
kqrpre: find obj row cache objects 20359370 20969580 0
kglhdgn: child: library cache 23782557 9952093 0
kcbgtcr: fast path cache buffers chains 26729974 23166337 0
kglpnc: child library cache 27385354 7707204 0
Oracle10gR1中的latch
在Oracle10g之前,所有的latch等待都显示为latch free等待事件。你可以通过latch free事件的p2参数和v$latch.latch#关联或者通过10046事件来查找某个进程争用的是哪个latch。而在Oracle10g中,latch被分成许多独立的等待。下面是oracle10gR1的latch一个列表:
Select name
from v$event_name
where name like ’latch%’
order by 1;
name
----------------------------------------------------------------
latch activity
latch free
latch: in memory undo latch
latch: kcl gc element parent latch
latch: mql tracking latch
latch: cache buffer handles
latch: cache buffers chains
latch: cache buffers lru chain
latch: checkpoint queue latch
latch: enqueue hash chains
latch: gcs resource hash
latch: ges resource hash list
latch: latch wait list
latch: library cache
latch: library cache lock
latch: library cache pin
latch: messages
latch: object queue header heap
latch: object queue header operation
latch: parallel query alloc buffer
latch: redo allocation
latch: redo copy
latch: redo writing
latch: row cache objects
latch: session allocation
latch: shared pool
latch: undo global data
latch: virtual circuit queues
28 rows selected.
Latch产生的原因,诊断以及对策
Latch争用通常意味这某个进程持有latch的时间过长。如果latch争用明显,系统性能将显著下降。在高并发的环境中,latch争用经常发生,并且你无法完全消除latch争用。在v$system_event中总会出现latch free等待事件。只有当time_waited相对实例启动以来的总时间比较明显时,你才需要关注latch争用。当latch在系统范围内的等待时间比较显著时,你可以通过v$latch中的sleeps列来发现争用显著的latch:
Select name, gets, misses, immediate_gets, immediate_misses, sleeps
from v$latch
order by sleeps;
immediate immediate
name gets misses gets misses sleeps
-------------------- ---------- ---------- ----------- --------- ----------
enqueue hash chains 42770950 4279 0 0 1964
shared pool 9106650 5400 0 0 2632
row cache objects 69059887 27938 409 0 7517
enqueues 80443314 330167 0 0 13761
library cache 69447172 103349 465827 190 44328
cache buffers chains 1691040252 1166249 61532689 5909 127478
. . .
对不同的latch,其产生的原因以及可采取的对策都有所不同。详细的说明所有的latch可以写成一本书了。这里我们只选择最常见的五个latch加以说明:shared pool, library cache, cache buffers chains, cache buffers lru chain和row cache objects。
Shared pool和library cache latch
Oracle的共享池由不同的结构组成。主要包括:数据字典缓存,sql区和库缓存。通过v$sgastat你可以查看其他一些结构。Shared pool latch主要用来保护共享池的内存结构,当分配或者释放共享池内存时需要先获得该latch。例如,为一个新的sql语句或pl/sql过程、函数、包,触发器等分配空间(硬解析)时,或者为换出、清除某些内存块,以便为新的对象腾出足够的空间时,都需要获取shared pool latch。
在oracle9i之前,共享池内存结构由一个独立shared pool latch保护,从9i开始,则有最多7个子latch可以用于共享池的保护。这也是为什么oracle9i可以将共享池分成多个子共享池的原因(服务器至少需要4颗cpu,并且shared_pool_size大于250m才能使用多个子共享池的特性)。子共享池的个数可以通过隐含参数_kghdsidx_count手动调节,该参数同时会指定合适的shared pool子latch的个数。如果你手动增加子共享池的个数,你应该同时增加shared_pool_size的值,因为每个子共享池都有自己的结构,lru列表和shared pool latch。否则,实例启动时可能会遇到以下错误:
Ora-04031: unable to allocate 32 bytes of shared memory ("shared pool","unknown object","sga heap(5,0)","fixed allocation callback").
下面的统计信息是从一个16颗cpu,shared_pool_size为256m的oracle9i数据库中读取的。由_kghdsidx_count参数可知共享池被分成2个子池,通过x$kghlu(kernel generic heap lru)可以知道lru列表也有2个。v$latch_children视图显示了7个子latch中的2个已经被使用。
Select a.ksppinm, b.ksppstvl
from x$ksppi a, x$ksppsv b
where a.indx = b.indx
and a.ksppinm = ’_kghdsidx_count’;
ksppinm ksppstvl
------------------ ----------
_kghdsidx_count 2
select addr, kghluidx, kghlufsh, kghluops, kghlurcr, kghlutrn, kghlumxa
from x$kghlu;
addr kghluidx kghlufsh kghluops kghlurcr kghlutrn kghlumxa
---------------- -------- ---------- ---------- -------- -------- ----------
80000001001581b8 2 41588416 496096025 14820 17463 2147483647
8000000100157e18 1 46837096 3690967191 11661 19930 2147483647
select addr, name, gets, misses, waiters_woken
from v$latch_children
where name = ‘shared pool’;
addr name gets misses waiters_woken
---------------- ------------- ----------- ---------- -------------
c00000004c5b06b0 shared pool 0 0 0
c00000004c5b0590 shared pool 0 0 0
c00000004c5b0470 shared pool 0 0 0
c00000004c5b0350 shared pool 0 0 0
c00000004c5b0230 shared pool 0 0 0
c00000004c5b0110 shared pool 1385021389 90748637 12734879
c00000004c5afff0 shared pool 2138031345 413319249 44738488
库缓存中主要保存游标,sql语句,执行计划,分析树等。这些结构由library cache latch保护。当oracle进程修改、检查、销连接(pinning)、锁定、装载,或者执行库缓存中的结构时,都需要先获得library cache latch。通过查询v$latch_children可以得知当前实例中的library cache子latch的个数。通常应该为大于cpu个数的最小质数,该值由隐含参数_kgl_latch_count控制。从oracle9i开始,v$sqlarea视图增加了一个child_latch列,用来指示游标在各个library cache latch是如何分布的。
Select count(*)
from v$latch_children
where name = ‘library cache’;
Shared pool 和 library cache latch争用原因一 ―― 分析
Shared pool和library cache latch争用通常是由于硬分析引起。硬分析需要分配新的游标,或者将已经换出的游标重新执行。硬分析过多说明sql语句没有充分绑定变量。硬分析是代价十分昂贵的操作,在分析期间需要一直持有ibrary cache latch。
n 通过下列查询可以发现系统中是否存在大量硬分析。软分析数则可以用总分析数减去硬分析数获得
Select a.*, sysdate-b.startup_time days_old
from v$sysstat a, v$instance b
where a.name like ‘parse%’;
statistic# name class value days_old
---------- ------------------------- ----- ---------- ----------
230 parse time cpu 64 33371587 4.6433912
231 parse time elapsed 64 63185919 4.6433912
232 parse count (total) 64 2137380227 4.6433912
233 parse count (hard) 64 27006791 4.6433912
234 parse count (failures) 64 58945 4.6433912
“ora-00942: table or view does not exist”
n 查看当前会话是否有大量硬分析
Select a.sid, c.username, b.name, a.value,
round((sysdate - c.logon_time)*24) hours_connected
from v$sesstat a, v$statname b, v$session c
where c.sid = a.sid
and a.statistic# = b.statistic#
and a.value > 0
and b.name = ‘parse count (hard)’
order by a.value;
sid username name value hours_connected
---- ---------- ------------------ ---------- ---------------
510 sys parse count (hard) 12 4
413 pmappc parse count (hard) 317 51
37 pmhcmc parse count (hard) 27680 111
257 pmappc parse count (hard) 64652 13
432 pmappc parse count (hard) 105505 13
在oracle10g中,通过v$sess_time_model视图中对硬分析和失败分析的时间统计信息,可以知道硬分析的来源。下面的例子显示了某个会话的v$sess_time_model信息。
Select *
From v$sess_time_model
Where sid = (select max(sid) from v$mystat);
sid stat_id stat_name value
---- ---------- ------------------------------------------------ ----------
148 3649082374 db time 11141191
148 2748282437 db cpu 9530592
148 4157170894 background elapsed time 0
148 2451517896 background cpu time 0
148 4127043053 sequence load elapsed time 0
148 1431595225 parse time elapsed 3868898
148 372226525 hard parse elapsed time 3484672
148 2821698184 sql execute elapsed time 9455020
148 1990024365 connection management call elapsed time 6726
148 1824284809 failed parse elapsed time 0
148 4125607023 failed parse (out of shared memory) elapsed time 0
148 3138706091 hard parse (sharing criteria) elapsed time 11552
148 268357648 hard parse (bind mismatch) elapsed time 4440
148 2643905994 pl/sql execution elapsed time 70350
148 290749718 inbound pl/sql rpc elapsed time 0
148 1311180441 pl/sql compilation elapsed time 268477
148 751169994 java execution elapsed time 0
上面的分析统计信息可以按照下面的方法分组:
1. parse time elapsed
2. hard parse elapsed time
3. hard parse (sharing criteria) elapsed time
4. hard parse (bind mismatch) elapsed time
2. failed parse elapsed time
3. failed parse (out of shared memory) elapsed time
n 确定系统中的常量sql语句(literal sql),它们往往都是可以使用并且应该使用绑定变量的。下面通过查询v$sqlarea视图,列出超过4个执行实例的sql语句的前40个字符,这里假设你的系统中前40个字符相同的语句被认为是没有使用绑定变量的常量sql。很明显,如果使用更长的字符串或者更多的执行实例作为过滤条件,得到的结果sql语句会少很多。然后你可以根据得到的结果,建议程序开发人员对这些常量sql语句尽量使用绑定变量。
Select hash_value, substr(sql_text,1,80)
from v$sqlarea
where substr(sql_text,1,40) in (select substr(sql_text,1,40)
from v$sqlarea
having count(*) > 4
group by substr(sql_text,1,40))
order by sql_text;
hash_value substr(sql_text,1,80)
---------- -----------------------------------------------------------------
2915282817 select revenue.customer_id, revenue.orig_sys, revenue.service_typ
2923401936 select revenue.customer_id, revenue.orig_sys, revenue.service_typ
303952184 select revenue.customer_id, revenue.orig_sys, revenue.service_typ
416786153 select revenue.customer_id, revenue.orig_sys, revenue.service_typ
2112631233 select revenue.customer_id, revenue.orig_sys, revenue.service_typ
3373328808 select region_id from person_to_chair where chair_id = 988947
407884945 select region_id from person_to_chair where chair_id = 990165
3022536167 select region_id from person_to_chair where chair_id = 990166
3204873278 select region_id from person_to_chair where chair_id = 990167
643778054 select region_id from person_to_chair where chair_id = 990168
2601269433 select region_id from person_to_chair where chair_id = 990169
3453662597 select region_id from person_to_chair where chair_id = 991393
3621328440 update plan_storage set last_month_plan_id = 780093, pay_code
2852661466 update plan_storage set last_month_plan_id = 780093, pay_code
380292598 update plan_storage set last_month_plan_id = 780093, pay_code
2202959352 update plan_storage set last_month_plan_id = 780093, pay_code
. . .
在oracle9i中,也可以通过如下的语句查询v$sql视图中使用了相同的执行计划的sql语句,这些语句也可能是常量sql语句。
Select plan_hash_value, hash_value
from v$sql
order by 1,2;
如果你的系统中存在大量的常量sql语句,当你将它们改为充分使用绑定变量后,对shared pool latch和library cache latch的争用将会显著减少。更改sql语句,使用绑定变量,这通常需要修改应用程序。另外一个不需要改动应用的方法是,修改初始化参数cursor_sharing,将其值改为force(注:原文如此。一般情况下请尽量使用similar而不是force),这个参数允许系统对一些只有常量值不一样的sql语句共享游标,以减少latch争用、内存占用和硬分析。
注意:在oracle8i早期版本中,使用cursor_sharing可能导致bug。在使用了物化视图的环境中,请慎用该参数,否则可能导致长久的library cache pin等待。另外,使用cursor_sharing = force可能导致优化器生成错误的执行计划。这或多或少的会对系统性能造成影响。从oracle9i起,优化器可以通过窥视pga中的信息来进行变量绑定,以此生成合适的执行计划,该特性可以通过隐含参数_optim_peek_user_binds来开启,并且该特性只对那些需要硬分析的sql语句有效,这意味着会基于绑定变量的第一个值来生成执行计划。
当一个新的sql语句到达时,oracle首先在库缓存中检查是否已经有相同的语句存在。如果已经存在,则可以花费较小的代价执行该语句,这就是所谓的软分析。硬分析通常意味着较坏的性能,而软分析过多也不是什么好事。在软分析期间,需要持有library cache latch,并且oracle依然需要对语句进行语法和语义检查,除非该语句已经在会话的游标缓存中。你可以通过设置参数session_cached_cursors来减少library cache latch的持有时间(具体信息请查看oracle metalin,编号#30804.1和 #62143.1)。但是,减少软分析的最佳方法还是优化应用程序。最好是分析一次,执行多次(很像java的宣传口号),而不要分析一次,执行一次。你可以通过v$sqlarea的parse_calls列来查找分析过多的sql语句。
Shared pool latch争用原因二 ―― 过大的共享池
从oracle9i起,由于引入了多个子共享池的特性,过大的共享池不再是一种坏事。在9i之前,过大的共享池通常会引起shared pool latch争用。共享池中可用内存分成不同的内存块(chunk),不同大小范围的块由不同的可用列表(freelist)来管理。在共享池中分配空间时,需要扫描可用列表,扫描期间,需要持有shared pool latch。过大的共享池会使得可用列表过长,从而使得shared pool latch的持有时间变长。在高并发环境中,latch持有时间过长就可能造成latch争用(表现为较高的sleeps和misses值),尤其是大量使用常量sql的系统,对这样的系统,不要一味想着加大共享池,更重要的是想一想你为什么会需要保存这么多不能共享的语句到共享池中。
通过alter session set events ’immediate trace name heapdump level 2’可以转存共享池信息,从中可以看到共享池的可用列表信息。在生成的跟踪文件中查找bucket,你可以发现内存块(chunk)分配到不同的bucket上。另外,你也可以通过下面的方法生成一个查询来列出共享池的可用内存管理信息。该查询只要生成一次,就可以在生成该跟踪文件的数据库中重复使用,但不要在其他不同版本的数据库中使用,否则可能得到错误的结果。该查询在oracle10gR1中不可用,因为它限制了case分支数不能超过128(具体信息参考oracle metalink 编号#131557.1和bug号#3503496),或者你可以通过使用decode和sign函数来完成该功能。
Sql> oradebug setmypid
statement processed.
Sql> oradebug dump heapdump 2
statement processed.
Sql> oradebug tracefile_name
/u01/admin/webmon/udump/orcl_ora_17550.trc
sql> exit
(oradebugalter session set events ’immediate trace name heapdump level 2’)
$ grep bucket /u01/admin/webmon/udump/orcl_ora_17550.trc > tmp.lst
$ sed ’s/size=/ksmchsiz>=/’ tmp.lst > tmp2.lst
$ sed ’s/ bucket //’ tmp2.lst | sort –nr > tmp.lst
# shell
echo ’select ksmchidx, (case’
cat tmp.lst | while read line
do
echo $line | awk ’{print "when " $2 " then " $1}’
done
echo ’end) bucket#,’
echo ’ count(*) free_chunks,’
echo ’ sum(ksmchsiz) free_space,’
echo ’ trunc(avg(ksmchsiz)) avg_chunk_size’
echo ’from x$ksmsp’
echo "where ksmchcls = ’free’"
echo ’group by ksmchidx, (case’;
cat tmp.lst | while read line
do
echo $line | awk ’{print "when " $2 " then " $1’}
done
echo ’end);’
如果你发现数据库的共享池可用列表过长,并且系统中使用了常量sql,你或许应该考虑减少shared_pool_size。这会降低系统中对shared pool latch的争用。但要注意共享池不能太小,否则可能导致ora-04031错误。另外,通过使用dbms_shared_pool.keep将常用对象钉在内存中也是个好主意,v$db_object_cache中保存了钉在内存中的对象的信息。
Library cache latch争用原因三 ―― 语句版本数过多
对于字符完全一致但是由于引用不同的对象而不能共享的sql语句,oracle使用多个子游标来指向该语句的不同版本。例如,系统中有三个名叫customer的表,但是属于不同的模式。则对于语句select * from customer,不同的模式执行该语句,语句字符上完全一样,其hash值完全一样,但是该语句无法共享,因为它引用的对象不同。所以会生成该语句的不同子版本。当一个sql语句有多个子版本时,oracle需要比较该语句的所有存在的子版本,在此期间需要持有library cache latch,这样可能导致library cache latch争用。解决这种情况也很简单,在系统中,尽量不要使用相同的对象名。下面的查询列出了v$sqlarea中子版本超过20的所有sql语句:
Select version_count, sql_text
from v$sqlarea
where version_count > 20
order by version_count, hash_value;
注意:在oracle8i中,语句的版本过多,可能导致和sql执行监控相关的bug(参考oracle metalink 编号#62143.1)。这个bug会导致sql无法共享。可以通过将隐含参数_sqlexec_progression_cost设置为0来禁用sql执行监控特性,该参数同时会禁止v$session_longops中的数据。
Oracle提供了视图v$sql_shared_cursor,其中可以看到为什么无法共享一个已经存在子游标。每个列都限制了游标无法共享的一个原因。
-- 大写的列是oracle9i起才有列.
-- oracle 10g R1还有其他8个列
select a.*, b.hash_value, b.sql_text
from v$sql_shared_cursor a, v$sqltext b, x$kglcursor c
where a.unbound_cursor || a.sql_type_mismatch ||
a.optimizer_mismatch || a.outline_mismatch ||
a.stats_row_mismatch || a.literal_mismatch ||
a.sec_depth_mismatch || a.explain_plan_cursor ||
a.buffered_dml_mismatch || a.pdml_env_mismatch ||
a.inst_drtld_mismatch || a.slave_qc_mismatch ||
a.typecheck_mismatch || a.auth_check_mismatch ||
a.bind_mismatch || a.describe_mismatch ||
a.language_mismatch || a.translation_mismatch ||
a.row_level_sec_mismatch || a.insuff_privs ||
a.insuff_privs_rem || a.remote_trans_mismatch ||
a.logminer_session_mismatch || a.incomp_ltrl_mismatch ||
a.overlap_time_mismatch || a.sql_redirect_mismatch ||
a.mv_query_gen_mismatch || a.user_bind_peek_mismatch ||
a.typchk_dep_mismatch || a.no_trigger_mismatch ||
a.flashback_cursor <> ’nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn’
and a.address = c.kglhdadr
and b.hash_value = c.kglnahsh
order by b.hash_value, b.piece;
Cache buffers chains latch
当一个数据块读入到sga中时,该块的缓冲区头(buffer header)会放置在一个hash bucket的链表(hash chain)中。该内存结构由一系列cache buffers chains子latch保护(又名hash latch或者cbc latch)。下图描述了hash latch,hash bucket,buffer header和hash chain的关系
图一. Oracle8i以上的数据缓冲区示意图
一个进程要添加,删除,查找,检视,读取或者修改hash chain上的块,必须先获得cache buffers chains latch,以保证对该chain的排他访问,为保证完整性,必须牺牲并发性。
注:从oracle9i开始,对cache buffer chains latch可用只读共享访问,这可以减少部分争用,但并不能完全消除争用。
一个特定的块头具体分配到哪个hash bucket,是通过dba(data block address)和隐含参数_db_block_hash_buckets实现的。例如,hash bucket = mod(dba, _db_block_hash_buckets)。通过查询v$bh和x$bh视图可以发现缓冲区头的争用,也可以通过以下语句转存缓冲区头的信息:
Alter system set events ’immediate trace name buffers level 1’;
在oracle8.0之前,每一个hash bucket都有一个cache buffers chains latch(hash latch),并且hash bucket都只有一个hash chain链表。换句话说,hash latch,hash backet和hash chain之间是1:1:1的关系。默认的hash bucket个数为大于db_block_buffers / 4的最小质数,通过隐含参数_db_block_hash_buckets可以修改该值。例如,假如db_block_buffers = 50000,则该实例中有12501个hash latch,有12501个hash bucket,有12501个hash chain。
从oracle8i开始,oracle将hash latch和hash bucket之前的关系改成了 1:m,但hash bucket和hash chain之间还是1:1,也就是一个hash latch可以同时保护多个hash chain链表。这样,可以显著的减少系统中hash latch的个数。Hash latch的默认个数还是和db_block_buffers的值相关。当数据缓冲区小于1G时,一般都是1024个。通过隐含参数_db_blocks_hash_latches可以修改该值。下面的语句查询实例中的hash latch数目:
Hash bucket的默认个数等于2 * db_block_buffers,可以通过隐含参数_db_block_hash_buckets修改。这样,假如db_block_buffers=50000,则系统中有100000个hash bucket和100000个hash chain,但是只有1024个hash latch(假设块大小为8k)。由此可以看出,oracle8i和oracle8.0中,hash latch数目发生了显著的变化。许多DBA认为,由于hash latch数目显著减少,可能会导致latch争用的增加。但是,oracle解释说,通过以8为因子增加hash chain链表的个数,单个链表会比以前变得更短,这样每次cache buffer chains latch的持有时间也变得更短,以此来补偿latch个数减少带来的latch争用。但不要依赖这个机制,你还是会看到很多latch争用。
Oracle10g使用了不同的算法来决定系统中hash bucket的默认个数。一开始好像等于db_cache_size / 4。但后来的测试证明,在某些不同的db_cache_size值范围内,例如当db_cache_size在132m和260m之间时,hash bucket的个数是一个常数。下面的表中列出了从oracle8i到oracle10g,不同的数据缓冲大小时的hash bucket的默认个数,其中数据块大小都是8k。
Oracle 10g 操作系统solaris | |||||||
Db_cache_size | 32m | 64m | 128m | 256m | 512m | 1024m | 2048m |
_ksmg_granule_size | 4m | 4m | 4m | 4m | 4m | 16m | 16m |
_db_block_buffers | 3976 | 7952 | 15904 | 31808 | 63616 | 127232 | 254464 |
_db_block_hash_buckets | 8192 | 16384 | 32768 | 65536 | 131072 | 262144 | 524288 |
_db_block_hash_latches | 1024 | 1024 | 1024 | 1024 | 1024 | 1024 | 2048 |
Oracle9i 操作系统solaris | |||||||
Db_cache_size | 32m | 64m | 128m | 256m | 512m | 1024m | 2048m |
_ksmg_granule_size | 4m | 4m | 16m | 16m | 16m | 16m | 16m |
_db_block_buffers | 4000 | 8000 | 16016 | 32032 | 64064 | 128128 | 256256 |
_db_block_hash_buckets | 8009 | 16001 | 32051 | 64067 | 128147 | 256279 | 512521 |
_db_block_hash_latches | 1024 | 1024 | 1024 | 1024 | 1024 | 1024 | 2048 |
Oracle8i 操作系统solaris | |||||||
Db_block_buffers | 4000 | 8000 | 16016 | 32032 | 64064 | 128128 | 192192 |
_db_block_hash_buckets | 8000 | 16000 | 32032 | 64064 | 128128 | 256256 | 384384 |
_db_block_hash_latches | 1024 | 1024 | 1024 | 1024 | 1024 | 1024 | 2048 |
Cache buffers chains latch争用原因一 ―― 低效的sql语句
低效的sql语句是导致cache buffers chains latch争用的主要原因。在高并发系统中, atch free时间可能因此非常明显。典型的情况是,应用程序开启多个并发会话执行相同的低效sql,并且访问同样的数据集。
你应该时刻铭记下面三点:
n 每次逻辑读都需要请求一次latch。
n 只有获得某个latch之后才会停止对该latch的不断请求。
n 在某个时刻,只有一个进程可以获得cache buffers chains latch,而该latch可能用于保护很多的数据块,其中的某些块可能正在被其他进程请求(当然,前面也已经提过,oracle9i允许只读性质的cache buffers chains latch共享)。
一般而言,较少的逻辑读意味着较少的latch请求,也就意味着较少的latch争用和更好的系统性能。所以,你应该找出导致cache buffers chains latch争用的低效sql语句,优化这些语句,尽量降低其逻辑读。那些buffers_get/executions比值较大的sql可能就是你需要调整的语句。
注1:某些dba可能通过修改隐含参数_db_blocks_hash_latches来增加系统中cache buffers chains latch的个数,而不是首先去优化低效的sql语句,这是不正确的,增加latch能暂时降低对latch的争用,但这是治标不治本的方法。
注2:在sun solareis平台上,我们将一个数据库从oracle8.1.7.4升级到oracle9.2.05之后,发现了大量的cache buffers chains latch争用,新的优化器为应用程序生成了低效的执行计划。一些隐藏的优化器相关的参数,在oracle8i中是无效的,但在oracle9i中有效。在重设这些参数后,问题得意解决。如果你遭遇到同样的情况,建议请求oracle的技术支持。
Cache buffers chains latch争用原因二 ―― 热点块
热点块是导致cache buffers chains latch争用的另外一个主要原因。当多个进程重复访问一个或多个由同一个cache buffers chains latch保护的块时会导致该问题。这通常是应用程序引起的。在这种情况下,增加cache buffers chains latch的个数对热点块导致的争用没有什么作用。因为数据块分布在哪个hash bucket和hash chain上是由块地址(dba:data block address)和hash bucket的个数决定的,和hash latch的个数没有关系。只要块地址和hash bucket数没有改变,这些热点块还是会分布在原来的hash bucket和hash chain上,还是由原来的hash latch保护,那么就还会对这些hash latch产生争用。除非系统中latch数目显著的增加(这样每个latch管理的hash bucket就会很少,甚至一个latch管理一个hash bucket,这样原来的热点块可能就会有其他的几个latch来管理,而不再需要争用原来的那个latch)。
解决这样的cache buffers chains latch争用,最好的方法是找出热点块。通过latch free等待事件的p1raw参数可以知道是否是因为热点块导致了latch争用。(在oracle10g中,cache buffers chains latch的相关等待事件不再是latch free,而是cache buffers chains)。P1raw参数是latch的地址。如果多个会话都在等待同一个latch地址,那么恭喜你遇到热点块问题了。下面的例子中,可以发现由地址为00000400837d7800 和00000400837de400的latch保护的hash chain中存在热点块(多个会话都在等待这两个地址的latch)。
Select sid, p1raw, p2, p3, seconds_in_wait, wait_time, state
from v$session_wait
where event =’latch free’
order by p2, p1raw;
sid p1raw p2 p3 seconds_in_wait wait_time state
---- ---------------- --- --- --------------- ---------- ------------------
38 00000400837d7800 98 1 1 2 waited known time
42 00000400837d7800 98 1 1 2 waited known time
44 00000400837d7800 98 3 1 4 waited known time
58 00000400837d7800 98 2 1 10 waited known time
85 00000400837d7800 98 3 1 12 waited known time
214 00000400837d7800 98 1 1 2 waited known time
186 00000400837d7800 98 3 1 14 waited known time
149 00000400837d7800 98 2 1 3 waited known time
132 00000400837d7800 98 2 1 2 waited known time
101 00000400837d7800 98 3 1 4 waited known time
222 00000400837d7800 98 3 1 12 waited known time
229 00000400837d7800 98 3 1 4 waited known time
230 00000400837d7800 98 3 1 11 waited known time
232 00000400837d7800 98 1 1 20 waited known time
257 00000400837d7800 98 3 1 16 waited known time
263 00000400837d7800 98 3 1 5 waited known time
117 00000400837d7800 98 4 1 4 waited known time
102 00000400837d7800 98 3 1 12 waited known time
47 00000400837d7800 98 3 1 11 waited known time
49 00000400837d7800 98 1 1 2 waited known time
99 00000400837d9300 98 1 1 32 waited known time
51 00000400837dd200 98 1 1 1 waited known time
43 00000400837de400 98 1 1 2 waited known time
130 00000400837de400 98 1 1 10 waited known time
89 00000400837de400 98 1 1 2 waited known time
62 00000400837de400 98 0 1 -1 waited known time
150 00000400837de400 98 1 1 9 waited known time
195 00000400837de400 98 1 1 3 waited known time
67 00000400837de400 98 1 1 2 waited known time
下一步,就是找出这些热点块以及造成latch争用的sql语句。这是因为cache buffers chains latch通常保护很多个块,这些热点块可能属于这些sql中使用的某个表。从oracle8i开始,你可以通过接触点计数(tch:touch count)来发现热点块。一般来说,热点块的tch会比较高。但是要记住,当块从lru列表的冷端移动到热端后,tch会被清0。所以, tch为0的块不一定就不是热点块。
如前所述,热点块通常是应用程序导致的。找出这些程序,检查他们为什么重复访问相同的块,并且做出相应的调整。
另外一个解决办法,就是尽量将热点块分配到不同的hash chain链表,由不同的cache buffers chains latch来保护。这可以通过调整热点块中的行数据分布到不同的块中来实现。新的块有不同的块地址,这样原来在同一个hash chain上的数据就可能会分布到其他不同的hash chain上。改变块中行数据的分布有很多方法,包括:
n 通过rowid删除并且重新插入某些行。
n 将表exp出来,加大 pctfree,然后再imp表。这样会使每个块中的数据减少,使数据分布到更多的块上。同时,也会导致占用更多的空间,全表扫描的性能也会受到影响。
n 尽量减每个块中的记录数。首先需要dump一些数据块来分析现在每个块中的记录数。Dump出来的跟踪文件中,nrow就是块中的记录总数。然后exp表,再truncate表,在表中插入你想要在每个块中保存的条数的记录,然后使用alter table table_name minimize records_per_block,再truncate表,最后imp回数据即可。
n 可以考虑减少块的大小。从oracle9i开始,数据库可以支持不同的块大小。例如当前块大小为16k,你可以将表及其索引移动块大小为8k的表空间中。这也会对全表扫描造成负面影响。并且,多个块大小也会使得管理更复杂。
另外,从oracle9iR2开始,也可以通过增加隐含参数_spin_count的值来解决热点块导致的cache buffers chains latch争用。最后,也可以通过隐含参数_db_block_hash_buckets来增加hash bucket的数量,从oracle8i开始,一般不建议采用这种办法,如果实在要用,请保证_db_block_hash_buckets的值为一个质数,否则,oracle也会自动采用大于你提供的值的最小的质数值。
Cache buffers chains latch争用原因三 ―― 过长的hash chain
多个数据块可能分配到同一个hash bucket上。这些块组成一个链表(hash chain)。在一个大型系统中,一个hash bucket中可能有上百个数据块。从一个hash chain链表中搜索某个块,需要获得cache buffers chains latch,串行的进行。如果链表太长,使得latch持有时间相应增加,可能导致其他进程请求cache buffers chains latch失败。
在oracle8.0之前,由于hash latch,hash bucket,hash chain之间是1:1:1的关系,很容易计算一个hash chain的长度,等于一个latch需要保护的数据块数。通过下面的查询可以知道一个hash chain上的数据块数。一般而言,一个hash chain链表上超过10个数据块就认为太长了。
Select hladdr, count(*)
from x$bh
group by hladdr
order by 2;
从oracle8i起,hash latch和hash bucket之间的关系变成了1:m。这样就很难计算某个hash chain具体的长度了。只能计算一个hash latch需要保护多少个数据块。而一个hash latch可能同时保护多个hash chain链表。上面的那个查询的结果变成了每个hash latch需要保护的数据块数。在你判断一个hash latch保护的数据块是否过量之前,需要先得到hash latch和hash bucket的比值。在下面的例子中,每个hash latch保护125个hash chain。如果你想要每个hash chain上不超过10个数据块,则每个hash latch保护的数据块不能超过1250个。通过隐含参数_db_block_hash_buckets可以增加hash bucket的数目,这样可以减少每个hash chain上的数据块数(因为hash bucket和hash chain之间是1:1的关系)。从oracle8i开始,一般不建议这么做。
_db_block_hash_buckets = 128021
_db_block_hash_latches = 1024
ratio = 128021 / 1024 = 125
Cache buffers lru chain latch
除了hash chain,缓冲头同样组成一个列表,这个列表指向其他的列表比如lru,lruw和ckpt-q。Lru和lruw列表并不是什么新东西,他们是数据缓冲区中最早的两个链表。Lru列表包含了不同状态的缓存块,而lruw就是俗称的“脏表”,只包含脏数据块。Lru和lruw列表是互斥的,他们合称一个工作集(a working set)。每个工作集由一个cache buffers lru chain latch保护。换句话说,数据缓冲区中工作集的个数是由cache buffers lru chain latch的个数决定的。通过内部视图x$kcbwds (kernel cache buffer working sets descriptors)可以知道工作集的个数。我们注意到x$kcbwds 的set_latc的值就是v$latch_children的addr列。
lru + lruw = a working set
Select set_id, set_latch
from x$kcbwds
order by set_id;
set_id set_latc
---------- --------
1 247e299c
2 247e2e68
3 247e3334
4 247e3800
5 247e3ccc
6 247e4198
7 247e4664
8 247e4b30
select addr
from v$latch_children
where name = ’cache buffers lru chain’
order by addr;
addr
--------
247e299c
247e2e68
247e3334
247e3800
247e3ccc
247e4198
247e4664
247e4b30
一般来讲,当进程需要查找可用的缓存空间时,需要访问lru列表。后台进程DBWn则会将lruw列表中的干净块移到lru列表中,也会将lru中的脏块移到lruw列表中。在一个工作集中进行以上的任何操作都需要先获得cache buffers lru chain latch。
各个数据缓冲区中(包括不同块大小的缓冲区,keep池和recycle池),每个缓冲区至少需要有一个cache buffers lru chain latch,而一个DBWn进程可能需要多个latch。否则,一个工作集就可能变得很长。在oracle9i和oracle10g中,cache buffers lru chain latch的个数默认是cpu个数的4倍,如果,db_writer_processes大于4,则等于cpu的个数乘以db_writer_processes。可以通过隐含参数_db_block_lru_latches来调节cache buffers lru chain latch的个数。
Cache buffers laru cahin latch的争用,主要表现为由于低效的sql语句导致数据缓冲区过度活跃。全表扫描和对某些选择性较差的大索引的反复扫描是造成cache buffers laru cahin latch争用的主要原因。解决办法是,查找latch free等待事件中关于cache buffers lru chain latch相关的sql语句(在oracle10g中,已经变成一个独立的cache buffers lru chain等待事件),优化这些sql,降低其物理读和逻辑读。
Row cache objects latch
Row cache objects latch用来保护数据字典缓冲区(row cache的名字主要是因为其中的信息是按行存储的,而不是按块存储)。进程在装载、引用或者清除数据字典缓冲区中的对象时必须获得该latch。在oracle8i之前,这是一个独立latch。从oracle9i起,由于引入了多个子共享池的新特性,存在多个row cache objects子latch。Oracle10g中,该latch也有了一个独立的等待事件:row cache objects。
从oracle7.0起,数据字典缓冲成为了共享池的一部分。而在7.0之前,每个数据字典对象都是由独立的dc_*初始化参数控制。Oracle7.0的这个改变也意味着,不能再直接的调整数据字典缓冲,而只能通过调整shared_pool_size来间接的调整。V$rowcache视图包含了每个数据字典对象的统计信息。你可以通过下面的查询发现最热的数据字典对象。
对数据字典缓冲区的调节手段是有限的。最好的办法是降低对前面的查询结果中一些热点数据字典对象的访问。举个例子,如果对某些sequence访问频繁,可以将考虑将这些sequnce缓存在内存中。包含多个基表连接或者基于视图的视图可能导致该latch争用。一般的解决办法是增加shared_pool_size的值。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/16179598/viewspace-627155/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/16179598/viewspace-627155/