web-dev-qa-db-fra.com

Python et Django OperationalError (2006, 'Le serveur MySQL est parti')

Original: J'ai récemment commencé à récupérer MySQL OperationalErrors à partir de certains de mes anciens codes et n'arrive pas à retracer le problème. Comme cela fonctionnait auparavant, je pensais que c’était peut-être une mise à jour logicielle qui avait cassé quelque chose. J'utilise python 2.7 avec Django runfcgi avec nginx. Voici mon code original:

views.py

DBNAME = "test"
DBIP = "localhost"
DBUSER = "Django"
DBPASS = "password"
db = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME)
cursor = db.cursor()

def list(request):
    statement = "SELECT item from table where selected = 1"
    cursor.execute(statement)
    results = cursor.fetchall()

J'ai essayé ce qui suit, mais cela ne fonctionne toujours pas:

views.py

class DB:
    conn = None
    DBNAME = "test"
    DBIP = "localhost"
    DBUSER = "Django"
    DBPASS = "password"
def connect(self):
    self.conn = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME)
def cursor(self):
    try:
        return self.conn.cursor()
    except (AttributeError, MySQLdb.OperationalError):
        self.connect()
        return self.conn.cursor()

db = DB()
cursor = db.cursor()

def list(request):
    cursor = db.cursor()
    statement = "SELECT item from table where selected = 1"
    cursor.execute(statement)
    results = cursor.fetchall()

Actuellement, ma seule solution consiste à faire MySQLdb.connect() dans chaque fonction qui utilise mysql. J'ai aussi remarqué qu'en utilisant le manage.py runserver de Django, je n'aurais pas ce problème alors que nginx lançait ces erreurs. Je doute que la connexion soit en retard, car list() est appelé quelques secondes après le démarrage du serveur. Y a-t-il des mises à jour du logiciel que j'utilise qui causeraient des problèmes/existe-t-il un correctif?

Edit: J'ai réalisé que j'avais récemment écrit un middle-ware pour démoniser une fonction et que c'était la cause du problème. Cependant, je ne peux pas comprendre pourquoi. Voici le code pour le middleware

def process_request_handler(sender, **kwargs):
    t = threading.Thread(target=dispatch.execute,
        args=[kwargs['nodes'],kwargs['callback']],
        kwargs={})
    t.setDaemon(True)
    t.start()
    return
process_request.connect(process_request_handler)
26
Franz Payer

Conformément à la documentation MySQL , votre message d'erreur est généré lorsque le client ne peut pas envoyer de question au serveur, probablement parce que le serveur lui-même a fermé la connexion. Dans le cas le plus courant, le serveur ferme une connexion inactive après un délai (par défaut) de 8 heures. Ceci est configurable côté serveur.

La documentation MySQL donne un certain nombre d’autres causes possibles qui mériteraient d’être examinées pour voir si elles correspondent à votre situation.

Une alternative à l'appel de connect() dans chaque fonction (qui risquerait de créer inutilement de nouvelles connexions) serait d'investir dans la méthode ping() sur l'objet de connexion; ceci teste la connexion avec l'option de tenter une reconnexion automatique. J'ai eu du mal à trouver décent de la documentation pour la méthode ping() en ligne, mais la réponse à cette question pourrait aider. 

Notez que la reconnexion automatique peut être dangereuse lors du traitement des transactions, car il semble que la reconnexion provoque une annulation implicite (et semble être la raison principale pour laquelle la reconnexion automatique n'est pas une fonctionnalité de l'implémentation de MySQLdb).

34
Mark Streatfield

Parfois, si vous voyez "OperationalError: (2006," Le serveur MySQL est parti ")", c'est parce que vous émettez une requête trop volumineuse. Cela peut arriver, par exemple, si vous stockez vos sessions dans MySQL et que vous essayez de mettre quelque chose de vraiment gros dans la session. Pour résoudre le problème, vous devez augmenter la valeur du paramètre max_allowed_packet dans MySQL.

La valeur par défaut est 1048576.

Donc, voir la valeur actuelle pour la valeur par défaut, exécutez le SQL suivant:

select @@max_allowed_packet;

Pour définir temporairement une nouvelle valeur, exécutez le code SQL suivant:

set global max_allowed_packet=10485760;

Pour résoudre le problème de manière plus permanente, créez un fichier /etc/my.cnf contenant au moins les éléments suivants:

[mysqld]
max_allowed_packet = 16M

Après avoir édité /etc/my.cnf, vous devrez redémarrer MySQL ou redémarrer votre ordinateur si vous ne savez pas comment.

42
Shannon -jj Behrens

J'ai eu du mal avec ce problème aussi. Je n'aime pas l'idée d'augmenter le délai d'attente sur mysqlserver. La reconnexion automatique avec CONNECTION_MAX_AGE ne fonctionne pas non plus, comme il a été mentionné. Malheureusement, j'ai fini par emballer chaque méthode qui interroge la base de données comme ceci

def do_db( callback, *arg, **args):
    try:
        return callback(*arg, **args)
    except (OperationalError, InterfaceError) as e:  # Connection has gone away, fiter it with message or error code if you could catch another errors
        connection.close()
        return callback(*arg, **args)

do_db(User.objects.get, id=123)  # instead of User.objects.get(id=123)

