【原创】用示例说明表数据中出现热块&Latch的场景,并给出解决方案?

引言:Latch争用就是由于多个会话同一时间访问同一个数据块引起的,这也是我们常说的热块。解决方法:把记录打散到多个数据块中,减少多个会话同一时间频繁访问一个数据块概率,防止由于记录都集中在一个数据块里产生热块现象。下面我们用实验来说明热块是如何产生和解决的。

session19  

LEO1@LEO1>select distinct sid from v$mystat;    大家先了解一下LEO1用户的SID19

       SID

-----------------

        19

LEO1@LEO1>create table latch_table1 as select * from dba_objects;   创建latch_table1

Table created.

LEO1@LEO1>select count(*) from latch_table1;     这个表中有71961条记录

  COUNT(*)

----------------

     71961

LEO1@LEO1>execute dbms_stats.gather_table_stats('LEO1','latch_table1');   我们对表做一个全面分析让优化器了解表数据是如何分布的。

PL/SQL proceduresuccessfully completed.

下面我们用dbms_rowid.rowid_block_number 函数来查出一个数据块上有多少条记录

dbms_rowid.rowid_block_number作用:函数返回输入ROWID对应的数据块编号

selectdbms_rowid.rowid_block_number(rowid), count(*) block_sum_rows from latch_table1 group bydbms_rowid.rowid_block_number(rowid) order by block_sum_rows ;

这里显示出每个数据块上有多少条记录,按记录数从大到小排列

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)BLOCK_SUM_ROWS

--------------------------------------------------

                                 234             81

                                 391             81

                                 225             82

                                 259             82

                                 220             83

                                 233             83

                                 279             84

                                 274             85

                                 219             88

                                 275             89

                                 277             89

                                 276             90

                                 278             90

我们看到一个数据块中最多是90行记录

select'LATCH_TABLE1' , block_sum_rows , count(*) con_rows_sum_blocks from

(selectdbms_rowid.rowid_block_number(rowid), count(*) block_sum_rows from latch_table1 group bydbms_rowid.rowid_block_number(rowid) order by block_sum_rows)

group byblock_sum_rows order by con_rows_sum_blocks;

有一致记录数的数据块有多少个,举个例子好理解,上面我们看到276278块上都用90条记录,现在我们想知道有90条记录的块一共有多少个我们用con_rows_sum_blocks列名表示(一致记录数的数据块总和),每个块上的记录数我们用block_sum_rows列名表示。

'LATCH_TABLE  BLOCK_SUM_ROWS  CON_ROWS_SUM_BLOCKS

-------------------------- ----------------------- -------------- -------------------

LATCH_TABLE1             85                   1

LATCH_TABLE1             54                   1

LATCH_TABLE1             84                   1

LATCH_TABLE1             88                   1

LATCH_TABLE1             25                   1

LATCH_TABLE1             63                   2

LATCH_TABLE1             90                   2

LATCH_TABLE1             83                   2

LATCH_TABLE1             82                   2

LATCH_TABLE1             89                   2

LATCH_TABLE1             64                   4

LATCH_TABLE1             81                  10

LATCH_TABLE1             80                  17

LATCH_TABLE1             65                  18

LATCH_TABLE1             79                  20

LATCH_TABLE1             74                  27

LATCH_TABLE1             73                  28

LATCH_TABLE1             77                  28

LATCH_TABLE1             72                  29

LATCH_TABLE1             78                  29

LATCH_TABLE1             75                  33

LATCH_TABLE1             76                  36

LATCH_TABLE1             71                  54

LATCH_TABLE1             66                  69

LATCH_TABLE1             70                  75

LATCH_TABLE1             69                 152

LATCH_TABLE1             67                 158

LATCH_TABLE1             68                 223

28 rows selected.

我们从上面看到有676869条记录的数据块数是最多的(152158223),这就说明我们的LATCH_TABLE1表中大部分数据块上每个数据块中都分配了70条左右的记录。如果有多个会话同一时间对同一个数据块做DML操作那么就会出现latch争用。

LEO1@LEO1> setlinesize 300

