您的位置:首页 > 其它

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

2012-12-08 16:19 281 查看
引言:Latch争用就是由于多个会话同一时间访问同一个数据块引起的,这也是我们常说的热块。解决方法:把记录打散到多个数据块中,减少多个会话同一时间频繁访问一个数据块概率,防止由于记录都集中在一个数据块里产生热块现象。下面我们用实验来说明热块是如何产生和解决的。
session:19
LEO1@LEO1>select distinct sid from v$mystat; 大家先了解一下LEO1用户的SID是19
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;
有一致记录数的数据块有多少个,举个例子好理解,上面我们看到276和278块上都用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.
我们从上面看到有67,68,69条记录的数据块数是最多的(152,158,223),这就说明我们的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的消耗情况
session:19
LEO1@LEO1>execute p1;
successfully
PL/SQL proceduresuccessfully completed.
session:148
LEO1@LEO1>execute p1;
successfully
PL/SQL proceduresuccessfully completed.
session:147
LEO1@LEO1>execute p1;
successfully
PL/SQL proceduresuccessfully completed.
session:27
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的次数,可能是等待模式,也可能是非等待模式。
当19,147,148会话一起执行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次的执行过程也是需要一段时间的。这我有点啰嗦了,为了给初学的朋友说明一下。
session:19
LEO1@LEO1>execute p2;
successfully
PL/SQL proceduresuccessfully completed.
session:148
LEO1@LEO1>execute p3;
successfully
PL/SQL proceduresuccessfully completed.
session:147
LEO1@LEO1>execute p4;
successfully
PL/SQL proceduresuccessfully completed.
在另外开启一个窗口,在上面三个存储过程执行的过程中,我们查看v$latchholder视图,看看没有没出现latch争用现象,在看看会话等待事件视图中有没有buffer busy waits等待事件发生。大家在测试的时候需要多执行几遍,因为latch的等待时间非常短一闪而过,多抓几遍就可以找到了。
session:27
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的争用。
session:19
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_table2比latch_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_table2比latch_table1的物理读多了18033次,原因就是latch_table2表中的行分布到更多的数据块上,它在内存中找不到后就会去磁盘上找,导致oracle需要读取更多的数据块来完成查询。这样我们就可以减少latch在数据块上的争用现象。但同时也增加了读写数据块的个数,缺点就是对数据库性能有一定影响。
我们可以想象当有多个会话同时访问数据的时候,数据越分散,会话访问的数据块范围越广,热块越不容易形成,latch争用概率就会大大降低。

2012-12-8 16:11 上传
下载次数: 0

Leonarding

2012.12.08

天津&winter

分享技术~成就梦想

Blog:www.leonarding.com

本文出自 “刘盛分享技术~成就梦想” 博客,请务必保留此出处http://leonarding.blog.51cto.com/6045525/1082747
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