Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Wann Cursor mit MySQLdb geschlossen werden sollen

Anstatt zu fragen, was gängige Praxis ist, da dies oft unklar und subjektiv ist, könnten Sie versuchen, im Modul selbst nach Anleitung zu suchen. Verwenden Sie im Allgemeinen with Schlüsselwort, wie von einem anderen Benutzer vorgeschlagen, ist eine großartige Idee, aber in diesem speziellen Fall bietet es Ihnen möglicherweise nicht ganz die Funktionalität, die Sie erwarten.

Ab Version 1.2.5 des Moduls MySQLdb.Connection implementiert das Kontextmanagerprotokoll mit dem folgenden Code (github ):

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

Es gibt mehrere bestehende Fragen und Antworten zu with bereits, oder Sie können Die "with"-Anweisung von Python verstehen lesen , aber im Wesentlichen passiert das __enter__ wird am Anfang von with ausgeführt block und __exit__ wird ausgeführt, wenn with verlassen wird Block. Sie können die optionale Syntax with EXPR as VAR verwenden um das von __enter__ zurückgegebene Objekt zu binden zu einem Namen, wenn Sie beabsichtigen, später auf dieses Objekt zu verweisen. Angesichts der obigen Implementierung ist hier also eine einfache Möglichkeit, Ihre Datenbank abzufragen:

connection = MySQLdb.connect(...)
with connection as cursor:            # connection.__enter__ executes at this line
    cursor.execute('select 1;')
    result = cursor.fetchall()        # connection.__exit__ executes after this line
print result                          # prints "((1L,),)"

Die Frage ist nun, wie sind die Zustände der Verbindung und des Cursors nach Verlassen des with Block? Der __exit__ Die oben gezeigte Methode ruft nur self.rollback() auf oder self.commit() , und keine dieser Methoden ruft close() auf Methode. Der Cursor selbst hat kein __exit__ Methode definiert – und wäre egal, weil with verwaltet nur die Verbindung. Daher bleiben sowohl die Verbindung als auch der Cursor nach dem Verlassen des with geöffnet Block. Dies lässt sich leicht bestätigen, indem man dem obigen Beispiel den folgenden Code hinzufügt:

try:
    cursor.execute('select 1;')
    print 'cursor is open;',
except MySQLdb.ProgrammingError:
    print 'cursor is closed;',
if connection.open:
    print 'connection is open'
else:
    print 'connection is closed'

Sie sollten die Ausgabe „cursor is open; connection is open“ sehen, die auf stdout ausgegeben wird.

Ich glaube, Sie müssen den Cursor schließen, bevor Sie die Verbindung bestätigen.

Wieso den? Die MySQL-C-API , die die Basis für MySQLdb ist , implementiert kein Cursor-Objekt, wie in der Moduldokumentation impliziert:"MySQL unterstützt keine Cursor; Cursor lassen sich jedoch leicht emulieren." Tatsächlich der MySQLdb.cursors.BaseCursor Klasse erbt direkt von object und erlegt den Cursorn keine solche Einschränkung in Bezug auf Commit/Rollback auf. Ein Oracle-Entwickler hatte dies zu sagen :

cnx.commit() vor cur.close() klingt für mich am logischsten. Vielleicht können Sie sich an die Regel halten:"Schließen Sie den Cursor, wenn Sie ihn nicht mehr benötigen." Also commit() vor dem Schließen des Cursors. Am Ende macht es für Connector/Python keinen großen Unterschied, aber für andere Datenbanken vielleicht.

Ich nehme an, das ist so nah wie möglich an der "Standardpraxis" zu diesem Thema.

Gibt es einen signifikanten Vorteil, Transaktionssätze zu finden, die keine zwischenzeitlichen Festschreibungen erfordern, sodass Sie nicht für jede Transaktion neue Cursor erhalten müssen?

Ich bezweifle das sehr, und wenn Sie dies versuchen, könnten Sie zusätzliche menschliche Fehler einführen. Entscheiden Sie sich lieber für eine Konvention und bleiben Sie dabei.

Gibt es viel Overhead, um neue Cursor zu bekommen, oder ist es einfach keine große Sache?

Der Overhead ist vernachlässigbar und berührt den Datenbankserver überhaupt nicht; es liegt vollständig innerhalb der Implementierung von MySQLdb. Sie können sich BaseCursor.__init__ ansehen auf github wenn Sie wirklich neugierig sind, was passiert, wenn Sie einen neuen Cursor erstellen.

