sql >> Database >  >> RDS >> Mysql

Wanneer cursors te sluiten met MySQLdb

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


  1. Dynamische SQL-LUS

  2. Hoe om te gaan met fouten in in SQL Server geneste transacties

  3. Records filteren met geaggregeerde functie SUM

  4. De SQL OVER()-clausule - wanneer en waarom is het nuttig?