web-dev-qa-db-de.com

Zwischenspeichern/Wiederverwenden einer DB-Verbindung zur späteren Verwendung der Ansicht

Ich speichere die Datenbankverbindung eines Benutzers. Beim ersten Eingeben ihrer Anmeldeinformationen mache ich Folgendes:

self.conn = MySQLdb.connect (
    Host = 'aaa',
    user = 'bbb',
    passwd = 'ccc',
    db = 'ddd',
    charset='utf8'
)
cursor = self.conn.cursor()
cursor.execute("SET NAMES utf8")
cursor.execute('SET CHARACTER SET utf8;')
cursor.execute('SET character_set_connection=utf8;')

Ich habe dann die Variable conn für alle Benutzeranfragen bereit. Ich möchte jedoch nicht jedes Mal erneut eine Verbindung herstellen, wenn die Variable view geladen wird. Wie würde ich diese "offene Verbindung" speichern, damit ich in der Ansicht Folgendes tun kann:

def do_queries(request, sql):
    user = request.user
    conn = request.session['conn']
    cursor = request.session['cursor']
    cursor.execute(sql)

Update : Es scheint, als ob das oben Genannte nicht möglich ist und keine gute Praxis. Lassen Sie mich das, was ich versuche, umformulieren:

Ich habe einen SQL-Editor, den ein Benutzer verwenden kann, nachdem er seine Anmeldeinformationen eingegeben hat (etwa Navicat oder SequelPro). Beachten Sie, dass diesNICHTdie standardmäßige Django-Datenbankverbindung ist - ich kenne die Anmeldeinformationen nicht im Voraus. Sobald der Benutzer die Verbindung hergestellt hat, möchte ich, dass er so viele Abfragen ausführen kann, wie er möchte, ohne dass ich jedes Mal erneut eine Verbindung herstellen muss. Zum Beispiel - um es noch einmal zu wiederholen - etwa Navicat oder SequelPro. Wie wäre das mit Python, Django oder MySQL? Vielleicht verstehe ich nicht wirklich, was hier notwendig ist (Zwischenspeichern der Verbindung (Verbindungspooling) usw.), daher wäre jede Anregung oder Hilfe sehr dankbar.

6
David542

Sie können einen IoC-Container verwenden, um einen Einzelanbieter für Sie zu speichern. Anstatt eine neue Verbindung jedes Mal aufzubauen, wird sie im Wesentlichen nur einmal aufgebaut (beim ersten Aufruf von ConnectionContainer.connection_provider()) und danach immer die zuvor erstellte Verbindung zurückgeben.

Sie benötigen das dependency-injector-Paket, damit mein Beispiel funktioniert:

import dependency_injector.containers as containers
import dependency_injector.providers as providers


class ConnectionProvider():
    def __init__(self, Host, user, passwd, db, charset):
        self.conn = MySQLdb.connect(
            Host=host,
            user=user,
            passwd=passwd,
            db=db,
            charset=charset
        )


class ConnectionContainer(containers.DeclarativeContainer):
    connection_provider = providers.Singleton(ConnectionProvider,
                                              Host='aaa',
                                              user='bbb',
                                              passwd='ccc',
                                              db='ddd',
                                              charset='utf8')


def do_queries(request, sql):
    user = request.user
    conn = ConnectionContainer.connection_provider().conn
    cursor = conn.cursor()
    cursor.execute(sql)

Ich habe die Verbindungszeichenfolge hier hartcodiert, es ist jedoch auch möglich, sie abhängig von einer veränderbaren Konfiguration variabel zu machen. In diesem Fall können Sie auch einen Container für die Konfigurationsdatei erstellen und den Verbindungscontainer seine Konfiguration von dort aus lesen lassen. Dann setzen Sie die Konfig zur Laufzeit. Wie folgt:

import dependency_injector.containers as containers
import dependency_injector.providers as providers

class ConnectionProvider():
    def __init__(self, connection_config):
        self.conn = MySQLdb.connect(**connection_config)

class ConfigContainer(containers.DeclarativeContainer):
    connection_config = providers.Configuration("connection_config")

class ConnectionContainer(containers.DeclarativeContainer):
    connection_provider = providers.Singleton(ConnectionProvider, ConfigContainer.connection_config)

def do_queries(request, sql):
    user = request.user
    conn = ConnectionContainer.connection_provider().conn
    cursor = conn.cursor()
    cursor.execute(sql)


# run code
my_config = {
    'Host':'aaa',
    'user':'bbb',
    'passwd':'ccc',
    'db':'ddd',
    'charset':'utf8'
}

ConfigContainer.connection_config.override(my_config)
request = ...
sql = ...

