系列
Latch概述
Latch造成的等待事件 和 Lock造成的阻塞 ,是两个不同的概念,在性能优化上如果能区分开这两个因素引起的性能问题,将极大的提高我们性能分析的判断能力。
那什么是latch呢?
首先我们试想一个场景: 一个数据块正在被在被一个会话从磁盘读入内存中,请注意,是正在读取中,此时另外一个会话正好也需要这个数据块,那改怎么办呢?
为了保持数据的一致性,正常的处理逻辑另一个会话需要等待这个数据块被读取到内存中 ,那么正在读取数据块的会话 如何 阻止其他会话继续读取这个数据块呢? 方法是 它需要获得一种类似锁一样的资源,这种资源在Oracle中称为Latch。
Latch是Oracle为了保护内存结构而发明的一种资源。
在Oracle复杂的内存结构中,比如在SGA中,各种数据被反复从磁盘读取到内存,又被重新写会到磁盘上,如果有并发的用户做相同的事情,Oracle必须使用一种机制,来保证数据在读取的时候,只能由一个会话来完成,这种保护机制就用到了Latch。
可以把Latch理解为一种轻量级的锁,它不会造成阻塞,只会导致等待。
阻塞是一种系统设计上的问题,而等待是一种系统资源争用的问题。
分清楚这两个概念后,就能够对系统性能下降时,做出客观的判断,比如当我们发现系统缓慢的原因是由于很多Latch争用的时候,要考虑系统及数据库自身设计上是否有问题,比如是否绑定变量,是否存在热块,数据存储参数设计是否合理等因素。
导致Latch争用而等待的原因很多,内存中很多资源都可能存在这争用。下面介绍两类最常见的latch争用。
共享池中的latch争用
共享池shared pool 中如果存在大量的SQL被反复解析,就会造成很大的latch争用和长时间的等待, 最常见的现象就是没有绑定变量。
最常见的几种共享池里的Latch是:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0
SQL> select * from v$latchname a where a.NAME like 'library cache%';
LATCH# NAME HASH
---------- ------------------------ ----------
337 library cache load lock 2952162927
SQL>
在分析系统性能时,如果看到有 library cache 这样的 Latch 争用,就可以断定是共享池中出现了问题,这种问题基本是由 SQL 语句导致的,比如没有绑定变量 或者一些存储过程被反复分析。
数据缓冲池Latch争用
访问频率非常高的数据块被称为热块( Hot Block),当很多用户一起去访问某几个数据块时,就会导致一些 Latch 争用.
最常见的 latch 争用有:
- buffer busy waits
- cache buffer chain
这两个Latch的争用分别发生在访问数据块的不同时刻。
Buffer busy waits 产生原因
当一个会话需要访问一个数据块,而这个数据块正在被另一个用户从磁盘读取到内存中或者这个数据块正在被另一个会话修改时,当前的会话就需要等待,就会产生一个 buffer busy waits 等待。
产生这些 Latch 争用的直接原因是太多的会话去访问相同的数据块导致热块问题, 造成热块的原因可能是数据库设置导致或者重复执行的 SQL 频繁访问一些相同的数据块导致
Cache buffer chian 产生原因
当一个会话需要去访问一个内存块时,它首先要去一个像链表一样的结构中去搜索这个数据块是否在内存中,当会话访问这个链表的时候需要获得一个Latch,如果获取失败,将会产生 Latch cache buffer chain 等待,导致这个等待的原因是访问相同的数据块的会话太多或者这个列表太长(如果读到内存中的数据太多,需要管理数据块的 hash 列表就会很长,这样会话扫描列表的时间就会增加,持有 chache buffer chain latch 的时间就会变长,其他会话获得这个 Latch 的机会就会降低,等待就会增加)。
Latch 是简单的、低层次的序列化技术,用以保护 SGA 中的共享数据结构,比如并发用户列表和 buffer cache 里的 blocks 信息。一个服务器进程或后台进程在开始操作或寻找一个共享数据结构之前必须获得对应的 latch,在完成以后释放 latch。 不必对 latch 本身进行优化,如果 latch 存在竞争,表明 SGA 的一部分正在经历不正常的资源使用。
热块产生的原因
热块产生的原因不尽相同,按照数据块的类型,可以分成一下几种类型,不同热块类型处理的方式都是不同的。
- ( 1) 表数据块
- ( 2) 索引数据块
- ( 3) 索引根数据块
- ( 4) 段头数据块
表数据块
比如在 OLTP 系统中,对于一些小表,会给出某些数据块被频繁查询或者修改的操作,这时候,这些被频繁访问的数据块就会变成热块,导致内存中 Latch争用。
处理方式:
如果出现这样热块的情况,并且表不太大,一个方法是可以考虑将表数据分布在更多的数据块上,减少数据块被多数会话同时访问的频率。
可以通过一下命令将每个数据块存放记录的数量减少到最少:
Alter table tableName minimize records_per_block;
功能:当前表所有block中容纳的最大行数,并会把这个数字记录到数据字典,以后任何导致block行数超过这个数字的插入都会被拒绝
缺点:
我们把数据分布到更多的数据块上,大大降低了一个数据块被重复读取的概率。 但是这种方法的缺点很明显,就是降低了数据的性能,在这种情况下,访问相同的数据意味着需要读取更多的数据块,性能会有所降低。
索引数据块
这样的情况通常发生在一个 RAC 架构里,某个表的索引键值出现典型的“右倾”现象,
比如一个表的主键使用一个序列来生成键值,那么在这个主键在索引数据块上的键值就是以一种顺序递增的方式排列的,比如: 1, 2, 3, 4,5….,由于这些键值分布得非常接近,当许多用户在 RAC 的不同实例来向表中插入主键时,就会出现相同的索引数据块在不同实例的内存中被调用,形成一种数据块的争用。
对于这种情况,使用反向索引可以缓解这种争用。
反向索引是将从前的索引键值按照反向的方式排列,在正常的主键 B-Tree 引中,键值会按照大小的顺序排列,比如: 1234,反向索引,键值就变成 4321.
原理:这样,本来是放在相同的索引数据块上的键值,现在分布在不同的数据块上,这样用户在 RAC 不同的实例上插入的主键值因为分布在不同的数据块上,所以不会导致热块的产生,这基本是反向索引被使用的唯一情况。
反向索引使用场合之所以如此受限制,是因为它丢弃了 B-Tree 索引的一个最重要的功能:
Index range scan
索引访问方式中,这个方式最常见,但是反向索引却不能使用这个功能,因为反向索引已经把键值的排列顺序打乱,当按照键值顺序查找一个范围时,在反向索引中,由于键值被反向存储,这些值已经不是连续存放的了。 所以 Index range scan 的方式在反向索引中没有任何意义。 在反向索引中只能通过全表扫描或者全索引扫描的方式来实现。 这就是反向索引的一个非常严重的缺陷。
索引根数据块
热块也可能发生在索引的根数据块上。
在 B-Tree 索引里,当 Oracle 访问一个索引键值时,首先访问索引的根,然后是索引的分支,最后才是索引的叶块。
索引的键值就是存储在叶块上面。
当索引的根,枝数据都集中在几个数据块上时,比如 D, G 所在的枝数据块,
当用户访问的范围从 A-F,都会访问这个数据块,如果很多用户频繁的访问这个范围的索引键值,有可能导致这个枝数据块变成热块。
当出现这种现象时,可以考虑对索引做分区,以便于使用这些根,枝数据块分布到不同的数据段(分区)上,减少数据块的并行访问的密度,从而避免由于索引根,枝数据块太集中导致热块产生。
段头数据块
从 Oracle 9i 开始,引入了一个自动段管理的技术 ASSM( Automatic SegmentSpace Management: ASSM),它让 Oracle 自动管理“ Free List”。 实际上在 ASSM里,已经没有 Free List 这样的结构, Oracle 使用位图方式来标记数据块是否可用,这种数据块的空间管理方式比用一个列表来管理效率更高。
对于 OLTP 系统,表的 DML 操作非常密集,对于这些表,使用 ASSM 方式来管理会比人工管理更加方便和准确,能有效的避免段头变成热块。
对于 OLAP 系统,这个参数并没有太大的实际意义,因为在这样的数据库中,很少有表发生频繁的修改, OLAP 系统主要的工作是报表和海量数据的批量加载。
检查 Latch 的相关 SQL
查看造成 LATCH BUFFER CACHE CHAINS 等待事件的热块
SELECT DISTINCT a.owner, a.segment_name
FROM dba_extents a,
(SELECT dbarfil, dbablk
FROM x$bh
WHERE hladdr IN
(SELECT addr
FROM (SELECT addr FROM v$latch_children ORDER BY sleeps DESC)
WHERE ROWNUM < 20)) b
WHERE a.RELATIVE_FNO = b.dbarfil
AND a.BLOCK_ID <= b.dbablk
AND a.block_id + a.blocks > b.dbablk;
x$bh 只有sys用户有访问权限
X$表是oracle数据库运行的基础,在数据库启动时由Oracle应用程序动态创建,是不允许sysdba之外的用户直接访问的,显示授权不被允许。
http://www.itpub.net/thread-950606-1-1.html
非SYS用户访问x$表
1。
访问具体做法如下:
第一步:create view v_ps$bh as select * from x$bh;
第二步:grant select on v_ps$bh to party2;
第三步:create public synonym v_ps$bh for v_ps$bh;
完成三步后就可以访问x$bh了,如同自己的Object。
2、完成前面的两步后,
select * from sys.v_ps$bh也可以的。
询当前数据库最繁忙的 Buffer, TCH(Touch)表示访问次数越高,热块竞争问题就存在
SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11;
查询当前数据库最繁忙的 Buffer,结合 dba_extents 查询得到这些热点Buffer 来自哪些对象
SELECT e.owner, e.segment_name, e.segment_type
FROM dba_extents e,
(SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11) b
WHERE e.relative_fno = b.dbarfil
AND e.block_id <= b.dbablk
AND e.block_id + e.blocks > b.dbablk;
如果在 Top 5 中发现 latch free 热点块事件时,可以从 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)
WHERE ROWNUM < 11;
获取当前持有最热点数据块的 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
WHERE a.hladdr = b.addr;
利用前面的 SQL 可以找到这些热点 Buffer 的对象信息
SELECT distinct e.owner, e.segment_name, e.segment_type
FROM dba_extents e,
(SELECT *
FROM (SELECT addr, ts#, file#, dbarfil, dbablk, tch
FROM x$bh
ORDER BY tch DESC)
WHERE ROWNUM < 11) b
WHERE e.relative_fno = b.dbarfil
AND e.block_id <= b.dbablk
AND e.block_id + e.blocks > b.dbablk;
结合 SQL 视图可以找到操作这些对象的相关 SQL,然后通过优化 SQL减少数据的访问,或者优化某些容易引起争用的操作(如 connect by 等操作)来减少热点块竞争
SELECT /*+ rule */
hash_value, sql_text
FROM v$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 tch DESC)
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 a.sql_text LIKE '%' || b.segment_name || '%'
AND b.segment_type = 'TABLE')
ORDER BY hash_value, address, piece;