In plaats van te vragen wat de standaardpraktijk is, aangezien dat vaak onduidelijk en subjectief is, kunt u voor begeleiding naar de module zelf kijken. Over het algemeen gebruikt u de with
zoekwoord zoals een andere gebruiker suggereerde is een geweldig idee, maar in deze specifieke omstandigheid geeft het u misschien niet helemaal de functionaliteit die u verwacht.
Vanaf versie 1.2.5 van de module, MySQLdb.Connection
implementeert het contextmanager-protocol
met de volgende 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()
Er zijn verschillende bestaande Q&A's over with
al, of je kunt Python's "with"-statement
lezen , maar in wezen wat er gebeurt is dat __enter__
wordt uitgevoerd aan het begin van de with
blokkeren, en __exit__
wordt uitgevoerd bij het verlaten van de with
blok. U kunt de optionele syntaxis with EXPR as VAR
om het object te binden dat is geretourneerd door __enter__
naar een naam als u van plan bent later naar dat object te verwijzen. Dus, gezien de bovenstaande implementatie, is hier een eenvoudige manier om uw database te doorzoeken:
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,),)"
De vraag is nu, wat zijn de statussen van de verbinding en de cursor na het verlaten van de with
blok? De __exit__
bovenstaande methode roept alleen self.rollback()
. aan of self.commit()
, en geen van beide methoden gaat verder met het aanroepen van de close()
methode. De cursor zelf heeft geen __exit__
methode gedefinieerd – en het zou niet uitmaken als dat zo was, omdat with
beheert alleen de verbinding. Daarom blijven zowel de verbinding als de cursor open na het verlaten van de with
blok. Dit is eenvoudig te bevestigen door de volgende code toe te voegen aan het bovenstaande voorbeeld:
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'
U zou moeten zien dat de uitvoer "cursor is open; verbinding is open" afgedrukt naar stdout.
Ik geloof dat je de cursor moet sluiten voordat je de verbinding tot stand brengt.
Waarom? De MySQL C API
, wat de basis is voor MySQLdb
, implementeert geen cursorobject, zoals geïmpliceerd in de moduledocumentatie:"MySQL ondersteunt geen cursors; cursors kunnen echter gemakkelijk worden geëmuleerd."
Inderdaad, de MySQLdb.cursors.BaseCursor
klasse erft rechtstreeks van object
en legt geen dergelijke beperking op aan cursors met betrekking tot commit/rollback. Een Oracle-ontwikkelaar had dit te zeggen
:
cnx.commit() voor cur.close() klinkt het meest logisch voor mij. Misschien kun je de regel volgen:"Sluit de cursor als je hem niet meer nodig hebt." Dus commit() voordat je de cursor sluit. Uiteindelijk maakt het voor Connector/Python niet veel uit, maar of andere databases misschien wel.
Ik verwacht dat dit het dichtst in de buurt komt van de "standaardpraktijk" over dit onderwerp.
Is er een significant voordeel bij het vinden van sets transacties waarvoor geen tussentijdse commits nodig zijn, zodat u niet voor elke transactie nieuwe cursors hoeft te krijgen?
Ik betwijfel het ten zeerste, en door dit te proberen, zou je een extra menselijke fout kunnen introduceren. Het is beter om een conventie te kiezen en je eraan te houden.
Is er veel overhead voor het verkrijgen van nieuwe cursors, of is het gewoon geen probleem?
De overhead is verwaarloosbaar en raakt de databaseserver helemaal niet; het valt volledig binnen de implementatie van MySQLdb. Je kunt kijken naar BaseCursor.__init__
op github
als je echt benieuwd bent wat er gebeurt als je een nieuwe cursor maakt.
Teruggaan naar eerder toen we with
bespraken , misschien begrijp je nu waarom de MySQLdb.Connection
klasse __enter__
en __exit__
methoden geven je een gloednieuw cursorobject in elke with
blok en doe niet de moeite om het bij te houden of het aan het einde van het blok te sluiten. Het is vrij licht van gewicht en bestaat puur voor uw gemak.
Als het echt zo belangrijk voor je is om het cursorobject te micromanagen, kun je contextlib.sluiten
om het feit goed te maken dat het cursorobject geen gedefinieerde __exit__
. heeft methode. Je kunt het trouwens ook gebruiken om het verbindingsobject te dwingen zichzelf te sluiten bij het afsluiten van een with
blok. Dit zou moeten output "my_curs is gesloten; my_conn is gesloten":
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'
Merk op dat with closing(arg_obj)
zal de __enter__
. van het argument object niet aanroepen en __exit__
methoden; het zal alleen roep de close
. van het argumentobject aan methode aan het einde van de with
blok. (Om dit in actie te zien, definieert u eenvoudig een klasse Foo
met __enter__
, __exit__
, en close
methoden met eenvoudige print
instructies, en vergelijk wat er gebeurt als u with Foo(): pass
naar wat er gebeurt als je with closing(Foo()): pass
.) Dit heeft twee belangrijke implicaties:
Ten eerste, als de autocommit-modus is ingeschakeld, zal MySQLdb BEGIN
een expliciete transactie op de server wanneer u with connection
. gebruikt en de transactie aan het einde van het blok vast te leggen of terug te draaien. Dit zijn standaardgedragingen van MySQLdb, bedoeld om u te beschermen tegen het standaardgedrag van MySQL door onmiddellijk alle DML-statements uit te voeren. MySQLdb gaat ervan uit dat wanneer u een contextmanager gebruikt, u een transactie wilt, en gebruikt de expliciete BEGIN
om de autocommit-instelling op de server te omzeilen. Als je gewend bent om with connection
te gebruiken , zou je kunnen denken dat autocommit is uitgeschakeld, terwijl het eigenlijk alleen werd omzeild. U kunt voor een onaangename verrassing komen te staan als u closing
. toevoegt aan uw code en de transactie-integriteit verliezen; u kunt wijzigingen niet ongedaan maken, u ziet mogelijk gelijktijdigheidsbugs en het is misschien niet meteen duidelijk waarom.
Ten tweede, with closing(MySQLdb.connect(user, pass)) as VAR
bindt het verbindingsobject naar VAR
, in tegenstelling tot with MySQLdb.connect(user, pass) as VAR
, die een nieuw cursorobject bindt naar VAR
. In het laatste geval zou u geen directe toegang hebben tot het verbindingsobject! In plaats daarvan zou u de connection
. van de cursor moeten gebruiken attribuut, dat proxytoegang tot de oorspronkelijke verbinding biedt. Wanneer de cursor gesloten is, is de connection
attribuut is ingesteld op None
. Dit resulteert in een verbroken verbinding die blijft bestaan totdat een van de volgende dingen gebeurt:
- Alle verwijzingen naar de cursor zijn verwijderd
- De cursor valt buiten bereik
- De verbinding valt uit
- De verbinding wordt handmatig gesloten via serverbeheertools
U kunt dit testen door open verbindingen te controleren (in Workbench of door met behulp van SHOW PROCESSLIST
) terwijl u de volgende regels één voor één uitvoert:
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