Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Richard.

Asked: April 29, 2003 - 9:55 pm UTC

Last updated: September 01, 2011 - 8:19 am UTC

Version: 8.1.7.4

Viewed 10K+ times! This question is

You Asked

Hi Tom,

Thanks for this great site.

I wrote a stored procedure which uses SQL Type, in the end it
will return it as a ref cursor:

open myrc for
select *
from TABLE ( cast ( routeData as routeTableType) );

During a trace, I saw a high parse count for this "open myrc ...".
See below, Why is there such a high parse rate? I always use bind
variables. Is this something I should worry about?

Thanks.

From sql trace:
=========================================

SELECT *
FROM
TABLE(CAST(:b1 AS ROUTETABLETYPE))


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 5000 2.21 2.18 0 0 0 0
Execute 5000 2.74 2.69 0 0 0 0
Fetch 5000 0.68 0.64 0 0 0 4958
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 15000 5.63 5.51 0 0 0 4958

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 133 (RJI3) (recursive depth: 1)

Rows Row Source Operation
------- ---------------------------------------------------
1 COLLECTION ITERATOR PICKLER FETCH

Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
1 COLLECTION ITERATOR (PICKLER FETCH)



and Tom said...

refcursors are parsed evertime they are opened. They have to be -- they cannot be cached like other statements -- since they are in fact "variables".


Now, that said - we can observe that 4999 of these parses were "soft" parses -- the misses in library cache during parse tells us that. One hard parse, 4999 soft.

We can make these parses "softer soft parses". Consider:


ops$tkyte@ORA920> create or replace package demo_pkg
2 as
3 type rc is ref cursor;
4
5 procedure parse1( p_output in out rc );
6 procedure parse2( p_output in out rc );
7 end;
8 /

Package created.

ops$tkyte@ORA920> create or replace type array as table of number
2 /

Type created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
2 as
3
4 g_table array := array(1,2,3);
5
6 procedure parse1( p_output in out rc )
7 as
8 begin
9 open p_output for
10 select * from table( cast( g_table as array ) ) A;
11 end;
12
13 procedure parse2( p_output in out rc )
14 as
15 begin
16 open p_output for
17 select * from table( cast( g_table as array ) ) B;
18 end;
19
20 end;
21 /

Package body created.

Now, those are arguably the "same" -- but we'll observe their differences using runstats
</code> http://asktom.oracle.com/~tkyte/runstats.html <code>


ops$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=100;
Session altered.

ops$tkyte@ORA920> declare
2 l_cursor demo_pkg.rc;
3 l_num number;
4 begin
5 for i in 1 .. 1000
6 loop
7 demo_pkg.parse1( l_cursor );
8 fetch l_cursor into l_num;
9 close l_cursor;
10 end loop;
11 end;
12 /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_middle

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> alter session set session_cached_cursors=0;

Session altered.

ops$tkyte@ORA920> declare
2 l_cursor demo_pkg.rc;
3 l_num number;
4 begin
5 for i in 1 .. 1000
6 loop
7 demo_pkg.parse2( l_cursor );
8 fetch l_cursor into l_num;
9 close l_cursor;
10 end loop;
11 end;
12 /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(500) /* only show those things that differ by at least 500 */
Run1 ran in 73 hsecs
Run2 ran in 82 hsecs
run 1 ran in 89.02% of the time

Name Run1 Run2 Diff
STAT...session cursor cache hi 997 1 -996
LATCH.library cache pin 4,080 6,080 2,000
LATCH.shared pool 4,144 6,153 2,009
LATCH.library cache pin alloca 2,046 6,036 3,990
LATCH.library cache 8,167 13,160 4,993

Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
45,778 58,643 12,865 78.06%

PL/SQL procedure successfully completed.


as you can see -- it is "marginally" faster on the wall clock (do not expect it to be many times faster) but the important thing is the reduction in latches. We had a softer soft parse (see the 997 session cursor cache hits there -- softer soft parses)



So, make sure your session cached cursors is set (100 is a reasonable number I've found) and it'll reduce the impact of this.



Rating

  (27 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks Tom

Richard, May 05, 2003 - 2:00 pm UTC

Well, thanks once again for a great answer.
Your suggestion did help out with some improvement.

Clarification

A reader, June 23, 2004 - 4:27 pm UTC

Tom,

Your test harness script is very useful. However, I have the following questions :
You mention that the test harness cannot be used in a multi-user environment and can be useful for a controlled environment.
1. Does that mean only for single-user session? If that is the case, how can we have latch contentions (since only our session will be accessesing the memory structures and there is no other competing session/process).
2. Also, the total latches for Run1 and Run2 per summary is 45778 and 58643 respectively. However, the individual LATCH.* counts for Run1 and Run2 is only close to 20000 and 32000 respectively. How did you arrive at 45778 and 58643. Are there some part of the LATCH.* outputs that you are not showing in the output?
3. Also, the Session Cursor cache hits for Run1 is 997 and for Run2 is 1. Does that mean that Run1 had more session cursor cache hits (a.k.a softer soft parse) because of the ALTER SESSION SET SESSION_CACHED_CURSORS=100 for Run1 and ALTER SESSION SET SESSION_CACHED_CURSORS=0 for Run2.

Thanks much for your great help and suggestions to Oracle community

Tom Kyte
June 23, 2004 - 9:23 pm UTC

1) you look at latch USAGE.  the more latches you get, the more contention probable.

in a single user mode, we can measure how many latches (which are serialization devices, AKA locks) you took.  the more you take, the less likely the implementation will scale.

2) 

ops$tkyte@ORA920> exec runstats_pkg.rs_stop(500)  /* only show those things 
that differ by at least 500 */

so i only showed these:

LATCH.library cache pin            4,080     6,080     2,000
LATCH.shared pool                  4,144     6,153     2,009
LATCH.library cache pin alloca     2,046     6,036     3,990
LATCH.library cache                8,167    13,160     4,993

but there were others not shown.  the diffs for this big ones were 2+2+4+5 = 13k

3) yes, that was exactly the goal of this simulation -- to show the effect of session_cached_cursors on latching.

Not a huge impact on runtime in single user mode.

Potential HUGE impact on scalability. 

softer soft parse vs. no parse

Anil Pant, July 05, 2004 - 7:03 am UTC

I was going through your book 'Effective Oracle By Design'. In chapter 5 on page 288-289 you have discussed about No Parse I didnt understand how static cursor can be no parse. If so then cannot we always go for static cursor ?
When you say No Parse approach it means using static cursor ?

Also on page 300 regarding triggers.
<<text>>
what we can ascertain from this example is that the sql statements are cached .... we will package our sql and call the packaged sql code from the trigger
<</text>>
Does this mean if the triggers has 5 different sql statements then each statement will be processed as opposed to a single unit ?
Pls explain


Tom Kyte
July 05, 2004 - 10:26 am UTC

if you have a procedure:

procedure p
is
begin
for x in ( select * from t )
loop
....


and you enable sql_trace and you call p 50 times -- see how many times select * from t was parsed -- under normal day to day circumstances, it'll be parsed once.

that is the beauty of static sql in plsql (and pro*c). In java, odbc, whatever - -you have to do it yourself, you have to parse once and execute many.


with the triggers -- my point was this statement caching does not happen with triggers. so, move all sql out of trigger into a plsql package and make the trigger be a one line call to the packaged code.


