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