2010-06-12 3 views
15

Ich arbeite an einem latenzempfindlichen Teil einer Anwendung, im Grunde werde ich ein Netzwerkereignis erhalten, um die Daten zu transformieren und dann alle Daten in die DB einzufügen. Nach dem Profiling sehe ich, dass meine gesamte Zeit damit verbracht wird, die Daten zu speichern. hier ist der CodeHochleistungs-Hibernate-Insert

private void insertAllData(Collection<Data> dataItems) 
{ 
    long start_time = System.currentTimeMillis(); 
    long save_time = 0; 
    long commit_time = 0; 
    Transaction tx = null; 
    try 
    { 
     Session s = HibernateSessionFactory.getSession(); 
     s.setCacheMode(CacheMode.IGNORE); 
     s.setFlushMode(FlushMode.NEVER); 
     tx = s.beginTransaction(); 
     for(Data data : dataItems) 
     { 
      s.saveOrUpdate(data); 
     } 
     save_time = System.currentTimeMillis(); 
     tx.commit(); 
     s.flush(); 
     s.clear(); 
    } 
    catch(HibernateException ex) 
    { 
     if(tx != null) 
      tx.rollback(); 
    } 
    commit_time = System.currentTimeMillis(); 
    System.out.println("Save: " + (save_time - start_time)); 
    System.out.println("Commit: " + (commit_time - save_time)); 
    System.out.println(); 
} 

Die Größe der Sammlung immer kleiner als 20 ist hier die Zeitdaten, die ich sehe:

Save: 27 
Commit: 9 

Save: 27 
Commit: 9 

Save: 26 
Commit: 9 

Save: 36 
Commit: 9 

Save: 44 
Commit: 0 

Diese verwirrend für mich ist. Ich denke, dass die save sollte schnell sein und die ganze Zeit sollte auf commit ausgegeben werden. aber klar liege ich falsch. Ich habe auch versucht, die Transaktion zu entfernen (es ist nicht wirklich notwendig), aber ich sah schlechtere Zeiten ... Ich habe hibernate.jdbc.batch_size = 20 ...

Ich kann erwarten, dass so viele wie 500 Nachrichten/Sek also brauche ich die Verarbeitung einzelner Nachrichten unter 20 Millisekunden.

ich brauche diese Operation, um so schnell wie möglich zu sein, idealerweise würde es nur einen Hin- und Rückweg zur Datenbank geben. Wie kann ich das machen?

+0

BTW, sollen Sie 'commit()' nach dem 'flush()' wenn 'FlushMode # NIEMALS' verwenden? –

+0

@Pascal Thivent. Ich weiß nicht :-) – luke

+1

Nun, lesen Sie das Javadoc von 'Transaction # commit()' :) –

Antwort

13

Verschieben Sie die Primärschlüsselgenerierung von einem serverseitigen automatischen Inkrement. Ihr Java-Code muss für die PK-Generierung verantwortlich sein, um Rundreisen zu vermeiden.

Für anständige Bulk-Insert-Leistung benötigen Sie eine Methode, die nicht bei jedem Aufruf von saveOrUpdate auf die Datenbank zugreifen muss. Die Verwendung von UUIDs als Primärschlüssel oder implementing HiLo kann dabei helfen. Sonst wird tatsächlich kein Bulk-Insert ausgeführt.

Um sowohl Leistung als auch Interoperabilität mit anderen externen Systemen zu haben, sind die gepoolten oder die pooled-lo Optimierer die beste Wahl.

+1

Ich verwende derzeit eine Oracle-Sequenz, um IDs zu generieren. ist das nicht machbar? – luke

+1

Das war genau das !, Ich entfernte die Sequenz und fügte eine Abfrage an den Start, um herauszufinden, wo die Sequenz und bam, 7.5X Beschleunigung zu starten, die es deutlich unter meiner Schwelle. – luke

+0

Ich bin froh zu hören, dass es funktioniert :-) – Michael

3

Ehrlich gesagt, weiß ich nicht, was aus Ihrem Test und von den "Maßnahmen", die Sie zeigen, vernünftigerweise geschlossen werden kann (ich vermute viel Overhead vom Aufwärmen, die Sammlung ist sehr klein, und die Probe ist sehr klein).

Wie auch immer, ich kann Ihnen sagen, dass Ihr aktueller Code nicht skaliert wird und Sie sehr wahrscheinlich die Sitzung explodieren werden, wenn Sie eine größere Sammlung übergeben. Sie müssen die Sitzung in regelmäßigen Abständen leeren und löschen (jeweils 20 Datensätze, wenn die Stapelgröße 20 beträgt).

Eigentlich empfehle ich das ganze Chapter 13. Batch processing zu lesen.

+0

Ich spüle und lösche die Sitzung im obigen Code. Sammlungen werden nie größer als 20 sein. – luke

0

Einige grundlegende Dinge:

  • Haben Sie Trigger oder Fremdschlüssel Zwänge ohne Index?
  • Haben Sie Batch-Treiber?
  • Sind Ihre Treiber im Batch-Modus (siehe hibernate.jdbc.batch_size von Pascal's Referenz)?
  • Alle Indizes auf Ihren Tabellen (wenn Sie eine Menge von Indizes haben, kann es manchmal langsamer einfügen)?

Batching ist Teil von JDBC 2.0, es ermöglicht Ihnen, mehrere Anweisungen in einem 'Batch' auszuführen; Die Idee besteht darin, die Umlaufzeit zu reduzieren (Sie können mehrere Stapel pro Transaktion ausführen).

Statement stmt = dbCon.createStatement("insert into DataTable values (?,?,?)"); 
stmt.setInt(1, x1); stmt.setInt(2, x2), stmt.setString(3, "some value"); 
stmt.addBatch(); 
... 
stmt.setInt(1, x2); stmt.setInt(2, x3), stmt.setString(3, "some other value"); 
stmt.addBatch(); 

stmt.executeBatch(); 
dbCon.commit(); 

Sie können dies wahrscheinlich als Benchmark-Test verwenden. Ich würde auch auf die SQL schauen, die Hibernate erzeugt, um zu sehen, ob es eine Abfrage pro Einfügung durchführt, um die erzeugten Ids zu erhalten.

+1

Wie kann ich feststellen, ob ich einen Batch-Treiber habe? – luke