upsert VS merge

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

[email protected]> create global temporary table ao
  2  on commit PRESERVE ROWS
  3  as
  4  select *
  5    from all_objects
  6   where 1=0;

Table created.


[email protected]> alter table ao
  2  add constraint
  3  ao_pk primary key(object_id);
Table altered.

[email protected]> insert into ao select * from all_objects;
29311 rows created.

[email protected]> create table t1
  2  as
  3  select *
  4    from all_objects
  5    where rownum<= (select count(*)/2 from all_objects);
Table created.

[email protected]> alter table t1 add constraint t1_pk primary 
key(object_id);
Table altered.

[email protected]> analyze table t1 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

[email protected]> create table t2
  2  as
  3  select *
  4    from all_objects
  5   where rownum<= (select count(*)/2 from all_objects);
Table created.

[email protected]> alter table t2 add constraint t2_pk primary 
key(object_id);
Table altered.

[email protected]> analyze table t2 compute statistics
  2  for table for all indexes for all indexed columns;
Table analyzed.

So, t1 and t2 are for all intents and purposes the same -- we'll upsert them from 
AO..

[email protected]> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_cur rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      merge into t1
 13      using ao on ( t1.object_id = ao.object_id )
 14      when matched then
 15          update set owner = ao.owner,
 16            object_name = ao.object_name,
 17            subobject_name = ao.subobject_name,
 18            data_object_id = ao.data_object_id,
 19            object_type = ao.object_type,
 20            created = ao.created,
 21            last_ddl_time = ao.last_ddl_time,
 22            timestamp = ao.timestamp,
 23            status = ao.status, temporary = ao.temporary,
 24            generated = ao.generated,
 25            secondary = ao.secondary
 26      when not matched then
 27          insert ( OWNER, OBJECT_NAME,
 28                   SUBOBJECT_NAME, OBJECT_ID,
 29                   DATA_OBJECT_ID, OBJECT_TYPE,
 30                   CREATED, LAST_DDL_TIME,
 31                   TIMESTAMP, STATUS, TEMPORARY,
 32                   GENERATED, SECONDARY )
 33          values ( ao.OWNER, ao.OBJECT_NAME,
 34                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 35                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 36                   ao.CREATED, ao.LAST_DDL_TIME,
 37                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 38                   ao.GENERATED, ao.SECONDARY);
 39      commit;
 40      l_run1 := (dbms_utility.get_time-l_start);
 41      dbms_output.put_line( l_run1 || ' hsecs' );
 42
 43      insert into run_stats select 'after 1', stats.* from stats;
 44      l_start := dbms_utility.get_time;
 45      for x in ( select * from ao )
 46      loop
 47          update t2 set ROW = x where object_id = x.object_id;
 48          if ( sql%rowcount = 0 )
 49          then
 50              insert into t2 values X;
 51          end if;
 52      end loop;
 53      commit;
 54      l_run2 := (dbms_utility.get_time-l_start);
 55      dbms_output.put_line( l_run2 || ' hsecs' );
 56      dbms_output.put_line
 57      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 58
 59      insert into run_stats select 'after 2', stats.* from stats;
 60  end;
 61  /
424 hsecs
2116 hsecs
run 1 ran in 20.04% of the time

PL/SQL procedure successfully completed.

merge is faster wallclock wise then procedural code and...

[email protected]> 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
------------------------------ ---------- ---------- ----------
...
STAT...redo entries                 30661      45670      15009
LATCH.redo allocation               30780      46012      15232
STAT...db block gets                47239      62630      15391
STAT...table scan blocks gotte        597      29311      28714
n

STAT...buffer is not pinned co        693      29409      28716
unt

STAT...index fetch by key               9      29320      29311
STAT...db block changes             60912      90825      29913
STAT...no work - consistent re        260      36398      36138
ad gets

STAT...calls to get snapshot s        450      44200      43750
cn: kcmgss

STAT...execute count                   63      44015      43952
LATCH.shared pool                     463      44606      44143
STAT...consistent gets - exami        729      51860      51131
nation

STAT...recursive calls                838      73844      73006
STAT...consistent gets               1748      88444      86696
LATCH.library cache pin               436      88558      88122
LATCH.library cache                   757      89093      88336
STAT...session pga memory           95732          0     -95732
STAT...session logical reads        48987     151074     102087
LATCH.cache buffers chains         212197     405774     193577
STAT...session pga memory max      947700          0    -947700
STAT...redo size                 12908776   16933156    4024380

100 rows selected.

[email protected]>


it did less work -- generated 75% of the redo and so on...



Now, I ran that as a pipelined function (two merges -- merge vs merge) and the results 
were that the merge using a TABLE bested the pipelined function with about the same 
ratios.  

...
[email protected]> create type myScalarType as object (
  2   OWNER               VARCHAR2(30),
  3   OBJECT_NAME         VARCHAR2(30),
  4   SUBOBJECT_NAME      VARCHAR2(30),
  5   OBJECT_ID           NUMBER,
  6   DATA_OBJECT_ID      NUMBER,
  7   OBJECT_TYPE         VARCHAR2(18),
  8   CREATED             DATE,
  9   LAST_DDL_TIME       DATE,
 10   TIMESTAMP           VARCHAR2(19),
 11   STATUS              VARCHAR2(7),
 12   TEMPORARY           VARCHAR2(1),
 13   GENERATED           VARCHAR2(1),
 14   SECONDARY           VARCHAR2(1)
 15  )
 16  /

