Anstatt zu fragen, was Standardpraxis ist, da dies oft unklar und subjektiv ist, können Sie versuchen, das Modul selbst als Anleitung zu betrachten. Im Allgemeinen ist die Verwendung des with
Schlüsselworts als Vorschlag eines anderen Benutzers eine gute Idee, aber unter diesen besonderen Umständen erhalten Sie möglicherweise nicht die erwartete Funktionalität.
MySQLdb.Connection
Implementiert ab Version 1.2.5 des Moduls 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 bereits mehrere Fragen und with
Antworten, oder Sie können die "with" -Anweisung von Understanding Python lesen. Im Wesentlichen wird diese jedoch __enter__
am Anfang des with
Blocks und __exit__
beim Verlassen des with
Blocks ausgeführt. Sie können die optionale Syntax verwenden with EXPR as VAR
, um das von zurückgegebene Objekt __enter__
an einen Namen zu binden, wenn Sie später auf dieses Objekt verweisen möchten. Angesichts der obigen Implementierung haben Sie hier 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 dem Verlassen des with
Blocks? Die __exit__
oben gezeigte Methode ruft nur self.rollback()
oder auf self.commit()
, und keine dieser Methoden ruft die close()
Methode auf. Für den Cursor selbst ist keine __exit__
Methode definiert - und es wäre egal, ob dies der Fall ist, da with
nur die Verbindung verwaltet wird. Daher bleiben sowohl die Verbindung als auch der Cursor nach dem Verlassen des with
Blocks geöffnet . Dies kann leicht bestätigt werden, indem dem obigen Beispiel der folgende Code hinzugefügt wird:
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 sehen, dass die Ausgabe "Cursor ist offen; Verbindung ist offen" auf stdout gedruckt wird.
Ich glaube, Sie müssen den Cursor schließen, bevor Sie die Verbindung herstellen.
Warum? Die MySQL C-API , auf der die Basis basiert MySQLdb
, implementiert kein Cursorobjekt, wie in der Moduldokumentation impliziert: "MySQL unterstützt keine Cursor; Cursor können jedoch leicht emuliert werden." In der Tat MySQLdb.cursors.BaseCursor
erbt die Klasse direkt von object
Cursorn und legt keine solche Einschränkung in Bezug auf Commit / Rollback fest. Ein Oracle-Entwickler hatte folgendes 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." Legen Sie also commit () fest, bevor Sie den Cursor schließen. Letztendlich macht es für Connector / Python keinen großen Unterschied, aber für andere Datenbanken.
Ich gehe davon aus, dass dies so nah wie möglich an der "Standardpraxis" zu diesem Thema liegt.
Gibt es einen signifikanten Vorteil beim Finden von Transaktionssätzen, für die keine Zwischen-Commits erforderlich sind, damit Sie nicht für jede Transaktion neue Cursor erhalten müssen?
Ich bezweifle es sehr, und wenn Sie dies versuchen, können Sie zusätzliche menschliche Fehler einführen. Entscheiden Sie sich besser für eine Konvention und bleiben Sie dabei.
Gibt es viel Aufwand, 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 in der Implementierung von MySQLdb. Sie können sich BaseCursor.__init__
Github ansehen, wenn Sie wirklich neugierig sind, was passiert, wenn Sie einen neuen Cursor erstellen.
Wenn Sie zu einem früheren Zeitpunkt zurückkehren, als wir darüber gesprochen haben with
, können Sie jetzt vielleicht verstehen, warum die MySQLdb.Connection
Klasse __enter__
und die __exit__
Methoden Ihnen in jedem with
Block ein brandneues Cursorobjekt geben, und Sie müssen sich nicht die Mühe machen, es zu verfolgen oder am Ende des Blocks zu schließen. Es ist ziemlich leicht und existiert nur für Ihre Bequemlichkeit.
Wenn es für Sie wirklich so wichtig ist, das Cursorobjekt zu verwalten, können Sie contextlib.closing verwenden , um die Tatsache auszugleichen , dass das Cursorobjekt keine definierte __exit__
Methode hat. Sie können es auch verwenden, um das Verbindungsobjekt zu zwingen, sich beim Verlassen eines with
Blocks selbst zu schließen . Dies sollte "my_curs ist geschlossen; my_conn ist geschlossen" 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)
die Argumente __enter__
und __exit__
Methoden des Argumentobjekts nicht aufgerufen werden. Die Methode des Argumentobjekts wird nurclose
am Ende des with
Blocks aufgerufen . (Um dies in Aktion zu sehen, definieren Sie einfach eine Klasse Foo
mit __enter__
, __exit__
und close
Methoden, die einfache print
Anweisungen enthalten, und vergleichen Sie, was passiert, wenn Sie es tun, with Foo(): pass
mit dem, was passiert, wenn Sie es tun with closing(Foo()): pass
.) Dies hat zwei wesentliche Auswirkungen:
Wenn der Autocommit-Modus aktiviert ist, führt MySQLdb zunächst BEGIN
eine explizite Transaktion auf dem Server durch, wenn Sie with connection
die Transaktion am Ende des Blocks verwenden und festschreiben oder zurücksetzen . Dies sind Standardverhalten von MySQLdb, das Sie vor dem Standardverhalten von MySQL schützen soll, bei dem alle DML-Anweisungen sofort festgeschrieben werden. MySQLdb geht davon aus, dass Sie bei Verwendung eines Kontextmanagers eine Transaktion wünschen, und verwendet die explizite Option BEGIN
, um die Autocommit-Einstellung auf dem Server zu umgehen. Wenn Sie es gewohnt sind with connection
, denken Sie möglicherweise, dass Autocommit deaktiviert ist, wenn es tatsächlich nur umgangen wurde. Sie könnten eine unangenehme Überraschung bekommen, wenn Sie hinzufügenclosing
zu Ihrem Code und verlieren die Transaktionsintegrität; Sie können Änderungen nicht rückgängig machen, es treten möglicherweise Parallelitätsfehler auf, 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
, das bindet ein neues Cursor - Objekt zu VAR
. Im letzteren Fall hätten Sie keinen direkten Zugriff auf das Verbindungsobjekt! Stattdessen müssten Sie das connection
Attribut des Cursors verwenden , das den Proxy-Zugriff auf die ursprüngliche Verbindung ermöglicht. Wenn der Cursor geschlossen ist, wird sein connection
Attribut auf gesetzt None
. Dies führt zu einer abgebrochenen Verbindung, die so lange bestehen bleibt, bis eine der folgenden Situationen eintritt:
- Alle Verweise auf den Cursor werden entfernt
- Der Cursor verlässt den Bereich
- Die Verbindung läuft ab
- Die Verbindung wird manuell über Serververwaltungstools geschlossen
Sie können dies testen, indem Sie offene Verbindungen überwachen (in Workbench oder mithilfe vonSHOW PROCESSLIST
), während Sie die folgenden Zeilen einzeln 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