LEO1@LEO1> setserveroutput on                            启动屏幕显示功能

LEO1@LEO1>create or replace procedure p1       创建存储过程,作用:循环执行8000次访问数据块

as

      leo number;

begin

      for i in 1..8000

      loop

         select count(*) into leo fromlatch_table1 where object_type='TABLE';

      end loop;

      dbms_output.put_line('successfully');

end;

/

  2   3    4    5   6    7    8   9   10   11  

Procedure created.

从会话级别定位latch的消耗情况

session19

LEO1@LEO1>execute p1;

successfully

PL/SQL proceduresuccessfully completed.

session148

LEO1@LEO1>execute p1;

successfully

PL/SQL proceduresuccessfully completed.

session147

LEO1@LEO1>execute p1;

successfully

PL/SQL proceduresuccessfully completed.

session27

LEO1@LEO1>select * from v$latchholder;       从会话级别定位latch的消耗情况                                                         

PID    SID  LADDR                NAME                 GETS        

--------------------------------------------------------------------------------------------------------

33     147 0000000087596920      cache buffers chains     107426

28     19  000000008762E260      cache buffers chains     200573

31     148 00000000875981B8      cache buffers chains     208601

PID :持有latch的进程id

SID :持有latch的会话id

LADDR latch的地址

NAME latch名字

GETS :获得latch的次数,可能是等待模式,也可能是非等待模式。

19147148会话一起执行execute p1存储过程时,我抓取到了这3个会话latch争用信息,说明它们不是按顺序来持有的,是一种无序无规则的争抢,谁先抢到算谁的。

这么说是有根据的,你在抓取的时候会发现它们是按照无序的状态断断续续蹦出来的,以一种无规则的方式呈现,显示的时间很短暂。

LEO1@LEO1>select s1.sid,s2.event from v$session s1,v$session_wait s2 where s1.sid=s2.sidand s1.status='ACTIVE' and s2.event like 'latch%';

       SID EVENT

--------------------------------------------------------------------------

        19 latch: cache buffers chains

       147 latch: cache buffers chains

       148 latch: cache buffers chains

看在会话等待事件视图中总是有这3个会话的“latch 链表”的争用,原因当多个会话同时去访问相同数据块时,它们首先申请latch获得访问权限,如果此时申请失败,将会产生“latch: cache buffers chains”等待事件,这是因为多个会话在争抢latch资源所致。

我们再来看看buffer busy waits 争用是怎么产生的。

首先创建p2  p3  p4  存储过程,这3个存储过程分别更新不同对象类型的行。我为什么要这么做呢?select object_type,count(*) sum_object  from latch_table1 group by object_type orderby sum_object; 这句SQL统计出latch_table1表中相同对象类型各有多少行从小到大排序,如下所示

OBJECT_TYPE         SUM_OBJECT

-----------------------------

JAVA RESOURCE              834

LOB                                    864

PACKAGE BODY              1251

PACKAGE                         1311

TYPE                                 2779

TABLE                              2806

INDEX                              3798

VIEW                               5029

JAVA CLASS                    22920

SYNONYM                      27785

为了平衡执行时间,我选取了TABLE  INDEX  VIEW这三个对象类型的行为测试目标,大家想一想这些行在数据块中存储的位置都是无序的,也就是说是打散存放的(因为插入时就是无序插入的,所以存储时也是无序存储的),既然是无序的,那么我们可以认为大部分数据块上都保存着这三类的记录行(当然也保存着其他对象类型的行)。当多个会话同时对包含这三类的数据块进行DML操作的时候就会发生“buffer busy waits”争用,我们来看一看。

create or replaceprocedure p2          循环100次修改对象类型为表的记录行

as

      leo number;

begin

      for i in 1..100

      loop

         update latch_table1 setobject_type='TABLE' where object_type='TABLE';

      end loop;

      dbms_output.put_line('successfully');

end;

/

create or replaceprocedure p3          循环100次修改对象类型为索引的记录行

as

      leo number;

