# Select vs Update ins blaue, was ist teurer?



## OnDemand (26. Apr 2020)

Hallo zusammen,

versuch grad heraus zu finden, was in meinem folgenden Szenario DB lastiger ist um die DB nicht unnötig zu belasten.

Ich habe eine List mit 5000 Datensätzen aus einer Text-Datei, diese 5000 Objekte sind bereits in der Datenbank und müssen updated werden.
Sagen wir es sind 5000 Autos, jeweils muss der Preis updated werden (um es einfach zu halten, es werden noch wesentlich mehr Merkmale updated) und es muss ein Flag gesetzt werden, ob der Wagen noch in der Datei ist.

Nun die Frage, was ist "teurer" Jedes Auto einzeln erst mit Select holen, dann prüfen ob der Preis, Lagermenge, Lagerort etc geändert hat, wenn ja > updaten oder ist es schlauer direkt den Update Query absetzen und dabei auch gleich isavailable = true setzen?

Ich vermute, dass es die DB doppelt belastet wenn das Objekt erst geholt wird und dann nochmal updated wird oder?

Ich versuch es hier nochmal mit Dummycode darzustellen:

Plan A: Jedes Auto per select erstmal anfragen und während dessen als "Auto ist noch in der Textdatei" markieren.

```
void arbeiteTextDateiab() {

    updateAvailable();

    List<Auto> autos = leseDatei();
    for (Auto autoAusDatei : autos) {
        Auto existingAuto = getExistingAutoAusDb(autoAusDatei.id);
        if (existingAuto != null) {
            if (existingAuto.gerPreis() != autoAusDatei.getPreis()) {
                updatePrice();
            }
        }
          //else {
          //nichts machen, weil keine Änderung
         // }
    }


updateAvailable(){
Update auto set isavailable = false; //Alle Autos erstmal als nicht verfügbar markieren, wird dann auf true gesetzt, wenn es abgefragt wird. Alle die nach dem durchlauf immer noch false sind, sind werden deaktiviert, da sie nicht mehr in der Datei waren.
}

getExistingAutoAusDb(int id) {
    Select * from auto where id = id;
    Update auto set isavailable = true where id = id;
}

lesedatei() {
//Lese Datei
    return List < Auto >;
}
```

Plan B:
Auto einfach auf gut Glück updaten:

```
List<Auto> autos = leseDatei();
    for(Auto autoAusDatei :autos){
        updatePrice(autoAusDatei);
    }

    updatePrice(Auto autoAusDatei){
        Update auto set price = autoAusDatei.getPreis(), SET isavailable=true WHERE id= autoAusDatei.getId();
    }

    lesedatei() {
        //Lese Datei
        return List < Auto >;
    }

    updateAvailable() {
        Update auto set isavailable = false; //Alle Autos erstmal als nicht verfügbar markieren, wird dann auf true gesetzt, wenn es abgefragt wird. Alle die nach dem durchlauf immer noch false sind, sind werden deaktiviert, da sie nicht mehr in der Datei waren.
    }
```


----------



## httpdigest (26. Apr 2020)

Am schnellsten sind parametrisierte JDBC PreparedStatements zusammen mit JDBC Batch Updates. Google das am besten mal. Ich habe ein Projekt, bei dem das Aktualisieren von 138.000 Datensätzen mit ca. 10 Spalten in unter 5 Sekunden passiert.
JDBC PreparedStatements sorgen dafür, dass der JDBC-Treiber das SQL nicht bei jedem Update parsen und die Datenbank nicht jedesmal ein Execution Plan zu erzeugen braucht und Batch Update sorgt dafür, dass nicht für jedes Update ein Netzwerk-Request gesendet wird.


----------



## OnDemand (26. Apr 2020)

Oh klingt spannend, hätte noch dazu sagen solle, dass ich Spring Boot nutze. Ist das da auch machbar?


----------



## httpdigest (26. Apr 2020)

https://www.baeldung.com/spring-jdbc-jdbctemplate#batch-operations


----------



## OnDemand (26. Apr 2020)

