Untitled 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 (k ernel s erverice l ock l atches 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(k ernel g eneric h eap l ru ) 可以知道 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 ( 注:上面是使用 oradebug 获得转存文件,也可以使用 alter 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 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 数目:
Select count(distinct(hladdr)) from x$bh; Count(distinct(hladdr)) ----------------------- 1024 Select count(*) from v$latch_children where name = 'cache buffers chains'; count(*) ---------- 1024 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 的块不一定就不是热点块。
-- 这里使用了前面例子中的 p1raw (00000400837d7800). Select a.hladdr, a.file#, a.dbablk, a.tch, a.obj, b.object_name from x$bh a, dba_objects b where (a.obj = b.object_id or a.obj = b.data_object_id) and a.hladdr = '00000400837d7800' union select hladdr, file#, dbablk, tch, obj, null from x$bh where obj in (select obj from x$bh where hladdr = '00000400837d7800' minus select object_id from dba_objects minus select data_object_id from dba_objects) and hladdr = '00000400837d7800' order by 4; hladdr file# dbablk tch obj object_name ---------------- ----- ------- ---- ----------- -------------------- 00000400837d7800 16 105132 0 19139 route_history 00000400837d7800 16 106156 0 19163 telco_orders 00000400837d7800 26 98877 0 23346 t1 00000400837d7800 16 61100 0 19163 telco_orders 00000400837d7800 16 26284 0 19059 fp_eq_tasks 00000400837d7800 7 144470 0 18892 report_process_queue 00000400837d7800 8 145781 0 18854 pa_equipment_union 00000400837d7800 249 244085 0 4294967295 00000400837d7800 7 31823 1 18719 candidate_events 00000400837d7800 13 100154 1 19251 event 00000400837d7800 7 25679 1 18730 candidate_zoning 00000400837d7800 7 8271 1 18719 candidate_events 00000400837d7800 7 32847 2 18719 candidate_events 00000400837d7800 8 49518 2 18719 candidate_events 00000400837d7800 7 85071 2 18719 candidate_events 00000400837d7800 275 76948 2 4294967295 00000400837d7800 7 41039 3 18719 candidate_events 00000400837d7800 7 37967 4 18719 candidate_events 00000400837d7800 8 67950 4 18719 candidate_events 00000400837d7800 7 33871 7 18719 candidate_events 00000400837d7800 7 59471 7 18719 candidate_events 00000400837d7800 8 8558 24 18719 candidate_events
如前所述,热点块通常是应用程序导致的。找出这些程序,检查他们为什么重复访问相同的块,并且做出相应的调整。
另外一个解决办法,就是尽量将热点块分配到不同的 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 (k ernel c ache b uffer w orking sets d escriptors) 可以知道工作集的个数。我们注意到 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 视图包含了每个数据字典对象的统计信息。你可以通过下面的查询发现最热的数据字典对象。
Select cache#, type, parameter, gets, getmisses, modifications mod from v$rowcache where gets > 0 order by gets; cache# type parameter gets getmisses mod ------ ----------- ------------------ ---------- ---------- ------ 7 subordinate dc_user_grants 1615488 75 0 2 parent dc_sequences 2119254 189754 100 15 parent dc_database_links 2268663 2 0 10 parent dc_usernames 7702353 46 0 8 parent dc_objects 11280602 12719 400 7 parent dc_users 81128420 78 0 16 parent dc_histogram_defs 182648396 51537 0 11 parent dc_object_ids 250841842 3939 75
对数据字典缓冲区的调节手段是有限的。最好的办法是降低对前面的查询结果中一些热点数据字典对象的访问。举个例子,如果对某些 sequence 访问频繁,可以将考虑将这些 sequnce 缓存在内存中。包含多个基表连接或者基于视图的视图可能导致该 latch 争用。一般的解决办法是增加 shared_pool_size 的值。