Jak radzić sobie z połączeniami z bazą danych w module biblioteki Python


23

W Pythonie utworzyłem bibliotekę, która zawiera funkcje dostępu do bazy danych. Jest to biblioteka otoki wokół bazy danych aplikacji innych firm, napisana z powodu faktu, że aplikacja innej firmy nie oferuje przyzwoitego interfejsu API. Teraz początkowo pozwalałem każdej funkcji otwierać połączenie z bazą danych na czas trwania wywołania funkcji, co było OK, dopóki moja logika programu nie używała zagnieżdżonych wywołań funkcji, w których wówczas wywoływałabym określoną funkcję kilka tysięcy razy. To nie było zbyt wydajne. Profilowanie tego pokazało, że narzut był w konfiguracji połączenia z bazą danych - raz na wywołanie funkcji. Dlatego przeniosłem otwarte połączenie z funkcji do samego modułu, aby połączenie z bazą danych zostało otwarte podczas importowania modułu biblioteki. To dało mi akceptowalną wydajność.

Teraz mam dwa pytania dotyczące tego. Po pierwsze, czy muszę się martwić, że nie zamykam już wyraźnie połączenia z bazą danych i jak mogę to zrobić w tej konfiguracji? Po drugie, czy to, co zrobiłem, nie jest zbliżone do obszaru dobrych praktyk i jak mógłbym do tego podejść?


1
Podaj openConnfunkcję i spraw, aby użytkownik przekazał ją do każdej wywoływanej funkcji, w ten sposób można zawęzić zakres połączenia w withinstrukcji lub czymkolwiek innym
Daniel Gratzer

1
Zgadzam się z jozfegiem, rozważam utworzenie klasy, która otwiera połączenie db wewnątrz konstruktora i która zamyka połączenie przy wyjściu
Nick Burns

Odpowiedzi:


31

To zależy od używanej biblioteki. Niektóre z nich mogą same zamykać połączenie (Uwaga: sprawdziłem wbudowaną bibliotekę sqlite3, ale tak się nie dzieje). Python wywoła destruktor, gdy obiekt wykracza poza zasięg, a biblioteki te mogą implementować destruktor, który z wdziękiem zamyka połączenia.

Jednak może tak nie być! Poleciłbym, jak inni mają w komentarzach, zawinięcie go w obiekt.

class MyDB(object):

    def __init__(self):
        self._db_connection = db_module.connect('host', 'user', 'password', 'db')
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

Spowoduje to utworzenie instancji połączenia z bazą danych na początku i zamknięcie go, gdy miejsce, w którym utworzono instancję, wykracza poza zakres. Uwaga: Jeśli utworzysz instancję tego obiektu na poziomie modułu, będzie on trwał dla całej aplikacji. Jeśli nie jest to zamierzone, sugerowałbym oddzielenie funkcji bazy danych od funkcji innych niż baza danych.

Na szczęście Python ustandaryzował interfejs API bazy danych , więc będzie on działał ze wszystkimi zgodnymi bazami danych :)