if you have a trigger that is:

for each row
begin
for x in ( select * from dual ) loop null; end loop;
end;
/

and you insert into table values ( 1 ); 50 times -- with sql trace - see how many times select * from dual was parsed, it'll be 50. Now, make the trigger be:

begin
p( ..... );
end;
/

where p has the select * from dual -- do the 50 single row inserts and you'll see the select from dual parsed once.

runstats question

Ryan Gaffuri, August 25, 2004 - 11:17 am UTC

Not sure how to explain this, so I am providing as much info as possible. If I am missing something let me know. My main question is, how can a query that consumes more resources return in less time when I am the only user connected to the instance? I ran this several times.


I wanted to test hash partitionings effect on performance. So I have 2 tables with 16 hash partions. I did a join of the two. I then created duplicate tables with the same indexes that are not partioned.

Run 1 is the the non-hash partioned tables
run 2 is the hash partitioned table.

This is more of a general question. Any idea how to identify why this would happen? Its more a curiousity than anything else.

tab1 has about 800,000 rows
tab2 has about 6.5m rows

t.col7 = '<val>' reduces the set to 6,000 rows.
I.col7 IN (<vals>) reduces the set to about 25,000 rows


The result of the join is about 200 rows. So I expect a nested loop, with tab1 as the lead table in the join. I get that in both queries.

The interesting thing is that both queries use different indexes...

hash partioned tables use indexes of the form
tab1 just an index on col8. No join columns.
tab2 index on all the join columns leading with the hash partiioned key(its a local index).

non hash partitioned tables use the following indexes
-- tab1 uses an index that leads col8, then uses an indexes on the join columns. I have them ordered from most selective
-- tab2 uses an index that leads with col8 followed by the join columns.


hash_col is the column that is hashed.
select COUNT(col1)
FROM tab1 t, tab2 I
WHERE I.hash_col= T.hash_col
AND I.col2 = T.col2
AND I.col3 = T.col3
AND I.col4 = T.col4
AND I.col5 = T.col5
AND I.col6 = '<val>'
AND I.col7 IN (<vals>)
AND T.col8 = <value>;

exec runstats_pkg.rs_stop(500);
Run1 ran in 1203 hsecs
Run2 ran in 1713 hsecs
run 1 ran in 70.23% of the time

Name Run1 Run2 Diff
STAT...session uga memory 2,048 0 -2,048
STAT...index scans kdiixs1 13,681 6,841 -6,840
STAT...shared hash latch upgra 13,681 6,841 -6,840
LATCH.cache buffers chains 99,782 91,333 -8,449
STAT...buffer is not pinned co 20,864 31,279 10,415
STAT...no work - consistent re 21,218 34,103 12,885
STAT...session logical reads 62,792 48,321 -14,471
STAT...consistent gets 62,265 47,792 -14,473
STAT...consistent gets - exami 27,366 6,848 -20,518
STAT...table fetch by rowid 7,271 30,081 22,810
STAT...buffer is pinned count 7,357 35,722 28,365

Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
100,697 92,363 -8,334 109.02%

PL/SQL procedure successfully completed.

If I need to provide more information please let me know. This isn't the first time I have seen runstats results where one query consumes more resources but has a shorter response time.



Tom Kyte
August 25, 2004 - 11:38 am UTC

latches are locks, locks are serialization devices, serialization devices inhibit scalability in a multi-user environment


Here, the difference in latching is not very huge
and given you were the only user -- well, you had to wait for no one.

soft parse

APL, December 22, 2004 - 7:09 am UTC

Our application is using jdbc 2.1. Its showing lot of soft parses. They are using bind variables. By using the parameter session_cached_cursors we can increase the performance.My question is how to avoid this soft parse in web based applications?
Our applications were running fine for the past 6 months. But suddenly its performance degraded and when i go through the trace file the major problem i identified is this parsing. What can i do for it?




Tom Kyte
December 22, 2004 - 9:54 am UTC

RE : Elapsed time

A reader, January 26, 2005 - 12:01 pm UTC

Hi Tom,

I installed the runstats components on my database and tried running the following piece of code :

declare
cursor c1 is select object_name from user_objects where object_type = 'FUNCTION' and object_name like 'F%';
type t_c1rec is table of user_objects.object_name%TYPE INDEX BY BINARY_INTEGER;
c1rec t_c1rec;
i number;
begin
runStats_pkg.rs_start;
for crec in c1
loop
i := i + 1;
end loop;
runstats_Pkg.rs_middle;
i := 0;
open c1;
loop
fetch c1 bulk collect into c1rec limit 100;
i := i + 1;
exit when c1%notfound;
end loop;

close c1;
runstats_pkg.rs_stop;
end;

I have the following runstats output

Run1 ran in 5 hsecs
Run2 ran in 4 hsecs
run 1 ran in 125% of the time

Name Run1 Run2 Diff
LATCH.active checkpoint queue 0 1 1
LATCH.messages 8 9 1
STAT...cursor authentications 1 0 -1
STAT...parse count (total) 1 0 -1
STAT...recursive cpu usage 11 10 -1
STAT...opened cursors current 1 0 -1
STAT...opened cursors cumulati 1 0 -1
LATCH.Consistent RBA 2 0 -2
LATCH.mostly latch-free SCN 0 2 2
LATCH.row cache objects 50 52 2
LATCH.row cache enqueue latch 50 52 2
LATCH.lgwr LWN SCN 0 2 2
LATCH.redo writing 5 2 -3
LATCH.enqueues 4 0 -4
STAT...Elapsed Time 14 18 4
LATCH.session allocation 8 0 -8
STAT...buffer is not pinned co 106 93 -13
STAT...table scan blocks gotte 106 93 -13
STAT...session logical reads 627 614 -13
STAT...no work - consistent re 106 93 -13
STAT...consistent gets 110 97 -13
STAT...recursive calls 17 3 -14
LATCH.shared pool 26 3 -23
LATCH.library cache pin alloca 26 0 -26
LATCH.session idle bit 26 0 -26
LATCH.library cache pin 38 8 -30
LATCH.library cache 75 8 -67
LATCH.checkpoint queue latch 0 109 109
LATCH.cache buffers chains 2,981 2,769 -212

Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
3,814 3,532 -282 107.98%

I was expecting that the elapsed time for the second run to be lesser than the first (since I was using bulk operations). However, I always see extra time for run2. I tried this for two or three runs and got the same results. Let me know if I am doing something wrong

Tom Kyte
January 26, 2005 - 1:25 pm UTC

you don't want to do it like that, you want 5 calls to the database


and unless you have thousands or at least HUNDREDS of rows -- you cannot really expect to see much benefit from a bulk collect (you are not doing enough work)

and lastly, the difference between:

Run1 ran in 5 hsecs
Run2 ran in 4 hsecs

is nonexistent.  I would expect if you ran that over and over you would find the numbers to vary between 0 and 10 -- for each (insufficient work happening here to use a wall clock time)



ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      cursor c1 is select object_name from all_objects;
  3      type t_c1rec is table of user_objects.object_name%TYPE INDEX BY BINARY_INTEGER;
  4      c1rec t_c1rec;
  5      i number;
  6  begin
  7      for crec in c1
  8      loop
  9         i := i + 1;
 10      end loop;
 11  end;
 12  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runstats_Pkg.rs_middle;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      cursor c1 is select object_name from all_objects;
  3      type t_c1rec is table of user_objects.object_name%TYPE INDEX BY BINARY_INTEGER;
  4      c1rec t_c1rec;
  5      i number;
  6  begin
  7      i := 0;
  8      open c1;
  9      loop
 10          fetch c1 bulk collect into c1rec limit 100;
 11          i := i + 1;
 12          exit when c1%notfound;
 13      end loop;
 14
 15      close c1;
 16  end;
 17  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec runstats_pkg.rs_stop(1000);
