热点块竞争和解决--cache buffers chains

热点块竞争和解决--cache buffers chains

http://hi.baidu.com/myitil/blog/item/b9c69513e92f712edc540164.html

热点块的定义

    数据库的热点块,从简单了讲,就是极短的时间内对少量数据块进行了过于频繁的访问。定义看起来总是很简单的,但实际在数据库中,我们要去观察或者确定热点块的问题,却不是那么简单了。要深刻地理解数据库是怎么通过一些数据特征来表示热点块的,我们需要了解一些数据库在这方面处理机制的特性。

 

数据缓冲区的结构

我们都知道,当查询开始的时候,进程首先去数据缓冲区中查找是否存在查询所需要的数据块,如果没有,就去磁盘上把数据块读到内存中来。在这个过程中,涉及到数据缓冲区中 LRU 链的管理( 8i 开始以接触点计数为标准衡量 buffer 冷热从而决定 buffer 是在 LRU 的冷端还是热端),关于这部分内容,从 oracle concepts 中就能得到详尽的文档,我不准备去论述这部分内容,这也不是本文的重点。现在我们的重点是,到底进程是如何地去快速定位到自己所想要的 block 的,或者如何快速确定想要的 block 不在内存中而去进行物理读的。

   我们仔细想一想,随着硬件的发展,内存越来越大, cache buffer 也越来越大,我们如何才能在大量的内存中迅速定位到自己想要的 block ?总不能去所有 buffer 中遍历吧!在此数据库引出了 hash 的概念( oracle 中快速定位信息总是通过 hash 算法的,比如快速定位 sql 是否在 shared pool size 中存在就是通过 hash value 来定位的,也就是说 shared pool size 中对象也是通过 hash table 来管理的),了解一点数据结构的基本知识就知道, hash 的一大重要功能就是快速地查找。举个最简单的例子,假设我们有一个 hash table 就是一个二维数组 a[200][100], 现在有 1000 个无序数字,我们要从这 1000 个数字里面查找某个值是否存在,或者说当我们接收到某个数字的时候必须判断是否已经存在,当然,我们可以遍历这 1000 个数字,但这样的效率就很低。但现在我们考虑这样一种方法,那就是把 1000 个数字除以 200 ,根据其余数,放在 a[200][100] 里面 ( 假设相同余数的最大数量不超过 100) ,余数就是数组的下标。这样,平均来说一个数组 a[i] 里面可能有 5 个左右的数字。当我们要去判别一个数字是否存在的时候,对这个数字除以 200( 这就是一个最简单的 hash 算法 ) ,根据余数 i 作为下标去数组 a[i] 中查找,大约进行 5 次查找就能判别是否已经存在,这样通过开辟内存空间 a[200][100] 来换取了时间 ( 当然 hash 算法的选取和 hash table 的大小是一个很关键的问题 ) 。

明白了基本的 hash 原理之后,我们再来看 oracle 的 block 的管理。数据库为这些 block 也开辟了 hash table ,假设是 a, 则在一维上的数量是由参数 _db_block_hash_buckets 来决定的,也就是存在 hash table a[_db_block_hash_buckets ], 从 oracle8i 开始, _db_block_hash_buckets =db_block_buffers*2 。而一个 block 被放到哪个 buckets 里面,则是由 block 的文件编号、块号 (x$bh.dbarfl 、 x$bh.dbablk 对应了 block 的文件属于表空间中的相关编号和 block 在文件中的编号, x$bh 是所有 cache buffer 的 header 信息,通过表格的形式可以查询 ) 做 hash 算法决定放到哪个 bucket 的,而 bucket 里面就存放了这些 buffers 的地址。这样当我们要访问数据的时候,可以获得 segment 的 extent( 可以通过 dba_extents 查到看,详细的信息来源这里不做探讨 ) ,自然知道要访问的文件编号和 block 编号,根据文件和 block 编号可以通过 hash 算法计算出 hash bucket, 然后就可以去 hash bucket 里面去找 block 对应的 buffer 。