do_queries(request, sql)
2
Karl

Ich verstehe nicht, warum Sie hier eine zwischengespeicherte Verbindung benötigen und warum nicht einfach bei jeder Anforderung eine Verbindung erneut herstellen, die Anmeldeinformationen des Benutzers irgendwo zwischenspeichert, aber ich versuche trotzdem, eine Lösung zu finden, die Ihren Anforderungen entspricht.

Ich würde vorschlagen, zuerst eine generischere Aufgabe zu prüfen: Zwischenspeichern Sie etwas zwischen nachfolgenden Anforderungen, die Ihre App verarbeiten muss, und kann nicht in Django-Sitzungen serialisiert werden .. In Ihrem speziellen Fall wäre dieser gemeinsame Wert eine Datenbankverbindung) (oder mehrere Verbindungen) . Beginnen wir mit der einfachen Aufgabe, ein einfaches Zählervariable zwischen Anforderungen gemeinsam zu verwenden, nur um zu verstehen, was unter der Haube tatsächlich geschieht.

Interessanterweise hat keine Antwort etwas über einen Webserver erwähnt, den Sie verwenden könnten! Tatsächlich gibt es mehrere Möglichkeiten, gleichzeitige Verbindungen in Web-Apps zu handhaben:

  1. Mit mehreren prozessen kommt jede Anfrage zufällig in one
  2. Mit mehreren thread wird jede Anfrage von einem zufälligen thread bearbeitet.
  3. s. 1 und S. 2 kombiniert
  4. Verschiedene async -Techniken, wenn es singleprocess + event loop bei der Verarbeitung von Anforderungen mit einer Einschränkung gibt, dass Request-Handler nicht lange blockieren sollten

Aus meiner eigenen Erfahrung sind S.1-2 für die Mehrheit der typischen Webapps geeignetApache1.x konnte nur mit S.1 funktionieren, Apache2.x kann alle von 1-3 verarbeiten.

Beginnen wir mit der folgenden Django-App und führen Sie einen Einzelprozess gunicorn webserver aus ..__ Ich werde gunicorn verwenden, da es ziemlich einfach ist, ihn anders als Apache zu konfigurieren (persönliche Meinung :-)

views.py

import time

from Django.http import HttpResponse

c = 0

def main(self):
    global c
    c += 1
    return HttpResponse('val: {}\n'.format(c))


def heavy(self):
    time.sleep(10)
    return HttpResponse('heavy done')

urls.py

from Django.contrib import admin
from Django.urls import path

from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.main, name='main'),
    path('heavy/', views.heavy, name='heavy')
]

Ausführen in einem einzigen Prozessmodus:

gunicorn testpool.wsgi -w 1

Hier ist unsere Prozessstruktur - es gibt nur einen Mitarbeiter, der ALLE Anfragen bearbeiten würde

pstree 77292
-+= 77292 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1
 \--- 77295 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1

Versuch unsere App zu nutzen:

curl 'http://127.0.0.1:8000'
val: 1

curl 'http://127.0.0.1:8000'
val: 2

curl 'http://127.0.0.1:8000'
val: 3

Wie Sie sehen, können Sie den Zähler problemlos für nachfolgende Anfragen freigeben. Das Problem ist, dass Sie nur eine einzige Anfrage parallel bearbeiten können. Wenn Sie in einem Tab nach / heavy/ fragen, funktioniert / erst, wenn / heavy abgeschlossen ist

Jetzt verwenden wir 2 Arbeitsprozesse:

gunicorn testpool.wsgi -w 2

So würde der Prozessbaum aussehen:

 pstree 77285
-+= 77285 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2
 |--- 77288 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2
 \--- 77289 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2

Testen Sie unsere App:

curl 'http://127.0.0.1:8000'
val: 1

curl 'http://127.0.0.1:8000'
val: 2

curl 'http://127.0.0.1:8000'
val: 1

Die ersten beiden Anforderungen wurden vom ersten worker process und vom 3rd bearbeitet - vom zweiten Worker-Prozess, der über einen eigenen Speicherplatz verfügt, sodass 1 anstelle von 3 ..__ angezeigt wird. Beachten Sie, dass sich Ihre Ausgabe unterscheiden kann, da Prozess 1 und 2 zufällig ausgewählt werden. Aber früher oder später treffen Sie einen anderen -Prozess.

Das ist für uns nicht sehr hilfreich, da wir multiple gleichzeitige Anfragen bearbeiten müssen und wir müssen unsere Anfrage irgendwie durch einen bestimmten Prozess bearbeiten lassen, der im Allgemeinen nicht möglich ist.