Jak można tego uniknąć selfw def query(self,?
samayo,

2
zdefiniować unikanie? Jaźń definiuje to jako metodę instancji, a nie metodę klasy. Myślę, że możesz utworzyć bazę danych jako właściwość statyczną w klasie, a następnie użyć tylko metod klasowych (nigdzie nie jest to konieczne), ale wtedy baza danych byłaby globalna dla klasy, a nie tylko jej pojedyncza instancja.
Travis,

Tak, ponieważ próbowałem użyć twojego przykładu, aby wykonać proste zapytanie, db.query('SELECT ...', var)i narzekałem na potrzebę trzeciego argumentu.
samayo,

@samson, musisz MyDBnajpierw utworzyć instancję obiektu:db = MyDB(); db.query('select...', var)
cowbert

To zapobiegło wiadomościomResourceWarning: unclosed <socket.socket...
Bob Stein,

3

podczas obsługi połączeń z bazą danych należy się martwić o dwie rzeczy:

  1. zapobiegać wielu wystąpieniom połączeń, pozwalanie każdej funkcji na otwarcie połączenia z bazą danych jest uważane za złą praktykę, biorąc pod uwagę ograniczoną liczbę sesji bazy danych, których zabraknie; przynajmniej twoje rozwiązanie nie skaluje się, zamiast tego używasz wzorca singletonu, twoja klasa byłaby tworzona tylko raz, aby uzyskać więcej informacji na temat tego wzorca zobacz link

  2. zamykając połączenie przy wyjściu z aplikacji, powiedzmy, że nie, i że masz co najmniej tuzin uruchomionych aplikacji, które robią to samo, na początku wszystko pójdzie dobrze, ale zabraknie sesji bazy danych i jedyna poprawka byłoby zrestartowanie serwera bazy danych, co nie jest dobre dla aplikacji na żywo, dlatego w miarę możliwości używaj tego samego połączenia.

aby zestalić wszystkie te pojęcia, zobacz następujący przykład, który otacza psycopg2

import psycopg2


class Postgres(object):
"""docstring for Postgres"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            # normally the db_credenials would be fetched from a config file or the enviroment
            # meaning shouldn't be hardcoded as follow
            db_config = {'dbname': 'demo', 'host': 'localhost',
                     'password': 'postgres', 'port': 5432, 'user': 'postgres'}
            try:
                print('connecting to PostgreSQL database...')
                connection = Postgres._instance.connection = psycopg2.connect(**db_config)
                cursor = Postgres._instance.cursor = connection.cursor()
                cursor.execute('SELECT VERSION()')
                db_version = cursor.fetchone()

            except Exception as error:
                print('Error: connection not established {}'.format(error))
                Postgres._instance = None

            else:
                print('connection established\n{}'.format(db_version[0]))

        return cls._instance

    def __init__(self):
        self.connection = self._instance.connection
        self.cursor = self._instance.cursor

    def query(self, query):
        try:
            result = self.cursor.execute(query)
        except Exception as error:
            print('error execting query "{}", error: {}'.format(query, error))
            return None
        else:
            return result

    def __del__(self):
        self.connection.close()
        self.cursor.close()

1
Cześć! Dziękuję za Twoją odpowiedź. Ale kiedy próbuję to zaimplementować w moim przypadku, mam if Database._instance is None: NameError: name 'Database' is not defined. Nie rozumiem, co to Databasejest i jak mogę to naprawić.
Ilya Rusin

1
@IlyaRusin to była moja wina, w rzeczywistości baza danych to tylko klasa nadrzędna, w której umieściłem wspólne metody dla różnych metod obsługi RDBMS, ponieważ łączę się nie tylko z Postgres. Przepraszamy jednak za pomyłkę i mam nadzieję, że poprawiona wersja może Ci się przydać, zachęcamy do dodania, zmodyfikowania kodu do swoich potrzeb, jeśli masz jakieś powiązane pytanie, nie wahaj się.
ponach

Gdybym wielokrotnie zadzwonić Postgres.query(Postgres(), some_sql_query)w whilepętli, to nadal otwierać i zamykać połączenie w każdej iteracji, czy pozostawić go otworzyć przez cały czas w whilepętli aż do zjazdu z programu?

@Michael klasa połączeń jest zaimplementowana jako singleton, dlatego zostałaby utworzona tylko raz, ale ogólnie
odradzałbym

1
@ponach Dzięki, robi dokładnie to, co chciałem osiągnąć. Trochę zaadaptowałem twój kod i próbowałem użyć instrukcji UPDATE w twojej query()funkcji, ale wydaje się, że jest problem z moim kodem, kiedy uruchamiam moją aplikację w trybie „równoległym”.

2

Interesujące byłoby zaoferowanie możliwości zarządzania kontekstem dla twoich obiektów. Oznacza to, że możesz napisać taki kod:

class MyClass:
    def __init__(self):
       # connect to DB
    def __enter__(self):
       return self
    def __exit__(self):
       # close the connection

Zapewni to wygodny sposób na automatyczne zamknięcie połączenia z bazą danych poprzez wywołanie klasy za pomocą instrukcji with:

with MyClass() as my_class:
   # do what you need
# at this point, the connection is safely closed.

-1

Długo się nad tym zastanawiałam. Do dnia znalazłem sposób. nie wiem, to najlepszy sposób. tworzysz plik o nazwie: conn.py i zapisujesz go w folderze /usr/local/lib/python3.5/site-packages/conn/. Używam freebsd i to jest ścieżka do mojego folderu pakietów witryn. w moim conn.py: conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` a w skrypcie chcę wywołać połączenie, piszę:

import psycopg2 import psycopg2.extras import psycopg2.extensions

from conn import conn try: conn = psycopg2.connect (conn.conn) z wyjątkiem: page = "Nie można uzyskać dostępu do bazy danych"

cur = conn.cursor ()

i bla bla ...

mam nadzieję, że to przydatne

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.