Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Kiedy zamykać kursory za pomocą MySQLdb

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


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wyświetlić błędy dla mojego zapytania MySQLi?

  2. Typ MySQL ENUM a dołączanie tabel

  3. Łącznik MySQL w języku Python — znaleziono nieprzeczytane wyniki podczas używania fetchone

  4. Hibernate 5:- org.hibernate.MappingException:Nieznana jednostka

  5. Jak połączyć wiele wierszy w jedną kolumnę w MySQL?