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

Quando chiudere i cursori usando MySQLdb

Invece di chiedere qual è la pratica standard, dal momento che spesso non è chiara e soggettiva, potresti provare a guardare il modulo stesso per una guida. In generale, usando il with parola chiave suggerita da un altro utente è un'ottima idea, ma in questa specifica circostanza potrebbe non darti le funzionalità che ti aspetti.

A partire dalla versione 1.2.5 del modulo, MySQLdb.Connection implementa il protocollo di gestione del contesto con il codice seguente (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()

Esistono diverse domande e risposte su with già, oppure puoi leggere Capire l'istruzione "with" di Python , ma essenzialmente quello che succede è che __enter__ viene eseguito all'inizio di with blocco e __exit__ viene eseguito all'uscita da with bloccare. Puoi usare la sintassi opzionale with EXPR as VAR per associare l'oggetto restituito da __enter__ a un nome se intendi fare riferimento a quell'oggetto in seguito. Quindi, data l'implementazione di cui sopra, ecco un modo semplice per interrogare il tuo database:

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,),)"

La domanda ora è, quali sono gli stati della connessione e del cursore dopo essere usciti da with bloccare? Il __exit__ il metodo mostrato sopra chiama solo self.rollback() o self.commit() , e nessuno di questi metodi continua a chiamare close() metodo. Il cursore stesso non ha __exit__ metodo definito – e non avrebbe importanza se lo facesse, perché with gestisce solo la connessione. Pertanto, sia la connessione che il cursore rimangono aperti dopo essere usciti dal with bloccare. Ciò è facilmente confermabile aggiungendo il seguente codice all'esempio precedente:

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'

Dovresti vedere l'output "cursore aperto; connessione aperta" stampato su stdout.

Credo che tu debba chiudere il cursore prima di confermare la connessione.

Come mai? L'API MySQL C , che è la base per MySQLdb , non implementa alcun oggetto cursore, come implicito nella documentazione del modulo:"MySQL non supporta i cursori; tuttavia, i cursori sono facilmente emulabili." Infatti, il MySQLdb.cursors.BaseCursor la classe eredita direttamente da object e non impone tale restrizione ai cursori per quanto riguarda il commit/rollback. Uno sviluppatore Oracle ha detto questo :

cnx.commit() prima di cur.close() mi sembra più logico. Forse puoi seguire la regola:"Chiudi il cursore se non ne hai più bisogno." Quindi commit() prima di chiudere il cursore. Alla fine, per Connector/Python, non fa molta differenza, ma o per altri database potrebbe.

Mi aspetto che sia il più vicino possibile alla "prassi standard" su questo argomento.

C'è qualche vantaggio significativo nel trovare insiemi di transazioni che non richiedono commit intermedi in modo da non dover ottenere nuovi cursori per ogni transazione?

Ne dubito fortemente e, nel tentativo di farlo, potresti introdurre un ulteriore errore umano. Meglio decidere una convenzione e attenersi ad essa.

C'è molto sovraccarico per ottenere nuovi cursori o semplicemente non è un grosso problema?

L'overhead è trascurabile e non tocca affatto il server del database; è interamente all'interno dell'implementazione di MySQLdb. Puoi guardare BaseCursor.__init__ su github se sei davvero curioso di sapere cosa succede quando crei un nuovo cursore.

Tornando a quando stavamo discutendo di with , forse ora puoi capire perché MySQLdb.Connection classe __enter__ e __exit__ i metodi ti danno un nuovo oggetto cursore in ogni with blocco e non preoccuparti di tenerne traccia o chiuderlo alla fine del blocco. È abbastanza leggero ed esiste esclusivamente per la tua comodità.

Se è davvero così importante per te microgestire l'oggetto cursore, puoi usare contextlib.closing per compensare il fatto che l'oggetto cursore non ha __exit__ definito metodo. Del resto, puoi anche usarlo per forzare la chiusura dell'oggetto connessione all'uscita da un with bloccare. Questo dovrebbe restituire "my_curs è chiuso; my_conn è chiuso":

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'

Nota che with closing(arg_obj) non chiamerà __enter__ dell'oggetto argomento e __exit__ metodi; sarà solo chiama close dell'oggetto argomento metodo alla fine del with bloccare. (Per vederlo in azione, definisci semplicemente una classe Foo con __enter__ , __exit__ e close metodi contenenti una semplice print e confronta cosa succede quando esegui with Foo(): pass a cosa succede quando fai with closing(Foo()): pass .) Ciò ha due implicazioni significative:

Innanzitutto, se la modalità autocommit è abilitata, MySQLdb BEGIN una transazione esplicita sul server quando utilizzi with connection e eseguire il commit o il rollback della transazione alla fine del blocco. Questi sono comportamenti predefiniti di MySQLdb, intesi a proteggerti dal comportamento predefinito di MySQL di eseguire immediatamente il commit di tutte le istruzioni DML. MySQLdb presuppone che quando utilizzi un gestore di contesto, desideri una transazione e utilizza l'esplicito BEGIN per ignorare l'impostazione di autocommit sul server. Se sei abituato a usare with connection , potresti pensare che l'autocommit sia disabilitato quando in realtà veniva solo bypassato. Potresti ricevere una spiacevole sorpresa se aggiungi closing al tuo codice e perdere l'integrità transazionale; non sarai in grado di ripristinare le modifiche, potresti iniziare a vedere bug di simultaneità e potrebbe non essere immediatamente ovvio il motivo.

Secondo, with closing(MySQLdb.connect(user, pass)) as VAR associa l'oggetto di connessione a VAR , in contrasto con with MySQLdb.connect(user, pass) as VAR , che associa un nuovo oggetto cursore a VAR . In quest'ultimo caso non avresti accesso diretto all'oggetto connessione! Invece, dovresti usare la connection del cursore attributo, che fornisce l'accesso proxy alla connessione originale. Quando il cursore è chiuso, la sua connection l'attributo è impostato su None . Ciò si traduce in una connessione abbandonata che rimarrà attiva fino a quando non si verifica uno dei seguenti eventi:

  • Tutti i riferimenti al cursore vengono rimossi
  • Il cursore esce dall'ambito
  • La connessione è scaduta
  • La connessione viene chiusa manualmente tramite gli strumenti di amministrazione del server

Puoi verificarlo monitorando le connessioni aperte (in Workbench o utilizzando SHOW PROCESSLIST ) mentre si eseguono le seguenti righe una per una:

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