Comme vous pouvez le constater, je préfère préférer intercepter l’exception que d’envoyer une requête ping à la base de données avant de l’interroger. Car attraper une exception est un cas rare. Je m'attendrais à ce que Django se reconnecte automatiquement, mais ils semblaient refuser ce problème.

1
deathangel908

Vérifiez si vous êtes autorisé à créer un objet de connexion mysql dans un thread, puis utilisez-le dans un autre.

Si c'est interdit, utilisez threading.Local pour les connexions par thread:

class Db(threading.local):
    """ thread-local db object """
    con = None

    def __init__(self, ...options...):
        super(Db, self).__init__()
        self.con = MySQLdb.connect(...options...)

db1 = Db(...)


def test():
    """safe to run from any thread"""
    cursor = db.con.cursor()
    cursor.execute(...)
1
Dima Tisnek

Cette erreur est mystérieuse car MySQL ne signale pas pourquoi elle se déconnecte, elle disparaît simplement.

Il semble qu’il existe de nombreuses causes à ce type de déconnexion. Un que je viens de trouver est, si la chaîne de requête est trop grande, le serveur se déconnectera. Ceci est probablement lié au paramètre max_allowed_packets.

0
Chris Johnson

Tout d’abord, vous devez vous assurer que les valeurs wait_timeout et interactive_timeout de la session MySQL et des environnements globaux ont été respectées. Et deuxièmement, votre client devrait essayer de se reconnecter au serveur en dessous des valeurs de ces environnements.

0
Ryan Chou

J'ai eu ce problème et n'ai pas eu la possibilité de changer ma configuration. J'ai finalement compris que le problème se produisait dans ma boucle de 50000 enregistrements avec 49500 enregistrements, car c’était à peu près à ce moment-là que j’essayais de nouveau (après avoir essayé depuis longtemps) d’atteindre ma deuxième base de données. 

J'ai donc modifié mon code de sorte que tous les quelques milliers d'enregistrements, j'ai touché à nouveau la deuxième base de données (avec un nombre () d'une très petite table), et cela a été corrigé. Nul doute que "ping" ou un autre moyen de toucher à la base de données fonctionnerait également.

0
HelenM

À mon avis, le problème le plus fréquent en ce qui concerne cet avertissement est le fait que votre application a atteint la valeur wait_timeout de MySQL.

J'ai eu le même problème avec une application PythonFLASK que j'ai développée et que j'ai résolue très facilement.

P.S: J'utilisais MySQL 5.7.14

$ grep timeout /etc/mysql/mysql.conf.d/mysqld.cnf 
# https://support.rackspace.com/how-to/how-to-change-the-mysql-timeout-on-a-server/
# wait = timeout for application session (tdm)
# inteactive = timeout for keyboard session (terminal)
# 7 days = 604800s / 4 hours = 14400s 
wait_timeout = 604800
interactive_timeout = 14400

Une observation importante: si vous recherchez les variables via le mode batch MySQL, les valeurs apparaîtront telles quelles. Mais si vous effectuez "AFFICHER VARIABLES COMME 'wait%';" ou "SHOW VARIABLES LIKE" interactive% '; ", la valeur configurée pour" interactive_timeout ", apparaîtra pour les deux variables, et je ne sais pas pourquoi, mais le fait est que les valeurs configurées pour chaque variable dans'/etc /mysql/mysql.conf.d/mysqld.cnf ', sera respecté par le processus MySQL.

Cordialement!

0
ivanleoncz

SQLAlchemy explique maintenant comment utiliser le ping pour être pessimiste à propos de la fraîcheur de votre connexion:

http://docs.sqlalchemy.org/en/latest/core/pool.html#disconnect-handling-pessimistic

De là,

from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool

@event.listens_for(Pool, "checkout")
def ping_connection(dbapi_connection, connection_record, connection_proxy):
    cursor = dbapi_connection.cursor()
    try:
        cursor.execute("SELECT 1")
    except:
        # optional - dispose the whole pool
        # instead of invalidating one at a time
        # connection_proxy._pool.dispose()

        # raise DisconnectionError - pool will try
        # connecting again up to three times before raising.
        raise exc.DisconnectionError()
    cursor.close()

Et un test pour vous assurer que cela fonctionne:

from sqlalchemy import create_engine
e = create_engine("mysql://scott:tiger@localhost/test", echo_pool=True)
c1 = e.connect()
c2 = e.connect()
c3 = e.connect()
c1.close()
c2.close()
c3.close()

# pool size is now three.

print "Restart the server"
raw_input()

for i in xrange(10):
    c = e.connect()
    print c.execute("select 1").fetchall()
    c.close()
0
Milimetric

Quel âge a ce code? Django a des bases de données définies dans les paramètres depuis au moins .96. La seule chose à laquelle je peux penser est le support multi-db, qui a un peu changé les choses, mais même cela était 1.1 ou 1.2.

Même si vous avez besoin d'une base de données spéciale pour certaines vues, je pense que vous feriez mieux de la définir dans les paramètres. 

0
Tim Baxter

Cela peut être dû au fait que les connexions à la base de données ont été copiées dans vos threads enfants à partir du thread principal. J'ai rencontré la même erreur lors de l'utilisation de la bibliothèque de multitraitement de python pour générer différents processus. Les objets de connexion sont copiés entre les processus lors du forgeage et mènent à MySQL OperationalErrors lors d'appels de base de données dans le thread enfant.

Voici une bonne référence pour résoudre ceci: Multi-traitement Django et connexions à la base de données

0
Vedant Goenka