Zamiast pytać, jaka jest standardowa praktyka, ponieważ jest to często niejasne i subiektywne, możesz spróbować poszukać wskazówek w samym module. Ogólnie rzecz biorąc, użycie with
słowa kluczowego w sposób sugerowany przez innego użytkownika jest świetnym pomysłem, ale w tych konkretnych okolicznościach może nie zapewnić oczekiwanej funkcjonalności.
Od wersji 1.2.5 modułu MySQLdb.Connection
implementuje protokół zarządzania kontekstami 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 już kilka pytań i odpowiedzi na ten temat with
lub możesz przeczytać instrukcję Understanding Python "with" , ale zasadniczo dzieje się tak, że jest __enter__
wykonywana na początku with
bloku i __exit__
wykonywana po opuszczeniu with
bloku. Możesz użyć opcjonalnej składni, with EXPR as VAR
aby powiązać obiekt zwracany przez __enter__
z nazwą, jeśli zamierzasz później odwoływać się do tego obiektu. Tak więc, biorąc pod uwagę powyższą implementację, oto prosty sposób przeszukiwania bazy danych:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
Teraz pytanie brzmi, jakie są stany połączenia i kursora po wyjściu z with
bloku? __exit__
Sposób pokazany powyżej połączeń tylko self.rollback()
czy self.commit()
, a żadna z tych metod przejść do wywołania close()
metody. Sam kursor nie ma __exit__
zdefiniowanej metody - 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
bloku. 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" wypisane na standardowe wyjście.
Uważam, że przed wykonaniem połączenia należy zamknąć kursor.
Czemu? Interfejs MySQL C API , który jest podstawą MySQLdb
, nie implementuje żadnego obiektu kursora, jak wynika z dokumentacji modułu: „MySQL nie obsługuje kursorów, jednak kursory są łatwo emulowane”. W rzeczywistości MySQLdb.cursors.BaseCursor
klasa dziedziczy bezpośrednio po object
kursorach i nie nakłada na nie takich ograniczeń w odniesieniu do zatwierdzenia / wycofania. Programista Oracle tak powiedział :
cnx.commit () przed cur.close () brzmi dla mnie najbardziej logicznie. Może możesz skorzystać z reguły: „Zamknij kursor, jeśli już go nie potrzebujesz”. Tak więc commit () przed zamknięciem kursora. Ostatecznie w przypadku Connector / Pythona nie ma to większego znaczenia, ale w przypadku innych baz danych może.
Spodziewam się, że jest to tak blisko, jak zbliżasz się do „standardowej praktyki” w tym temacie.
Czy jest jakaś istotna zaleta znajdowania zestawów transakcji, które nie wymagają pośrednich zatwierdzeń, aby nie trzeba było 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 się jej trzymać.
Czy zdobycie nowych kursorów wiąże się z dużymi kosztami, czy to po prostu nic wielkiego?
Narzut jest pomijalny iw ogóle nie dotyka serwera bazy danych; jest całkowicie w ramach implementacji MySQLdb. Możesz spojrzeć BaseCursor.__init__
na github, jeśli naprawdę chcesz wiedzieć, co się dzieje, gdy tworzysz nowy kursor.
Wracając do wcześniejszego okresu, kiedy omawialiśmy with
, być może teraz możesz zrozumieć, 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 lub zamykaniem go na końcu bloku. Jest dość lekki i istnieje wyłącznie dla Twojej wygody.
Jeśli naprawdę ważne jest dla ciebie mikrozarządzanie obiektem kursora, możesz użyć contextlib.closing, aby zrekompensować fakt, że obiekt kursora nie ma zdefiniowanej __exit__
metody. W tym przypadku można go również użyć do wymuszenia zamknięcia obiektu połączenia po wyjściu z with
bloku. Powinno to spowodować wyświetlenie komunikatu „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'
Zauważ, że with closing(arg_obj)
nie wywoła argumentów obiektów __enter__
i __exit__
metod; wywoła metodę obiektu argumentu tylkoclose
na końcu with
bloku. (Aby zobaczyć w akcji, wystarczy zdefiniować klasę Foo
z __enter__
, __exit__
i close
metody zawierający proste print
wypowiedzi i porównać to, co się dzieje, kiedy zrobić with Foo(): pass
, co się dzieje, kiedy zrobić with closing(Foo()): pass
). Ma to dwie poważne konsekwencje:
Po pierwsze, jeśli tryb automatycznego zatwierdzania jest włączony, MySQLdb wykona BEGIN
jawną transakcję na serwerze, gdy użyjesz with connection
i zatwierdzisz lub wycofasz transakcję na końcu bloku. Są to domyślne zachowania MySQLdb, mające na celu ochronę użytkownika przed domyślnym zachowaniem MySQL polegającym na natychmiastowym zatwierdzaniu wszystkich instrukcji DML. MySQLdb zakłada, że kiedy używasz menedżera kontekstu, chcesz 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. Jeśli dodasz, możesz spotkać się z nieprzyjemną niespodziankąclosing
do swojego 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 miałbyś bezpośredniego dostępu do obiektu połączenia! Zamiast tego musiałbyś użyć connection
atrybutu kursora , który zapewnia dostęp proxy do oryginalnego 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 nastąpi jedna z następujących sytuacji:
- Wszystkie odniesienia do kursora zostaną usunięte
- Kursor wychodzi poza zasięg
- Limit czasu połączenia
- Połączenie jest zamykane ręcznie za pomocą narzędzi administracyjnych serwera
Możesz to sprawdzić, monitorując otwarte połączenia (w programie Workbench lub używającSHOW PROCESSLIST
) podczas wykonywania następujących wierszy jeden po drugim:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs