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 withsł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.Connectionimplementuje 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 withlub możesz przeczytać instrukcję Understanding Python "with" , ale zasadniczo dzieje się tak, że jest __enter__wykonywana na początku withbloku i __exit__wykonywana po opuszczeniu withbloku. Możesz użyć opcjonalnej składni, with EXPR as VARaby 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 withbloku? __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ż withzarządza tylko połączeniem. Dlatego zarówno połączenie, jak i kursor pozostają otwarte po wyjściu z withbloku. 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.BaseCursorklasa dziedziczy bezpośrednio po objectkursorach 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.Connectionklasa __enter__i __exit__metody dają ci zupełnie nowy obiekt kursora w każdym withbloku 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 withbloku. 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 withbloku. (Aby zobaczyć w akcji, wystarczy zdefiniować klasę Fooz __enter__, __exit__i closemetody zawierający proste printwypowiedzi 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 BEGINjawną transakcję na serwerze, gdy użyjesz with connectioni 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, BEGINaby 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ąclosingdo 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 VARwiąż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ć connectionatrybutu kursora , który zapewnia dostęp proxy do oryginalnego połączenia. Kiedy kursor jest zamknięty, jego connectionatrybut 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