Run1 ran in 114 hsecs
Run2 ran in 71 hsecs
run 1 ran in 160.56% of the time
 
Name                                  Run1        Run2        Diff
LATCH.simulator hash latch           4,992       2,816      -2,176
STAT...recursive calls              27,953         284     -27,669
STAT...session logical reads       151,886     115,460     -36,426
STAT...consistent gets             151,876     115,445     -36,431
STAT...no work - consistent re      86,583      50,149     -36,434
STAT...buffer is pinned count       49,119      85,760      36,641
STAT...buffer is not pinned co     129,219      65,117     -64,102
LATCH.cache buffers chains         301,724     228,847     -72,877
STAT...session pga memory                0     131,072     131,072
 
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
308,200     233,149     -75,051    132.19%
 
PL/SQL procedure successfully completed.
 



and if you run this in 10g:



ops$tkyte@ORA10G> exec runStats_pkg.rs_start;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> declare
  2      cursor c1 is select object_name from all_objects;
  3      type t_c1rec is table of user_objects.object_name%TYPE INDEX BY BINARY_INTEGER;
  4      c1rec t_c1rec;
  5      i number;
  6  begin
  7      for crec in c1
  8      loop
  9         i := i + 1;
 10      end loop;
 11  end;
 12  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec runstats_Pkg.rs_middle;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> declare
  2      cursor c1 is select object_name from all_objects;
  3      type t_c1rec is table of user_objects.object_name%TYPE INDEX BY BINARY_INTEGER;
  4      c1rec t_c1rec;
  5      i number;
  6  begin
  7      i := 0;
  8      open c1;
  9      loop
 10          fetch c1 bulk collect into c1rec limit 100;
 11          i := i + 1;
 12          exit when c1%notfound;
 13      end loop;
 14
 15      close c1;
 16  end;
 17  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G> exec runstats_pkg.rs_stop(1000);
Run1 ran in 66 hsecs
Run2 ran in 60 hsecs
run 1 ran in 110% of the time


Name                                  Run1        Run2        Diff
 
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
128,649     127,623      -1,026    100.80%
 
PL/SQL procedure successfully completed.