Top, danke! Was mir da spontan (ohne tieferes Prüfen) noch in den Sinn kommt; ich muss auch "Autos" anlegen, wenn diese nicht in der DB sind. Daher meine Überlegung erstmal den Updatequery zu senden, wenn ich 1 zurück gekomme, wurde das Auto updated, wenn ich 0 zurück bekomme ist es noch nicht drin und ich muss es anlegen. Wie könnte man das in Verbindung mit Batch machen?


----------



## Wurstkopp (26. Apr 2020)

Grundsätzlich würde ich wenn es um Performance geht immer versuchen wenige große IO Operationen durchzuführen statt viele kleine. Also in diesem Fall erstmal alle in Frage kommenden Autos in den Speicher laden, dort dann im Code deine Logiken ausführen und anhand dieser eigene Listen für die verschiedenen Updatetypen aufbauen. Also eine Liste die geupdated werden müssen, eine Liste die neu angelegt werden soll, welche gelöscht werden sollen usw. Diese Listen werden dann erst am Ende wie bereits beschrieben mit den PreparedStatements zur Datenbank gefeuert.

Das hat natürlich auch Nachteile, weshalb man das Vorgehen nicht pauschal als das beste bezeichnen kann. Wenn du z.B. vorher nicht weißt, welche Daten (In dem Fall Autos) du denn aus der Datenbank brauchst. Wenn die DB sagen wir mal 5.000.000 Autos hat und du in deiner Liste für gewöhnlich nur 5.000 zufällige hast, dann ist das sehr ineffektiv. Auch ist der RAM Bedarf je nach Datenmenge sehr hoch. Man muss das also im Zweifel je nach Fall weiter optimieren. In der Praxis hatte ich damit aber meist sehr gute Ergebnisse.


----------



## OnDemand (28. Apr 2020)

@httpdigest danke für den Tipp mit dem Prepared Statement! Der war gold wert. 5000 Updates in 20 Sek. Auf dem Livesystem gehts sicher noch schneller, die Verbindung von Test zur externen DB ist etwas lahm. Gibt es hier was einzuwenden? Das schließen der Session ist richtig oder?
Ich hole mir die Connection über den EntityManager da wir Hibernate im Einsatz haben und die DB Tenant aufgebaut ist, daher komm ich nicht wirklich anders an die reine Connection.


```
public void updateAuto(@RequestHeader("tenant") String tenant, @RequestBody List<Auto> autos) {
        TenantContext.setCurrentTenant(tenant);
        try {
            Session hibernateSession = em.unwrap(Session.class);
            hibernateSession.doWork(new org.hibernate.jdbc.Work() {

                @Override
                public void execute(Connection connection) throws SQLException {
                    PreparedStatement ps = connection
                            .prepareStatement("update auto set xxx=?, WHERE id=?");

                    for(Auto auto : autos){
                        ps.setInt(1, auto.getXxx());
                        ps.setString(2,auto.getId());
                        ps.addBatch();
                    }
                    ps.executeBatch();
                }
            });
            hibernateSession.close();
        } catch (Exception e) {
            log.error("Error : " + tenant, e);
        }
```


----------



## mrBrown (28. Apr 2020)

Kannst auch Spring Data JPA dafür nutzen, wenn du sowieso Spring Boot im Einsatz hast


----------



## OnDemand (28. Apr 2020)

Öh ok, müsste ich mich beschäftigen erstmal. Hätte das Vorteile/Nachteile?


----------



## mrBrown (28. Apr 2020)

Wenn du noch nie Spring Data JPA genutzt hast würd ich mich nicht deswegen darin einarbeiten, wag nur als Hinweis gedacht, dass man nicht nur wegen Batch Update darauf verzichten muss


----------



## OnDemand (28. Apr 2020)

ok danke  Mit der og. Lösung fahr ich ganz gut. Geht ratzi fatzi


----------



## OnDemand (29. Apr 2020)

Hallo nochmal, wenn ich wie im Code siehe #7 den EM nutze, muss ich den closen?
Ich bekomme nämlich an anderer Stelle in der Klasse keine Verbindung mehr zur DB. Mit close wird es wohl endgültig geschlossen uns aus dem Pool entfernt. Kann ich die Verbindung "zurückgeben" oder soll ich einfach nichts machen und das Framework kümmert sich drum?
Caused by: java.lang.IllegalStateException: Session/EntityManager is closed


----------

