Verwendung von with und closing

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Servus Leute,

ich hätte eine kurze und allgemeine Frage zur Verwendung von with und closing.
Diese beiden Funktionen werden ja verwendet um Ressourcen nach deren Verwendung wieder frei zu geben.

Meine Frage dazu ist, wann verwende ich nun genau with open(.....), bzw. with closing(...).
In einem Post auf Stackoverflow wurde gesagt dies ist abhängig von dem was verwendet wird.

Mich interessiert dies in Verwendung mit MYSQL Datenbanken, bzw. mit SQLAlchemy.
Aus einer früheren Frage von mir habe ich von __blackjack__ eine Möglichkeit gezeigt bekommen,
wie man eine Datenbankverbindung mit "with closing" herstellt. (siehe unten)

Code: Alles auswählen

#!/usr/bin/env python3
from contextlib import closing

import mysql.connector
from mysql.connector import errorcode


def erstelle_datenbankverbindung(hostname, username, password, database):
    """
    Erstellt die Verbindung zum angegeben Datenbankserver mit dem angegebenen
    Benuternamen und dem entsprechenden Passwort.
    Es wird dabei die angegebene Datenbank geöffnet und gegebenfalls erstellt
    falls es sie noch nicht gibt.
    """
    try:
        return mysql.connector.connect(
            host=hostname,
            user=username,
            passwd=password,
            database=database,
        )
    except mysql.connector.Error as err:
        if err.errno == errorcode.ER_BAD_DB_ERROR:
            verbindung = verbindung = mysql.connector.connect(
                host=hostname, user=username, passwd=password
            )
            verbindung.cursor().execute(f"CREATE DATABASE {database}")
            return verbindung
        else:
            raise


def main():
    with closing(
        erstelle_datenbankverbindung(
            "localhost", "root", "passwort", "tabellenname"
        )
    ) as verbindung:
        #
        # TODO UNIQUE für die Kombination von Vor- und Nachname?
        #
        verbindung.cursor().execute(
            "CREATE TABLE pause ("
            "  id INTEGER AUTO_INCREMENT PRIMARY KEY"
            "  vorname VARCHAR(30) NOT NULL,"
            "  nachname VARCHAR(30) NOT NULL"
            ")"
        )
Erstelle ich nun jedes mal eine Verbindung zur Datenbank, oder erstelle ich nun
zu Beginn des Programms eine Verbindung zur Datenbank und wird nur das Cursor-Objekt
geschlossen und wieder geöffnet?

Also ich meine jetzt wie wird dies richtig gehandhabt in einem normalen Programmablauf?
Wenn die Datenbank dauerhaft geöffnet ist, kann es durch einen Absturz zu Datenverlust, bzw.
zur Unbrauchbarkeit führen. Wie beuge ich so etwas vor?

Über Rückmeldungen würde ich mich sehr freuen.
LG
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Unabhängig vom Datenbankthema: Du vermischt zwei Dinge, die sich aufgrund ihrer Benennung anhören, als gehörten sie zueinander: open() und contextlib.closing().
with <object> as <name>:
...
Sorgt dafür, dass am Ende des with-Blocks das <object> automatisch geschlossen wird. Das funktioniert aber nur, wenn <object> das selbst schon implementiert hat (als 'Contextmanager' zu agieren). Wenn dies nicht der Fall ist, wie bei der Datenbankverbindung hier, dann kann man das mit contextlib.closing() zumindest für das Schließen der Verbindung "nachrüsten". Zurück zum Beispiel: open() kann das von alleine, die Datenbankverbindung kann das nicht. Deshalb das contextlib.closing() drum herum. Diese wird dann geschlossen, wenn der with-Block verlassen wird.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt Typen, die kann man schließen. Und es gibt Typen, die kann man als Contextmanager benutzen, und dann schließen die sich automatisch nach verlassen des with. file zb ist ein Type, der beides ist. DB Connections haben aber nur ein close. Die sind nicht als Contextmanager für with gebaut. closing hilft einem einfach nur dabei, das dann doch mit with machen zu können.

Dein Programm erstellt eine Verbindung. Das war es. Sie erstellt ggf zwei cursor. Wobei dieses ‘anlegen wenn nicht vorhanden’ Murks ist. Nicht zuletzt weil du danach ja die Datenbank gar nicht wechselst. Dein SQL wird also krachen.

Und deinen Ausführungen zu Abstürzen Pflichte ich so nicht bei. Eine Committete Transaktion sollte in der DB sein. Egal ob dauerhaft geöffnet oder nicht.
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Hallo,

danke an die Antworten bisher.
@ _deets_: mit dem Code Schnipsel wollte ich eher allgemein hinzufügen, bezüglich des with und closing.
Meine Frage ist allgemein und hat mit einem Programm momentan gar nichts zu tun.
Es geht mir rein um die allgemeine Handhabung von "with" und "closing". Vor allem in Bezug auf Datenbanken.

