Zamiast pytać, co jest standardową praktyką, ponieważ jest to często niejasne i subiektywne, możesz spróbować poszukać wskazówek w samym module. Ogólnie rzecz biorąc, używanie with
słowo kluczowe, jak zasugerował inny użytkownik, to świetny pomysł, ale w tej konkretnej sytuacji może nie zapewniać pełnej funkcjonalności, jakiej oczekujesz.
Od wersji 1.2.5 modułu MySQLdb.Connection
implementuje protokół menedżera kontekstu
z następującym kodem (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()
Istnieje kilka istniejących pytań i odpowiedzi na temat with
już lub możesz przeczytać Zrozumienie oświadczenia Pythona „with”
, ale zasadniczo dzieje się tak, że __enter__
wykonuje na początku with
blok i __exit__
wykonuje po opuszczeniu with
blok. Możesz użyć opcjonalnej składni with EXPR as VAR
powiązać obiekt zwrócony przez __enter__
do nazwy, jeśli zamierzasz później odwołać się do tego obiektu. Tak więc, biorąc pod uwagę powyższą implementację, oto prosty sposób na zapytanie bazy danych:
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,),)"
Pytanie brzmi teraz, jakie są stany połączenia i kursora po wyjściu z with
blok? __exit__
metoda pokazana powyżej wywołuje tylko self.rollback()
lub self.commit()
, a żadna z tych metod nie wywołuje metody close()
metoda. Sam kursor nie ma __exit__
zdefiniowana metoda – i nie miałoby znaczenia, gdyby tak było, ponieważ with
zarządza tylko połączeniem. Dlatego zarówno połączenie, jak i kursor pozostają otwarte po wyjściu z with
blok. Można to łatwo potwierdzić, dodając następujący kod do powyższego przykładu:
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'
Powinieneś zobaczyć wyjście "kursor jest otwarty; połączenie jest otwarte" wydrukowane na standardowe wyjście.
Uważam, że przed nawiązaniem połączenia musisz zamknąć kursor.
Czemu? MySQL C API
, który jest podstawą MySQLdb
, nie implementuje żadnego obiektu kursora, jak sugeruje dokumentacja modułu:"MySQL nie obsługuje kursorów; jednak kursory są łatwo emulowane."
Rzeczywiście, MySQLdb.cursors.BaseCursor
klasa dziedziczy bezpośrednio z object
i nie nakłada takich ograniczeń na kursory w odniesieniu do zatwierdzania/wycofywania. Programista Oracle miał to do powiedzenia
:
cnx.commit() przed cur.close() brzmi dla mnie najbardziej logicznie. Może możesz postępować zgodnie z zasadą:„Zamknij kursor, jeśli już go nie potrzebujesz”. Zatem commit() przed zamknięciem kursora. W końcu, w przypadku Connectora/Pythona nie ma to większego znaczenia, ale lub innych baz danych może.
Spodziewam się, że jest to tak blisko, jak do "standardowej praktyki" w tym temacie.
Czy jest jakaś znacząca zaleta znajdowania zestawów transakcji, które nie wymagają pośrednich zatwierdzeń, dzięki czemu nie trzeba pobierać nowych kursorów dla każdej transakcji?
Bardzo w to wątpię, a próbując to zrobić, możesz wprowadzić dodatkowy błąd ludzki. Lepiej zdecydować się na konwencję i trzymać się jej.
Czy zdobycie nowych kursorów wiąże się z dużym obciążeniem, czy po prostu nie jest to wielka sprawa?
Narzut jest znikomy iw ogóle nie dotyka serwera bazy danych; jest całkowicie w ramach implementacji MySQLdb. Możesz zajrzeć na BaseCursor.__init__
na github
jeśli naprawdę chcesz wiedzieć, co się dzieje, gdy tworzysz nowy kursor.
Wracając do wcześniejszej rozmowy z with
, być może teraz rozumiesz, dlaczego MySQLdb.Connection
klasa __enter__
i __exit__
metody dają ci zupełnie nowy obiekt kursora w każdym with
bloku i nie zawracaj sobie głowy śledzeniem go ani zamykaniem na końcu bloku. Jest dość lekki i istnieje wyłącznie dla Twojej wygody.
Jeśli naprawdę tak ważne jest dla Ciebie mikrozarządzanie obiektem kursora, możesz użyć kontekstlib.zamykanie
aby zrekompensować fakt, że obiekt kursora nie ma zdefiniowanego __exit__
metoda. W tym przypadku możesz również użyć go do wymuszenia zamknięcia obiektu połączenia po wyjściu z with
blok. Powinno to dać komunikat „my_curs jest zamknięty; my_conn jest zamknięty”:
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'
Zauważ, że with closing(arg_obj)
nie wywoła __enter__
argumentu obiektu i __exit__
metody; to tylko wywołaj close
argumentu obiektu metoda na końcu with
blok. (Aby zobaczyć to w akcji, po prostu zdefiniuj klasę Foo
z __enter__
, __exit__
i close
metody zawierające proste print
i porównaj, co się stanie, gdy zrobisz with Foo(): pass
do tego, co się stanie, gdy zrobisz with closing(Foo()): pass
.) Ma to dwie istotne implikacje:
Po pierwsze, jeśli włączony jest tryb automatycznego zatwierdzania, MySQLdb BEGIN
jawna transakcja na serwerze, gdy używasz with connection
i zatwierdź lub wycofaj transakcję na końcu bloku. Są to domyślne zachowania MySQLdb, mające na celu ochronę przed domyślnym zachowaniem MySQL polegającym na natychmiastowym wykonywaniu wszelkich instrukcji DML. MySQLdb zakłada, że kiedy używasz menedżera kontekstu, potrzebujesz transakcji i używa jawnego BEGIN
aby ominąć ustawienie automatycznego zatwierdzania na serwerze. Jeśli jesteś przyzwyczajony do używania with connection
, możesz pomyśleć, że automatyczne zatwierdzanie jest wyłączone, podczas gdy w rzeczywistości było tylko pomijane. Możesz otrzymać niemiłą niespodziankę, jeśli dodasz closing
do kodu i utracić integralność transakcyjną; nie będziesz w stanie cofnąć zmian, możesz zacząć widzieć błędy współbieżności i może nie być od razu oczywiste, dlaczego.
Po drugie, with closing(MySQLdb.connect(user, pass)) as VAR
wiąże obiekt połączenia do VAR
, w przeciwieństwie do with MySQLdb.connect(user, pass) as VAR
, który wiąże nowy obiekt kursora do VAR
. W tym drugim przypadku nie będziesz miał bezpośredniego dostępu do obiektu połączenia! Zamiast tego musiałbyś użyć connection
kursora atrybut, który zapewnia dostęp proxy do pierwotnego połączenia. Kiedy kursor jest zamknięty, jego connection
atrybut jest ustawiony na None
. Powoduje to porzucone połączenie, które będzie się utrzymywać, dopóki nie wydarzy się jedna z następujących sytuacji:
- Wszystkie odniesienia do kursora zostały usunięte
- Kursor wychodzi poza zakres
- Upłynął limit czasu połączenia
- Połączenie jest zamykane ręcznie za pomocą narzędzi do administrowania serwerem
Możesz to przetestować, monitorując otwarte połączenia (w Workbenchu lub przez za pomocą SHOW PROCESSLIST
) wykonując kolejno następujące linie:
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