Prog. läuft ruckartig bei größeren Datenmengen

Rol

Aktives Mitglied
Hi,

ich hole einige tausend bis einige Mio Datensätze (je vier double) aus einer MySQL DB, führe damit ein paar Operationen aus und stelle das Ergebnis in einem Chart dar.
Das funktioniert auch ganz gut, aber ab einer bestimmten Datenmenge (ca. 1 Mio Datensätze) läuft es nicht mher flüssig sondern läuft nur ca. 0,5. Sekunden und steht dann ca. 0,5 SeKunden, läuft dann wieder usw..
Laut Windows7 Task Manager benutzt das Programm dabei ca. 400 MByte Ram vom 4GB.

Hat jemand eine Idee wn was das liegen könnte?
 

fastjack

Top Contributor
Das hängt ja auch stark davon ab, wie Du das implementiert hast und wieviel Speicher der VM zur Verfügung steht. Wenn Du den erhöhst, wird es whl. wieder schneller laufen, bis du 2Mio Datensätze hast,..
 

Rol

Aktives Mitglied
Das hängt ja auch stark davon ab, wie Du das implementiert hast

Java:
        String SQL = "SELECT ...";
        try {
            stmt = con.createStatement();
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
        try {
            rs = stmt.executeQuery(SQL);
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
        try {
            while (rs.next()) {
            meineMethode(rs.getDouble("foo"));
            ...
       }

Wie könnte man das besser machen?

und wieviel Speicher der VM zur Verfügung steht. Wenn Du den erhöhst, wird es whl. wieder schneller laufen, bis du 2Mio Datensätze hast,..

Wie und wo kann ich das denn ändern?
 
S

SlaterB

Gast
mit den elementarsten Mitteln eines Browsers, z.B. 'Speicher der VM' aus dem Text in eine Suchmaschine eintippen,
notfalls intelligent modifizieren, 'java', 'ändern', 'erhöhen' sind mögliche Schlüsselwörter, oder gleich alles auf englisch
 

parabool

Bekanntes Mitglied
Berechnungen eventuell von der DB ausführen lassen
oder Datenmenge schrittweise durchlaufen, Berechnungen durchführen, Ergebnisse zusammenführen (falls die Berechnung
nicht die gesamte Datenmenge auf einmal benötigt)
 
G

Gast2

Gast
Java:
        String SQL = "SELECT ...";
        try {
            stmt = con.createStatement();
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
        try {
            rs = stmt.executeQuery(SQL);
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
        try {
            while (rs.next()) {
            meineMethode(rs.getDouble("foo"));
            ...
       }

Wie könnte man das besser machen?

Ohne jezt auf den Performanceteil einzugehen. Dein Exception Handling ist ein wenig ungeschickt. Wenn createStatement fehlschlägt hast du eine NullPointerException in Zeile 8. Ebenso wenn executeQuery fehlschlägt einen NullPointerException in Zeile 13.

Sinnvollerweise bricht man den Block ab wenn der Rest nicht mehr zu erreichen ist oder sich durch einen früheren Fehler nur noch weitere Fehler ergeben können, also:
Java:
        String SQL = "SELECT ...";
        try {
            stmt = con.createStatement();
            rs = stmt.executeQuery(SQL);

            while (rs.next()) {
            meineMethode(rs.getDouble("foo"));
            ...
            
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
 

hansmueller

Bekanntes Mitglied
Hallo,

vielleicht kannst du die SQL-Abfrage so gestallten, daß du nicht alle Datensätze auf einmal im ResultSet hast, sondern nur ein paar oder sogar jeweils nur einen.

Das könnte dann zwar insgesamt etwas länger dauern, aber sollte etwas speicherschonender sein.

MfG
hansmueller
 
A

anonym

Gast
Hallo,

ich habe gerade ein ähnliches Problem und ein bisschen gegoogelt. Dabei kam heraus:

Der Tip, den Query so zu gestalten, dass jeweils nur ein Teil der Datensätze geholt wird, hilft nur bedingt. Er hilft dann, wenn alle Datensätze zusammen mehr sind, als in den Arbeitsspeicher passen. Allerdings frage ich mich dann, wie du deine Charts aufbaust, da sind ja auch alle Sachen drin (und vermutlich ist alles, was in der Chart ist, auch im Arbeitsspeicher). Dieser Ansatz hilft dir also nur, wenn du eine Chart mit "blättern" baust, so dass jeweils nur ein Blatt geladen wird. Dazu ist noch etwas zu bedenken:

SELECT*FROM table ORDER BY field_a LIMIT 1000 OFFSET 2000

wird dir zwar nur 1000 Datensätze liefern, nämlich die an den Postionen 2000 bis 2999 in Sortierung nach field_a, aber:
Der Server wird erst alle Datensätze von der Festplatte lesen, sortieren und dann die nicht benötigten wegschmeißen. Und das mehrmals, nämlich für jede Portion einmal. Das ist nicht unbedingt performant...(gilt nicht, wenn field_a ein Index ist, dann sind die Datensätze nämlich schon richtig sortiert). Gegebenenfalls muss der Server sogar eine Auslagerungsdatei auf der Festplatte nutzen (erstellen und wegschmeißen), weil deine x- tausend Datensätze eben nicht ins RAM passen (btw. embedded Database oder Server?). Ich kann hier gerade wunderschön beobachten, wie die Auslagerungsdatei erzeugt wird, wächst, gelöscht wird, neu erzeugt wird...

Allerdings ist meine Situation auch etwas anders. Ich muss aus den geholten Datensätzen neue Werte berechnen und diese in die Datenbank schreiben. Allerdings lese ich ja gerade aus dem Ding, was bedeutet, dass sie gelockt ist (SQLite). Folglich starte ich eine Transaktion, mache die Updates in der Transaktion und commite die jeweils zwischen den Paketen. Das Performanteste, was mir dabei gelungen ist, war bisher, die Paketgröße (das LIMIT im SQL- Query) so zu wählen, dass für die Update- Transaktion keine Auslagerungsdatei gebraucht wird. Die kurze Pause wann immer der Server alle Einträge liest nur um dann die, die nicht in LIMIT und OFFSET fallen, wegzuwerfen, merke ich trotzdem deutlich.
 
G

Gast2

Gast
Das lässt sich so auch nicht verallgemeinern. Hängt schon noch sehr vom eingesetzen Datenbanksystem, Infrakstruktur und der Szenario ab. Ist aber immer gut sich mal anzusehen was das RDBMS so treibt...
 
A

anonym

Gast
Das lässt sich so auch nicht verallgemeinern. Hängt schon noch sehr vom eingesetzen Datenbanksystem, Infrakstruktur und der Szenario ab. Ist aber immer gut sich mal anzusehen was das RDBMS so treibt...

Ja, klar. Ich vergaß, dass zu erwähnen. Solche Infos finden sich für gewöhnlich in den Handbüchern des RDBMS
 

Rol

Aktives Mitglied
Hallo,
Der Tip, den Query so zu gestalten, dass jeweils nur ein Teil der Datensätze geholt wird, hilft nur bedingt. Er hilft dann, wenn alle Datensätze zusammen mehr sind, als in den Arbeitsspeicher passen.
Allerdings frage ich mich dann, wie du deine Charts aufbaust, da sind ja auch alle Sachen drin (und vermutlich ist alles, was in der Chart ist, auch im Arbeitsspeicher).

Der Chart zeigt nur die jeweils letzte 100 Datensätze an.

Ich habe jetzte den Code weitestgehend auf das wesentliche zusammen gestutzt:
Java:
     private void Test() {
         Connection con = Main.sampleFrame.historicalDatabase.con;
         Statement stmt = null;
         try {
             stmt = con.createStatement();
         } catch (SQLException ex) {
             System.out.println("SQL Exception: " + ex.toString());
         }
         ResultSet rs = null;
         String SQL = "SELECT a, b, c, d, e FROM meineTabelle WHERE b='foo' ORDER BY a LIMIT 1100000";
         try {
             stmt = con.createStatement();
             rs = stmt.executeQuery(SQL);

             while (rs.next()) {
                 testLabel.setText(String.valueOf(rs.getLong("a")));
             }
         } catch (SQLException ex) {
             System.out.println("SQL Exception: " + ex.toString());
         }
     }

Der Netbeans profiler zeigt mir dabei an, dass die "Relative Time Spent in GC" kontinuierlich bis auf 74% ansteigt und mein Programm dabei immer langsamer und ruckeliger wird.

Wenn ich jetzt Zeile 16 durch:
Java:
                 testLabel.setText("foo");
ersetzte läuft die "Relative Time Spent in GC" nur bis auf 6% hoch und das Programm ist in kurzer Zeit fertig. Ich habe dann noch einen Couter eingebaut der in der while (rs.next()) Schleife inkrementiert wird und am Ende angezeigt wird. Die Schleife wird in jedem Fall sooft durchlaufen wie Datensätze aus der DB kommen (1100000).

Also entsteht der ganze Streß beim auslesen der Werte aus dem ResultSet, oder?

Gibts da eine Lösung?
 
G

Gast2

Gast
Wenn ich jetzt Zeile 16 durch:
Java:
                 testLabel.setText("foo");
ersetzte läuft die "Relative Time Spent in GC" nur bis auf 6% hoch und das Programm ist in kurzer Zeit fertig. Ich habe dann noch einen Couter eingebaut der in der while (rs.next()) Schleife inkrementiert wird und am Ende angezeigt wird. Die Schleife wird in jedem Fall sooft durchlaufen wie Datensätze aus der DB kommen (1100000).

Also entsteht der ganze Streß beim auslesen der Werte aus dem ResultSet, oder?

Gibts da eine Lösung?

Das in zweiten Fall der GC nicht viel zu tun hat liegt daran das "foo" schon vom Compiler als Konstanter Wert gesehn wird. Es wird also nur einmal "foo" in den String Pool abgelegt und somit wegoptimiert.

Probier mal:
Java:
                 testLabel.setText(new String("foo"));

Dann solltest du das gleiche Problem haben. Was machst du da, direkt in der while Schleife die GUI updaten? Das ist kein gute Idee...
 
Zuletzt bearbeitet von einem Moderator:
M

maki

Gast
@Rol

Ich sehe in deinem Code nicht dass Ressourcen (Statement, ResultSet) geschlossen werden.
 

Rol

Aktives Mitglied
Probier mal:
Java:
                 testLabel.setText(new String("foo"));

Dann solltest du das gleiche Problem haben. Was machst du da, direkt in der while Schleife die GUI updaten? Das ist kein gute Idee...

Ja, habe ich. Jedoch auch bei
Java:
            long foo = 0;
            while (rs.next()) {
                foo = rs.getLong("a");
            }

Es wird also in der while Schleife nur das ResultSet ausgelesen. Der Wert für den GC läuft nach ein paar Sekunden auf knapp 80%.

Hast du denn der VM schon mehr Speicher zugestanden, wie Fastjack gestern vorgeschlagen hat?

Ich habe in der Systemsteuerung (Windows 7)->Java->Reiter"Java"->Anzeigen bei Runtime.Parameter: -Xms1024m eingetragen und die Kiste dann neu gestartet. Allerdings sagt der Netbeans Profiler das Java den Heap nur bis 256MB erhöht (Der Chart steigt nach Programmstart und schein dann bei 256MB gedeckelt zu sein).

Ich sehe in deinem Code nicht dass Ressourcen (Statement, ResultSet) geschlossen werden.
Nein, während der while Schleife (dort schein ja der Müll anzufallen) kann ich doch auch nichts schließen, oder wie meinst Du das?
 
G

Gast2

Gast
Nebenbei - wenn [c]Der Chart zeigt nur die jeweils letzte 100 Datensätze an.[/c] gilt. Warum ziehst du dir dann eine Millionen Records?

Wenn du irgendwelche analytischen Funktionen auf den Werten nutzen willst guck mal ob du das direkt in dem Query auf der Datenbank vorbereiten kannst. Mit gut gewählten Indexen ist das mit Sicherheit performanter.

Ansonsten probier mal folgendes. Lad dir alle rows in den Speicher und guck wie lange das dauert und wie die speicherauslastung deiner JVM ist:

Java:
    private void Test() {
        Connection con = null;
        Statement stmt = null;
//        try { 
//            stmt = con.createStatement();  // machst du ja später schon
//        } catch (SQLException ex) {
//            System.out.println("SQL Exception: " + ex.toString());
//        }
        ResultSet rs = null;
        String SQL = "SELECT a, b, c, d, e FROM meineTabelle WHERE b='foo' ORDER BY a LIMIT 1100000";
        List<Object[]> rows = new ArrayList<Object[]>(); 
        try {
        	long start = System.currentTimeMillis();
            rs = stmt.executeQuery(SQL);
            while (rs.next()) {
            	rows.add(new Object[]{
            		rs.getLong("a"),
            		rs.getLong("b"),
            		rs.getLong("c"),
            		rs.getLong("d"),
            		rs.getLong("e")
            	});
            }
            // benchmark... nur bedingt nutzvoll, aber kann man als anhaltspunkt nehmen ob das was stockt.
            System.out.println("Needed "+(System.currentTimeMillis() - start) + "ms");
            // closing resources. Eigentlich reicht auch nur das con.close()
            rs.close();
            stmt.close();
            con.close();
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
    }

Dann am besten nochmal den gleichen Query mit dem MySQL Query Browser absetzen, zum letzten Datensatz springen und gucken wie lange der QueryBrowser braucht und vergleichen.
 
G

Gast2

Gast
Ich habe in der Systemsteuerung (Windows 7)->Java->Reiter"Java"->Anzeigen bei Runtime.Parameter: -Xms1024m eingetragen und die Kiste dann neu gestartet. Allerdings sagt der Netbeans Profiler das Java den Heap nur bis 256MB erhöht (Der Chart steigt nach Programmstart und schein dann bei 256MB gedeckelt zu sein).

Netbeans nutzt mit Sicherheit eigene Setting für die JVM. Versuch es mal so:
NetBeans Tuning JVM switches for performance

Es kann auch sein das du da was in den Project Settings in Netbeans zu einstellen kannst. Musst du dich mal duchklicken.

Nochwas:

Ja, habe ich. Jedoch auch bei
Java:
            long foo = 0;
            while (rs.next()) {
                foo = rs.getLong("a");
            }

Es wird also in der while Schleife nur das ResultSet ausgelesen. Der Wert für den GC läuft nach ein paar Sekunden auf knapp 80%.

Ist ja auch klar... Du ließt einen Wert und weist den einem primitiven Datentyp zu. Der ist immuntable, kann also nicht geändert werden. Dann ließt du einen weiteren Wert und "überschreibst" foo. Dabei wird natürlich ein neuer long angelegt und der alte landed auf dem Müll... wird nicht mehr referenziert und vom GC weggesammelt. Etwas ungeschickt so.
 
Zuletzt bearbeitet von einem Moderator:

fastjack

Top Contributor
1.100.000 werden auf DB-Ebene schon whl. schnell selektiert, der Transport dauert auch nicht. Die meiste Zeit wird verbraucht, wenn der DB-Treiber in Java daraus ein Result-Object zusammenstellt.
Dem GC sollte das egal sein, er hat nichts zu tun, das ResultObjekt enthält nun mal bis zum schließen/nullen soviele Datensätze (wenn es nicht manipuliert wird).
Ich wette das Du eine gute Geschwindigkeit erzielst wenn Du z.B. immer 10.000 oder 20.000 Datensätze "lädst" und verarbeitest. Das ist auch speichersparender.
 

Rol

Aktives Mitglied
Netbeans verwendet eine eigene EInstellung für den Heapsize:
Run -> Set Priject Configuration -> Customize -> Run -> VM Options: -Xms1024m
Hat das Problem gelöst. Es wird zwar lauf Profiler nur maximal ca. 256MB nenötigt, aber es wird eben zu keinem Zeitpunkt mehr knapp und der GC bleibt ruhig.

Danke an alle, habe wieder mal einiges gelernt.
Gutes Forum hier.

So long...
 
M

maki

Gast
Nein, während der while Schleife (dort schein ja der Müll anzufallen) kann ich doch auch nichts schließen, oder wie meinst Du das?
Aber danach sind Statement/ResultSet zu schliessen, ausser diese Methode ist dein ganzes Programm und wird nur ein einziges Mal aufgerufen.
 

Ähnliche Java Themen

Neue Themen


Oben