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