Je construis un Django (ver. 3.0.5) application qui utilise mysqlclient
(ver. 2.0.3) en tant que backend dB. De plus, j'ai écrit un Django commande qui exécute un bot écrit à l'aide de l'API Python-Telegram-bot API, de sorte que la mission de ce bot est de fonctionner indéfiniment, car elle doit répondre aux commandes à toute heure.
Le problème est qu'environ 24h. Après avoir exécuté le bot (pas nécessairement d'inactivité tout le temps), je reçois une exception Django.db.utils.OperationalError: (2006, 'MySQL server has gone away')
après avoir exécuté une commande.
Je suis absolument sûr que le serveur MySQL fonctionne tout le temps et est toujours en cours d'exécution à l'époque où je reçois cette exception. La version Server MySQL est 5.7.35
.
Mon hypothèse est que certains threads MySQL sont âgés de vieillissement et sont fermés, alors après les avoir réutilisés, ils ne seront pas renouvelés.
Quelqu'un a-t-il heurté cette situation et sait comment résoudre?
Traceback (most recent call last):
File "/opt/Django/gip/venv/lib/python3.6/site-packages/telegram/ext/dispatcher.py", line 555, in process_update
handler.handle_update(update, self, check, context)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/telegram/ext/handler.py", line 198, in handle_update
return self.callback(update, context)
File "/opt/Django/gip/gip/hospital/gipcrbot.py", line 114, in ayuda
perfil = get_permiso_efectivo(update.message.from_user.id)
File "/opt/Django/gip/gip/hospital/telegram/funciones.py", line 33, in get_permiso_efectivo
u = Telegram.objects.get(idtelegram=userid)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 411, in get
num = len(clone)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 258, in __len__
self._fetch_all()
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 1261, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 57, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/sql/compiler.py", line 1151, in execute_sql
cursor.execute(sql, params)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 100, in execute
return super().execute(sql, params)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/mysql/base.py", line 74, in execute
return self.cursor.execute(query, args)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 206, in execute
res = self._query(query)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 319, in _query
db.query(q)
File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/connections.py", line 259, in query
_mysql.connection.query(self, query)
Django.db.utils.OperationalError: (2006, 'MySQL server has gone away')
J'ai déjà essayé de changer le Django settings.py fichier donc j'ai défini une valeur explicite pour CONN_MAX_AGE
, et j'ai également défini une valeur pour le client MySQL wait_timeout
Paramètre, être CONN_MAX_AGE
plus bas que wait_timeout
.
Paramètres.py :
DATABASES = {
'default': {
'ENGINE': 'Django.db.backends.mysql',
'OPTIONS': {
'read_default_file': '/opt/Django/gip/gip/gip/my.cnf',
},
'CONN_MAX_AGE': 3600,
}
}
my.cnf :
[client]
...
wait_timeout = 28800
Malheureusement, le comportement est exactement le même: je reçois une exception environ 24h. après avoir exécuté le bot.
Réglage CONN_MAX_AGE
à None
ne fera aucune différence non plus.
J'ai installé le mysql-server-has-gone-away
python package tel que proposé par @ r-marolahy, mais cela ne fera pas une différence non plus. Après près de 24 heures après l'avoir exécuté, le message "parti" montre à nouveau.
J'ai aussi essayé l'approche de la fermeture de vieilles connexions:
from Django.db import close_old_connections
try:
#do your long running operation here
except Django.db.utils.OperationalError:
close_old_connections()
#do your long running operation here
Obtenir toujours le même résultat.
J'ai fini par programmer une requête DB toutes les x heures (dans ce cas, 6h) dans le bot. Le python-telegram-bot
A une classe appelée JobQueue qui a une méthode appelée run_repeating
. Cela dirigera une tâche chaque N secondes. Alors j'ai déclaré:
def check_db(context):
# Do the code for running "SELECT 1" in the DB
return
updater.job_queue.run_repeating(check_db, interval=21600, first=21600)
Après ce changement, je n'ai plus eu le même problème.
En outre, appelez la plupart des indocumés close_if_unusable_or_obsolete()
Django méthode de temps à autre fonctionne aussi bien dans mon cas.
from Django.db import connection
connection.close_if_unusable_or_obsolete()
La connexion se brisera pour l'une des raisons pour des raisons, pas seulement des délais d'attente. Donc, il suffit de planifier la rupture.
Planifier une (solution la plus robuste):
Chaque fois que vous exécutez une requête, vérifiez pour les erreurs et avez du code à reconnecter et à relancer la requête (ou transaction).
Plan B (risqué pour les transactions):
Activer la reconnexion automatique. Ceci est mauvais Si vous utilisez des transactions multi-sorties. La reconnexion automatique au milieu d'une transaction peut conduire à un jeu de données corrompu (en violant la sémantique d'une "transaction".) C'est parce que la première partie de la transaction est ROLLBACK'd
(en raison du déconnexion) et du repos est COMMITted
.
Plan c (direct droit):
Se connecter lorsqu'un message arrive; Fais ton travail; puis déconnectez-vous. C'est-à-dire être déconnecté la plupart du temps. Cette approche est nécessaire si vous pouviez avoir beaucoup de clients qui se connectent (mais font rarement n'importe quoi).
Plan D (ne vaut pas la peine d'être envisagé):
Augmenter divers délai d'attente. Pourquoi la résolution de la résolution d'un problème (faible délai d'attente) lorsque d'autres problèmes sont non résolus (Network Hiccup).
Ma préférence? C est facile et presque toujours suffisant. A prend plus de code, mais est "meilleur". Les deux C et A sont peut-être encore mieux.
La raison pour laquelle cela se produise est parce que le close_old_connection
une fonction.
Ainsi, ce que vous pouvez essayer de faire est d'ajouter l'appel à la fermeture ancienne avant d'interagir avec DB:
Exemple de code :
from Django.db import close_old_connections
close_old_connections()
# do some db actions, it will reconnect db
S'il vous plaît laissez-moi savoir si cela ne résout pas votre problème.