Die meisten Pooling - Techniken, die aus der Box herauskommen, würden Verbindungen im Rahmen eines single - Prozesses nur zwischenspeichern, wenn Ihre Anforderung von einem anderen Prozess bedient wird - einem NEW Verbindung müsste hergestellt werden.

Wir können zu Threads wechseln

gunicorn testpool.wsgi -w 1 --threads 2

Wieder - nur 1 Prozess

pstree 77310
-+= 77310 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1 --threads 2
 \--- 77313 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1 --threads 2

Wenn Sie nun / heavy in einem Tab ausführen, können Sie / immer noch abfragen, und Ihr Zähler wird beibehalten zwischen den Anforderungen! Sein. Auch wenn die Anzahl der Threads Je nach Workload wächst oder schrumpft es immer noch einwandfrei.

Probleme: Sie müssen synchronisieren Zugriff auf die gemeinsam genutzte Variable wie diese mit Python-Threads zur Synchronisationstechnik ( read more ) ..__ haben. Ein weiteres Problem besteht darin, dass derselbe Benutzer dies tun kann müssen mehrere Abfragen parallel ausgeben, dh mehrere Registerkarten öffnen.

Um damit umzugehen, können Sie multiple -Verbindungen bei der ersten Anforderung öffnen, wenn Sie über Datenbankanmeldeinformationen verfügen.

Wenn ein Benutzer mehr Verbindungen benötigt, als Ihre App auf die Sperre warten kann, bis eine Verbindung verfügbar wird.

Zurück zu deiner Frage

Sie können eine Klasse mit den folgenden Methoden erstellen:

from contextlib import contextmanager

class ConnectionPool(object):

   def __init__(self, max_connections=4):
      self._pool = dict()
      self._max_connections = max_connections

   def preconnect(self, session_id, user, password):
       # create multiple connections and put them into self._pool
       # ...

    @contextmanager
    def get_connection(sef, session_id):
       # if have an available connection:
            # mark it as allocated
            # and return it
            try:
                yield connection
           finally:
              # put it back to the pool
              # ....
       # else
        # wait until there's a connection returned to the pool by another thread

pool = ConnectionPool(4)

def some_view(self):
     session_id = ...
     with pool.get_connection(session_id) as conn:
        conn.query(...)

Dies ist keine vollständige Lösung - Sie müssen veraltete, auf längere Zeit nicht verwendete Verbindungen löschen.

Wenn ein Benutzer nach langer Zeit wiederkommt und seine Verbindung geschlossen wurde, muss er seine Anmeldeinformationen erneut eingeben - hoffentlich ist dies aus Sicht Ihrer App in Ordnung.

Denken Sie auch daran, dass Python threads seine Performance-Strafen hat und nicht sicher ist, ob dies ein Problem für Sie ist.

Ich habe es nicht auf Apache2 geprüft (zu viel Konfigurationsaufwand, ich habe es seit Ewigkeiten nicht verwendet und benutze im Allgemeinen uwsgi ), aber es sollte auch dort funktionieren - würde mich freuen, von Ihnen zu hören wenn Sie es schaffen, es auszuführen

Und vergessen Sie auch nicht p.4 (asynchroner Ansatz) - Sie werden es wahrscheinlich nicht für Apache verwenden können, aber es ist eine Untersuchung wert - Schlüsselwörter: Django + gevent, Django + Asyncio. Es hat seine Vor-/Nachteile und kann die Implementierung Ihrer App erheblich beeinflussen. Daher ist es schwierig, Lösungen zu finden, ohne die App-Anforderungen im Detail zu kennen

1
ffeast

Dies ist keine gute Idee, so etwas synchron im Web-App-Kontext zu tun. Denken Sie daran, dass Ihre Anwendung möglicherweise auf mehrere Prozess-/Thread-Weise arbeiten muss und Sie die Verbindung zwischen Prozessen normalerweise nicht freigeben können. Wenn Sie also eine Verbindung für Ihren Benutzer in einem Prozess erstellen, gibt es keine Garantie, dass Sie eine Anfrage für dieselbe Anfrage erhalten. Eine bessere Idee ist es, einen einzelnen Prozess-Hintergrundarbeiter zu haben, der Verbindungen in mehreren Threads (einen Thread pro Sitzung) verarbeitet, um Abfragen in der Datenbank zu erstellen und das Ergebnis in Web-App abzurufen. Ihre Anwendung sollte jeder Sitzung eine eindeutige ID zuweisen, und der Hintergrundarbeiter verfolgt jeden Thread anhand der Sitzungs-ID. Sie können celery oder andere Task-Warteschlangen verwenden, die ein asynchrones Ergebnis unterstützen. Das Design wäre also wie folgt:

             |<--|        |<--------------|                   |<--|
