sql >> Databasteknik >  >> RDS >> Mysql

När ska man stänga markörer med MySQLdb

Istället för att fråga vad som är standardpraxis, eftersom det ofta är otydligt och subjektivt, kan du försöka titta på själva modulen för vägledning. I allmänhet använder du with nyckelord som en annan användare föreslog är en bra idé, men i den här specifika situationen kanske det inte ger dig riktigt den funktionalitet du förväntar dig.

Från och med version 1.2.5 av modulen, MySQLdb.Connection implementerar context manager-protokollet med följande kod (githubions.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()

Det finns flera befintliga frågor och svar om with redan, eller så kan du läsa Förstå Pythons "with"-påstående , men vad som egentligen händer är att __enter__ körs i början av with block och __exit__ körs när with lämnas blockera. Du kan använda den valfria syntaxen with EXPR as VAR för att binda objektet som returneras av __enter__ till ett namn om du tänker referera till det objektet senare. Så, med tanke på implementeringen ovan, här är ett enkelt sätt att fråga din databas:

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

Frågan är nu, vad är tillstånden för anslutningen och markören efter att ha avslutat with blockera? __exit__ metoden som visas ovan anropar endast self.rollback() eller self.commit() , och ingen av dessa metoder fortsätter att anropa close() metod. Markören själv har ingen __exit__ metod definierad – och skulle inte spela någon roll om den gjorde det, eftersom with hanterar bara anslutningen. Därför förblir både anslutningen och markören öppna efter att ha avslutat with blockera. Detta bekräftas enkelt genom att lägga till följande kod i exemplet ovan:

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 bör se utgången "markören är öppen; anslutningen är öppen" utskriven till stdout.

Jag tror att du måste stänga markören innan du gör anslutningen.

Varför? MySQL C API , som är grunden för MySQLdb , implementerar inte något markörobjekt, som antyds i moduldokumentationen:"MySQL stöder inte markörer, men markörer emuleras lätt." Faktum är att MySQLdb.cursors.BaseCursor klass ärver direkt från object och lägger ingen sådan begränsning på markörer när det gäller commit/rollback. En Oracle-utvecklare hade detta att säga :

cnx.commit() före cur.close() låter mest logiskt för mig. Kanske kan du följa regeln:"Stäng markören om du inte behöver den längre." Sålunda commit() innan du stänger markören. I slutändan, för Connector/Python, gör det inte så stor skillnad, men eller andra databaser kanske det.

Jag förväntar mig att det är så nära som du kommer att komma "standardpraxis" i detta ämne.

Finns det någon betydande fördel med att hitta uppsättningar av transaktioner som inte kräver mellanliggande åtaganden så att du inte behöver få nya markörer för varje transaktion?

Jag tvivlar mycket på det, och när du försöker göra det kan du introducera ytterligare mänskliga misstag. Bättre att bestämma sig för en konvention och hålla fast vid den.

Finns det mycket omkostnader för att få nya markörer, eller är det bara ingen stor sak?

Overheaden är försumbar och berör inte databasservern alls; det är helt inom implementeringen av MySQLdb. Du kan titta på BaseCursor.__init__ på github om du verkligen är nyfiken på vad som händer när du skapar en ny markör.

Går tillbaka till tidigare när vi diskuterade with , kanske kan du nu förstå varför MySQLdb.Connection klass __enter__ och __exit__ metoder ger dig ett helt nytt markörobjekt i varje with blockera och bry dig inte om att hålla reda på det eller stänga det i slutet av blocket. Den är ganska lätt och finns enbart för din bekvämlighet.

Om det verkligen är så viktigt för dig att mikrohantera markörobjektet kan du använda contextlib.closing för att kompensera för det faktum att markörobjektet inte har någon definierad __exit__ metod. För den delen kan du också använda den för att tvinga anslutningsobjektet att stänga sig själv när du avslutar en with blockera. Detta bör mata ut "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'

Observera att with closing(arg_obj) kommer inte att anropa argumentobjektets __enter__ och __exit__ metoder; det kommer bara anropa argumentobjektets close metod i slutet av with blockera. (För att se detta i aktion, definiera helt enkelt en klass Foo med __enter__ , __exit__ och close metoder som innehåller enkel print uttalanden och jämför vad som händer när du gör with Foo(): pass till vad som händer när du gör with closing(Foo()): pass .) Detta har två betydande konsekvenser:

För det första, om autocommit-läget är aktiverat, kommer MySQLdb att BEGIN en explicit transaktion på servern när du använder with connection och begå eller återställa transaktionen i slutet av blockeringen. Dessa är standardbeteenden för MySQLdb, avsedda att skydda dig från MySQL:s standardbeteende att omedelbart begå alla DML-satser. MySQLdb antar att när du använder en kontexthanterare vill du ha en transaktion och använder den explicita BEGIN för att kringgå autocommit-inställningen på servern. Om du är van vid att använda with connection , du kanske tror att autocommit är inaktiverat när det faktiskt bara förbigicks. Du kan få en obehaglig överraskning om du lägger till closing till din kod och förlora transaktionsintegritet; du kommer inte att kunna återställa ändringar, du kan börja se samtidiga buggar och det kanske inte är direkt uppenbart varför.

För det andra, with closing(MySQLdb.connect(user, pass)) as VAR binder anslutningsobjektet till VAR , till skillnad från with MySQLdb.connect(user, pass) as VAR , som binder ett nytt markörobjekt till VAR . I det senare fallet skulle du inte ha någon direkt tillgång till anslutningsobjektet! Istället måste du använda markörens connection attribut, som ger proxyåtkomst till den ursprungliga anslutningen. När markören är stängd, är dess connection attribut är satt till None . Detta resulterar i en övergiven anslutning som kommer att stanna kvar tills något av följande händer:

  • Alla referenser till markören tas bort
  • Markören går utanför omfånget
  • Anslutningen timeout
  • Anslutningen stängs manuellt via serveradministrationsverktyg

Du kan testa detta genom att övervaka öppna anslutningar (i Workbench eller genom med SHOW PROCESSLIST ) medan du kör följande rader en efter en:

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. Hur man återställer MySQL eller MariaDB Root Password i Linux

  2. Hur man åtgärdar "ALTER TABLE SWITCH-satsen misslyckades"

  3. Returnera en resultatuppsättning

  4. Versionering av SQL Server-databas