除此之外,为了维护对这些 block 的访问和更改, oracle 还提供了一种 latch 来保护这些 block 。因为要避免不同的进程随意地径直并发修改和访问这些 block ,这样很可能会破坏 block 的结构的。 latch 是数据库内部提供的一种维护内部结构的一种低级锁, latch 的生存周期极短 ( 微秒以下级别 ) ,进程加 latch 后快速的进行某个访问或者修改动作然后释放 latch( 关于 latch 不再过多的阐述,那可能又是需要另一篇文章才能阐述清楚 ) 。这种 latch 数量是通过参数 _db_block_hash_latches 来定义的,一个 latch 对应的保护了多个 buckets 。从 8i 开始,这个参数的 default 规则为 :

当 cache buffers 少于 2052 buffers

_db_block_hash_latches = power(2,trunc(log(2, db_block_buffers - 4) - 1))

当 cache buffers 多于 131075 buffers

_db_block_hash_latches = power(2,trunc(log(2, db_block_buffers - 4) - 6))

当 cache buffers 位于 2052 与 131075 buffers 之间

_db_block_hash_latches = 1024

通过这个规则我们可以看出,一个 latch 大约可以维护 128 个左右的 buffers 。由于 latch 使得对 block 的操作的串行化 (9i 中有改进,读与读可以并行,但读与写、写与写依然要串行 ) ,很显然我们可以想到一个道理,如果大量进程对相同的 block 进程进行操作,必然在这些 latch 上造成竞争,也就是说必然形成 latch 的等待。这在宏观上就表现为系统级的等待。明白了这些原理,为我们下面的在数据库中的诊断奠定了基础。

如何确定热点对象

    如果我们经常关注 statspack 报告,会发现有时候出现 cache buffer chains 的等待。这个 cache buffer chains 就是 _db_block_hash_latches 所定义的 latch 的总称,通过查询 v$latch 也可得到:

select latch#,name,gets,misses,sleeps

from v$latch

where name like 'cache buffer%';

LATCH#       NAME                           GETS        MISSES      SLEEPS
---------- ------------------------------ ---------- ---------- ----------
93          cache buffers lru chain         54360446    21025       238
98          cache buffers chains            6760354603 1680007     27085
99          cache buffer handles            554532      6           0

在这个查询结果里我们可以看到记录了数据库启动以来的所有 cahce buffer chains 的 latch 的状况, gets 表示总共有这么多次请求, misses 表示请求失败的次数 ( 加锁不成功 ) ,而 sleeps 表示请求失败休眠的次数,通过 sleeps 我们可以大体知道数据库中 latch 的竞争是否严重,这也间接的表征了热点块的问题是否严重。由于 v$latch 是一个聚合信息,我们并不能获得哪些块可能存在频繁访问。那我们要来看另一个 view 信息,那就是 v$latch_children,v$latch_children.addr 记录的就是这个 latch 的地址。

select addr,LATCH#,CHILD#,gets,misses,sleeps

from v$latch_children
where name = 'cache buffers chains'

and rownum < 21;

ADDR      LATCH#      CHILD#      GETS        MISSES      SLEEPS
-------- ---------- ---------- ---------- ---------- ----------
91B23B74 98          1024        10365583    3957        33
91B23374 98          1023        5458174     964         25
91B22B74 98          1022        4855668     868         15
91B22374 98          1021        5767706     923         22
91B21B74 98          1020        5607116     934         31
91B21374 98          1019        9389325     1111        25
91B20B74 98          1018        5060207     994         31
91B20374 98          1017        18204581    1145        18
91B1FB74 98          1016        7157081     920         23
91B1F374 98          1015        4660774     922         22
91B1EB74 98          1014        6954644     976         32
91B1E374 98          1013        4881891     970         19
91B1DB74 98          1012        5371135     971         28
91B1D374 98          1011        5154497     990         26
91B1CB74 98          1010        5013796     936         18
91B1C374 98          1009        5667446     939         25
91B1BB74 98          1008        4673421     883         14
91B1B374 98          1007        4589646     986         17
91B1AB74 98          1006        10380781    1020        20
91B1A374 98          1005        5142009     1110        19

20 rows selected.


到此我们可以根据 v$latch_child.addr 关联到对应的 x$bh.hladdr( 这是 buffer header 中记录的当前 buffer 所处的 latch 地址 ) ,通过 x$bh 可以获得块的文件编号和 block 编号。

select dbarfil,dbablk
from x$bh
where hladdr in
(select addr
from (select addr
      from v$latch_children
      order by sleeps desc)
      where rownum < 11);

DBARFIL     DBABLK
---------- ----------
4           6498
40          14915
15          65564
28          34909
40          17987
1           24554
8           21404
39          29669
28          46173
28          48221

