Mittwoch, Januar 26, 2011

INSERT-Performance (APPEND-Hint)

Mal wieder ein kleiner Konzepttest, allerdings einer, von dessen Plausibilität ich selbst nicht zu 100% überzeugt bin. Grundsätzlich geht es darum, zu bestimmen, wie groß der Effekt von Indizes auf die INSERT-Performance ist. Ich erinnere mich, dass Tom Kyte in (mindestens) einem seiner Bücher solche Tests durchgeführt hat, aber man wird das Rad ja wohl noch mal neu erfinden dürfen.

Hier noch ein paar Rahmenbedingungen (die ich sonst gerne verschweige, was eigentlich ziemlich unklug ist...)
  • Version 11.1.0.7
  • die Datenbank befindet sich im Noarchivelog-Modus
  • Blockgröße 16K
  • ASSM-Tablespace

Zunächst lege ich mir eine Datenquelle (Tabelle) an, aus der ich meine eigentliche Testtabelle über INSERT ... AS SELECT fülle. Durch die Verwendung der Tabelle will ich vermeiden, in den Ergebnissen die Performance der Satzgenerierung über die CONNECT BY LEVEL-Operation zu messen. Allerdings bin ich relativ sicher, dass es noch eine ganze Reihe weiterer Seiteneffekte gibt, die ich hier komplett vernachlässige, aber vielleicht fallen sie mir im Rahmen der Test ja noch ein.

-- Quelltabelle mit Testdaten
drop table test_insert_source;
create table test_insert_source
as
select rownum rn
     , mod(rownum , 2) col1
     , mod(rownum , 4) col2
     , mod(rownum , 8) col3
     , mod(rownum , 16) col4
     , lpad('*', 50, '*') col_pad
  from dual
connect by level <= 1000000;

-- mehrfache Verdoppelung der Satzanzahl
insert into test_insert_source
select *
  from test_insert_source;
 insert into test_insert_source
 
select *
  from test_insert_source;
 insert into test_insert_source

exec dbms_stats.gather_table_stats (ownname=>user, tabname=>'TEST_INSERT_SOURCE')
 
select count(*) 
  from test_insert_source;
  
  COUNT(*)
----------
   4000000  

 
Also eine Quelle mit 4.000.000 Sätzen - laut USER_SEGMENTS sind das ca. 320 MB. Jetzt lege ich noch eine leere Testtabelle an, in die diese Sätze übertragen werden:

drop table test_insert;
create table test_insert
as
select *
  from test_insert_source
 where 1 = 0;

Nun zu den Tests:

Fall 1: Logging 

insert into test_insert
select *
  from test_insert_source;
 
4000000 Zeilen wurden erstellt.

Abgelaufen: 00:00:08.50

Ausführungsplan
----------------------------------------------------------
Plan hash value: 1139762557

-----------------------------------------------------------------------------------------------
| Id  | Operation                | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                    |  4000K|   255M|  6148   (1)| 00:04:13 |
|   1 |  LOAD TABLE CONVENTIONAL | TEST_INSERT        |       |       |            |          |
|   2 |   TABLE ACCESS FULL      | TEST_INSERT_SOURCE |  4000K|   255M|  6148   (1)| 00:04:13 |
-----------------------------------------------------------------------------------------------

Statistiken
----------------------------------------------------------
       6004  recursive calls
     176803  db block gets
      59553  consistent gets
      11716  physical reads
  317893156  redo size
        760  bytes sent via SQL*Net to client
        833  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         18  sorts (memory)
          0  sorts (disk)
    4000000  rows processed

Auffällig ist zunächst mal, dass die Rows-Angabe akkurat ist, aber die Bytes zu niedrig geschätzt werden, was wahrscheinlich mit der AVG_ROW_LEN=67 zusammenhängt, die DBMS_STATS ermittelt hat ((67 * 4000000) / 1024 / 1024 = 255,584717) - aber das nur am Rande. Die größte Zahl hier ist die redo size - und da die Daten ja komplett nutzlos sind, schaue ich mir mal an, was passiert, wenn ich das Logging für die Tabelle abschalte:

Fall 2: Nologging 

truncate table TEST_INSERT;
alter table TEST_INSERT nologging;

insert into test_insert
select *
  from test_insert_source; 

