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



## Rol (17. Mrz 2011)

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 (17. Mrz 2011)

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 (17. Mrz 2011)

fastjack hat gesagt.:


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




```
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?


----------



## SlaterB (17. Mrz 2011)

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 (17. Mrz 2011)

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)


----------



## Gast2 (17. Mrz 2011)

Rol hat gesagt.:


> ```
> String SQL = "SELECT ...";
> try {
> stmt = con.createStatement();
> ...



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:

```
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 (17. Mrz 2011)

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


----------



## anonym (17. Mrz 2011)

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.


----------



## Gast2 (17. Mrz 2011)

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...


----------



## anonym (17. Mrz 2011)

fassy hat gesagt.:


> 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 (18. Mrz 2011)

anonym hat gesagt.:


> 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:

```
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:

```
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?


----------



## Murray (18. Mrz 2011)

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


----------



## Gast2 (18. Mrz 2011)

Rol hat gesagt.:


> Wenn ich jetzt Zeile 16 durch:
> 
> ```
> testLabel.setText("foo");
> ...



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:

```
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...


----------



## maki (18. Mrz 2011)

@Rol

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


----------



## Rol (18. Mrz 2011)

fassy hat gesagt.:


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



Ja, habe ich. Jedoch auch bei

```
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%.



Murray hat gesagt.:


> 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).



maki hat gesagt.:


> 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?


----------



## Gast2 (18. Mrz 2011)

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:


```
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.


----------



## Gast2 (18. Mrz 2011)

Rol hat gesagt.:


> 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:



Rol hat gesagt.:


> Ja, habe ich. Jedoch auch bei
> 
> ```
> long foo = 0;
> ...



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.


----------



## fastjack (18. Mrz 2011)

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 (18. Mrz 2011)

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...


----------



## maki (18. Mrz 2011)

> 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.


----------



## Rol (18. Mrz 2011)

maki hat gesagt.:


> Aber danach sind Statement/ResultSet zu schliessen, ausser diese Methode ist dein ganzes Programm und wird nur ein einziges Mal aufgerufen.



Ja, am Ende schließe ich sie.


----------