begin

      for i in 1..100

      loop

         update latch_table1 setobject_type='INDEX' where object_type='INDEX';

      end loop;

      dbms_output.put_line('successfully');

end;

/

create or replaceprocedure p4           循环100次修改对象类型为视图的记录行

as

      leo number;

begin

      for i in 1..100

      loop

         update latch_table1 setobject_type='VIEW' where object_type='VIEW';

      end loop;

      dbms_output.put_line('successfully');

end;

/

注意:这3个存储过程要一起执行才可以,其实你不用担心要多么多么同步精确,你打开3个窗口,按顺序在每个窗口上执行即可,因为我们是循环100次的执行过程也是需要一段时间的。这我有点啰嗦了,为了给初学的朋友说明一下。

session19

LEO1@LEO1>execute p2;

successfully

PL/SQL proceduresuccessfully completed.

session148

LEO1@LEO1>execute p3;

successfully

PL/SQL proceduresuccessfully completed.

session147

LEO1@LEO1>execute p4;

successfully

PL/SQL proceduresuccessfully completed.

在另外开启一个窗口,在上面三个存储过程执行的过程中,我们查看v$latchholder视图,看看没有没出现latch争用现象,在看看会话等待事件视图中有没有buffer busy waits等待事件发生。大家在测试的时候需要多执行几遍,因为latch的等待时间非常短一闪而过,多抓几遍就可以找到了。

session27

LEO1@LEO1>select * from v$latchholder;       从会话级别定位latch的消耗情况                                                         

PID    SID  LADDR                NAME                 GETS        

--------------------------------------------------------------------------------------------------------

33     147 0000000087687710      cache buffers chains     748903

28     19  0000000087587460       cache buffers chains     2979267

31     148 00000000875B10A8      cache buffers chains     162116

LEO1@LEO1>select s1.sid,s2.event from v$session s1,v$session_wait s2 where s1.sid=s2.sidand s1.status='ACTIVE' and s2.event like '%buffer%';

       SID EVENT

--------------------------------------------------------------------------

        19 buffer busywaits

       147 buffer busywaits

       148 buffer busywaits

        19 latch: cachebuffers chains

       147 latch: cachebuffers chains

       148 latch: cachebuffers chains

了然,我们要找的东东全都呈现在我们眼前,跟我们一开始设想的一样,当多个会话同时DML访问数据块时就会造成热块,例如19会话现在需要访问一个数据块,而这个数据块正在被另一个会话从硬盘读取到内存或者正在修改这个数据块,那么19会话就需要等待,此时会产生一个buffer busy waits等待事件。我们看到了DML操作不仅有cache buffers chains 还有bufferbusy waits,这两个latch是分别发生在访问数据块的不同时刻。我们理解了这些就能定位我们的系统瓶颈在哪里。

解决方案

既然我们已经知道latch是由于热块导致的,我们在生产中会频繁执行sql访问一些相同的数据块,我们很纠结,是降低sql执行次数还是把数据行分布到更多的数据块上来解决latch争用问题呢,显然后者才是可行性方案,下面我们把相同的数据分布到更多的数据块上来消除latch的争用。

session19

LEO1@LEO1>create table latch_table2 as select * from dba_objects where rownum<=4;  创建新表

Table created.

LEO1@LEO1> selectcount(*) from latch_table2;     表里只有4条记录

  COUNT(*)

-----------------

         4

LEO1@LEO1>execute dbms_stats.gather_table_stats('LEO1','latch_table2');  对表做个分析

PL/SQL proceduresuccessfully completed.

LEO1@LEO1>alter table latch_table2 minimize records_per_block;  指定每个数据块中可存放行数减少到最小(即4行)

Table altered.

LEO1@LEO1>insert into latch_table2 select * from dba_objects;   我们又插入71965行记录

71965 rowscreated.

LEO1@LEO1>commit;    提交

Commit complete.

LEO1@LEO1>select count(*) from latch_table2; 我们现在表里有71969行,那么存放在几个数据块中呢

  COUNT(*)

-----------------

     71969