user (id: x) |   | webapp |   | queue |   | worker (thread x) |   | DB
             |-->|        |-->|       |-->|                   |-->|

Sie können auch eine Warteschlange für jeden Benutzer erstellen, bis er eine aktive Sitzung hat. Als Ergebnis können Sie für jede Sitzung einen separaten Hintergrundprozess ausführen.

1
sharez

Ich habe tatsächlich meine Lösung für dieses genaue Problem mitgeteilt. Was ich hier gemacht habe, war das Erstellen eines Pools von Verbindungen, mit dem Sie das Maximum angeben können, und dann Abfrageanforderungen asynchron über diesen Kanal in die Warteschlange gestellt. Auf diese Weise können Sie eine bestimmte Anzahl von Verbindungen geöffnet lassen, aber es wird asynchron in die Warteschlange eingereiht und gebündelt und die gewohnte Geschwindigkeit beibehalten. 

Dies erfordert Gevent und Postgres.

Python Postgres psycopg2 ThreadedConnectionPool erschöpft

1
eatmeimadanish

Ich teile gerade mein Wissen hier.

Installieren Sie PyMySQL, um MySql zu verwenden

Für Python 2.x

pip install PyMySQL

Für Python 3.x

pip3 install PyMySQL

1. Wenn Sie Django Framework verwenden möchten, ist es sehr einfach, die SQL-Abfrage ohne erneute Verbindung auszuführen.

Fügen Sie in der Datei setting.py die folgenden Zeilen hinzu

DATABASES = {
        'default': {
            'ENGINE': 'Django.db.backends.mysql',
            'NAME': 'test',
            'USER': 'test',
            'PASSWORD': 'test',
            'Host': 'localhost',
            'OPTIONS': {'charset': 'utf8mb4'},
        }
    }

Fügen Sie in der Datei views.py diese Zeilen hinzu, um die Daten abzurufen. Sie können Ihre Anfrage an Ihre Bedürfnisse anpassen

from Django.db import connection
def connect(request):
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM Tablename");
    results = cursor.fetchall()
    return results 

Sie erhalten die gewünschten Ergebnisse.

Klicken Sie auf hier für weitere Informationen 

2. Für Python-Tkinter

from Tkinter import *
import MySQLdb

db = MySQLdb.connect("localhost","root","root","test")
# prepare a cursor object using cursor() method
cursor = db.cursor()
cursor.execute("SELECT * FROM Tablename")
if cursor.fetchone() is not None:
    print("In If")
else:
    print("In Else")
cursor.close()

Siehe this für weitere Informationen 

PS: Sie können diesen Link für Ihre Frage zur Wiederverwendung einer DB-Verbindung für einen späteren Zeitpunkt überprüfen. 

Wie kann ich die automatische erneute Verbindung des MySQL-Clients mit MySQLdb aktivieren?

0
Anoop Kumar

Ich bin kein Experte auf diesem Gebiet, aber ich glaube, dass PgBouncer die Arbeit für Sie erledigen würde, vorausgesetzt, Sie können ein PostgreSQL-Back-End verwenden (dies ist ein Detail, das Sie nicht klargestellt haben). PgBouncer ist ein connection pooler, mit dem Sie Verbindungen wiederverwenden können, ohne dass der Aufwand für jede Anfrage entsteht.

Nach ihrer Dokumentation :

Benutzer-Passwort

Wenn Benutzer = gesetzt ist, werden alle Verbindungen zur Zieldatenbank mit dem angegebenen Benutzer hergestellt. Dies bedeutet, dass es nur einen Pool für diese Datenbank gibt.

Andernfalls versucht PgBouncer, sich mit dem Client-Benutzernamen bei der Zieldatenbank anzumelden. Dies bedeutet, dass es einen Pool pro Benutzer gibt.

Sie können also pro Benutzer einen einzigen Pool an Verbindungen haben, der sich so anhört, wie Sie möchten.

In MySQL-Land können Sie mit dem Modul mysql.connector.pooling etwas Verbindungspooling durchführen, obwohl ich nicht sicher bin, ob Sie das Pooling pro Benutzer durchführen können. In Anbetracht der Tatsache, dass Sie den Poolnamen einrichten können, könnte der Name des Benutzers zur Identifizierung des Pools verwendet werden.

Unabhängig von dem, was Sie verwenden, werden Sie wahrscheinlich Gelegenheiten haben, bei denen eine erneute Verbindung unvermeidlich ist (ein Benutzer stellt eine Verbindung her, erledigt ein paar Dinge, geht zu einem Meeting und zum Mittagessen, kommt zurück und möchte weitere Maßnahmen ergreifen).

0
Jonah Bishop