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
cursor.close()
ist Teil der Python DB-API , die nicht speziell für MySQL geschrieben wurde.my_curs
enthält den letzten Verweis auf dasconnection
Objekt. Sobald diese Referenz nicht mehr vorhanden ist , dasconnection
Objekt soll Müll gesammelt werden.with
undMySQLdb.Connection
‚s__enter__
und__exit__
Funktionen. Nochmals vielen Dank @Air.Es ist besser, es mit dem Schlüsselwort 'with' umzuschreiben. 'Mit' sorgt dafür, dass der Cursor automatisch geschlossen wird (wichtig, da es sich um eine nicht verwaltete Ressource handelt). Der Vorteil ist, dass der Cursor auch im Ausnahmefall geschlossen wird.
quelle
with
eine gute Option ist, wenn Sie es in Flask oder einem anderen Webframework verwenden möchten. Wenn die Situation ist,http://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
wird es Probleme geben.with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Hinweis: Diese Antwort bezieht sich auf PyMySQL , einen Ersatz für MySQLdb und effektiv die neueste Version von MySQLdb, da MySQLdb nicht mehr gewartet wird. Ich glaube, dass hier alles auch für die alte MySQLdb gilt, habe es aber nicht überprüft.
Zunächst einige Fakten:
with
Syntax ruft die__enter__
Methode des Kontextmanagers auf , bevor der Hauptteil deswith
Blocks ausgeführt wird, und anschließend die__exit__
Methode.__enter__
Methode, die nichts anderes tut, als einen Cursor zu erstellen und zurückzugeben, und eine__exit__
Methode, die entweder festschreibt oder zurücksetzt (abhängig davon, ob eine Ausnahme ausgelöst wurde). Die Verbindung wird nicht geschlossen.__enter__
Methode, die nichts tut, und eine__exit__
Methode, die den Cursor "schließt" (was nur bedeutet, den Verweis des Cursors auf seine übergeordnete Verbindung auf Null zu setzen und alle auf dem Cursor gespeicherten Daten wegzuwerfen).__del__
Methode, die sie schließtWenn wir diese Dinge zusammenfassen, sehen wir, dass naiver Code wie dieser theoretisch problematisch ist:
Das Problem ist, dass nichts die Verbindung geschlossen hat. Wenn Sie den obigen Code in eine Python-Shell einfügen und dann
SHOW FULL PROCESSLIST
in einer MySQL-Shell ausführen , können Sie die von Ihnen erstellte Leerlaufverbindung sehen. Da die Standardanzahl von MySQL-Verbindungen 151 ist , was nicht sehr groß ist , könnten theoretisch Probleme auftreten, wenn Sie viele Prozesse hätten, die diese Verbindungen offen halten.In CPython gibt es jedoch eine Speichergnade, die sicherstellt, dass Code wie in meinem obigen Beispiel wahrscheinlich nicht dazu führt, dass Sie viele offene Verbindungen verlassen. Diese Rettung besteht darin, dass der Referenzzähler , sobald
cursor
er den Gültigkeitsbereich verlässt (z. B. die Funktion, in der er erstellt wurde, beendet wird odercursor
ihm ein anderer Wert zugewiesen wird), Null erreicht, wodurch er gelöscht wird und der Referenzzähler der Verbindung gelöscht wird auf Null, wodurch die Verbindungsmethode__del__
aufgerufen wird, die die Verbindung zwangsweise schließt. Wenn Sie den obigen Code bereits in Ihre Python-Shell eingefügt haben, können Sie dies jetzt durch Ausführen simulierencursor = 'arbitrary value'
. Sobald Sie dies tun, verschwindet die Verbindung, die Sie geöffnet haben, aus derSHOW PROCESSLIST
Ausgabe.Sich darauf zu verlassen ist jedoch unelegant und kann theoretisch in anderen Python-Implementierungen als CPython fehlschlagen. Cleaner wäre theoretisch,
.close()
die Verbindung explizit zu machen (um eine Verbindung in der Datenbank freizugeben, ohne darauf zu warten, dass Python das Objekt zerstört). Dieser robustere Code sieht folgendermaßen aus:Dies ist hässlich, hängt jedoch nicht davon ab, dass Python Ihre Objekte zerstört, um Ihre (endlich verfügbare Anzahl) Datenbankverbindungen freizugeben.
Beachten Sie, dass das Schließen des Cursors völlig sinnlos ist, wenn Sie die Verbindung bereits explizit auf diese Weise schließen.
Um die sekundären Fragen hier zu beantworten:
Nein, das Instanziieren eines Cursors trifft MySQL überhaupt nicht und tut im Grunde nichts .
Dies ist situativ und schwer zu beantworten. Wie https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html es ausdrückt, „könnte eine Anwendung Begegnung Performance - Probleme , wenn es tausende Male pro Sekunde und andere Performance - Probleme verpflichtet , wenn es legt nur alle 2-3 Stunden fest " . Sie zahlen einen Leistungsaufwand für jedes Commit. Wenn Sie jedoch Transaktionen länger offen lassen, erhöhen Sie die Wahrscheinlichkeit, dass andere Verbindungen Zeit auf Sperren warten müssen, erhöhen das Risiko von Deadlocks und erhöhen möglicherweise die Kosten einiger von anderen Verbindungen durchgeführter Suchvorgänge .
1 MySQL hat ein Konstrukt, das einen Cursor aufruft, aber nur in gespeicherten Prozeduren vorhanden ist. Sie unterscheiden sich grundlegend von PyMySQL-Cursorn und sind hier nicht relevant.
quelle
Ich denke, Sie sollten besser versuchen, einen Cursor für alle Ihre Ausführungen zu verwenden und ihn am Ende Ihres Codes zu schließen. Es ist einfacher, damit zu arbeiten, und es könnte auch Effizienzvorteile haben (zitieren Sie mich nicht dazu).
Der Punkt ist, dass Sie die Ergebnisse der Ausführung eines Cursors in einer anderen Variablen speichern können, wodurch Ihr Cursor für eine zweite Ausführung freigegeben wird. Auf diese Weise treten nur dann Probleme auf, wenn Sie fetchone () verwenden und eine zweite Cursorausführung durchführen müssen, bevor Sie alle Ergebnisse der ersten Abfrage durchlaufen haben.
Andernfalls würde ich sagen, schließen Sie einfach Ihre Cursor, sobald Sie alle Daten aus ihnen herausgeholt haben. Auf diese Weise müssen Sie sich später in Ihrem Code keine Gedanken mehr darüber machen, lose Enden zu binden.
quelle
Ich schlage vor, es wie PHP und MySQL zu tun. Beginnen Sie i am Anfang Ihres Codes, bevor Sie die ersten Daten drucken. Wenn Sie also einen Verbindungsfehler erhalten, können Sie eine Fehlermeldung
50x
(Ich erinnere mich nicht, was ein interner Fehler ist) anzeigen . Halten Sie es für die gesamte Sitzung offen und schließen Sie es, wenn Sie wissen, dass Sie es nicht mehr benötigen.quelle