> 第3周 Latch

时间:2021-01-06 06:00:13

1.说说你是如何理解latch和enqueue的?

相同点:Latch和enqueue(Lock)都是为了保护资源的串行化访问.

不同点如下表:

    Latch            Lock
队列性    X             O
时长     很短             可能很长
层面     数据库资源层         业务应用层
目的     保证资源的完整性     保证业务操作的完整性


--EOF--


2.模拟PPT中的例子,做一个绑定变量和非绑定变量的资源消耗对比,给出过程和结果。可以使用latch_test.txt文档中提供的脚本。

2.1 创建runstats_pkg包.

http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551378329289980701

SQL> create global temporary table run_stats
  2  ( runid varchar2(15),
  3    name varchar2(80),
  4    value int )
  5  on commit preserve rows;

Table created.

SQL> create or replace view stats
  2  as select 'STAT...' || a.name name, b.value
  3        from v$statname a, v$mystat b
  4       where a.statistic# = b.statistic#
  5      union all
  6      select 'LATCH.' || name,  gets
  7        from v$latch
  8     union all
  9     select 'STAT...Elapsed Time', hsecs from v$timer;

View created.

SQL> create or replace package runstats_pkg
  2  as
  3      procedure rs_start;
  4      procedure rs_middle;
  5      procedure rs_stop( p_difference_threshold in number default 0 );
  6  end;
  7  /

Package created.

