Wie kommuniziert man "best practice" mit einer MySQL Datenbank?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
alexbennik
User
Beiträge: 7
Registriert: Dienstag 16. Dezember 2014, 21:01

Ich habe gerade meine erste MySQL in AWS RDS erstellt. Um die TABLES zu erstellen, habe ich mir das offizielle GUI von MySQL "MySQLWorkbench" installiert. Das hat auch ohne Probleme geklappt. Für Python habe ich mir auch das offizielle Modul mysql-connector-python installiert. Ich habe mehrere IP-Adressen, welche ich in der Tabelle submissions eintragen will. Als erstes muss ich sagen, dass das alles funktioniert! Habe trotzdem das Gefühl, dass ich hier nicht ganz nach "best practice" geschrieben habe (ein kleiner Ausschnitt):

Code: Alles auswählen

def db_connect():
    con = mysql.connector.connect(PLATZHALTER FÜR CONFIG)
    return con


def create_submission(con, ip):
    cursor = con.cursor()
    sql = "INSERT INTO submissions (ip) VALUES (%s)"

    try:
        cursor.execute(sql, (ip,))
        con.commit()
    except:
        con.rollback()

    cursor.close()
    con.close()

def submit_ip(ip):
    create_submission(db_connect(), ip)
    
    
for ip in tqdm(ips):
    self.submit_ip(ip)
Meine Fragen:
  • Ist es besser ein Datenbank ORM zu nutzen, und wenn ja welches?
  • Was ist "best practice" beim eintragen von mehreren Zeilen, vor allem im Hinblick auf das Connection-Object und den Cursor?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zwei offensichtliche Dinge, die ich anders machen wuerde:

- es ist "teuer", eine Datenbank-Verbindung aufzumachen. Statt also pro IP einmal eine Verbindung zu erstellen, erstell die Verbindung EINMAL vorher, und benutz sie fuer alle Eintraege.
- auch wenn Funktionen gut sind, ist die db_connect-Funktion in meinen Augen ueberfluessig - eine einzelne Zeile Code durch 3 Zeilen zu ersetzen ist sinnlos. Dafuer wiederum *fehlt* eine Funktion fuer die eigentliche Aufgabe, die IPs einzufuegen. db_connect also wuerde rauswerfen, und dafuer deine for-schleife am Ende in eine Funktion stecken, die eben erstmal eine Verbindung aufmacht, und dann alle Werte eintraegt.

Last but not least: dir fehlt ein explizites commit. Mein MySQL-Wissen ist da etwas rostig, aber je nach Konfiguration der DB ist das ggf. notwendig, sonst landen die Daten nicht in der DB. Du solltest also eines einfuegen.
Benutzeravatar
alexbennik
User
Beiträge: 7
Registriert: Dienstag 16. Dezember 2014, 21:01

Vielen Dank für die schnelle Antwort.
Statt also pro IP einmal eine Verbindung zu erstellen, erstell die Verbindung EINMAL vorher
Genau in die Richtung hatte ich ehh schon geschielt, werde ich ändern.
db_connect-Funktion in meinen Augen ueberfluessig
Habe das so gemacht, weil es auch in vielen offiziellen Beispielen so gebaut wird. Außerdem ist da beim "PLATZHALTER" ja die kompletten Parameter für Verbindung. Werde darüber nachdenken.
Dafuer wiederum *fehlt* eine Funktion fuer die eigentliche Aufgabe, die IPs einzufuegen
Die for-schleife am Ende gehört eigentlich in eine Methode einer eigenen Klasse. Ist also nur fragebedingt rausgeholt, damit der Quelltext übersichtlicher ist. Das eintragen aller IPs wird also sauber abgewickelt.
Last but not least: dir fehlt ein explizites commit.
Das muss ich recherchieren, danke für den Tipp 8)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@alexbennik: Bei `db_connect()` als eigene Funktion sehe ich den Vorteil, dass man die Datenbank oder die Anbindung gegen ein anderes Modul austauschen kann ohne überall im Code etwas ändern zu müssen. Denn bei manchen Datenbanken reicht ein einfacher Aufruf nicht, da möchte man eventuell noch irgendwelche PRAGMA-Anweisungen oder ähnliches bei der Verbindung ausführen. Ausserdem hat man hier einen schönen Punkt um die Funktion für Unit-Tests durch ein Mock-Objekt zu ersetzen.