……………………

由此我们就打通了 cache buffers chains 和具体 block 之间的关系,那再继续下来,知道了 block ,我们需要知道究竟是哪些 segment 。这个可以通过 dba_extents 来获得。

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 < 11)

      ) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk

and a.block_id + a.blocks > b.dbablk;


OWNER                           SEGMENT_NAME                    SEGMENT_TYPE
---------------------------- ------------------------------ ------------------
ALIBABA                         BIZ_SEARCHER                    TABLE
ALIBABA                         CMNTY_USER_MESSAGE              TABLE
ALIBABA                         CMNTY_VISITOR_INFO_PK           INDEX
ALIBABA                         COMPANY_AMID_IND                INDEX
ALIBABA                         COMPANY_DRAFT                   TABLE
ALIBABA                         FEEDBACK_POST                   TABLE
ALIBABA                         IM_BLACKLIST_PK                 INDEX
ALIBABA                         IM_GROUP                        TABLE
ALIBABA                         IM_GROUP_LID_IND                INDEX
ALIBABA                         MEMBER                          TABLE
ALIBABA                         MEMBER_PK                       INDEX
ALIBABA                         MLOG$_SAMPLE                    TABLE

……………………

由此我们就打通了 cache buffers chains 和具体 block 之间的关系,那再继续下来,知道了 block ,我们需要知道究竟是哪些 segment 。这个可以通过 dba_extents 来获得。

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 < 11)

      ) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk

and a.block_id + a.blocks > b.dbablk;


OWNER                           SEGMENT_NAME                    SEGMENT_TYPE
---------------------------- ------------------------------ ------------------
ALIBABA                         BIZ_SEARCHER                    TABLE
ALIBABA                         CMNTY_USER_MESSAGE              TABLE
ALIBABA                         CMNTY_VISITOR_INFO_PK           INDEX
ALIBABA                         COMPANY_AMID_IND                INDEX
ALIBABA                         COMPANY_DRAFT                   TABLE
ALIBABA                         FEEDBACK_POST                   TABLE
ALIBABA                         IM_BLACKLIST_PK                 INDEX
ALIBABA                         IM_GROUP                        TABLE
ALIBABA                         IM_GROUP_LID_IND                INDEX
ALIBABA                         MEMBER                          TABLE
ALIBABA                         MEMBER_PK                       INDEX
ALIBABA                         MLOG$_SAMPLE                    TABLE

我们还有另外一种方式

select object_name
from dba_objects
where data_object_id in
      (select obj
       from x$bh
       where hladdr in
            (select addr
             from (select addr
                   from v$latch_children
                   order by sleeps desc)
             where rownum < 11

             )

        )

;


OBJECT_NAME
------------------------------------
I_CCOL2
RESOURCE_PLAN$
DUAL
FGA_LOG$
AV_TRANSACTION
COMPANY_DRAFT
MEMBER
SAMPLE
SAMPLE_GROUP
VERTICAL_COMPONENT
MEMBER_PK
SAMPLE_GROUP_PK
IM_BLACKLIST_PK
IM_CONTACT
IM_GROUP
CMNTY_USER_MESSAGE
CMNTY_VISITOR_INFO_PK
IM_OFFLINEMSG_TID_IND
OFFER
OFFER_PK
OFFER_EMAIL_IND
OFFER_DRAFT
CMNTY_USER_MESSAGE_TD_BSM_IND
CMNTY_MESSAGE_NUM_PK
BIZ_EXPRESS_MEMBER_ID_IND

……………………

到这里我们基本能找到热点块对对应的对象。但实际上还有另外一个途径来获取这些信息,那就是和 x$bh.tch 相关的一种方法。对于 8i 开始 oracle 提供了接触点 (touch count) 来作为 block 是冷热的标志,在一定条件满足的情况下 block 被进程访问一次 touch count 增加一,到某个标准之后被移动到 LRU 热端 ( 关于 touch count 在这里不做详细介绍,那又将是一大篇文章 ) 。那在短时间内从某种意义上讲, touch count 大的 block 可能暗示着在当前某个周期内被访问次数比较多。

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;

OWNER                           SEGMENT_NAME                    SEGMENT_TYPE
------------------------------ ------------------------------ ------------------
ALIBABA                         CMNTY_USER_MESSAGE              TABLE
ALIBABA                         MEMBER_PK                       INDEX
ALIBABA                         OFFER_DRAFT_GMDFY_IND           INDEX

