I stedet for at spørge, hvad der er standardpraksis, da det ofte er uklart og subjektivt, kan du prøve at kigge på selve modulet for at få vejledning. Generelt bruger du with
søgeord, som en anden bruger foreslog, er en god idé, men i denne specifikke situation giver det dig måske ikke helt den funktionalitet, du forventer.
Fra version 1.2.5 af modulet, MySQLdb.Connection
implementerer context manager-protokollen
med følgende kode (github/connections.py> ):
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()
Der er flere eksisterende spørgsmål og svar om with
allerede, eller du kan læse Forstå Pythons "med"-erklæring
, men det, der i bund og grund sker, er at __enter__
udføres i starten af with
blok og __exit__
udføres ved at forlade with
blok. Du kan bruge den valgfrie syntaks with EXPR as VAR
for at binde objektet returneret af __enter__
til et navn, hvis du agter at henvise til det objekt senere. Så givet ovenstående implementering er her en enkel måde at forespørge i din database på:
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,),)"
Spørgsmålet er nu, hvad er tilstanden for forbindelsen og markøren efter at have afsluttet with
blok? __exit__
metode vist ovenfor kalder kun self.rollback()
eller self.commit()
, og ingen af disse metoder fortsætter med at kalde close()
metode. Selve markøren har ingen __exit__
metode defineret – og ville ligegyldigt, hvis den gjorde det, fordi with
administrerer kun forbindelsen. Derfor forbliver både forbindelsen og markøren åbne efter at have afsluttet with
blok. Dette bekræftes nemt ved at tilføje følgende kode til ovenstående eksempel:
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'
Du skulle se outputtet "markøren er åben; forbindelsen er åben" udskrevet til stdout.
Jeg tror, du skal lukke markøren, før du foretager forbindelsen.
Hvorfor? MySQL C API
, som er grundlaget for MySQLdb
, implementerer ikke noget markørobjekt, som antydet i moduldokumentationen:"MySQL understøtter ikke markører, men markører emuleres let."
Faktisk, MySQLdb.cursors.BaseCursor
klasse arver direkte fra object
og pålægger ingen sådanne begrænsninger for markører med hensyn til commit/rollback. En Oracle-udvikler havde dette at sige
:
cnx.commit() før cur.close() lyder mest logisk for mig. Måske kan du gå efter reglen:"Luk markøren, hvis du ikke har brug for den længere." Altså commit() før du lukker markøren. I sidste ende, for Connector/Python, gør det ikke den store forskel, men eller andre databaser kan det måske.
Jeg forventer, at det er så tæt på, som du kommer til "standard praksis" om dette emne.
Er der nogen væsentlig fordel ved at finde sæt af transaktioner, der ikke kræver mellemliggende commits, så du ikke behøver at få nye markører for hver transaktion?
Jeg tvivler meget på det, og i forsøget på at gøre det, kan du introducere yderligere menneskelige fejl. Det er bedre at beslutte sig for en konvention og holde sig til den.
Er der meget overhead for at få nye markører, eller er det bare ikke en big deal?
Overheaden er ubetydelig og rører slet ikke databaseserveren; det er helt inden for implementeringen af MySQLdb. Du kan se på BaseCursor.__init__
på github
hvis du virkelig er nysgerrig efter at vide, hvad der sker, når du opretter en ny markør.
Går tilbage til tidligere, hvor vi diskuterede with
, måske kan du nu forstå hvorfor MySQLdb.Connection
klasse __enter__
og __exit__
metoder giver dig et helt nyt markørobjekt i hver with
blokere og ikke gider holde styr på den eller lukke den for enden af blokken. Den er ret let og eksisterer udelukkende for din bekvemmelighed.
Hvis det virkelig er så vigtigt for dig at mikrostyre markørobjektet, kan du bruge contextlib.closing
for at kompensere for det faktum, at markørobjektet ikke har nogen defineret __exit__
metode. For den sags skyld kan du også bruge den til at tvinge forbindelsesobjektet til at lukke sig selv, når du afslutter en with
blok. Dette skulle udsende "my_curs is closed; my_conn is closed":
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'
Bemærk at with closing(arg_obj)
vil ikke kalde argumentobjektets __enter__
og __exit__
metoder; det vil kun kald argumentobjektets close
metode i slutningen af with
blok. (For at se dette i aktion skal du blot definere en klasse Foo
med __enter__
, __exit__
, og close
metoder, der indeholder simpel print
udsagn, og sammenlign, hvad der sker, når du gør with Foo(): pass
til hvad der sker, når du gør with closing(Foo()): pass
.) Dette har to væsentlige implikationer:
For det første, hvis autocommit-tilstand er aktiveret, vil MySQLdb BEGIN
en eksplicit transaktion på serveren, når du bruger with connection
og commit eller rollback transaktionen i slutningen af blokeringen. Disse er standardadfærd for MySQLdb, beregnet til at beskytte dig mod MySQL's standardadfærd med øjeblikkelig at begå enhver og alle DML-sætninger. MySQLdb antager, at når du bruger en kontekstadministrator, vil du have en transaktion, og bruger den eksplicitte BEGIN
for at omgå autocommit-indstillingen på serveren. Hvis du er vant til at bruge with connection
, tror du måske, at autocommit er deaktiveret, når det faktisk kun blev omgået. Du kan få en ubehagelig overraskelse, hvis du tilføjer closing
til din kode og miste transaktionsintegritet; du vil ikke være i stand til at fortryde ændringer, du kan begynde at se samtidige fejl, og det er måske ikke umiddelbart indlysende hvorfor.
For det andet with closing(MySQLdb.connect(user, pass)) as VAR
binder forbindelsesobjektet til VAR
, i modsætning til with MySQLdb.connect(user, pass) as VAR
, som binder et nyt markørobjekt til VAR
. I sidstnævnte tilfælde ville du ikke have direkte adgang til forbindelsesobjektet! I stedet skal du bruge markørens connection
attribut, som giver proxy-adgang til den oprindelige forbindelse. Når markøren er lukket, er dens connection
attribut er indstillet til None
. Dette resulterer i en forladt forbindelse, der vil blive ved, indtil et af følgende sker:
- Alle referencer til markøren fjernes
- Markøren går uden for rækkevidde
- Forbindelsen timeout
- Forbindelsen lukkes manuelt via serveradministrationsværktøjer
Du kan teste dette ved at overvåge åbne forbindelser (i Workbench eller ved ved hjælp af SHOW PROCESSLIST
) mens du udfører følgende linjer én efter én:
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