Je sauvegarde la connexion à la base de données d'un utilisateur. La première fois qu'ils entrent dans leurs informations d'identification, je fais quelque chose comme ceci:
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;')
J'ai alors le conn
prêt à aller pour toutes les requêtes de l'utilisateur. Cependant, je ne veux pas me reconnecter chaque fois que la view
est chargée. Comment pourrais-je stocker cette "connexion ouverte" afin que je puisse simplement faire quelque chose comme ceci dans la vue:
def do_queries(request, sql):
user = request.user
conn = request.session['conn']
cursor = request.session['cursor']
cursor.execute(sql)
Mise à jour : il semble que ce qui précède n’est pas possible et ne constitue pas une bonne pratique, alors laissez-moi reformuler ce que j’essaie de faire:
J'ai un éditeur SQL que l'utilisateur peut utiliser après avoir entré ses informations d'identification (pensez à quelque chose comme Navicat ou SequelPro). Notez que ceci estPASla connexion Django db par défaut - je ne connais pas les informations d’identité à l’avance. Maintenant, une fois que l'utilisateur est "connecté", j'aimerais qu'il puisse faire autant de requêtes qu'il le souhaite sans que je ne doive me reconnecter à chaque fois. Par exemple, pour répéter, quelque chose comme Navicat ou SequelPro. Comment cela pourrait-il être fait avec python, Django ou mysql? Peut-être que je ne comprends pas vraiment ce qui est nécessaire ici (mise en cache de la connexion? Pooling de connexion? Etc.), toute suggestion ou aide serait donc grandement appréciée.
Vous pouvez utiliser un conteneur IoC pour stocker un fournisseur singleton pour vous. Essentiellement, au lieu de construire une nouvelle connexion à chaque fois, il ne la construira qu’une fois (la première fois que ConnectionContainer.connection_provider()
est appelé), puis restituera toujours la connexion précédemment construite.
Vous aurez besoin du paquetage dependency-injector
pour que mon exemple fonctionne:
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)
J'ai codé en dur la chaîne de connexion ici, mais il est également possible de la rendre variable en fonction d'une configuration modifiable. Dans ce cas, vous pouvez également créer un conteneur pour le fichier de configuration et demander au conteneur de connexion de lire sa configuration à partir de là. Vous définissez ensuite la configuration au moment de l'exécution. Comme suit:
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)
Je ne vois pas pourquoi vous avez besoin d'une connexion en cache ici et pourquoi ne pas simplement vous reconnecter à chaque demande mettant en cache les informations d'identification de l'utilisateur quelque part, mais je vais quand même essayer de vous présenter une solution qui pourrait répondre à vos besoins.
Je suggèrerais d’envisager d’abord une tâche plus générique - mettez en cache quelque chose entre les demandes ultérieures que votre application doit gérer et ne peut pas sérialiser dans les sessions de Django
. Dans votre cas particulier, cette valeur partagée serait une connexion à la base de données). (ou plusieurs connexions) . Commençons par une tâche simple consistant à partager une simple variable compteur entre les requêtes, afin de comprendre ce qui se passe réellement sous le capot.
De manière générale, aucune des deux réponses n’a mentionné quoi que ce soit concernant un serveur Web que vous pourriez utiliser! En fait, il existe plusieurs façons de gérer les connexions simultanées dans les applications Web:
D'après ma propre expérience, les p.1-2 conviennent à la majorité des applications Web typiquesApache1.x
ne peut fonctionner qu'avec p.1, Apache2.x
peut gérer les 1-3.
Commençons par l'application Django
suivante et exécutons un processus unique gunicorn webserver . Je vais utiliser gunicorn
car il est assez facile de le configurer contrairement à Apache
(opinion personnelle :-)
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')
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')
]
gunicorn testpool.wsgi -w 1
Voici notre arbre de processus - il n'y a qu'un seul travailleur capable de traiter TOUTES les demandes.
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
Essayer d'utiliser notre application:
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
Comme vous pouvez le constater, vous pouvez facilement partager le compteur entre les demandes suivantes ... Le problème ici est que vous ne pouvez traiter qu’une seule demande en parallèle. Si vous demandez / heavy/ dans un onglet, / ne fonctionnera pas tant que / heavy ne sera pas terminé
gunicorn testpool.wsgi -w 2
Voici à quoi ressemblerait l’arbre de processus:
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
Test de notre application:
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
Les deux premières demandes ont été traitées par le premier worker process
et la 3ème - par le deuxième processus de travail disposant de son propre espace mémoire, de sorte que vous voyez 1 au lieu de 3 ..__ Notez que votre sortie peut différer car les processus 1 et 2 sont sélectionnés de manière aléatoire. Mais tôt ou tard, vous lancerez un processus différent.
Ce n'est pas très utile pour nous car nous devons traiter plusieurs} demandes simultanées et nous devons d'une manière ou d'une autre faire traiter notre demande par un processus spécifique qui ne peut pas être effectué dans le cas général.
La plupart des regroupements techniques qui sortent de la boîte ne mettraient en cache que les connexions entrant dans le cadre d'un processus unique, si votre demande est traitée par un processus différent - un NOUVEAU la connexion doit être faite.
gunicorn testpool.wsgi -w 1 --threads 2
Encore une fois - seulement 1 processus
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
Maintenant, si vous exécutez / heavy dans un onglet, vous pourrez toujours interroger / et votre compteur sera préservé entre les requêtes! Même si le nombre de threads augmente ou diminue en fonction de votre charge de travail, il devrait quand même fonctionner correctement.
Problems: vous aurez besoin de synchroniser accéder à la variable partagée comme ceci en utilisant les techniques de synchronisation des threads python ( en savoir plus ) . Un autre problème est que le même utilisateur peut besoin d'émettre plusieurs requêtes en parallèle, c'est-à-dire d'ouvrir plusieurs onglets.
Pour le gérer, vous pouvez ouvrir plusieurs connexions sur la première demande lorsque vous disposez des informations d'identification de base de données.
Si un utilisateur a besoin de plus de connexions que votre application risque de rester bloqué jusqu'à ce qu'une connexion soit disponible.
Vous pouvez créer une classe qui aurait les méthodes suivantes:
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(...)
Ce n'est pas une solution complète - vous devrez en quelque sorte supprimer les connexions obsolètes non utilisées pendant longtemps.
Si un utilisateur revient après un long moment et que sa connexion a été fermée, il devra fournir à nouveau ses informations d'identification. J'espère que cela vous convient du point de vue de votre application.
Gardez également à l'esprit que python threads
a ses pénalités de performance, ne sachant pas si cela vous pose problème.
Je ne l'ai pas vérifié pour Apache2
(trop de configuration, je ne l'utilise pas depuis très longtemps et j'utilise généralement uwsgi ), mais cela devrait également fonctionner là-bas - je serais heureux de recevoir vos réponses. si vous parvenez à l'exécuter)
Et n'oubliez pas aussi p.4 (approche asynchrone) - vous ne pourrez probablement pas l’utiliser sur Apache, mais cela vaut la peine d’être examiné - mots-clés: Django + gevent, Django + asyncio. Il a ses avantages et ses inconvénients et peut grandement affecter la mise en œuvre de votre application. Il est donc difficile de proposer une solution sans connaître en détail les exigences de votre application.
Ce n'est pas une bonne idée de faire une telle chose de manière synchrone dans le contexte d'applications Web. N'oubliez pas que votre application doit parfois fonctionner en mode multi processus/thread et que vous ne pouvez pas partager la connexion entre les processus normalement. Donc, si vous créez une connexion pour votre utilisateur sur un processus, il n'y a aucune garantie de recevoir une requête sur le même. Une meilleure idée serait peut-être d’avoir un seul ouvrier en arrière-plan qui gérera les connexions dans plusieurs threads (un thread par session) pour effectuer des requêtes sur la base de données et récupérer le résultat sur une application Web. Votre application doit attribuer un identifiant unique à chaque session et l'agent en arrière-plan suit chaque thread à l'aide de l'identifiant de session. Vous pouvez utiliser celery
ou toute autre file de tâches prenant en charge le résultat asynchrone. Donc, la conception serait quelque chose comme ci-dessous:
|<--| |<--------------| |<--|
user (id: x) | | webapp | | queue | | worker (thread x) | | DB
|-->| |-->| |-->| |-->|
Vous pouvez également créer une file d'attente pour chaque utilisateur jusqu'à ce qu'il ait une session active. Vous pouvez ainsi exécuter un processus en arrière-plan distinct pour chaque session.
En fait, j'ai partagé ma solution à ce problème précis. Ce que j'ai fait ici était de créer un pool de connexions que vous pouvez spécifier avec max, puis de placer les requêtes en file d'attente de manière asynchrone via ce canal. De cette façon, vous pouvez laisser un certain nombre de connexions ouvertes, mais la file d'attente et le pool seront asynchrones tout en conservant la vitesse habituelle.
Cela nécessite gevent et postgres.
Je partage juste mes connaissances ici.
Installez PyMySQL pour utiliser MySql} _
Pour Python 2.x
pip install PyMySQL
Pour Python 3.x
pip3 install PyMySQL
1. Si vous êtes prêt à utiliser Django Framework, il est très facile de lancer la requête SQL sans aucune reconnexion.
Dans le fichier setting.py, ajoutez les lignes ci-dessous
DATABASES = {
'default': {
'ENGINE': 'Django.db.backends.mysql',
'NAME': 'test',
'USER': 'test',
'PASSWORD': 'test',
'Host': 'localhost',
'OPTIONS': {'charset': 'utf8mb4'},
}
}
Dans le fichier views.py, ajoutez ces lignes pour obtenir les données. Vous pouvez personnaliser votre requête en fonction de vos besoins
from Django.db import connection
def connect(request):
cursor = connection.cursor()
cursor.execute("SELECT * FROM Tablename");
results = cursor.fetchall()
return results
Vous obtiendrez les résultats souhaités.
Cliquez ici pour plus d'informations à ce sujet
2. Pour 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()
Reportez-vous this pour plus d'informations
PS: Vous pouvez vérifier ce lien pour votre question concernant la réutilisation d'une connexion à une base de données pour plus tard.
Comment activer la reconnexion automatique du client MySQL avec MySQLdb?
Je ne suis pas un expert dans ce domaine, mais je pense que PgBouncer ferait le travail à votre place, en supposant que vous puissiez utiliser un back-end PostgreSQL (c'est un détail que vous n'avez pas précisé). PgBouncer est un pooler de connexion, qui vous permet de réutiliser des connexions en évitant la surcharge liée à la connexion à chaque demande.
Selon leur documentation :
mot de passe de l'utilisateur
Si user = est défini, toutes les connexions à la base de données de destination seront effectuées avec l'utilisateur spécifié, ce qui signifie qu'il n'y aura qu'un seul pool pour cette base de données.
Sinon, PgBouncer essaie de se connecter à la base de données de destination avec le nom d'utilisateur du client, ce qui signifie qu'il y aura un pool par utilisateur.
Ainsi, vous pouvez avoir un seul pool de connexions par utilisateur, ce qui ressemble à ce que vous voulez.
Dans MySQL Land, le module mysql.connector.pool vous permet d'effectuer un regroupement de connexions, bien que je ne sache pas si vous pouvez réaliser un regroupement par utilisateur. Étant donné que vous pouvez configurer le nom du pool, j'imagine que vous pouvez utiliser le nom de l'utilisateur pour identifier le pool.
Indépendamment de ce que vous utilisez, vous aurez probablement des occasions où la reconnexion est inévitable (un utilisateur se connecte, fait un certain nombre de choses, s'absente pour une réunion ou un déjeuner, revient et souhaite prendre davantage de mesures).