同上面一样还有这个方法

select object_name
from dba_objects
where data_object_id in
       (select obj
        from (select obj
              from x$bh order by tch desc)

        where rownum < 11) ;


OBJECT_NAME
---------------------------------------------------
DUAL
MEMBER_PK
SAMPLE_GROUP_PK
CMNTY_USER_MESSAGE_TD_BSM_IND
OFFER_DRAFT_MID_GMDFY_IND
OFFER_MID_GPOST_IND
OFFER_DRAFT_PK
MEMBER_GLLOGIN_IND
OFFER_MID_STAT_GEXPIRE_IND
SAMPLE_MID_STAT_IND

10 rows selected.


到这里,我们寻找热点块和热点对象的工作算是完成了,但我们还并没有解决问题。

热点问题的解决

    热点块和热点对象我们都找到了,但是我们该怎么来解决这个问题呢?一般来说,热点块会导致 cache buffers chains 竞争等待,但并不是说 cache buffer chains 一定是因为热点块而起,在特别情况下有可能是因为 latch 数量的问题导致的,也就是一个 latch 管理的 buffers 数量太多而导致竞争激烈。但是 latch 数量我们一般是不会轻易去设置的,这是 oracle 的隐藏参数。

    实际上最有效的办法,是从优化 sql 入手,不良的 sql 往往带来大量的不必要的访问,这是造成热点块的根源。比如本该通过全表扫描的查询却走了索引的 range scan ,这样将带来大量的对块的重复访问。从而形成热点问题。再或者比如不当地走了 nested loops 的表连接,也可能对非驱动表造成大量的重复访问。那么在这个时候,我们的目标就是找出这些 sql 来并尝试优化。在 statspack 报告中,根据报告中 sql 列表,我们如果是通过 dba_extents 确定的热点对象而不是通过 dba_objects 确定的,则可以通过查找出的热点 segment 转换为对应的表,对于非分区的索引, index_name 就是 segment_name, 通过 dba_indexes 很容易的找到对应的 table_name, 对于分区表和分区索引也能通过和 dba_tab_partition 和 dba_ind_partitions 找到 segment 和 table 的对应关系。通过这些 table 到 statspack 报告中去找相关的 sql 。

select sql_text
from stats$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 a.hash_value,a.address,a.piece;

SQL_TEXT
----------------------------------------------------------------
SELECT SEQ_SMS_TRANSACTION.nextval FROM DUAL
SELECT SEQ_BIZ_EXPRESS.nextval FROM DUAL
SELECT bizgroup.seq_grp_post.NextVal FROM DUAL
SELECT SEQ_SAMPLE.nextval FROM DUAL
SELECT bizgroup.seq_grp_user.NextVal FROM DUAL
SELECT SEQ_BIZ_SEARCHER.nextval FROM DUAL
SELECT SEQ_OFFER_DRAFT.nextval FROM DUAL
select seq_Company_Draft.NextVal from DUAL
SELECT SEQ_SAMPLE_GROUP.nextval FROM DUAL
SELECT SEQ_CMNTY_USER_MESSAGE.nextval FROM DUAL
SELECT SYSDATE FROM DUAL
select seq_News_Forum.NextVal from DUAL
SELECT SEQ_SMS_USER.nextval FROM DUAL
select seq_Biz_Member.NextVal from DUAL
select seq_Pymt_Managing.NextVal from DUAL
E= '+08:00' NLS_DUAL_CURRENCY = '$' NLS_TIME_FORMAT = 'HH.MI.SSX
SELECT SEQ_COMPANY_DRAFT.nextval FROM DUAL
SELECT 1 FROM DUAL
select seq_offer_draft.NextVal from DUAL
select seq_Biz_Express_Category.NextVal from DUAL

20 rows selected.  
   除了优化 sql 外,当然对于热点的表或者索引来说,如果小的话,我们可以考虑 cache 在内存中,这样可能降低物理读提高 sql 运行速度 ( 这并不会减少 cache buffer chains 的访问次数 ) ,对于序列,我们可以对序列多设置一些 cache 。如果是并行服务器环境中的索引对象,并且这个索引是系列递增类型,我们可以考虑反向索引 ( 关于反向索引这里就不过多地做介绍了 ) 。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值