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
cursor.close()
jest to część API Python DB , która nie została napisana specjalnie z myślą o MySQL.my_curs
przechowuje ostatnie odniesienie doconnection
obiektu. Gdy to odwołanie już nie istnieje,connection
obiekt powinien zostać wyrzucony do pamięci.with
iMySQLdb.Connection
„S__enter__
i__exit__
funkcji. Jeszcze raz dziękuję @Air.Lepiej jest przepisać go za pomocą słowa kluczowego „with”. „Z” automatycznie zamknie kursor (jest to ważne, ponieważ jest to niezarządzany zasób). Zaletą jest to, że zamknie kursor również w przypadku wyjątku.
from contextlib import closing import MySQLdb ''' At the beginning you open a DB connection. Particular moment when you open connection depends from your approach: - it can be inside the same function where you work with cursors - in the class constructor - etc ''' db = MySQLdb.connect("host", "user", "pass", "database") with closing(db.cursor()) as cur: cur.execute("somestuff") results = cur.fetchall() # do stuff with results cur.execute("insert operation") # call commit if you do INSERT, UPDATE or DELETE operations db.commit() cur.execute("someotherstuff") results2 = cur.fetchone() # do stuff with results2 # at some point when you decided that you do not need # the open connection anymore you close it db.close()
źródło
with
była to dobra opcja, jeśli chcesz jej używać w Flask lub innym frameworku internetowym. Jeślihttp://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
tak się stanie, pojawią się problemy.with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Uwaga: ta odpowiedź dotyczy PyMySQL , który jest bezpośrednim zamiennikiem MySQLdb i faktycznie najnowszą wersją MySQLdb, ponieważ MySQLdb przestał być utrzymywany. Uważam, że wszystko tutaj dotyczy również starszej wersji MySQLdb, ale nie zostało to sprawdzone.
Przede wszystkim kilka faktów:
with
Składnia Pythona wywołuje__enter__
metodę menedżera kontekstu przed wykonaniem treściwith
bloku, a następnie jego__exit__
metody.__enter__
metodę, która nie robi nic poza tworzeniem i zwracaniem kursora oraz__exit__
metodę, która albo zatwierdza, albo wycofuje (w zależności od tego, czy został zgłoszony wyjątek). To nie zamknąć połączenie.__enter__
metodę, która nic nie robi i__exit__
metodę, która „zamyka” kursor (co oznacza po prostu zerowanie odniesienia kursora do jego połączenia nadrzędnego i wyrzucenie wszelkich danych przechowywanych na kursorze).__del__
metodę, która je zamykaŁącząc te rzeczy razem, widzimy, że taki naiwny kod jest w teorii problematyczny:
# Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelated
Problem w tym, że nic nie zamknęło połączenia. Rzeczywiście, jeśli wkleisz powyższy kod do powłoki Pythona, a następnie uruchomisz
SHOW FULL PROCESSLIST
w powłoce MySQL, będziesz mógł zobaczyć bezczynne połączenie, które utworzyłeś. Ponieważ domyślna liczba połączeń MySQL to 151 , co nie jest duże , teoretycznie możesz zacząć napotykać problemy, jeśli masz wiele procesów, które utrzymują te połączenia otwarte.Jednak w CPythonie istnieje możliwość oszczędzania, która zapewnia, że kod taki jak w powyższym przykładzie prawdopodobnie nie spowoduje pozostawienia wielu otwartych połączeń. Ta oszczędność polega na tym, że gdy tylko
cursor
wyjdzie poza zakres (np. Funkcja, w której została utworzona, kończy się lubcursor
otrzymuje inną przypisaną do niej wartość), jej liczba referencji osiąga zero, co powoduje jej usunięcie, upuszczenie liczby referencji połączenia do zera, powodując wywołanie metody połączenia,__del__
która wymusza zamknięcie połączenia. Jeśli już wkleiłeś powyższy kod do powłoki Pythona, możesz to teraz zasymulować, uruchamiająccursor = 'arbitrary value'
; gdy tylko to zrobisz, otwarte połączenie zniknie zSHOW PROCESSLIST
wyjścia.Jednak poleganie na tym jest nieeleganckie i teoretycznie może zawieść w implementacjach Pythona innych niż CPython. Teoretycznie czystszym rozwiązaniem byłoby jawne otwarcie
.close()
połączenia (zwolnienie połączenia w bazie danych bez czekania, aż Python zniszczy obiekt). Ten bardziej niezawodny kod wygląda następująco:import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1')
To jest brzydkie, ale nie polega na tym, że Python niszczy twoje obiekty, aby zwolnić (skończoną liczbę) połączeń z bazą danych.
Zauważ, że zamknięcie kursora , jeśli już zamykasz połączenie w ten sposób, jest całkowicie bezcelowe.
Na koniec, aby odpowiedzieć na pytania drugorzędne:
Nie, utworzenie instancji kursora w ogóle nie uderza w MySQL i zasadniczo nic nie robi .
Jest to sytuacja sytuacyjna i trudna do udzielenia ogólnej odpowiedzi. Jak to ujął https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html , „aplikacja może napotkać problemy z wydajnością, jeśli wykonuje tysiące razy na sekundę, i inne problemy z wydajnością, jeśli zatwierdza się tylko co 2-3 godziny ” . Płacisz narzut wydajności za każde zatwierdzenie, ale pozostawiając transakcje otwarte na dłużej, zwiększasz szansę, że inne połączenia będą musiały spędzać czas na czekaniu na blokady, zwiększasz ryzyko zakleszczenia i potencjalnie zwiększasz koszt niektórych wyszukiwań wykonywanych przez inne połączenia .
1 MySQL nie posiadają konstrukcję nie wywołuje kursor ale występują tylko wewnątrz procedur przechowywanych; są zupełnie inne niż kursory PyMySQL i nie mają tutaj znaczenia.
źródło
Myślę, że lepiej będzie, jeśli spróbujesz użyć jednego kursora do wszystkich swoich wykonań i zamknąć go na końcu kodu. Łatwiej się z nim pracuje i może również przynieść korzyści w zakresie wydajności (nie cytuj mnie na ten temat).
conn = MySQLdb.connect("host","user","pass","database") cursor = conn.cursor() cursor.execute("somestuff") results = cursor.fetchall() ..do stuff with results cursor.execute("someotherstuff") results2 = cursor.fetchall() ..do stuff with results2 cursor.close()
Chodzi o to, że możesz przechowywać wyniki wykonania kursora w innej zmiennej, zwalniając w ten sposób kursor do wykonania drugiego wykonania. W ten sposób napotkasz problemy tylko wtedy, gdy używasz funkcji fetchone () i musisz wykonać drugie wykonanie kursora, zanim przejdziesz przez wszystkie wyniki pierwszego zapytania.
W przeciwnym razie powiedziałbym, że po prostu zamknij kursory, gdy tylko skończysz pobierać z nich wszystkie dane. W ten sposób nie musisz się martwić o zawiązywanie luźnych końców w późniejszym kodzie.
źródło
Proponuję zrobić to jak php i mysql. Rozpocznij i na początku swojego kodu przed wydrukowaniem pierwszych danych. Więc jeśli pojawi się błąd połączenia, możesz wyświetlić komunikat o błędzie
50x
(Nie pamiętam, co to jest błąd wewnętrzny). I pozostaw ją otwartą przez całą sesję i zamknij, gdy wiesz, że nie będzie już potrzebna.źródło