sql >> Database teknologi >  >> RDS >> Mysql

Hvornår skal markører lukkes ved hjælp af MySQLdb

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


  1. Standard rækkefølge for udvalgt forespørgsel i oracle

  2. Halloween-problemet – del 1

  3. Sådan får du dagen fra en date i T-SQL

  4. En oversigt over Quests nyeste databaseovervågningstjeneste - Spotlight Cloud