Well, first, you are not comparing apples to apples AND you are so totally skewing the numbers with a FULL SCAN per row to be updated (no index on part_no) that the cost of doing the full scan 68,169 times overshadows the entire test!
So, allow me to rewrite this test as follows, using my simple test harness
</code>
http://asktom.oracle.com/~tkyte/runstats.html <code>
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE part_temp_tab(
2 part_no VARCHAR2(35) PRIMARY KEY,
3 contract VARCHAR2(5),
4 latest_price NUMBER);
Table created.
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> INSERT /*+ APPEND */ INTO part_temp_tab
2 SELECT object_name||to_char(rownum,'fm00009'), 'ABC', rownum
3 FROM all_objects;
23643 rows created.
ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> declare
2 l_start number;
3 l_run1 number;
4 l_run2 number;
5
6 TYPE Parts IS TABLE OF part_temp_tab.part_no%TYPE;
7 Part Parts;
8 begin
9 SELECT part_no BULK COLLECT
10 INTO Part
11 FROM part_temp_tab;
12
13 insert into run_stats select 'before', stats.* from stats;
14
15 l_start := dbms_utility.get_time;
16 FOR rec_ in 1 .. part.count
17 loop
18 UPDATE part_temp_tab for_loop
19 SET latest_price = 1
20 WHERE part_no = Part(rec_);
21 end loop;
22 commit;
23 l_run1 := (dbms_utility.get_time-l_start);
24 dbms_output.put_line( l_run1 || ' hsecs' );
25
26 insert into run_stats select 'after 1', stats.* from stats;
27 l_start := dbms_utility.get_time;
28 FORALL rec_ in Part.FIRST..Part.LAST
29 UPDATE part_temp_tab forall
30 SET latest_price = 2
31 WHERE part_no = Part(rec_);
32 commit;
33 l_run2 := (dbms_utility.get_time-l_start);
34 dbms_output.put_line( l_run2 || ' hsecs' );
35 dbms_output.put_line
36 ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
37
38 insert into run_stats select 'after 2', stats.* from stats;
39 end;
40 /
1027 hsecs
911 hsecs
run 1 ran in 112.73% of the time
PL/SQL procedure successfully completed.
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.name, b.value-a.value run1, c.value-b.value run2,
2 ( (c.value-b.value)-(b.value-a.value)) diff
3 from run_stats a, run_stats b, run_stats c
4 where a.name = b.name
5 and b.name = c.name
6 and a.runid = 'before'
7 and b.runid = 'after 1'
8 and c.runid = 'after 2'
9 and (c.value-a.value) > 0
10 and (c.value-b.value) <> (b.value-a.value)
11 order by abs( (c.value-b.value)-(b.value-a.value))
12 /
NAME RUN1 RUN2 DIFF
------------------------------ ---------- ---------- ----------
...
LATCH.checkpoint queue latch 613 428 -185
STAT...change write time 203 646 443
STAT...recursive cpu usage 781 327 -454
STAT...redo size 5807516 5816480 8964
LATCH.cache buffers chains 167131 143521 -23610STAT...buffer is pinned count 0 23641 23641
STAT...consistent gets 47298 23657 -23641
STAT...execute count 23648 6 -23642
STAT...recursive calls 23675 33 -23642
STAT...session logical reads 71516 47870 -23646
LATCH.library cache 47685 355 -47330STAT...session pga memory max 17024 186348 169324
STAT...session pga memory 5808 197564 191756
48 rows selected.
ops$tkyte@ORA817DEV.US.ORACLE.COM>
So, while it is interesting to see the wall clock differences -- what is crucial is the LATCHing that is going on.
Latches = Locks = Serialization Device = Scalability Inhibitor = Reduced concurrency.
The forall does signifcantly LESS work in that area.
Look at other things like the session logical reads -- very impressive that they are reduced by about 1/3 with array processing.
It is not all about speed in a single user situation (although with reasonable array sizes, like 100 or so you'll see more benefit there, array processing of thousands of rows just isn't as good) -- it is about building scalable solutions and the array processing example is definitely much more scalable (and faster to boot)