性能优化:Cache Buffer Chain Latch等待事件

彭小波

ACOUG核心成员,Oracle用户组年轻专家。擅长Oracle数据库架构规划、SQL,OWI方面的优化。曾服务于各大企业数据库的维护以及系统开发,目前从事百年人寿保险股份有限公司的数据库服务与运维工作。

产生的背景

Oracle为了将物理 IO 最小化,把最近使用过的数据块保持在内存中。为了管理这些内存,oracle 使用如图的结构,Hash Chain 的结构,Hash Chain 位于共享池中,使用典型内存结构 Bucket->Chain->Header 结构进行管理 lock# 组成的,当扫描块时使用Hash函数进行hash运算,使用hash值查找 hash bucket,具有相同hash值的 buffer haeder 在 hash bucket 上以 chain 形式链接。Buffer header 有指向实际缓冲区的指针。注意:Hash Chain结构是在共享池中,而实际缓冲区信息存储在高速缓冲区中。Hash Chain 结构利用 cache buffers chain Latch来保护。

当进程扫描特定的数据块时,必须获得相应数据块所在 Hash Chain 管理的 cache buffers chain Latch。基本上一个进程获得仅有的一个 cache buffers chain Latch,一个 cache buffers chain Latch 管理多个 Hash Chain。当多个进程同时检索Buffer Cache时,获得 cache buffers chain Latch 的过程中发生争用,就会产生该等待事件。Hash Chain 结构的起点是Hash表,Hash 表由多个 hash bucket 组成,块地址是由 file#+b cache buffers chain Latch 等待事件。

进程扫描特定的数据块的过程如下: 1、Oracle 以每个块的文件号、块号和类型做 HASH 运算,得到 HASH 值。根据 HASH值,到 HASH 表中取出指定块的内存地址 2、获取 CBC Latch 3、根据 HASH 值,搜索 CBC 链表 4、根据 DBA 找到 BH(Buffer Header) 加 Buffer Pin 5、加完 Buffer Pin 马上释放 CBC Latch 6、访问 Buffer 开始 fetch 数据 7、获取 CBC Latch 8、释放 Buffer Pin 9、释放 CBC Latch

使用 SQL 语句可以获得 hash_latches,hash_buckets 数量,因此一个锁存器保护 Bucket数量是1048576/32768=32个。

SQL> select x.ksppinm name, y.ksppstvl value, y.ksppstdf isdefault, decode(bitand(y.ksppstvf, 7), 1, 'MODIFIED', 4, 'SYSTEM_MOD', 8 'FALSE') ismod, decode(bitand(y.ksppstvf, 2), 2, 'TRUE', 'FALSE') isadj from sys.x$ksppi x, sys.x$ksppcv y where x.inst_id = userenv('Instance') and y.inst_id = userenv('Instance') and x.indx = y.indx and x.ksppinm like '%db_block_hash%' order by translate(x.ksppinm, ' _', ' ');

产生的原因

1.执行效率低下的SQL,低效的 SQL 语句是发生 Latch:cache buffers chains 争用的主要原因。发生在多个进程同时扫描大范围的表或索引时。

2.出现热块 hot block 时,由于编写 SQL 语句时,SQL 持续扫描少数特定块(between and ,in,not in, exists),多个会话同时执行 SQL 语句时,发生 Latch:cache buffers chains 争用。

【案例1 Latch:cache buffers chains争用事件重现 】

--1.创建测试表

create table t1(id int,name varchar2(10)); insert into values(1,'xiaobo'); commit;

--2.获取t1表的第一行数据及 ROWID,根据 dbms_rowid 包查出这行数据的文件号、块号

SQL> select rowid, dbms_rowid.rowid_relative_fno(rowid) file#, dbms_rowid.rowid_block_number(rowid) block#, id, name from emm.t1 where rownum = 1; 2 3 4 5 6 7 ROWID FILE# BLOCK# ID NAME ------------------ -------- ---------- ----- ---------- AAADfaAAFAAAACDAAA 5 131 1 xiaobo

注意:这里的 DBA(Data Block Address) 就是由5号文件和131号块组成

--3.根据 DBA获取 CBC Latch 的地址

SQL> select hladdr from x$bh where file#=5 and dbablk=131; HLADDR ---------------- 00000001D1C266D8

--4.根据CBC Latch的地址可以查出这个 CBC Latch 被获得的次数

SQL> select addr,name,gets from v$latch_children where addr='00000001D1C266D8'; ADDR NAME GETS ---------------- --------- ------ 00000001D1C266D8 cache buffers chains 46

--5.再次读取t1表的第一行数据,再次产生一次逻辑读

SQL>select id,name from emm.t1 where rowid='AAADfaAAFAAAACDAAA'; ID    NAME ------- ------------ 1    xiaobo

--6.CBC Latch 的次数变为 48,说明一次逻辑读产生两次 CBC Latch