LEO1@LEO1>select 'LATCH_TABLE2' , block_sum_rows , count(*) con_rows_sum_blocks from

(selectdbms_rowid.rowid_block_number(rowid), count(*) block_sum_rows from latch_table2 group bydbms_rowid.rowid_block_number(rowid) order by block_sum_rows)

group byblock_sum_rows order by con_rows_sum_blocks; 2    3  

'LATCH_TABLEBLOCK_SUM_ROWS CON_ROWS_SUM_BLOCKS

------------ ---------------------------------

LATCH_TABLE2              1                   1

LATCH_TABLE2              4               17992

这句sql我们之前已经用过,可以计算出具有相同行数的数据块有多少个,17992*4+1=71969,符合我们表里的行数,我们现在知道了这些数据是存放在17993个数据块中。我们可以看出来相同的数据量在latch_table1中和latch_table2中占用的数据块个数相差很大,latch_table2latch_table1多很多。

LEO1@LEO1> setautotrace traceonly;          我们看一下2个表的执行计划

LEO1@LEO1> altersystem flush shared_pool;    清空sharepool

System altered.

LEO1@LEO1> altersystem flush buffer_cache;    清空data_buffer_cache

System altered.

LEO1@LEO1>select * from latch_table1;   

71961 rowsselected.

Execution Plan

----------------------------------------------------------

Plan hash value:3200799752

----------------------------------------------------------------------------------

| Id  | Operation         | Name         | Rows | Bytes | Cost (%CPU)| Time     |

----------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |             | 71961 |  6816K|   287  (1)| 00:00:04 |

|   1 | TABLE ACCESS FULL  | LATCH_TABLE1  |71961 |  6816K|   287  (1)| 00:00:04 |

----------------------------------------------------------------------------------

全表扫描了71961行,这71961全都在内存中,没有发生物理读

Statistics

----------------------------------------------------------

          0        recursive calls

          0        db block gets

     113968 consistent gets

          0        physical reads

      12676   redo size

    8251368 bytes sent via SQL*Net to client

      53291   bytes received via SQL*Net from client

       4799    SQL*Net roundtrips to/from client

          0        sorts (memory)

          0        sorts (disk)

      71961   rows processed

LEO1@LEO1>select * from latch_table2;

71969 rowsselected.

Execution Plan

----------------------------------------------------------

Plan hash value:3844951557

----------------------------------------------------------------------------------

| Id  | Operation         | Name         | Rows | Bytes | Cost (%CPU)| Time     |

----------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |             |     4 |   400 |    3   (0)| 00:00:01 |

|   1 |  TABLE ACCESS FULL  | LATCH_TABLE2  |     4|   400 |     3  (0)| 00:00:01 |


全表扫描了71969行,但有18033次的物理读

Statistics

----------------------------------------------------------

          0       recursive calls

          0      db block gets

      21638 consistent gets

      18033 physical reads

          0       redo size

    8252148 bytes sent via SQL*Net to client

      53290   bytes received via SQL*Net from client

       4799    SQL*Net roundtrips to/from client

          0        sorts (memory)

          0        sorts (disk)

      71969   rows processed

小结:我们对两个数据量相同的表做了全表扫描执行计划对比,但发现latch_table2latch_table1的物理读多了18033次,原因就是latch_table2表中的行分布到更多的数据块上,它在内存中找不到后就会去磁盘上找,导致oracle需要读取更多的数据块来完成查询。这样我们就可以减少latch在数据块上的争用现象。但同时也增加了读写数据块的个数,缺点就是对数据库性能有一定影响。

我们可以想象当有多个会话同时访问数据的时候,数据越分散,会话访问的数据块范围越广,热块越不容易形成,latch争用概率就会大大降低。


PDF下载版

用示例说明表数据中出现热块&Latch的场景,并给出解决方案?.pdf (177.02 KB, 下载次数: 0)
2012-12-8 16:11 上传
下载次数: 0


Leonarding
2012.12.08
天津&winter
分享技术~成就梦想

Blogwww.leonarding.com



来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26686207/viewspace-750791/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/26686207/viewspace-750791/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值