<b>a dead heat (because PLSQL is silently array fetching 100 rows at a time for us automagically in 10g....</b>


 

Not clear

A reader, January 26, 2005 - 1:34 pm UTC

Tom,

I am not clear as to what you say hear and what you mean by "you don't want to do it like that, you want 5 calls to the database". Are you saying that the example I am using is not good enough for me to prove the point. Please explain



Tom Kyte
January 26, 2005 - 1:53 pm UTC

did you see the 5 calls

runstats_pkg.start (call 1)
your code (call 2)
runstats_pkg.middle (call 3)
your code version 2 (call 4)
runstats_pkg.stop (call 5)


some statistics are not put into the v$ tables until the end of a call.  consider:

ops$tkyte@ORA9IR2> declare
  2          x       number;
  3  begin
  4          runStats_pkg.rs_start;
  5          for i in 1 .. 100000
  6          loop
  7                  x := ln(i);
  8          end loop;
  9
 10          runStats_pkg.rs_middle;
 11          for i in 1 .. 500
 12          loop
 13                  x := ln(i);
 14          end loop;
 15          runStats_pkg.rs_stop(100);
 16  end;
 17  /
Run1 ran in 569 hsecs
Run2 ran in 2 hsecs
run 1 ran in 28450% of the time
 
Name                                  Run1        Run2        Diff
STAT...redo size                     1,804       1,932         128
LATCH.SQL memory manager worka         134           0        -134
STAT...Elapsed Time                    570           3        -567
 
<b>so, apparently, the first run ran lots slower -- but the difference in CPU time was neglible -- i mean, we see no recorded difference in CPU time???

However...</b>

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
357         113        -244    315.93%
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> declare
  2          x       number;
  3  begin
  4          for i in 1 .. 100000
  5          loop
  6                  x := ln(i);
  7          end loop;
  8  end;
  9  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_middle;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> declare
  2          x       number;
  3  begin
  4          for i in 1 .. 500
  5          loop
  6                  x := ln(i);
  7          end loop;
  8  end;
  9  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_stop(100);
Run1 ran in 567 hsecs
Run2 ran in 10 hsecs
run 1 ran in 5670% of the time
 
Name                                  Run1        Run2        Diff
STAT...redo size                     1,804       1,936         132
LATCH.SQL memory manager worka         134           0        -134
STAT...Elapsed Time                    568          11        -557
STAT...CPU used by this sessio         562           4        -558
STAT...CPU used when call star         562           4        -558
STAT...OS User time used               571           0        -571
STAT...session pga memory                0      65,536      65,536
 
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
738         478        -260    154.39%
 
PL/SQL procedure successfully completed.


<b>There we big time CPU difference and other things</b>


You need to use 5 independent database calls. 

Pls. clarify

A reader, January 26, 2005 - 2:03 pm UTC

Tom,

Now I understood what you meant by "5 calls to the database". However, in the last discussion thread, you use 100000 as the loop limit for the first for loop and 500 for the second for loop. Any reason for that?



Tom Kyte
January 26, 2005 - 2:30 pm UTC

so there would be measurable differences between the two.

I was trying to show that "some things are not recorded until the end of the call"

CPU is one of them.

So, I wanted something that used alot of cpu, and something that did not.

Pls. clarify

A reader, January 27, 2005 - 9:42 am UTC

Tom,

After your feedback, I ran the following code snippets in my development box to find out the effect of data type conversions on CPU time.

begin
runstats_pkg.rs_start;
end;
/
declare
x varchar2(10) := '01-JAN-05';
y date;
i integer;
begin
for i in 1..100000
loop
y := x;
end loop;
end;
/
begin
runstats_pkg.rs_middle;
end;
/
declare
x date := '01-JAN-05';
y x%type;
i integer;
begin
for i in 1..100000
loop
y := x;
end loop;
end;
/
begin
runstats_pkg.rs_stop;
end;
/

Run1 ran in 312 hsecs
Run2 ran in 11 hsecs
run 1 ran in 2836.36% of the time

Name Run1 Run2 Diff
LATCH.active checkpoint queue 2 1 -1
LATCH.event group latch 1 0 -1
LATCH.lgwr LWN SCN 1 2 1
LATCH.mostly latch-free SCN 1 2 1
LATCH.process allocation 1 0 -1
STAT...messages sent 1 0 -1
STAT...redo entries 514 513 -1
STAT...redo buffer allocation 1 0 -1
LATCH.session timer 1 0 -1
LATCH.Consistent RBA 2 0 -2
LATCH.channel handle pool latc 2 0 -2
LATCH.process group creation 2 0 -2
LATCH.post/wait queue 2 0 -2
LATCH.transaction allocation 2 0 -2
LATCH.loader state object free 4 2 -2
LATCH.list of block allocation 2 0 -2
LATCH.dummy allocation 2 0 -2
LATCH.dml lock allocation 2 0 -2
LATCH.sequence cache 3 0 -3
LATCH.undo global data 3 0 -3
LATCH.redo allocation 519 515 -4
LATCH.user lock 4 0 -4
LATCH.redo writing 7 3 -4
LATCH.channel operations paren 5 0 -5
LATCH.child cursor hash table 7 0 -7
STAT...bytes received via SQL* 275 266 -9
LATCH.enqueue hash chains 10 0 -10
STAT...change write time 11 0 -11
LATCH.messages 28 9 -19
LATCH.enqueues 31 0 -31
LATCH.session allocation 52 0 -52
LATCH.SQL memory manager worka 81 0 -81
LATCH.library cache pin alloca 122 8 -114
STAT...redo size 65,912 65,784 -128
LATCH.process queue reference 210 52 -158
LATCH.library cache pin 191 26 -165
LATCH.shared pool 207 27 -180
LATCH.checkpoint queue latch 288 96 -192
LATCH.session idle bit 276 8 -268
STAT...Elapsed Time 320 19 -301
STAT...CPU used by this sessio 317 11 -306
STAT...CPU used when call star 317 11 -306
LATCH.library cache 365 36 -329
LATCH.row cache enqueue latch 348 0 -348
LATCH.row cache objects 362 0 -362
LATCH.cache buffers chains 3,302 2,575 -727

Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
6,448 3,362 -3,086 191.79%

I am able to understand the difference in elapsed time and CPU times between the two approaches and was hoping that there should be no differences as far as latching is concerned (since per my understanding, there is no logic in the PL/SQL block that would involve any latches). Why is the output showing so much differences in the latch.

Please advise

Tom Kyte
January 27, 2005 - 10:03 am UTC

are you on a single user system and did you run this repeatedly.

The issue is latches are "system wide", the statistics -- they are your session, but the latches are system wide.

You do NO IO -- so those cache buffers chains latches, they are probably someone elses.  when run on a "quiet" system:

Name                                  Run1        Run2        Diff
LATCH.Consistent RBA                     1           0          -1
LATCH.lgwr LWN SCN                       1           0          -1
LATCH.mostly latch-free SCN              1           0          -1
LATCH.library cache                     52          54           2
LATCH.redo allocation                    9           7          -2
LATCH.library cache pin                 44          46           2
LATCH.redo writing                       3           0          -3
LATCH.messages                           4           0          -4
STAT...bytes received via SQL*         996         987          -9
STAT...CPU used by this sessio          48           3         -45
STAT...CPU used when call star          48           3         -45
STAT...Elapsed Time                     52           5         -47
 
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
263         255          -8    103.14%
 
PL/SQL procedure successfully completed.

which is what I would expect.  I think in your case, they are just "noise"


(and if you run this in 10g, beware of:

Run1 ran in 2 hsecs
Run2 ran in 0 hsecs
begin
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at "OPS$TKYTE.RUNSTATS_PKG", line 39
ORA-06512: at line 2
 
 
:) 

RE : Cache buffers latch

A reader, January 27, 2005 - 11:59 am UTC

Tom,

Cache buffers latch will show up if you do lot of logical I/Os. Correct?

Please advise

Tom Kyte
January 27, 2005 - 12:49 pm UTC

correct.

runStats

SR, February 08, 2005 - 4:44 pm UTC

Tom,

Is there any reason why you don't use v$sesstat, v$session_event for the sid = :s (where you do testing), which is saved from yet another session so as to not impact sid :s itself. This should give session specific latches etc? I got some wild number variations on runStats runs one system and then it became apparant that the system load may be causing it (?)

Thanks

Tom Kyte
February 09, 2005 - 1:48 am UTC

there is no such thing as "session specific latches"

runstats is explicity designed to be run "single user" as a measurment tool.

Trap to be careful when running runstats

Albert Nelson A, February 11, 2005 - 11:28 am UTC

Dear Tom,

Your runstats_pkg is nice. 

I like to share the following:

As the start time for the second run is captured at runstats_pkg.rs_middle, any delay in typing the second query adds to the run time of the second query. I think getting from a script solves this.

Example:
SQL> set serveroutput on size 40000
SQL> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

SQL> ed
Wrote file afiedt.buf

  1  declare
  2  n number;
  3  begin
  4  for i in 1..10000 loop
  5  select 1 into n from dual;
  6  end loop;
  7* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

--- Here waited for some time ---

SQL> ed
Wrote file afiedt.buf

  1  declare
  2  n number;
  3  begin
  4  for i in 1..1000 loop
  5  select 1 into n from dual;
  6  end loop;
  7* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> exec runstats_pkg.rs_stop(500);
Run1 ran in 3443 hsecs
Run2 ran in 3508 hsecs
run 1 ran in 98.15% of the time
        
Name                                  Run1        Run2        Diff
LATCH.messages                      16,841      17,426         585
LATCH.library cache pin alloca      35,794      38,412       2,618
LATCH.undo global data              19,541      16,899      -2,642
LATCH.multiblock read objects       18,400      14,710      -3,690
LATCH.session idle bit              75,622      79,699       4,077
LATCH.shared pool                   33,092      27,549      -5,543
STAT...buffer is not pinned co      10,000       1,000      -9,000
STAT...execute count                10,005       1,005      -9,000
STAT...recursive calls              10,003       1,003      -9,000
STAT...table scans (short tabl      10,000       1,000      -9,000
STAT...table scan rows gotten       10,000       1,000      -9,000
STAT...table scan blocks gotte      10,000       1,000      -9,000
STAT...no work - consistent re      10,000       1,000      -9,000
LATCH.transaction allocation        10,576           0     -10,576
LATCH.library cache pin             92,914      79,450     -13,464
LATCH.library cache                129,711     113,710     -16,001
STAT...session logical reads        30,018       3,020     -26,998
STAT...consistent gets              30,006       3,007     -26,999
STAT...calls to get snapshot s      30,001       3,001     -27,000
LATCH.cache buffers chains       1,016,867     851,500    -165,367
STAT...session pga memory         -196,608           0     196,608
        
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
1,601,553   1,394,274    -207,279    114.87%

PL/SQL procedure successfully completed.


Same thing from script:
SQL> set serveroutput on size 4000
SQL> get c:\rs.sql
  1  begin
  2  runstats_pkg.rs_start;
  3  end;
  4  /
  5  declare
  6  n number;
  7  begin
  8  for i in 1..10000 loop
  9  select 1 into n from dual;
 10  end loop;
 11  end;
 12  /
 13  begin
 14  runstats_pkg.rs_middle;
 15  end;
 16  /
 17  declare
 18  n number;
 19  begin
 20  for i in 1..1000 loop
 21  select 1 into n from dual;
 22  end loop;
 23  end;
 24  /
 25  begin
 26  runstats_pkg.rs_stop(500);
 27* end;
 28  
 29  

SQL> @c:\rs.sql

PL/SQL procedure successfully completed.


PL/SQL procedure successfully completed.


PL/SQL procedure successfully completed.


PL/SQL procedure successfully completed.

Run1 ran in 41 hsecs
Run2 ran in 7 hsecs
run 1 ran in 585.71% of the time
        
Name                                  Run1        Run2        Diff
LATCH.enqueue hash chains              557          56        -501
LATCH.library cache pin alloca         826          88        -738
LATCH.session idle bit               1,827         219      -1,608
LATCH.shared pool                    7,737         993      -6,744
STAT...buffer is not pinned co      10,000       1,000      -9,000
STAT...execute count                10,005       1,005      -9,000
STAT...no work - consistent re      10,000       1,000      -9,000
STAT...recursive calls              10,003       1,003      -9,000
STAT...table scan rows gotten       10,000       1,000      -9,000
STAT...table scans (short tabl      10,000       1,000      -9,000
STAT...table scan blocks gotte      10,000       1,000      -9,000
LATCH.library cache pin             21,709       2,194     -19,515
LATCH.library cache                 22,445       2,275     -20,170
STAT...session logical reads        30,015       3,023     -26,992
STAT...consistent gets              30,005       3,008     -26,997
STAT...calls to get snapshot s      30,001       3,001     -27,000
LATCH.cache buffers chains          73,074       8,328     -64,746
        
Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
131,431      14,473    -116,958    908.11%

PL/SQL procedure successfully completed.

SQL> 

Regards,

Albert Nelson A 

Tom Kyte
February 12, 2005 - 7:53 am UTC

the fix is to script it as I always do

test.sql:

exec runstats_pkg.rs_start
exec procedure1;
exec runstats_pkg.rs_middle
exec procedure2;
exec runstats_pkg.rs_stop



and run SQL> @test


I always do it in a script. 

A reader, February 23, 2005 - 12:12 pm UTC

Hi Tom,

From your previous post

"if you have a procedure:

procedure p
is
begin
for x in ( select * from t )
loop
....


and you enable sql_trace and you call p 50 times -- see how many times select *
from t was parsed -- under normal day to day circumstances, it'll be parsed
once.

that is the beauty of static sql in plsql (and pro*c). In java, odbc, whatever
- -you have to do it yourself, you have to parse once and execute many.


with the triggers -- my point was this statement caching does not happen with
triggers. so, move all sql out of trigger into a plsql package and make the
trigger be a one line call to the packaged code.


if you have a trigger that is:

for each row
begin
for x in ( select * from dual ) loop null; end loop;
end;
/

and you insert into table values ( 1 ); 50 times -- with sql trace - see how
many times select * from dual was parsed, it'll be 50. Now, make the trigger
be:

begin
p( ..... );
end;
/

where p has the select * from dual -- do the 50 single row inserts and you'll
see the select from dual parsed once.
"

If we call a procedure or package from inside a trigger, will we be still able to get the new and old values (after the before the change, if it is an update) by calling only the procedure/package ? Will that reduce the soft parsing count ?

Right now we are using something like this for before/after insert/update trigger

trade_ins_rec.trade_id := :new.trade_id ;
trade_ins_rec.award_type_id := :new.award_type_id ;
trade_ins_rec.currency_id:= :new.currency_id ;
trade_ins_rec.amount := :new.amount ;
trade_ins_rec.last_update := :new.last_update;

trigger_pkg.trade_ins_insert(trade_ins_rec);

where trade_ins_insert is a procedure in package trigger_pkg

but this is parsing once for each insert or update

thanks
Regi



Tom Kyte
February 24, 2005 - 4:45 am UTC

the trigger can pass the :new values using IN OUT or OUT parameters - and the plsql procedure you invoke can "write to them" and modify them.  So yes, you can access :new and :old, you pass them as parameters.


What is parsing once?  give us a tiny example demonstrating your point.  Here is mine:

ops$tkyte@ORA9IR2> create table t1 ( x int );
 
Table created.
 
ops$tkyte@ORA9IR2> create table t2 ( x int );
 
Table created.
 
ops$tkyte@ORA9IR2> create or replace procedure p( p_x in number )
  2  as
  3  begin
  4          insert into t2 in_proc values ( p_x );
  5  end;
  6  /
 
Procedure created.
 
ops$tkyte@ORA9IR2> create trigger t1_trigger
  2  after insert on t1 for each row
  3  begin
  4          insert into t2 in_trigg values ( :new.x );
  5          p(:new.x);
  6  end;
  7  /
 
Trigger created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @trace
ops$tkyte@ORA9IR2> alter session set events '10046 trace name context forever, level 12';
 
Session altered.
 
ops$tkyte@ORA9IR2> insert into t1 values ( 1 );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into t1 values ( 1 );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into t1 select rownum from all_users;
 
24 rows created.
 
INSERT INTO T2 IN_TRIGG VALUES ( :B1 )
                                                                                                                                                                            
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        3      0.00       0.00          0          0          0           0
Execute     26      0.00       0.00          0          3         33          26
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       29      0.00       0.00          0          3         33          26
********************************************************************************
INSERT INTO T2 IN_PROC VALUES ( :B1 )
                                                                                                                                                                            
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute     26      0.00       0.00          0          1         26          26
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       27      0.00       0.00          0          1         26          26


IN_TRIGG, parsed 3 times -- because we did three inserts.

IN_PROC, parsed 1 time -- because plsql cached it since the insert was in a stored, named object (a procedure) 

A reader, February 23, 2005 - 4:13 pm UTC

Hi Tom,

You can ignore the above post. It is incorrect.

The call of the package/procedure from inside the trigger does eliminate unnecessary parsing.

The soft parse count was increasing for the actual insert/update itself not for the one inside the trigger

thanks
Regi

Don't I need v$timer granted directly as well.

Mark Brady, July 12, 2006 - 12:08 pm UTC

I'm posting this as a review here because:
1. No review section for the runtats page
2. This is the example linked directly from there

I finagled the 3 privs to the v_$ views you listed as prerequisites but when I try to compile the view i get an error indicating I need privs on v$timer as well.

If this is the case perhaps you could update the runstats to add that prereq. If not what am I doing wrong?

Tom Kyte
July 12, 2006 - 3:47 pm UTC

you would of course need access to all of the v_$ views - sorry about that.

function in the Query

Pingu, December 28, 2006 - 7:26 am UTC

Hi

My developers runs queries with functions, the function is quite simple, it simply translates some character into normal a, b, c, d

RETURN LTRIM( RTRIM(
TRANSLATE(
UPPER( P_TEXTO ),
'ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÂÊÎÔÛ`´¨^',
'AEIOUAEIOUAEIOUAEIOU''''''''' )
) );

There is heavy latch contention in the database, library cache latch, I ran query against v$sysstat and v$db_object_cache I noticed that parse count is very high, 3500/second and number of executions of package is high as well, please see the results



STATISTIC# NAME CLASS VALUE
---------- ------------------------------ ---------- --------------------------------
177 parse time cpu 64 0
178 parse time elapsed 64 0
179 parse count (total) 64 40890933702
180 parse count (hard) 64 72106

!sleep 5

STATISTIC# NAME CLASS VALUE
---------- ------------------------------ ---------- --------------------------------
177 parse time cpu 64 0
178 parse time elapsed 64 0
179 parse count (total) 64 40890950930
180 parse count (hard) 64 72106


OWNER NAME DB_L NAMESPACE TYPE SHARABLE_MEM LOADS EXECUTIONS LOCKS PINS KEP
----------- ----------- ---- ------------------ -------------- ------------ ---------- --------------- ---------- ---------- ---
OPS$PROD PKG_INTGG TABLE/PROCEDURE PACKAGE 228179 31 -1767520083 17 5 YES
OPS$PROD PKG_INTGG BODY PACKAGE BODY 32551 29 -310023399 17 5 YES

!sleep 5

OWNER NAME DB_L NAMESPACE TYPE SHARABLE_MEM LOADS EXECUTIONS LOCKS PINS KEP
----------- ------------ ---- ------------------ -------------- ------------ ---------- --------------- ---------- ---------- ---
OPS$PROD PKG_INTGG TABLE/PROCEDURE PACKAGE 228179 31 -1767499820 17 3 YES
OPS$PROD PKG_INTGG BODY PACKAGE BODY 32551 29 -310014413 17 0 YES


I was wondering why this suffers latch contention if the package is loaded in the memory?

Anyway to improve a bit this?

TIA

Tom Kyte
December 28, 2006 - 9:49 am UTC

why do you think this is all about the function

function in the Query

pingu, December 28, 2006 - 10:44 am UTC

Hi

I suspected the function because there is nothing else I can suspect about, well except the number of soft parses but hell I dont think 3000 soft parses per second in a dedicated server with 8 CPUs would cause this sort of library cache contention?

TIA

Tom Kyte
December 29, 2006 - 9:04 am UTC

why do you fell compelled to curse?

Anyway, you typically do not parse a function call like that. You parse:

a) SQL
b) a call to a top level anonymous block to run plsql


but the function, it would not be parsed, I doubt you have code like:

parse( "begin :y := my_function(:x); end;" );
bind x
bind y
execute

more likely you have plsql that calls this function - and that would NOT incur a parse.

function in the query

Pingu, December 29, 2006 - 12:46 pm UTC

Hi

The function is called in a query such as

select pkg1.trans_text(column1), column2, column3
from table1
where ........

I checked the application code and it open cursor, fecth and closes the curosr ALL the time, I guess library cache latch contention may be caused by soft parses instead of function calls?

TIA

Tom Kyte
December 30, 2006 - 8:58 am UTC

yes, precisely my point.

If you have latch contention that is :)