SQL> create or replace package body runstats_pkg
  2  as
  3
  4  g_start number;
  5  g_run1  number;
  6  g_run2  number;
  7
  8  procedure rs_start
  9  is
 10  begin
 11      delete from run_stats;
 12
 13      insert into run_stats
 14      select 'before', stats.* from stats;
 15
 16      g_start := dbms_utility.get_cpu_time;
 17  end;
 18
 19  procedure rs_middle
 20  is
 21  begin
 22      g_run1 := (dbms_utility.get_cpu_time-g_start);
 23
 24      insert into run_stats
 25      select 'after 1', stats.* from stats;
 26      g_start := dbms_utility.get_cpu_time;
 27
 28  end;
 29
 30  procedure rs_stop(p_difference_threshold in number default 0)
 31  is
 32  begin
 33      g_run2 := (dbms_utility.get_cpu_time-g_start);
 34
 35      dbms_output.put_line
 36      ( 'Run1 ran in ' || g_run1 || ' cpu hsecs' );
 37      dbms_output.put_line
 38      ( 'Run2 ran in ' || g_run2 || ' cpu hsecs' );
 39          if ( g_run2 <> 0 )
 40          then
 41      dbms_output.put_line
 42      ( 'run 1 ran in ' || round(g_run1/g_run2*100,2) ||
 43        '% of the time' );
 44          end if;
 45      dbms_output.put_line( chr(9) );
 46
 47      insert into run_stats
 48      select 'after 2', stats.* from stats;
 49
 50      dbms_output.put_line
 51      ( rpad( 'Name', 30 ) || lpad( 'Run1', 12 ) ||
 52        lpad( 'Run2', 12 ) || lpad( 'Diff', 12 ) );
 53
 54      for x in
 55      ( select rpad( a.name, 30 ) ||
 56               to_char( b.value-a.value, '999,999,999' ) ||
 57               to_char( c.value-b.value, '999,999,999' ) ||
 58               to_char( ( (c.value-b.value)-(b.value-a.value)), '999,999,999' ) data
 59          from run_stats a, run_stats b, run_stats c
 60         where a.name = b.name
 61           and b.name = c.name
 62           and a.runid = 'before'
 63           and b.runid = 'after 1'
 64           and c.runid = 'after 2'
 65           -- and (c.value-a.value) > 0
 66           and abs( (c.value-b.value) - (b.value-a.value) )
 67                 > p_difference_threshold
 68         order by abs( (c.value-b.value)-(b.value-a.value))
 69      ) loop
 70          dbms_output.put_line( x.data );
 71      end loop;
 72
 73      dbms_output.put_line( chr(9) );
 74      dbms_output.put_line
 75      ( 'Run1 latches total versus runs -- difference and pct' );
 76      dbms_output.put_line
 77      ( lpad( 'Run1', 12 ) || lpad( 'Run2', 12 ) ||
 78        lpad( 'Diff', 12 ) || lpad( 'Pct', 10 ) );
 79
 80      for x in
 81      ( select to_char( run1, '999,999,999' ) ||
 82               to_char( run2, '999,999,999' ) ||
 83               to_char( diff, '999,999,999' ) ||
 84               to_char( round( run1/decode( run2, 0, to_number(null), run2) *100,2 ), '99,99
 85          from ( select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
 86                        sum( (c.value-b.value)-(b.value-a.value)) diff
 87                   from run_stats a, run_stats b, run_stats c
 88                  where a.name = b.name
 89                    and b.name = c.name
 90                    and a.runid = 'before'
 91                    and b.runid = 'after 1'
 92                    and c.runid = 'after 2'
 93                    and a.name like 'LATCH%'
 94                  )
 95      ) loop
 96          dbms_output.put_line( x.data );
 97      end loop;
 98  end;
 99
100  end;
101  /

Package body created.

SQL>

2.2 创建测试过程.

SQL> create table tt(x int);

Table created.

SQL>
SQL> create or replace procedure p1 as
  2  l_cnt number;
  3  begin
  4  for i in 1 .. 10000 loop
  5  execute immediate 'select count(*) from tt where x = ' || i into l_cnt;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>
SQL> create or replace procedure p2 as
  2  l_cnt number;
  3  begin
  4  for i in 1 .. 10000 loop
  5  select count(*) into l_cnt from tt where x = i ;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>


2.3 测试与结果.

SQL> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

SQL> exec p1;

PL/SQL procedure successfully completed.

SQL> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

SQL> exec p2;

PL/SQL procedure successfully completed.

SQL> set serveroutput on
SQL> exec runStats_pkg.rs_stop(500);
Run1 ran in 552 cpu hsecs
Run2 ran in 26 cpu hsecs
run 1 ran in 2123.08% of the time

Name                                  Run1        Run2        Diff
STAT...recursive cpu usage             528          21        -507
STAT...bytes sent via SQL*Net          622       1,149         527
STAT...CPU used by this sessio         554          27        -527
STAT...CPU used when call star         557          28        -529
STAT...parse time elapsed              532           1        -531
LATCH.session idle bit                 107         674         567
STAT...DB time                         613          29        -584
STAT...Elapsed Time                  1,351       2,798       1,447
STAT...session cursor cache hi          43      10,018       9,975
STAT...enqueue requests             10,010           5     -10,005
STAT...enqueue releases             10,010           5     -10,005
STAT...parse count (hard)           10,010           5     -10,005
STAT...parse count (total)          10,043          28     -10,015
STAT...consistent gets from ca      40,104      30,082     -10,022
STAT...calls to get snapshot s      50,087      40,041     -10,046
STAT...sql area evicted             10,095           3     -10,092
STAT...session logical reads        40,267      30,160     -10,107
STAT...consistent gets from ca      40,240      30,131     -10,109
STAT...consistent gets              40,240      30,131     -10,109
LATCH.transaction allocation             1      10,589      10,588
LATCH.cache buffers chains         100,493      89,335     -11,158
LATCH.enqueue hash chains           20,399         966     -19,433
LATCH.enqueues                      20,204         548     -19,656
STAT...recursive calls              40,844      10,224     -30,620
LATCH.kks stats                     65,341         242     -65,099
STAT...session uga memory                0     -65,408     -65,408
LATCH.shared pool simulator         97,478         906     -96,572
LATCH.row cache objects            184,921      11,855    -173,066
STAT...session pga memory           65,536    -131,072    -196,608
LATCH.DML lock allocation              187     241,131     240,944
LATCH.shared pool                  422,522      12,899    -409,623

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
925,152     384,698    -540,454    240.49%

PL/SQL procedure successfully completed.

SQL>


--EOF--


3.用示例说明表数据中出现热块的场景,并给出解决方案。

3.1 准备工作.

SQL> drop table tt purge;

Table dropped.

SQL>
SQL> create table tt(x int);

Table created.

SQL>
SQL> insert into tt values(1);

1 row created.

SQL>
SQL> insert into tt values(2);

1 row created.

SQL>
SQL> insert into tt values(3);

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL>
SQL> select x,
  2  dbms_rowid.ROWID_RELATIVE_FNO(rowid) file#,
  3  dbms_rowid.ROWID_BLOCK_NUMBER(rowid) block#
  4  from tt;

         X      FILE#     BLOCK#
---------- ---------- ----------
         1        237     356982
         2        237     356982
         3        237     356982


SQL>
SQL> create or replace procedure p1 as
  2  begin
  3  loop
  4  update tt set x=1 where x=1;
  5  commit;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>
SQL> create or replace procedure p2 as
  2  begin
  3  loop
  4  update tt set x=2 where x=2;
  5  commit;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>
SQL> create or replace procedure p3 as
  2  begin
  3  loop
  4  update tt set x=3 where x=3;
  5  commit;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>


3.2 分别开启3个session执行3个procedure, 为避免机器卡死session数最好不要超过cpu数.

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       335

SQL> exec p1;

=================================

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       214

SQL> exec p2;

=================================

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       225

SQL> exec p3;

=================================

3.3 验证热块争用是否出现.

SQL> SELECT sw.sid
  2        , (SELECT ds.segment_name
  3           FROM   dba_segments ds
  4           WHERE  ds.header_file = sw.p1 AND sw.event = 'buffer busy waits')
  5            segment_name
  6        ,sw.event
  7        ,sw.p1
  8        ,sw.p2
  9  FROM   v$session_wait sw
 10  WHERE  sw.sid IN (335, 214, 225);

   SID SEGMENT_NAME    EVENT                                  P1         P2
------ --------------- ------------------------------ ---------- ----------
   214 TT              buffer busy waits                     237     356982
   225 TT              buffer busy waits                     237     356982
   335                 log file switch (private stran          0          0
                       d flush incomplete)


SQL>

=================================

3.4 解决方案.

业务上可以考虑优化流程, 避免不必要的高并发.

技术上可以考虑减少热块上的行数(alter table minimize records_per_block).


以下参考来自Asktom.

http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:19119999822024

depends on why the block is hot.  One needs to understand the application and how it uses the data 
in order to come up with a method.

For example "hot index root block" -- possible solution is partitioning (you now have N index 
blocks)

"hot right hand side of index block" -- possible solutions are reverse key indexes (if application 
doesn't range scan) or partitioning

"hot block in a small table that gets updated lots" -- table has 10 rows, all 10 rows fit into a 
single block.  block gets slammed.  possible solution is to "alter table minimize 
records_per_block" and have just 2 records/block -- or set pctfree really high to go one record per 
block.


"application level change" comes into play as well -- change the way the application accesses the 
data. 

--EOF--


4.用示例说明索引数据块中出现热块的场景,并给出解决方案。

4.1准备工作

SQL> drop table tt purge;

Table dropped.

SQL>
SQL> create table tt(x int);

Table created.

SQL>
SQL> insert into tt values(1);

1 row created.

SQL>
SQL> insert into tt values(2);

1 row created.

SQL>
SQL> insert into tt values(3);

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL>
SQL> create index idx_t_x on tt(x);

Index created.

SQL>
SQL> select x,
  2  dbms_rowid.ROWID_RELATIVE_FNO(rowid) file#,
  3  dbms_rowid.ROWID_BLOCK_NUMBER(rowid) block#
  4  from tt;

         X      FILE#     BLOCK#
---------- ---------- ----------
         1        237     356983
         2        237     356983
         3        237     356983

SQL>
SQL> create or replace procedure p1 as
  2  l_x number;
  3  begin
  4  loop
  5  select /*+ index(t) */ x into l_x from tt where x=1;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>
SQL> create or replace procedure p2 as
  2  l_x number;
  3  begin
  4  loop
  5  select /*+ index(t) */ x into l_x from tt where x=2;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>
SQL> create or replace procedure p3 as
  2  l_x number;
  3  begin
  4  loop
  5  select /*+ index(t) */ x into l_x from tt where x=3;
  6  end loop;
  7  end;
  8  /

Procedure created.

SQL>


4.2 同时开启3个session进行测试

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       214

SQL> exec p1;

=====================================

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       237

SQL> exec p2;

=====================================

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       225

SQL> exec p3;

=====================================

4.3 验证结果

SQL> SELECT sid
  2        ,event
  3        ,p1
  4        ,p2
  5        , (SELECT o.object_name
  6           FROM   sys.x_$bh bh
  7                 ,dba_objects o
  8           WHERE      o.object_id = bh.obj
  9                  AND o.object_type = 'INDEX'
 10                  AND  bh.hladdr = sw.p1raw) obj
 11  FROM   v$session_wait sw
 12  WHERE  sid IN (214, 237, 225);

       SID EVENT                                  P1         P2 OBJ
---------- ------------------------------ ---------- ---------- ----------------
       214 latch: cache buffers chains    3720482416        144 IDX_T_X
       225 latch: cache buffers chains    3720482416        144 IDX_T_X
       237 latch: cache buffers chains    3720482416        144 IDX_T_X

SQL>

=====================================

4.4 解决方案

业务上可以考虑优化流程, 避免不必要的高并发.

技术上可以考虑减少索引热块上的行数(使用较小的数据块)或者反向索引(create index reverse).


--EOF--