SQL> select addr,name,gets from v$latch_children where addr='00000001D1C266D8'; ADDR NAME GETS ---------------- ------------------------ ------- 00000001D1C266D8 cache buffers chains 48

这里说明一次逻辑读要加两次 CBC Latch,一次为了加 Buffer Pin,一次为了释放 Buffer Pin!

使用 oradebug 跟踪 CBC Latch 争用事件

SQL> oradebug setmypid Statement processed. SQL> oradebug peek 0x1D1C266D8 4 -- 观察 CBC Latch 地址为 0x1D1C266D8 开始之后的4字节信息的值为0 [1D1C266D8, 1D1C266DC) = 00000000 SQL> oradebug poke 0x1D1C266D8 4 1 --修改 CBC Latch 地址为 0x1D1C266D8 开始的4字节信息的值为1,相当于获取了 Latch BEFORE: [1D1C266D8, 1D1C266DC) = 00000000 --修改前的值 AFTER: [1D1C266D8, 1D1C266DC) = 00000001 --修改后的值

--7. 再开一个新的会话,会话号为768

SQL> conn / as sysdba Connected. SQL> select sid from v$mystat where rownum=1; SID ---------- 768

--8.在新会话768下再查询T1表的第一行,我观察到不会堵塞

SQL>select id,name from emm.t1 where rowid='AAADfaAAFAAAACDAAA'; ID   NAME -------- ------------ 1   xiaobo

--9.我们回到 oradebug 的会话,这次我们不是使用 select 语句,而使用 update 语句来获 取 latch

SQL> update emm.t1 set id=2 where rowid='AAADfaAAFAAAACDAAA'; 1 row updated.

--10.再次使用 oradebug 模拟获取 latch

SQL> oradebug setmypid Statement processed. SQL> oradebug poke 0x1D1C266D8 4 1 BEFORE: [1D1C266D8, 1D1C266DC) = 00000000 AFTER: [1D1C266D8, 1D1C266DC) = 00000001

--11.回到刚才768会话下,查询T1表的第一行,这时我观察到产生了堵塞

SQL>select id,name from emm.t1 where rowid='AAADfaAAFAAAACDAAA';

--12.我们再开第三个会话,查看会话号768的等待事件,我们看到产生了 CBC Latch 的等待事件

最后在第一个会话中释放 lacth

SQL> oradebug poke 0x1D1C266D8 4 0 BEFORE: [1D1C266D8, 1D1C266DC) = 00000001 AFTER: [1D1C266D8, 1D1C266DC) = 00000000

总结: 在获取保护 hash bucket 的 cache buffers chains latch 时,如果是读取工作(select),就以 shared 模式获得(这也是我们刚才在实验中 select 时没有产生争用的原因)。如果是修改工作(update),就以 exclusive 模式获得。

原文发布于微信公众号 - 数据和云(OraNews)

原文发表时间:2016-08-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Janti

感悟优化——Netty对JDK缓冲区的内存池零拷贝改造

NIO中缓冲区是数据传输的基础,JDK通过ByteBuffer实现,Netty框架中并未采用JDK原生的ByteBuffer,而是构造了ByteBuf。

1422
来自专栏landv

一、K3 WISE 开发插件《K3 WISE常用数据表整理》

3637
来自专栏数据和云

运维技巧 - 活用临时表隔离冷热数据

编辑手记:Oracle给了我们很多工具,在日常数据库管理中活用这些工具方可发挥最大效能。 作者简介: 张洪涛 富士康 DBA 在数据库监控过程中发现考勤数据...

3455
来自专栏数据库新发现

Oracle的X$表系列介绍之-X$KSLLCLASS

« HRAY纳斯达克的IPO历程 | Blog首页 | Windows Xp中如何设置自动登录 »

863
来自专栏乐沙弥的世界

PGA的设置与调整

    PGA,即程序全局区(Program Global Area),是Oracle体系机构的重要组成部分。Oracle 数据库对系统内存的总开销即是PGA+...

542
来自专栏互联网高可用架构

伪共享和缓存行

1092
来自专栏数据库新发现

关于shared pool的深入探讨(五)

http://www.eygle.com/internal/shared_pool-5.htm

792
来自专栏乐沙弥的世界

Oracle 索引监控(monitor index)

      合理的为数据库表上创建战略性索引,可以极大程度的提高了查询性能。但事实上日常中我们所创建的索引并非战略性索引,恰恰是大量冗余或是根本没有用到的索引耗...

621
来自专栏沃趣科技

mysqldump与innobackupex备份过程你知多少(二)

1.2.3. 使用WITH CONSISTENT SNAPSHOT子句的作用 START TRANSACTION语句使用WITH CONSISTENT SNA...

3506
来自专栏用户2442861的专栏

MySQL详解--锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如...

543

扫码关注云+社区