You have not demonstrated that to us as yet either.


and if your developers cared about performance, they would not have used plsql right there - that should just be 100% plain SQL. if they want to hide that function with the nested builtins - they should have used a view.

function in the query

PINGU, December 30, 2006 - 12:06 pm UTC

Hi

Its Pingu again

Do you think select pkg1.trans_text(column1), is worse than select LTRIM( RTRIM(
TRANSLATE(
UPPER( P_TEXTO ),
'ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÂÊÎÔÛ`´¨^',
'AEIOUAEIOUAEIOUAEIOU''''''''' )
) )
?

Also what cana view help in this case... cant really see.

We have heavy latch contention yes, very very heavy library latch contention.

TIA
Tom Kyte
December 30, 2006 - 12:23 pm UTC

absolutely, it is exactly what I said - don't use plsql when plain old sql will more than do.

A view can be used to hide the transalte(upper(column),'string1','string2') - just like the plsql function is.

create a view with that function result as a column, so instead of:

select pkg.function(c1), .... from table;

you

select c1_translated, ..... from view_of_table;

function in a query

A reader, December 30, 2006 - 12:28 pm UTC

Hi

Performance wise do they make any difference? I dont think so, dont they?

select pkg.function(c1), .... from table;
select c1_translated, ..... from view_of_table;