Type created.

[email protected]> create type myArrayType as table of myScalarType
  2  /

Type created.

[email protected]>
[email protected]> create or replace function ao_function return myArrayType
  2  PIPELINED
  3  as
  4  begin
  5      for ao in (select * from all_objects)
  6      loop
  7          pipe row( myScalarType( ao.OWNER, ao.OBJECT_NAME,
  8                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
  9                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 10                   ao.CREATED, ao.LAST_DDL_TIME,
 11                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 12                   ao.GENERATED, ao.SECONDARY) );
 13      end loop;
 14      return;
 15  end;
 16  /

Function created.

....
[email protected]> declare
  2      l_start number;
  3      l_run1  number;
  4      l_run2  number;
  5
  6      type rc is ref cursor;
  7      l_cur rc;
  8  begin
  9      insert into run_stats select 'before', stats.* from stats;
 10
 11      l_start := dbms_utility.get_time;
 12      merge into t1
 13      using ao on ( t1.object_id = ao.object_id )
 14      when matched then
 15          update set owner = ao.owner,
 16            object_name = ao.object_name,
 17            subobject_name = ao.subobject_name,
 18            data_object_id = ao.data_object_id,
 19            object_type = ao.object_type,
 20            created = ao.created,
 21            last_ddl_time = ao.last_ddl_time,
 22            timestamp = ao.timestamp,
 23            status = ao.status, temporary = ao.temporary,
 24            generated = ao.generated,
 25            secondary = ao.secondary
 26      when not matched then
 27          insert ( OWNER, OBJECT_NAME,
 28                   SUBOBJECT_NAME, OBJECT_ID,
 29                   DATA_OBJECT_ID, OBJECT_TYPE,
 30                   CREATED, LAST_DDL_TIME,
 31                   TIMESTAMP, STATUS, TEMPORARY,
 32                   GENERATED, SECONDARY )
 33          values ( ao.OWNER, ao.OBJECT_NAME,
 34                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 35                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 36                   ao.CREATED, ao.LAST_DDL_TIME,
 37                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 38                   ao.GENERATED, ao.SECONDARY);
 39      commit;
 40      l_run1 := (dbms_utility.get_time-l_start);
 41      dbms_output.put_line( l_run1 || ' hsecs' );
 42
 43      insert into run_stats select 'after 1', stats.* from stats;
 44      l_start := dbms_utility.get_time;
 45      merge into t2
 46      using (select * from TABLE(ao_function)) ao on ( t2.object_id = ao.object_id )
 47      when matched then
 48          update set owner = ao.owner,
 49            object_name = ao.object_name,
 50            subobject_name = ao.subobject_name,
 51            data_object_id = ao.data_object_id,
 52            object_type = ao.object_type,
 53            created = ao.created,
 54            last_ddl_time = ao.last_ddl_time,
 55            timestamp = ao.timestamp,
 56            status = ao.status, temporary = ao.temporary,
 57            generated = ao.generated,
 58            secondary = ao.secondary
 59      when not matched then
 60          insert ( OWNER, OBJECT_NAME,
 61                   SUBOBJECT_NAME, OBJECT_ID,
 62                   DATA_OBJECT_ID, OBJECT_TYPE,
 63                   CREATED, LAST_DDL_TIME,
 64                   TIMESTAMP, STATUS, TEMPORARY,
 65                   GENERATED, SECONDARY )
 66          values ( ao.OWNER, ao.OBJECT_NAME,
 67                   ao.SUBOBJECT_NAME, ao.OBJECT_ID,
 68                   ao.DATA_OBJECT_ID, ao.OBJECT_TYPE,
 69                   ao.CREATED, ao.LAST_DDL_TIME,
 70                   ao.TIMESTAMP, ao.STATUS, ao.TEMPORARY,
 71                   ao.GENERATED, ao.SECONDARY);
 72      commit;
 73      l_run2 := (dbms_utility.get_time-l_start);
 74      dbms_output.put_line( l_run2 || ' hsecs' );
 75      dbms_output.put_line
 76      ( 'run 1 ran in ' || round(l_run1/l_run2*100,2) || '% of the time' );
 77
 78      insert into run_stats select 'after 2', stats.* from stats;
 79  end;
 80  /
494 hsecs
1737 hsecs
run 1 ran in 28.44% of the time

PL/SQL procedure successfully completed.


 [email protected]> 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  /

....
STAT...session pga memory          104256     232480     128224
STAT...session uga memory               0     130928     130928
STAT...session uga memory max           0     130928     130928
LATCH.row cache enqueue latch         362     177614     177252
LATCH.row cache objects               448     184995     184547
LATCH.cache buffers chains         211442     493338     281896
STAT...session pga memory max      956224     166944    -789280
STAT...redo size                 12876460   14459964    1583504

106 rows selected.

 
It is the removal of the procedural, handwritten code here that makes the difference.  We 
should strive for ways to do things SET ORIENTED (as sql likes that best).  The less 
procedural code we write in general the better.

你可能感兴趣的:(upsert VS merge)