Zurück zu früher, als wir über with diskutierten , vielleicht können Sie jetzt verstehen, warum die MySQLdb.Connection Klasse __enter__ und __exit__ Methoden geben Ihnen in jedem with ein brandneues Cursor-Objekt blockieren und sich nicht die Mühe machen, den Überblick zu behalten oder ihn am Ende des Blocks zu schließen. Es ist ziemlich leicht und existiert nur zu Ihrer Bequemlichkeit.

Wenn es Ihnen wirklich so wichtig ist, das Cursorobjekt im Mikromanagement zu verwalten, können Sie contextlib.closing um die Tatsache auszugleichen, dass das Cursor-Objekt keinen definierten __exit__ hat Methode. In diesem Zusammenhang können Sie es auch verwenden, um das Verbindungsobjekt zu zwingen, sich selbst zu schließen, wenn ein with beendet wird Block. Dies sollte "my_curs is closed; my_conn is closed" ausgeben:

from contextlib import closing
import MySQLdb

with closing(MySQLdb.connect(...)) as my_conn:
    with closing(my_conn.cursor()) as my_curs:
        my_curs.execute('select 1;')
        result = my_curs.fetchall()
try:
    my_curs.execute('select 1;')
    print 'my_curs is open;',
except MySQLdb.ProgrammingError:
    print 'my_curs is closed;',
if my_conn.open:
    print 'my_conn is open'
else:
    print 'my_conn is closed'

Beachten Sie, dass with closing(arg_obj) wird __enter__ des Argumentobjekts nicht aufrufen und __exit__ Methoden; es wird nur Rufen Sie close des Argumentobjekts auf Methode am Ende des with Block. (Um dies in Aktion zu sehen, definieren Sie einfach eine Klasse Foo mit __enter__ , __exit__ , und close Methoden, die einfaches print enthalten Anweisungen und vergleichen Sie, was passiert, wenn Sie with Foo(): pass ausführen zu dem, was passiert, wenn Sie with closing(Foo()): pass .) Dies hat zwei wesentliche Implikationen:

Erstens, wenn der Autocommit-Modus aktiviert ist, wird MySQLdb BEGIN eine explizite Transaktion auf dem Server, wenn Sie with connection verwenden und die Transaktion am Ende des Blocks festschreiben oder rückgängig machen. Dies sind Standardverhalten von MySQLdb, die Sie vor dem Standardverhalten von MySQL schützen sollen, alle DML-Anweisungen sofort festzuschreiben. MySQLdb geht davon aus, dass Sie eine Transaktion wünschen, wenn Sie einen Kontextmanager verwenden, und verwendet den expliziten BEGIN um die Autocommit-Einstellung auf dem Server zu umgehen. Wenn Sie es gewohnt sind, with connection zu verwenden , denken Sie vielleicht, dass Autocommit deaktiviert ist, obwohl es eigentlich nur umgangen wurde. Sie könnten eine unangenehme Überraschung erleben, wenn Sie closing hinzufügen zu Ihrem Code und verlieren Sie die Transaktionsintegrität; Sie können Änderungen nicht rückgängig machen, Sie sehen möglicherweise Parallelitätsfehler und es ist möglicherweise nicht sofort ersichtlich, warum.

Zweitens with closing(MySQLdb.connect(user, pass)) as VAR bindet das Verbindungsobjekt zu VAR , im Gegensatz zu with MySQLdb.connect(user, pass) as VAR , die ein neues Cursorobjekt bindet zu VAR . Im letzteren Fall hätten Sie keinen direkten Zugriff auf das Verbindungsobjekt! Stattdessen müssten Sie die connection des Cursors verwenden -Attribut, das Proxy-Zugriff auf die ursprüngliche Verbindung bereitstellt. Wenn der Cursor geschlossen ist, seine connection -Attribut ist auf None gesetzt . Dies führt zu einer abgebrochenen Verbindung, die bestehen bleibt, bis eines der folgenden Ereignisse eintritt:

  • Alle Verweise auf den Cursor werden entfernt
  • Der Cursor verlässt den Gültigkeitsbereich
  • Zeitüberschreitung der Verbindung
  • Die Verbindung wird manuell über Serververwaltungstools geschlossen

Sie können dies testen, indem Sie offene Verbindungen überwachen (in Workbench oder durch ). mit SHOW PROCESSLIST ), während Sie die folgenden Zeilen nacheinander ausführen:

with MySQLdb.connect(...) as my_curs:
    pass
my_curs.close()
my_curs.connection          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here