Tom Kyte
December 30, 2006 - 1:33 pm UTC

we never have to have an "i don't think so" situation - I try to teach people how to measure things....

So, let us measure things.

ops$tkyte%ORA10GR2> /*
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> drop table t;
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create table t as select * from all_objects;
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace function f( p_str in varchar2 ) return varchar2
ops$tkyte%ORA10GR2> as
ops$tkyte%ORA10GR2> begin
ops$tkyte%ORA10GR2>         return ltrim( rtrim( translate( upper( p_str ), '!@#$%^&*()_+', '------------'  ) ) );
ops$tkyte%ORA10GR2> end;
ops$tkyte%ORA10GR2> /
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace view vw
ops$tkyte%ORA10GR2> as
ops$tkyte%ORA10GR2> select object_name,
ops$tkyte%ORA10GR2>            ltrim( rtrim( translate( upper( object_name ), '!@#$%^&*()_+', '------------'  ) ) ) object_name_translated
ops$tkyte%ORA10GR2>   from t
ops$tkyte%ORA10GR2> /
ops$tkyte%ORA10GR2> */
ops$tkyte%ORA10GR2> exec runStats_pkg.rs_start;

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> set autotrace traceonly statistics
ops$tkyte%ORA10GR2> select f(object_name) from t;

50079 rows selected.


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       3985  consistent gets
          0  physical reads
          0  redo size
    1692818  bytes sent via SQL*Net to client
      37103  bytes received via SQL*Net from client
       3340  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      50079  rows processed

ops$tkyte%ORA10GR2> exec runStats_pkg.rs_middle;

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> select object_name_translated from vw;

50079 rows selected.


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       3985  consistent gets
          0  physical reads
          0  redo size
    1692826  bytes sent via SQL*Net to client
      37103  bytes received via SQL*Net from client
       3340  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      50079  rows processed

ops$tkyte%ORA10GR2> set autotrace off
ops$tkyte%ORA10GR2> exec runStats_pkg.rs_stop(10000);
Run1 ran in 1059 hsecs
Run2 ran in 941 hsecs
run 1 ran in 112.54% of the time

Name                                  Run1        Run2        Diff
LATCH.library cache pin             20,086          28     -20,058
LATCH.library cache                 30,161          65     -30,096

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
67,027      16,210     -50,817    413.49%

PL/SQL procedure successfully completed.


used less latching, ran a little faster (older releases will show a larger disparity between the run times)

function in a query

Pingu, January 01, 2007 - 2:21 pm UTC

Hi

I run the tests and I see that changing custom function call to standard function reduces latch contention.

Sorry about this, but why using a custom function needs so many library cache latches? It was only calling some standard functions.
Tom Kyte
January 01, 2007 - 7:49 pm UTC

did you actually have latch contention...

anyway, it does, it is visible, it is something you can see - when sql calls the plsql, the plsql cannot be "changed" - we need to access that stuff from the shared pool - hence we protect it.

Quick example for divisible equal to zero

Suresh, January 04, 2007 - 2:33 am UTC

Hi,

Tom

Being a great fan of you and learning all from your site...... Thanks once again for contributing your time. Can you give a quick example of "Divisible equal to zero error" as i am getting it in a single user system in 10g with no sessions connected while running stats.
Tom Kyte
January 05, 2007 - 9:02 am UTC

scott%ORA9IR2> select 1/0 from dual;
select 1/0 from dual
        *
ERROR at line 1:
ORA-01476: divisor is equal to zero



not sure what you are looking for really??

and if you are running statistics gathering, you have sessions connected.

Is the time to compute the plan available?

Charlie B., January 23, 2007 - 3:26 pm UTC

Tom -

Great information, as always!

The application I manage frequently does joins of 10+ tables. Views of views are also common. Is there anything in Perfstat that indicates how much time is consumed calculating the plans for these statements? Or is it just a part of (say) parse_time_cpu?

Statement cache on 10g triggers

Phil (UK), July 03, 2007 - 10:24 am UTC