Habe ich das richtig verstanden, dass wenn ein Commit ausgeführt wird, alle Daten geschrieben werden und nichts mehr kaputt gehen kann?
Also auch wenn die Verbindung zur Datenbank noch nicht geschlossen wurde?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist das prinzip von ACID, ja.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Hartmannsgruber hat geschrieben: Donnerstag 21. Oktober 2021, 17:19 Habe ich das richtig verstanden, dass wenn ein Commit ausgeführt wird, alle Daten geschrieben werden und nichts mehr kaputt gehen kann?
Also auch wenn die Verbindung zur Datenbank noch nicht geschlossen wurde?
Nein, du kannst in bestimmten Szenarien (z.B. Festplatte geht kaputt oder failover je nach Konfiguration) auch nach einem Commit noch Daten verlieren. Ob du die Verbindung schliesst oder nicht hat darauf aber keinen Einfluss.
Also ich meine jetzt wie wird dies richtig gehandhabt in einem normalen Programmablauf?
In der Regel hält man Verbindungen möglichst lange offen weil der Verbindungsaufbau Zeit und auch Resourcen (CPU vor allem) kostet.
Wenn die Datenbank dauerhaft geöffnet ist, kann es durch einen Absturz zu Datenverlust, bzw.
zur Unbrauchbarkeit führen. Wie beuge ich so etwas vor?
Ob du Verbindungen offen hältst hat keinen Einfluss auf Datenverlust. Um Datenverlust vorzubeugen gibt es Write Ahead Logs (WAL) auf Datenbank Seite, Backups, Archivieren des WALs und Replication. Das sind alles Maßnahmen die man nutzen kann um die Wahrscheinlichkeit das Daten verloren werden zu reduzieren und die Menge an Daten die man dann verliert zu reduzieren.
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Herzlichsten Dank für eure Antworten.
Es sind nun ein paar Große Fragezeichen verschwunden.

Ist dann die nachfolgende Aussage korrekt?

Wenn Verbindungen zu Datenbanken gebraucht werden, wird eine entsprechende Verbindung aufgebaut.
Diese Verbindung wird so lang Sie benötigt wird offen gelassen, da der Verbindungsaufbau Rechenzeit und Ressourcen verbraucht.
Bei unvorhersehbaren Problemen wie Abstürzen, o.Ä. können Daten dennoch verloren gehen, auch wenn ein vorheriges Commit stattgefunden hat.

Wäre demnach dies die richtige Reihenfolge bei Verwendung einer Datenbank?
- Verbindung zu Datenbankserver
- Cursor Objekt erstellen
- Abfragen ausführen
- Abfragen mit Commit bestätigen
- Cursor schließen
- Verbindung schließen

Ein "with", oder ein "with closing" würde demnach bei einer Datenbank nicht unbedingt gebraucht werden.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Natürlich muß man cursor und datenbank wieder sauber schließen, um Resourcen korrekt freizugeben und das macht man mit with.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Bei unvorhersehbaren Problemen wie Abstürzen, o.Ä. können Daten dennoch verloren gehen, auch wenn ein vorheriges Commit stattgefunden hat.
Nicht bei allen unvorhersehbaren Problem, mal abgesehen davon dass diese Probleme durchaus vorhersehbar sind, nur der genaue Zeitpunkt an dem sie auftreten nicht.

Nehmen wir mal an direkt nach einem Commit fällt der Strom aus, dann sollten die Daten noch da sein und sobald der Server wieder gestartet werden kann, vorausgesetzt die Festplatte ist unbeschädigt.

Im Prinzip kannst du Daten nur in zwei Szenarien verlieren. Das erste wäre dass die Festplatte schaden nimmt und du die Daten nicht schon wo anders hin kopiert hast. Das zweite Szenario tritt bei High Availability Konfigurationen mit Replikation auf, wo man ggfs. einen Gewissen Datenverlust in Kauf nimmt wenn eine Primärinstanz für einen bestimmten Zeitraum unerreichbar ist. Das macht Sinn falls man weiß dass der Verlust der Daten aus den letzten X Minuten günstiger ist als die Datenbank für die nächsten Y (wahrscheinlich >> X Minuten) nicht benutzbar zu haben.

Konkretes Beispiel: Du hast einen Online Shop und die Primärinstanz deiner Datenbank ist unerreichbar. Du weisst dass wird wahrscheinlich auch noch für ein paar Stunden so bleiben und auf der Datenbank liegen Bestellungen der letzten 30min. Da macht es dann Sinn den Verlust der Bestellungen aus den letzten 30min zu akzeptieren, da die Alternative wäre dass niemand etwas für die nächsten paar Stunden bestellen könnte.

Man kann das Risiko minimieren in dem man sicherstellt dass ein Commit nur dann erfolgreich ist, wenn man Kopien der Daten auf mindestens zwei (oder mehr) Servern hat (synchrone Replikation). Die könnten natürlich aber auch beide gleichzeitig unerreichbar werden und dann ist man in der selben Situation. Außerdem ist dann die Datenbank deutlich langsamer. Die Patroni Dokumentation (Patroni ist eine HA Lösung für Postgres) erklärt die unterschiedlichen Optionen und deren Vor- und Nachteile recht gut. Für MySQL wird dass etwas anders sein aber letztendlich wird man auch da die gleichen trade-offs haben.

Verbindungen sauber zu schliessen wenn du sie nicht mehr braucht ist trotz allem sinnvoll, jede offene Verbindung erfordert nämlich sowohl auf Client Seite als auch auf Server Seite Resourcen in der Form von Deskriptoren, Ports, Threads und Arbeitsspeicher.
Antworten