Die Asymmetrie beim öffnen und schliessen der Datenbankverbindung ist komisch. Das die ausserhalb der `create_submission()` geöffnet aber innerhalb geschlossen wird. Dann hätte man sie auch in der Funktion öffnen können, wenn ein Aufrufer sie danach sowieso nicht weiterverwenden kann. Diese Aufteilung verhindert auch die Verwendung von ``with`` (mit `contextlib.closing()).

`con` würde ich ausschreiben.

Hier würde sich auch `executemany()` anbieten.

Ungetestet:

Code: Alles auswählen

def create_submissions(ips):
    with closing(db_connect()) as connection:
        with closing(connection.cursor()) as cursor:
            sql = "INSERT INTO submissions (ip) VALUES (%s)"
            try:
                cursor.executemany(sql, ((ip,) for ip in ips))
                connection.commit()
            except:
                connection.rollback()
                raise
Ich bin ein Fan von SQLAlchemy. Auch wenn man das ORM nicht verwendet, ist es auch sehr praktisch SQL nicht als Zeichenketten im Programm zu haben, sondern das alles programmatisch erstellen zu können. Und unabhängig vom verwendeten DBMS, sofern man eines der Unterstützten benutzt.

Die SQLAlchemy-Dokumentation rät vom Connector-Modul direkt von MySQL übrigens ab.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Ein naktes except ist hier akzeptabel, weil es für den Rollback benutzt wird, trotzdem muß es dann ein raise im Exceptblock geben, damit man sehen kann, welcher Fehler aufgetreten ist. Eine Funktion, die die Connection als Parameter bekommt, sollte sie nicht schließen. Wenn man viele Einträge machen will, gibt es executemany.
Benutzeravatar
alexbennik
User
Beiträge: 7
Registriert: Dienstag 16. Dezember 2014, 21:01

@__blackjack__ : das Öffnen und Schließen der Datenbank habe ich bereits aus der Funktion rausgenommen. Was ja auch wieder hinfällig wird, wenn ich den Context Manager benutze. Apropro
mit `contextlib.closing()`
, lange habe ich mich vor sowas gedrückt. Außer beim Schreiben und Lesen von iwas, habe ich immer einen großen Bogen um den Context Manager gemacht. Wird Zeit, das zu ändern.

Code: Alles auswählen

cursor.executemany(sql, ((ip,) for ip in ips))
... da musste ich auch 3mal hinschauen, bis ich die list comprehension gesehen habe :lol:
und ja `con --> connection` ist super. Habe bisher immer den Grundsatz für mich gehabt, mich an die Namensgebung aus den Dokumentationen von den jeweiligen Modulen zu halten. Und das `executemany()` werden ich einbauen.

@Sirius3: Danke für den Tipp mit dem `raise`. Wird gemacht.


Versuche das mal heute Abend-Nacht noch zu implementieren. Ist aber auch davon abhängig, wie schnell ich den Context-Manager verstehe, sonst will ich den nicht einbauen. Vllt auch erstmal ohne :mrgreen:


EDIT: Ahja, die `contextlib.closing()` ist ja doch nicht so schwer. Ist alles implementiert, und funktioniert. Gab einen Fehler bei:

Code: Alles auswählen

cursor.executemany(sql, ((ip,) for ip in ips))

Code: Alles auswählen

>>> "Parameters for query must be list or tuple."
habs geändert in:

Code: Alles auswählen

cursor.executemany(sql, [(ip,) for ip in ips])
Funktioniert!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@alexbennik: Das war keine „list comprehension“ sondern ein Generatorausdruck.

Das ganze mal mit SQLAlchemy ohne ORM (ungetestst):

Code: Alles auswählen

DB_URL = 'mysql://user:password@hostname/dbname'

# ...

def create_submissions(ips):
    engine = create_engine(DB_URL)
    engine.execute(submission_table.insert(), ({'ip': ip} for ip in ips))
Hier fehlt natürlich noch die Definition von `submission_table`, die auf verschiedene Arten erfolgen kann. Zum Beispiel im Programm oder per „reflection“ aus der Datenbank.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
alexbennik
User
Beiträge: 7
Registriert: Dienstag 16. Dezember 2014, 21:01

Ok der Generator Ausdruck war mir auch fremd. Danke für das Beispiel in SQLAlchemy. Bin mir nur noch sicher, ob ich das direkt einbauen werde, oder lieber noch ein bisschen beim SQL bleibe, da ich da jetzt noch kaum Erfahrung drin habe. Finde es iwie doof SQL zu vereinfachen, wenn es für mich noch nicht einfach ist. Was meinst du @__blackjack__
Antworten