Hi Tom
Does 10g still not cache SQL in triggers? If so, is your advice from 2005 still valid in that we call packaged code from triggers rather than in the trigger itself?
Cheers!
Phil
Tom Kyte
July 03, 2007 - 10:44 am UTC

well, when you ran it... :)

10gr2 has not changed the behaviour (but, 11g is right around the corner)

insert inside trigger vs procedure call

Thomas, October 02, 2007 - 6:32 am UTC

Tom,

we're using update-triggers extensively to keep a history of changes on certain tables.
So I wanted to reproduce what you wrote above:

"with the triggers -- my point was this statement caching does not happen with
triggers. so, move all sql out of trigger into a plsql package and make the
trigger be a one line call to the packaged code."

I tried to measure the effect on latch statistics the way you did -and failed.
Here's what I did (running single-user on 9.2.0.7):


DELETE FROM run_stats;

CREATE TABLE watch AS SELECT rownum id, 1 value FROM all_objects;

CREATE TABLE watch_hist AS SELECT rownum id , 1 value, sysdate dt_changed FROM watch WHERE 1=2;

-- approach #1: direct insert into watch_hist

CREATE OR REPLACE TRIGGER WATCH_UPD
BEFORE UPDATE ON Watch FOR EACH ROW
BEGIN
INSERT INTO Watch_Hist ( id, value, dt_changed) VALUES
(
:OLD.id, :OLD.value, SYSDATE
);
END;
/

INSERT INTO Run_Stats SELECT 'before', stats.* FROM stats;

UPDATE watch SET value = 2;

COMMIT;

INSERT INTO Run_Stats SELECT 'after 1', stats.* FROM stats;

-- approach #2: call a proecedure to do the insert

CREATE OR REPLACE PROCEDURE ins_watch_hist( a_id IN NUMBER, a_value IN VARCHAR2 )
AS
BEGIN
INSERT INTO Watch_Hist ( id, value, dt_changed) VALUES
(
a_id, a_value, SYSDATE
);
END;
/

CREATE OR REPLACE TRIGGER WATCH_UPD
BEFORE UPDATE ON Watch FOR EACH ROW
BEGIN
ins_watch_hist
(
:OLD.id, :OLD.value
);
END;
/

UPDATE watch SET value = 3;

COMMIT;

INSERT INTO Run_Stats SELECT 'after 2', stats.* FROM stats;

SELECT A.name,
B.value - A.value "b-a",
C.value - B.value "c-b",
C.value - B.value - ( B.value - A.value ) diff
FROM Run_Stats A,
Run_Stats B,
Run_Stats C
WHERE A.name = B.name
AND C.name = B.name
AND A.runid = 'before'
AND B.runid = 'after 1'
AND C.runid = 'after 2'
AND C.value != A.value
ORDER BY ABS( diff ) DESC;




NAME b-a c-b DIFF
------------------------------------------------ ---------- ---------- ----------
STAT...session pga memory max 4919092 1049112 -3869980
STAT...session pga memory 4263732 1114648 -3149084
STAT...redo size 22686468 22739844 53376
STAT...table scan rows gotten 33957 35459 1502
LATCH.library cache 68938 70374 1436
STAT...session logical reads 107064 105948 -1116
LATCH.simulator hash latch 7226 6170 -1056
STAT...bytes received via SQL*Net from client 1055 2005 950
LATCH.library cache pin 68607 69429 822
LATCH.shared pool 34469 35280 811
STAT...consistent gets 1560 857 -703
STAT...consistent gets - examination 1196 542 -654
STAT...dirty buffers inspected 815 1438 623

I expected to see a measurable REDUCTION in library cache latches. What's wrong with my approach?

thanks in advance,
Thomas
Tom Kyte
October 03, 2007 - 5:22 pm UTC

you ran one update statement in each??

run a couple hundred, then measure.


and you are hard parsing first time around - you just created the table.

we want to see that the sql in the trigger gets parsed everytime a client executes a statement, whereas the statement is cached if plsql does it:

ops$tkyte%ORA10GR2> create table t1 ( x int );

Table created.

ops$tkyte%ORA10GR2> create table t2 ( x int );

Table created.

ops$tkyte%ORA10GR2> create table t3 ( x int );

Table created.

ops$tkyte%ORA10GR2> insert into t1 values ( 1 );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1 );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace procedure t2_proc( p_x in number )
  2  as
  3  begin
  4          insert into t3 (x) values ( p_x );
  5  end;
  6  /

Procedure created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace trigger t1
  2  after update on t1 for each row
  3  begin
  4          insert into t3 values (:new.x);
  5  end;
  6  /

Trigger created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace trigger t2
  2  after update on t2 for each row
  3  begin
  4          t2_proc(:new.x);
  5  end;
  6  /

Trigger created.

ops$tkyte%ORA10GR2> exec dbms_monitor.session_trace_enable;

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> begin
  2          for i in 1 .. 1000
  3          loop
  4                  execute immediate 'update t1 x'||mod(i,2) || ' set x = x+1';
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> begin
  2          for i in 1 .. 1000
  3          loop
  4                  execute immediate 'update t2 x'||mod(i,2) || ' set x = x+1';
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.




the execute immediate helps in 10g - cause 10g plsql cached dynamic sql, we want to mimick a client applications typical work - parse, execute, close.

tkprof says:

INSERT INTO T3 VALUES (:B1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     1000      0.03       0.04          0          0          0           0
Execute   1000      0.09       0.10          0       1001       3042        1000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2000      0.13       0.14          0       1001       3042        1000
********************************************************************************
INSERT INTO T3 (X) VALUES ( :B1 )
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      0.09       0.07          0          3       1030        1000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1001      0.09       0.07          0          3       1030        1000



you do this to reduce the parsing.

test of the same code

Fabian, August 25, 2011 - 7:16 am UTC

I've run some part of your first example as follow :
exec runstats_pkg.rs_start;

declare
        l_cursor demo_pkg.rc;
        l_num    number;
    begin
        for i in 1 .. 1000
        loop
            demo_pkg.parse1( l_cursor );
            fetch l_cursor into l_num;
            close l_cursor;
       end loop;
   end;
  /   
  
 exec runstats_pkg.rs_middle; 
 
declare
        l_cursor demo_pkg.rc;
        l_num    number;
    begin
        for i in 1 .. 1000
        loop
            demo_pkg.parse1( l_cursor );
            fetch l_cursor into l_num;
            close l_cursor;
       end loop;
   end;
  /   
  
exec runstats_pkg.rs_stop(0);  

so the second time I've run the same code as in first run
I've expected to get no difference but...

Run1 ran in 931 hsecs
Run2 ran in 630 hsecs
run 1 ran in 147.78% of the time
   
Name                                  Run1        Run2        Diff
STAT...free buffer requested             0           1           1
LATCH.transaction branch alloc           3           2          -1
STAT...heap block compress               9           8          -1
STAT...SQL*Net roundtrips to/f           3           2          -1
STAT...execute count                 1,004       1,003          -1
STAT...parse count (total)           1,003       1,002          -1
STAT...parse time cpu                    0           1           1
STAT...session cursor cache co           1           0          -1
STAT...session cursor cache hi       1,003       1,002          -1
LATCH.Shared B-Tree                      2           1          -1
LATCH.active checkpoint queue            3           2          -1
LATCH.lgwr LWN SCN                       3           2          -1
LATCH.mostly latch-free SCN              3           2          -1
LATCH.KMG MMAN ready and start           3           2          -1
STAT...non-idle wait count               3           2          -1
LATCH.Change Notification Hash           3           2          -1
STAT...redo ordering marks               0           1           1
LATCH.session timer                      3           2          -1
LATCH.job_queue_processes para           2           1          -1
LATCH.SQL memory manager latch           1           0          -1
LATCH.ksuosstats global area             1           0          -1
LATCH.kwqbsn:qsga                        1           0          -1
LATCH.FOB s.o list latch                 0           1           1
STAT...opened cursors cumulati       1,004       1,003          -1
STAT...redo subscn max counts            0           1           1
LATCH.PL/SQL warning settings            3           2          -1
STAT...calls to kcmgas                   0           1           1
STAT...sql area evicted                  3           1          -2
LATCH.ksz_so allocation latch            0           2           2
LATCH.OS process: request allo           0           2           2
LATCH.OS process allocation             18          16          -2
LATCH.channel handle pool latc           1           3           2
LATCH.resmgr:free threads list           0           2           2
LATCH.parameter table manageme           0           2           2
LATCH.session idle bit                  13          15           2
LATCH.process group creation             0           2           2
LATCH.process allocation                 0           2           2
LATCH.In memory undo latch               5           3          -2
LATCH.undo global data                  11           9          -2
LATCH.dummy allocation                   1           3           2
LATCH.resmgr:active threads              0           2           2
LATCH.ASM db client latch                6           4          -2
LATCH.JS slv state obj latch             1           3           2
STAT...user calls                        6           4          -2
LATCH.space background task la           7           4          -3
LATCH.redo writing                      12           9          -3
LATCH.simulator hash latch               1           4           3
STAT...consistent gets from ca          15          18           3
STAT...cleanout - number of kt           7          10           3
STAT...consistent gets - exami           7          10           3
STAT...consistent gets                  15          18           3
STAT...active txn count during           7          10           3
STAT...CPU used by this sessio           8          11           3
LATCH.qmn task queue latch               4           0          -4
LATCH.object queue header heap          13           9          -4
LATCH.redo allocation                   12           8          -4
LATCH.call allocation                    0           4           4
LATCH.session allocation                 1           5           4
STAT...parse time elapsed               10           6          -4
STAT...undo change vector size       3,336       3,332          -4
STAT...recursive cpu usage               5           9           4
STAT...consistent changes               29          33           4
STAT...db block changes                 44          49           5
STAT...calls to kcmgcs                  22          28           6
LATCH.OS process                         1           7           6
LATCH.row cache objects                 54          48          -6
LATCH.object queue header oper          15           9          -6
LATCH.shared pool simulator             52          45          -7
LATCH.active service list               20          12          -8
STAT...db block gets from cach          29          37           8
STAT...db block gets                    29          37           8
STAT...workarea memory allocat           8           0          -8
LATCH.session switching                  1          11          10
STAT...session logical reads            44          55          11
LATCH.cache buffers chains             182         168         -14
LATCH.channel operations paren          55          39         -16
LATCH.messages                          88          64         -24
LATCH.shared pool                       37          70          33
LATCH.JS queue state obj latch          72          36         -36
STAT...DB time                          80          18         -62
LATCH.SQL memory manager worka         204         137         -67
LATCH.enqueues                         158          80         -78
LATCH.enqueue hash chains              161          82         -79
LATCH.checkpoint queue latch           241         161         -80
STAT...redo size                     4,416       4,516         100
STAT...bytes sent via SQL*Net          441         262        -179
STAT...Elapsed Time                    931         631        -300
STAT...bytes received via SQL*         978         653        -325
 
Run1 latches total versus runs -- difference and pct
        Run1        Run2        Diff       Pct
       1,610       1,233        -377    130.58% 



Even if diffs are small for this example they seems to not safe for some more complicated scenarios. How to resolve it (out of the running in more one, which I used to use:
3x 
exec runstats_pkg.rs_start;  
<b>code1;</b>
exec runstats_pkg.rs_middle;   
<b>code2;</b>
exec runstats_pkg.rs_stop(500);
3x
exec runstats_pkg.rs_start;  
<b>code2;</b>
exec runstats_pkg.rs_middle;   
<b>code1;</b>
exec runstats_pkg.rs_stop(500); 

but that's not "perfect".
It comes from my "private" database (no other users), 11.2.0.2.
And here you have results from 10.2.0.5 for the other issue (comparing two method of lobs loading to table) with treshold set to 500:
Name                                  Run1        Run2        Diff
LATCH.shared pool                       16         571         555
STAT...undo change vector size       2,928       2,320        -608
LATCH.done queue latch                 134         756         622
LATCH.redo writing                     171         894         723
LATCH.object queue header heap         220         952         732
STAT...Elapsed Time                    481       1,280         799
LATCH.direct msg latch                 135         936         801
LATCH.session queue latch              135         936         801
LATCH.mostly latch-free SCN            172         986         814
LATCH.messages                         236       1,088         852
LATCH.redo allocation                  188       1,046         858
LATCH.session allocation               262       1,344       1,082
LATCH.enqueues                         256       1,542       1,286
LATCH.ksfv messages                    269       1,692       1,423
LATCH.In memory undo latch             370       1,973       1,603
LATCH.session idle bit                 390       2,364       1,974
LATCH.dml lock allocation              574       2,899       2,325
LATCH.simulator hash latch             440       2,934       2,494
LATCH.checkpoint queue latch           846       3,415       2,569
LATCH.undo global data                 564       3,213       2,649
LATCH.library cache pin                715       3,715       3,000
LATCH.cache buffers lru chain          439       3,627       3,188
LATCH.row cache objects                573       4,188       3,615
LATCH.enqueue hash chains              975       5,418       4,443
LATCH.library cache lock             1,003       5,629       4,626
LATCH.library cache                  1,211       6,720       5,509
LATCH.object queue header oper       1,053       8,229       7,176
LATCH.cache buffers chains          15,202     108,707      93,505

Looks like only STATS are more or less reliable.
Any comment, please?
Tom Kyte
August 30, 2011 - 4:19 pm UTC

elapsed times are really unpredictable. run it again and again and you'll seem run1 faster than run2, run2 faster than run1 and so on.

Perhaps better to use dbms_utility.get_CPU_time these days, that is what I use in my runstats now (wasn't available way back when)


and remember - latching is SYSTEM WIDE. If you are testing in a multi-user database - that can be wildly unpredictable. runstats is only really good for a single user instance.

Fabian, September 01, 2011 - 4:31 am UTC

maybe even it would be better to use hierarhical profiler instead runstats while using pl/sql these days.
anyway thank you for that comment.
regards,
Tom Kyte
September 01, 2011 - 8:19 am UTC

that shows time by line.

I'm trying to figure out "which approach is more scalable" - that comes down to resources (statistics) used and latching performed.

I might desire to use something that runs slower in single user mode than something else does - if it used a small fraction of some resource that must be shared.

If you are trying to tweak a plsql routine to run a bit faster by finding an inefficient algorithm - profiling (old or new) would be appropriate.

If you are trying to figure out what approach scales better - statistics, latches and locks - you want to reduce the amount of shared resources you need.


Especially in this example - the runtimes (in single user mode) would not be significantly different. However, the latch usage would be - that is what we were looking for, what our desired goal was. Profiling wouldn't have shown us that at all.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library