4000000 Zeilen wurden erstellt.

Abgelaufen: 00:00:09.70

Ausführungsplan
----------------------------------------------------------
Plan hash value: 1139762557

-----------------------------------------------------------------------------------------------
| Id  | Operation                | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                    |  4000K|   255M|  6148   (1)| 00:04:13 |
|   1 |  LOAD TABLE CONVENTIONAL | TEST_INSERT        |       |       |            |          |
|   2 |   TABLE ACCESS FULL      | TEST_INSERT_SOURCE |  4000K|   255M|  6148   (1)| 00:04:13 |
-----------------------------------------------------------------------------------------------

Statistiken
----------------------------------------------------------
       5009  recursive calls
     175689  db block gets
      59063  consistent gets
      19961  physical reads
  317771112  redo size
        760  bytes sent via SQL*Net to client
        833  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
    4000000  rows processed 
Scheint nicht viel geholfen zu haben. Aber da hilft mal wieder Tom Kyte mit seiner Matrix der Effekte von Nologging (die ich mir schon ziemlich oft angesehen habe, aber offenbar nicht merken kann):

Table Mode    Insert Mode     ArchiveLog mode      result
-----------   -------------   -----------------    ----------
LOGGING       APPEND          ARCHIVE LOG          redo generated
NOLOGGING     APPEND          ARCHIVE LOG          no redo
LOGGING       no append       ""                   redo generated
NOLOGGING     no append       ""                   redo generated
LOGGING       APPEND          noarchive log mode   no redo
NOLOGGING     APPEND          noarchive log mode   no redo
LOGGING       no append       noarchive log mode   redo generated
NOLOGGING     no append       noarchive log mode   redo generated

Offenbar ist Nologging für eine Noarchivelog-DB irrelevant. Da ich gerade keine Archivelog-DB zur Hand habe, muss dieser Test demnach verschoben werden.

Fall 3: APPEND

truncate table TEST_INSERT;
alter table TEST_INSERT logging;

insert /*+ append */ into test_insert
select *
  from test_insert_source;

Abgelaufen: 00:00:04.37

Ausführungsplan
----------------------------------------------------------
ERROR:
ORA-12838: Objekt kann nach paralleler Änderung nicht gelesen/geändert werden


SP2-0612: Fehler beim Generieren von AUTOTRACE EXPLAIN Bericht

Statistiken
----------------------------------------------------------
       4861  recursive calls
      23060  db block gets
      21167  consistent gets
      11701  physical reads
     372228  redo size
       1552  bytes sent via SQL*Net to client
        999  bytes received via SQL*Net from client
          6  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
    4000000  rows processed

Auffällig ist:
  • der ORA-12838-Fehler bei der Erzeugung des Zugriffsplans (wobei tatsächlich keine Parallelisierung im Spiel ist, wie man über DBMS_XPLAN.DISPLAY_CURSOR oder SQL_TRACE sehen kann)
  • die Laufzeit sinkt (gegenüber Fall 1) von 8,50 sec. auf 4,37 sec. Also etwa auf die Hälfte.
  • die redo size sinkt dramatisch (< 400K gegenüber > 300M)
  • auch die Anzahl von db block gets, consistent gets, physical reads reduziert sich stark. 
Autotrace ist hier nicht das geeignete Tool, aber ein SQL_TRACE zeigt für den APPEND-Fall Wait-Events wie direct path write und direct path sync, während der Fall ohne APPEND vor allem auf db file scattered read und log events (log file switch completion, log buffer space) wartet. Das zeigt einerseits, dass die Leseoperationen im APPEND-Fall erwartungsgemäß am Buffer Cache vorbei und über direct path Operationen laufen (was auch die Ursache für den ORA-12838-Fehler ist) - und andererseits, dass der Performance-Unterschied zwischen den beiden Fällen zumindest zum Teil von der redo-Generierung her kommt. Ich messe also offenbar mehrere Dinge, die nicht unmittelbar miteinander zu tun haben.

Damit habe ich also klar das Thema verfehlt... - und angesichts dieses Ergebnisses - und der Länge dieses Eintrags - vertage ich die Untersuchung des Effekts von Indizes auf einen späteren Zeitpunkt ...

Keine Kommentare:

Kommentar veröffentlichen